// 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/qffmpegdemuxer_p.h"
#include <qloggingcategory.h>

QT_BEGIN_NAMESPACE

// 4 sec for buffering. TODO: maybe move to env var customization
static constexpr qint64 MaxBufferingTimeUs = 4'000'000;

// Currently, consider only time. TODO: maybe move to env var customization
static constexpr qint64 MaxBufferingSize = std::numeric_limits<qint64>::max();

namespace QFFmpeg {

static Q_LOGGING_CATEGORY(qLcDemuxer, "qt.multimedia.ffmpeg.demuxer");

static qint64 streamTimeToUs(const AVStream *stream, qint64 time)
{
    Q_ASSERT(stream);

    const auto res = mul(time * 1000000, stream->time_base);
    return res ? *res : time;
}

Demuxer::Demuxer(AVFormatContext *context, const PositionWithOffset &posWithOffset,
                 const StreamIndexes &streamIndexes, int loops)
    : m_context(context), m_posWithOffset(posWithOffset), m_loops(loops)
{
    qCDebug(qLcDemuxer) << "Create demuxer."
                        << "pos:" << posWithOffset.pos << "loop offset:" << posWithOffset.offset.pos
                        << "loop index:" << posWithOffset.offset.index << "loops:" << loops;

    Q_ASSERT(m_context);
    Q_ASSERT(loops < 0 || m_posWithOffset.offset.index < loops);

    for (auto i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
        if (streamIndexes[i] >= 0) {
            const auto trackType = static_cast<QPlatformMediaPlayer::TrackType>(i);
            qCDebug(qLcDemuxer) << "Activate demuxing stream" << i << ", trackType:" << trackType;
            m_streams[streamIndexes[i]] = { trackType };
        }
    }
}

void Demuxer::doNextStep()
{
    ensureSeeked();

    Packet packet(m_posWithOffset.offset, AVPacketUPtr{ av_packet_alloc() }, id());
    if (av_read_frame(m_context, packet.avPacket()) < 0) {
        ++m_posWithOffset.offset.index;

        const auto loops = m_loops.loadAcquire();
        if (loops >= 0 && m_posWithOffset.offset.index >= loops) {
            qCDebug(qLcDemuxer) << "finish demuxing";
            setAtEnd(true);
        } else {
            m_seeked = false;
            m_posWithOffset.pos = 0;
            m_posWithOffset.offset.pos = m_endPts;
            m_endPts = 0;

            ensureSeeked();

            qCDebug(qLcDemuxer) << "Demuxer loops changed. Index:" << m_posWithOffset.offset.index
                                << "Offset:" << m_posWithOffset.offset.pos;
        }

        return;
    }

    const auto streamIndex = packet.avPacket()->stream_index;
    const auto stream = m_context->streams[streamIndex];

    auto it = m_streams.find(streamIndex);

    if (it != m_streams.end()) {
        const auto packetEndPos =
                streamTimeToUs(stream, packet.avPacket()->pts + packet.avPacket()->duration);
        m_endPts = std::max(m_endPts, m_posWithOffset.offset.pos + packetEndPos);

        it->second.bufferingTime += streamTimeToUs(stream, packet.avPacket()->duration);
        it->second.bufferingSize += packet.avPacket()->size;

        auto signal = signalByTrackType(it->second.trackType);
        emit (this->*signal)(packet);
    }

    scheduleNextStep(false);
}

void Demuxer::onPacketProcessed(Packet packet)
{
    Q_ASSERT(packet.isValid());

    if (packet.sourceId() != id())
        return;

    const auto streamIndex = packet.avPacket()->stream_index;
    auto it = m_streams.find(streamIndex);

    if (it != m_streams.end()) {
        it->second.bufferingTime -=
                streamTimeToUs(m_context->streams[streamIndex], packet.avPacket()->duration);
        it->second.bufferingSize -= packet.avPacket()->size;

        Q_ASSERT(it->second.bufferingTime >= 0);
        Q_ASSERT(it->second.bufferingSize >= 0);
    }

    scheduleNextStep();
}

bool Demuxer::canDoNextStep() const
{
    if (!PlaybackEngineObject::canDoNextStep() || isAtEnd() || m_streams.empty())
        return false;

    auto checkBufferingTime = [](const auto &streamIndexToData) {
        return streamIndexToData.second.bufferingTime < MaxBufferingTimeUs &&
               streamIndexToData.second.bufferingSize < MaxBufferingSize;
    };

    return std::all_of(m_streams.begin(), m_streams.end(), checkBufferingTime);
}

void Demuxer::ensureSeeked()
{
    if (std::exchange(m_seeked, true))
        return;

    const qint64 seekPos = m_posWithOffset.pos * AV_TIME_BASE / 1000000;
    auto err = av_seek_frame(m_context, -1, seekPos, AVSEEK_FLAG_BACKWARD);

    if (err < 0) {
        qDebug() << err2str(err);
        emit error(err, err2str(err));
        return;
    }

    setAtEnd(false);
    scheduleNextStep();
}

Demuxer::RequestingSignal Demuxer::signalByTrackType(QPlatformMediaPlayer::TrackType trackType)
{
    switch (trackType) {
    case QPlatformMediaPlayer::TrackType::VideoStream:
        return &Demuxer::requestProcessVideoPacket;
    case QPlatformMediaPlayer::TrackType::AudioStream:
        return &Demuxer::requestProcessAudioPacket;
    case QPlatformMediaPlayer::TrackType::SubtitleStream:
        return &Demuxer::requestProcessSubtitlePacket;
    default:
        Q_ASSERT(!"Unknown track type");
    }

    return nullptr;
}

void Demuxer::setLoops(int loopsCount)
{
    qCDebug(qLcDemuxer) << "setLoops to demuxer" << loopsCount;
    m_loops.storeRelease(loopsCount);
}

} // namespace QFFmpeg

QT_END_NAMESPACE

#include "moc_qffmpegdemuxer_p.cpp"
