/***************************************************************
 * Copyright (C) 2020, 2025, Val Doroshchuk <valbok@gmail.com> *
 *                                                             *
 * This file is part of QtAVPlayer.                            *
 * Free Qt Media Player based on FFmpeg.                       *
 ***************************************************************/

#ifndef QT_AVPLAYER_MULTIMEDIA
#define QT_AVPLAYER_MULTIMEDIA
#endif
#include <QtAVPlayer/qavplayer.h>
#include <QtAVPlayer/qavvideoframe.h>
#include <QtAVPlayer/qavaudiooutput.h>
#include <QtAVPlayer/qavmuxerframes.h>
#include <QtAVPlayer/qaviodevice.h>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QAbstractVideoSurface>
#include <private/qdeclarativevideooutput_p.h>
#else
#include <QVideoSink>
#include <QtMultimediaQuick/private/qquickvideooutput_p.h>
#endif

#include <QtQuick/QQuickView>
#include <QtQuick/QQuickItem>
#include <QtQml/QQmlEngine>
#include <QGuiApplication>
#include <QDebug>
#include <QElapsedTimer>
#include <QFile>

extern "C" {
#include <libavcodec/avcodec.h>
}
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
class Source : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QAbstractVideoSurface *videoSurface READ videoSurface WRITE setVideoSurface)
public:
    explicit Source(QObject *parent = 0) : QObject(parent) { }
    virtual ~Source() { }

    QAbstractVideoSurface* videoSurface() const { return m_surface; }
    void setVideoSurface(QAbstractVideoSurface *surface)
    {
        m_surface = surface;
    }

    QAbstractVideoSurface *m_surface = nullptr;
};
#endif

static bool isStreamCurrent(int index, const QList<QAVStream> &streams)
{
    for (const auto &stream: streams) {
        if (stream.index() == index)
            return true;
    }
    return false;
}

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQuickView viewer;
    viewer.setSource(QUrl(QString::fromLatin1("qrc:///main.qml")));
    viewer.setResizeMode(QQuickView::SizeRootObjectToView);
    QObject::connect(viewer.engine(), SIGNAL(quit()), &viewer, SLOT(close()));

    QQuickItem *rootObject = viewer.rootObject();

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    using VideoOutput = QDeclarativeVideoOutput;
#else
    using VideoOutput = QQuickVideoOutput;
#endif

    auto vo = rootObject->findChild<VideoOutput *>(QString::fromLatin1("videoOutput"));

    QAVAudioOutput audioOutput;
    QAVMuxerFrames muxer;
    QAVPlayer p;

#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
    Source src;
    vo->setSource(&src);
    auto videoSurface = src.m_surface;
#elif QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    auto videoSurface = vo->videoSurface();
    // Make sure that render geometry has been updated after a frame
    QObject::connect(vo, &QDeclarativeVideoOutput::sourceRectChanged, &p, [&] {
        vo->update();
    });
#else
    auto videoSurface = vo->videoSink();
#endif

    QObject::connect(&p, &QAVPlayer::videoFrame, &p, [&](const QAVVideoFrame &frame) {
        rootObject->setProperty("frame_fps", p.progress(frame.stream()).fps());
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        // Might download and convert data
        QVideoFrame videoFrame = frame;
        if (!videoSurface->isActive())
            videoSurface->start({videoFrame.size(), videoFrame.pixelFormat(), videoFrame.handleType()});
        if (videoSurface->isActive())
            videoSurface->present(videoFrame);
#endif
    });

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    QObject::connect(&p, &QAVPlayer::videoFrame, &p, [&](const QAVVideoFrame &frame) {
        // Might download and convert data
        QVideoFrame videoFrame = frame;
        videoSurface->setVideoFrame(videoFrame);
    }, Qt::DirectConnection);
#endif

#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
    //audioOutput.setChannelConfig(QAudioFormat::channelConfig(QAudioFormat::FrontLeft));
#endif

    QObject::connect(&p, &QAVPlayer::audioFrame, &p, [&audioOutput](const QAVAudioFrame &frame) { audioOutput.play(frame); }, Qt::DirectConnection);
    QString file = argc > 1 ? QString::fromUtf8(argv[1]) : QString::fromLatin1("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4");
    QString filter = argc > 2 ? QString::fromUtf8(argv[2]) : QString();
    QString output = argc > 3 ? QString::fromUtf8(argv[3]) : QString();

    QObject::connect(&p, &QAVPlayer::stateChanged, [&](auto s) { qDebug() << "stateChanged" << s << p.mediaStatus(); });
    QObject::connect(&p, &QAVPlayer::mediaStatusChanged, [&](auto status) {
        qDebug() << "mediaStatusChanged"<< status << p.state();
        if (status == QAVPlayer::LoadedMedia) {
            auto availableVideoStreams = p.availableVideoStreams();
            //p.setVideoStreams({});
            auto videoStreams = p.currentVideoStreams();
            qDebug() << "Video streams:" << availableVideoStreams.size();
            for (auto &s : p.availableVideoStreams())
                qDebug() << "[" << s.index() << "]" << s.metadata() << s.framesCount() << "frames," << s.frameRate() << "frame rate" << (isStreamCurrent(s.index(), videoStreams) ? "---current" : "");

            auto availableAudioStreams = p.availableAudioStreams();
            auto audioStreams = p.currentAudioStreams();
            qDebug() << "Audio streams:" << availableAudioStreams.size();

            for (auto &s : availableAudioStreams)
                qDebug() << "[" << s.index() << "]" << s.metadata() << s.framesCount() << "frames," << s.frameRate() << "frame rate" << (isStreamCurrent(s.index(), audioStreams) ? "---current" : "");

            auto availableSubtitleStreams = p.availableSubtitleStreams();
            qDebug() << "Subtitle streams:" << availableSubtitleStreams.size();
            for (auto &s : availableSubtitleStreams) {
                if (s.metadata()[QString::fromLatin1("language")] == QString::fromLatin1("eng")) {
                    p.setSubtitleStream(s);
                    break;
                }
            }

            auto subtitleStreams = p.currentSubtitleStreams();
            for (auto &s : availableSubtitleStreams) {
                qDebug() << "[" << s.index() << "]" << s.metadata() << s.framesCount() << "frames," << s.frameRate() << "frame rate" << (isStreamCurrent(s.index(), subtitleStreams) ? "---current" : "");
            }
            if (!filter.isEmpty() && !output.isEmpty()) {
                muxer.load(p.availableStreams(), output);
                QObject::connect(&p, &QAVPlayer::videoFrame, &p, [&](const QAVVideoFrame &f) { muxer.enqueue(f); }, Qt::DirectConnection);
                QObject::connect(&p, &QAVPlayer::audioFrame, &p, [&](const QAVAudioFrame &f) { muxer.enqueue(f); }, Qt::DirectConnection);
                QObject::connect(&p, &QAVPlayer::subtitleFrame, &p, [&](const QAVSubtitleFrame &f) { muxer.write(f); }, Qt::DirectConnection);
            }
            p.play();
        } else if (status == QAVPlayer::EndOfMedia) {
            for (const auto &s : p.availableVideoStreams())
                qDebug() << s << p.progress(s);
            for (const auto &s : p.availableAudioStreams())
                qDebug() << s << p.progress(s);
            for (const auto &s : p.availableSubtitleStreams())
                qDebug() << s << p.progress(s);
        }

    });
    QObject::connect(&p, &QAVPlayer::durationChanged, [&](auto d) { qDebug() << "durationChanged" << d; });

    QObject::connect(&p, &QAVPlayer::subtitleFrame, &p, [](const QAVSubtitleFrame &frame) {
        for (unsigned i = 0; i < frame.subtitle()->num_rects; ++i) {
            if (frame.subtitle()->rects[i]->type == SUBTITLE_TEXT)
                qDebug() << "text:" << frame.subtitle()->rects[i]->text;
            else
                qDebug() << "ass:" << frame.subtitle()->rects[i]->ass;
        }
    });

    QSharedPointer<QAVIODevice> qrc;
    if (file.startsWith(QString::fromLatin1(":/"))) {
        QSharedPointer<QIODevice> io(new QFile(file));
        if (io->open(QIODevice::ReadOnly))
            qrc.reset(new QAVIODevice(io));
    }
    p.setSource(file, qrc);
    p.setFilter(filter);
    // Force software decoding
    //p.setInputVideoCodec(QString::fromLatin1("software"));
    if (filter.isEmpty())
        p.setOutput(output);
    // Disable syncing frames using their pts
    //p.setSynced(false);

    viewer.setMinimumSize(QSize(300, 360));
    viewer.resize(1960, 1086);
    viewer.show();

    QElapsedTimer qmlElapsed;
    qmlElapsed.start();
    int qmlCount = 0;

    QObject::connect(&viewer, &QQuickView::afterRendering, &viewer, [&] {
        const int fps = qmlCount++ * 1000 / qmlElapsed.elapsed();
        rootObject->setProperty("qml_fps", fps);
    });

    return app.exec();
}

#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
#include "main.moc"
#endif
