// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "playbackengine/qffmpegtimecontroller_p.h"

#include "qglobal.h"
#include "qdebug.h"

#include <algorithm>

QT_BEGIN_NAMESPACE

namespace QFFmpeg {

TimeController::TimeController()
{
    sync();
}

TimeController::PlaybackRate TimeController::playbackRate() const
{
    return m_playbackRate;
}

void TimeController::setPlaybackRate(PlaybackRate playbackRate)
{
    if (playbackRate == m_playbackRate)
        return;

    Q_ASSERT(playbackRate > 0.f);

    scrollTimeTillNow();
    m_playbackRate = playbackRate;

    if (m_softSyncData)
        m_softSyncData = makeSoftSyncData(m_timePoint, m_position, m_softSyncData->dstTimePoint);
}

void TimeController::sync(TrackPosition trackPos)
{
    sync(SteadyClock::now(), trackPos);
}

void TimeController::sync(TimePoint tp, TrackPosition pos)
{
    m_softSyncData.reset();
    m_position = pos;
    m_timePoint = tp;
}

void TimeController::syncSoft(TimePoint tp, TrackPosition pos, SteadyClock::duration fixingTime)
{
    const auto srcTime = SteadyClock::now();
    const auto srcPos = positionFromTime(srcTime, true);
    const auto dstTime = srcTime + fixingTime;

    m_position = pos;
    m_timePoint = tp;

    m_softSyncData = makeSoftSyncData(srcTime, srcPos, dstTime);
}

TrackPosition TimeController::currentPosition(SteadyClock::duration offset) const
{
    return positionFromTime(SteadyClock::now() + offset);
}
void TimeController::start()
{
    m_started = true;
    updateActive();
}

void TimeController::deactivate()
{
    m_started = false;
    m_paused = true;
    updateActive();
}

void TimeController::setPaused(bool paused)
{
    m_paused = paused;
    updateActive();
}

void TimeController::updateActive()
{
    const bool active = !m_paused && m_started;
    if (m_active == active)
        return;

    scrollTimeTillNow();

    m_active = active;
}

TrackPosition TimeController::positionFromTime(TimePoint tp, bool ignoreInactive) const
{
    tp = !m_active && !ignoreInactive ? m_timePoint : tp;

    if (m_softSyncData && tp < m_softSyncData->dstTimePoint) {
        const PlaybackRate rate =
                tp > m_softSyncData->srcTimePoint ? m_softSyncData->internalRate : m_playbackRate;

        return m_softSyncData->srcPosition
                + toTrackDuration(tp - m_softSyncData->srcTimePoint, rate);
    }

    return positionFromTimeInternal(tp);
}

TimeController::TimePoint TimeController::timeFromPosition(TrackPosition pos,
                                                           bool ignoreInactive) const
{
    auto position = !m_active && !ignoreInactive ? m_position : TrackPosition(pos);

    if (m_softSyncData && position < m_softSyncData->dstPosition) {
        const auto rate = position > m_softSyncData->srcPosition ? m_softSyncData->internalRate
                                                                 : m_playbackRate;
        return m_softSyncData->srcTimePoint
                + toClockDuration(position - m_softSyncData->srcPosition, rate);
    }

    return timeFromPositionInternal(position);
}

TimeController::SoftSyncData TimeController::makeSoftSyncData(const TimePoint &srcTp,
                                                              const TrackPosition &srcPos,
                                                              const TimePoint &dstTp) const
{
    SoftSyncData result;
    result.srcTimePoint = srcTp;
    result.srcPosition = srcPos;
    result.dstTimePoint = dstTp;
    result.srcPosOffest = srcPos - positionFromTimeInternal(srcTp);
    result.dstPosition = positionFromTimeInternal(dstTp);
    result.internalRate =
            static_cast<PlaybackRate>(toClockDuration(result.dstPosition - srcPos).count())
            / (dstTp - srcTp).count();

    return result;
}

TrackPosition TimeController::positionFromTimeInternal(const TimePoint &tp) const
{
    return m_position + toTrackDuration(tp - m_timePoint, m_playbackRate);
}

TimeController::TimePoint TimeController::timeFromPositionInternal(const TrackPosition &pos) const
{
    return m_timePoint + toClockDuration(pos - m_position, m_playbackRate);
}

void TimeController::scrollTimeTillNow()
{
    const auto now = SteadyClock::now();
    if (m_active) {
        m_position = positionFromTimeInternal(now);

        // let's forget outdated syncronizations
        if (m_softSyncData && m_softSyncData->dstTimePoint <= now)
            m_softSyncData.reset();
    } else if (m_softSyncData) {
        m_softSyncData->dstTimePoint += now - m_timePoint;
        m_softSyncData->srcTimePoint += now - m_timePoint;
    }

    m_timePoint = now;
}

SteadyClock::duration TimeController::toClockDuration(TrackDuration trackDuration, PlaybackRate rate)
{
    return std::chrono::duration_cast<SteadyClock::duration>(
            std::chrono::microseconds(trackDuration.get()) / rate);
}

TrackDuration TimeController::toTrackDuration(SteadyClock::duration clockDuration, PlaybackRate rate)
{
    return TrackDuration(
            std::chrono::duration_cast<std::chrono::microseconds>(clockDuration * rate).count());
}

} // namespace QFFmpeg

QT_END_NAMESPACE
