#ifndef FFPLAYER_H
#define FFPLAYER_H

#include <condition_variable>
#include <thread>
#include <QImage>
#if(QT_VERSION_MAJOR > 5)
    #include <QAudioSink>
#else
    #include <QAudioOutput>
    typedef QAudioOutput QAudioSink;
    typedef QAudioDeviceInfo QAudioDevice;
#endif
extern "C"{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
}

template<class T>
class Queue {
public:
    virtual ~Queue() {
        delAll();
    }
    void add(T *node) {
        if(node==nullptr) {
            throw std::invalid_argument("node is null");
            return;
        }
        if(last!=nullptr) last->next = node;
        else first = node;
        last = node;
        size++;
    }
    void remove() {
        if(first==nullptr) return;
        if(first==last) first = last = nullptr;
        else first = first->next;
        size--;
    }
    T *poll() {
        auto node = first;
        remove();
        return node;
    }
    T *delGetNext() {
        delete poll();
        return first;
    }
    T *removeGetNext() {
        remove();
        return first;
    }
    void delAll() {
        while(first!=nullptr) delete poll();
    }

    T *volatile first{0};
    T *volatile last{0};
    volatile int size{0};
};

template<class T>
class ConQueue : public Queue<T> {
public:
    virtual ~ConQueue() {
        std::lock_guard<std::mutex> lock(mtx);
        this->delAll();
    }
    void lockAddNtf(T *node) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            this->add(node);
        }
        cv.notify_one();
    }
    void lockRemove() {
        std::lock_guard<std::mutex> lock(mtx);
        this->remove();
    }
    T *lockPoll() {
        std::lock_guard<std::mutex> lock(mtx);
        return this->poll();
    }
    T *lockGet() {
        std::lock_guard<std::mutex> lock(mtx);
        return this->first;
    }
    T *lockDelGetNext() {
        std::lock_guard<std::mutex> lock(mtx);
        return this->delGetNext();
    }
    T *lockRemoveGetNext() {
        std::lock_guard<std::mutex> lock(mtx);
        return this->removeGetNext();
    }
    void lockDelAll() {
        std::lock_guard<std::mutex> lock(mtx);
        this->delAll();
    }

    T *take(int ms = 0) {
        std::unique_lock<std::mutex> lock(mtx);
        if(ms==0) while(this->first==nullptr) cv.wait(lock);
        else if(this->first==nullptr) cv.wait_for(lock, std::chrono::milliseconds(ms));
        return this->poll();
    }
    std::mutex mtx;
    std::condition_variable cv;
};
struct Act {
    qint64 arg;
    Act *next;
    char act;
};
struct Packet {
    AVPacket *pkt;
    Packet *next;
    uchar act;
    ~Packet() {av_packet_free(&pkt);}
};
struct Img {
    QImage img;
    qint64 st;
    qint64 et;
    uint loop;
    Img *next;
};
struct Pcm {
    uint8_t *data;
    int size;
    qint64 st;
    qint64 et;
    uint loop;
    Pcm *next;
    ~Pcm() {if(data!=nullptr) delete [] data;}
};
class Type {
public:
    inline bool isNull(){return idx <= -1;}
    int idx{-1};
    int time_num{0}, time_den{0};
    qint64 start{0};
    AVCodecContext *ctx{0};
    std::thread *thd{0};
    std::atomic<bool> stop{false};
    AVFrame *frm{av_frame_alloc()};
    ConQueue<Packet> pkts;
    uint deLoop{0};
    ~Type() {
        av_frame_free(&frm);
        avcodec_free_context(&ctx);
    }
};
class FFPlayer;
class AVFmt {
    friend class FFPlayer;
public:
    static const char Run{0}, Pause{1}, Quit{2};
    AVFmt(QByteArray url);
    ~AVFmt();
    void mainRead();
    void mainViDecode();
    void mainAuDecode();
    bool isLoop{true};
protected:
    void checkAndUpd(FFPlayer *, qint64);
    void playAudio(Pcm *, FFPlayer *, qint64, qint64 lmt = INT64_MAX);
    void whiteAudio(Pcm *, FFPlayer *, bool, qint64 cur, qint64 lmt = INT64_MAX);
    QString errStr(int err) {
        if(av_strerror(err, errbuf, AV_ERROR_MAX_STRING_SIZE)==0) return QString::fromUtf8(errbuf);
        return QString::number(err);
    }

    QString err;
    char errbuf[AV_ERROR_MAX_STRING_SIZE]{};

    AVFormatContext *fmt_ctx{0};
    std::thread *read_thd{0};
    qint64 start{0};
    Type vi, au;
    qint64 frmDur{0};
    qint64 viLmt{0};

    SwsContext *sws_ctx{0};
    int dstStride[4]{0};
    SwrContext *swr_ctx{0};
    AVChannelLayout out_ch_layout
    #if(QT_VERSION_MAJOR > 5)
    AV_CHANNEL_LAYOUT_STEREO
    #endif
    ;
    int sample_rate{0}, pcm_sample_size{0};
    QAudioSink *sink{0};
    QIODevice *sinkWriter{0};
    int buf_size{0};

    ConQueue<Act> acts;
    AVPacket *packet{av_packet_alloc()};
    ConQueue<Img> vi_frms;
    ConQueue<Pcm> au_frms;
    qint64 start_epoch{0};
    uint playLoop{0};
};

class FFPlayer : public QObject {
    friend class AVFmt;
    Q_OBJECT
public:
    static const char Playing{0}, Paused{1}, Closed{2};

    explicit FFPlayer();
    ~FFPlayer();

    inline void open(QByteArray url) {
        status = Paused;
        emit emOpen(url);
    }
    inline void close() {emit emClose();}
    inline void play() {emit emPlay();}
    inline void pause() {emit emPause();}
    inline void stop() {emit emStop();}
    inline void setVol(qreal vol) {emit emVol(vol);}
    inline void quit() {emit emQuit();}

    std::atomic<char> status{Closed};
    QImage img;
    qint64 dur{0};
    int viSize{0}, auSize{0};
    qint64 viCurTime{0}, auCurTime{0};
public slots:
    void updFrame();
signals:
    void emOpen(QByteArray url);
    void emClose();
    void emPlay();
    void emPause();
    void emStop();
    void emVol(qreal vol);
    void emUpd(QImage);
    void emError(QString err);
    void emQuit();
protected slots:
    void onOpen(QByteArray url);
    void onClose();
    void onPlay();
    void onPause();
    void onStop();
    void onVol(qreal vol);
    void onQuit();
protected:
    void timerEvent(QTimerEvent *event) override;
    void startAudioDaemon() {
        if(audioDaemonTimerId==0 && ! ctx->au.isNull()) audioDaemonTimerId = startTimer(20);
    }
    QThread *thread;
    AVFmt *ctx{0};
    qint64 last_epoch{0};
    int audioDaemonTimerId{0};
    bool hasImg{false};
    qreal mVol{1.0};
};

#endif // FFPLAYER_H
