#include "audio_decode_thread.h"


#include "playercontroller.h"

#include <QLoggingCategory>

Q_LOGGING_CATEGORY(playerControllerAudioDecodeThread, "player.controller.AudioDecodeThread")

AudioDecodeThread::AudioDecodeThread(VideoState* pState)
    : m_pState(pState)
{}

AudioDecodeThread::~AudioDecodeThread()
{
    qCDebug(playerControllerAudioDecodeThread)
        << "[AudioDecodeThread] ~AudioDecodeThread, m_pState:" << (void*) m_pState;
}

void AudioDecodeThread::run()
{
    qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] run start, m_pState:" << (void*) m_pState;
    assert(m_pState);
    VideoState* is = m_pState;
    qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] VideoState* is:" << (void*) is
                                 << ", abort_request:" << is->abort_request;
    AVFrame* frame = av_frame_alloc();
    Frame* af;
#if USE_AVFILTER_AUDIO
    int last_serial = -1;
    AVChannelLayout dec_channel_layout; // int64_t
    int reconfigure;
#endif
    int got_frame = 0;
    AVRational tb;
    int ret = 0;

    if (!frame) {
        qCWarning(playerControllerAudioDecodeThread) << "[AudioDecodeThread] av_frame_alloc failed!";
        return;
    }
    
    qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] Starting decode loop";

    do {
        if (is->abort_request) {
            qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] abort_request set, exit.";
            break;
        }
        if (isExit()) {
            qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] m_exit set, exit.";
            break;
        }
        qCDebug(playerControllerAudioDecodeThread)
           << "[AudioDecodeThread] call decoder_decode_frame, auddec.avctx:"
           << (void*) is->auddec.avctx;
        if ((got_frame = decoder_decode_frame(&is->auddec, frame, nullptr)) < 0) {
            qCWarning(playerControllerAudioDecodeThread)
                 << "[AudioDecodeThread] decoder_decode_frame failed, ret:" << got_frame;
            goto the_end;
        }
        if (got_frame) {
            qCDebug(playerControllerAudioDecodeThread)
                 << "[AudioDecodeThread] got audio frame, pts:" << frame->pts
                 << ", sample_rate:" << frame->sample_rate << ", nb_samples:" << frame->nb_samples
                 << ", format:" << av_get_sample_fmt_name(AVSampleFormat(frame->format))
                 << ", channels:" << frame->ch_layout.nb_channels;
            tb = AVRational{1, frame->sample_rate};

#if USE_AVFILTER_AUDIO
            dec_channel_layout = frame->ch_layout; // frame->channel_layout; //

            reconfigure = cmp_audio_fmts(is->audio_filter_src.fmt,
                                         is->audio_filter_src.ch_layout.nb_channels,
                                         AVSampleFormat(frame->format),
                                         frame->ch_layout.nb_channels)
                          || is->audio_filter_src.ch_layout.nb_channels
                                 != dec_channel_layout.nb_channels
                          || is->audio_filter_src.freq != frame->sample_rate
                          || is->auddec.pkt_serial != last_serial;

            if (reconfigure || is->req_afilter_reconfigure) {
                char buf1[1024], buf2[1024];
                av_channel_layout_describe(&is->audio_filter_src.ch_layout, buf1, sizeof(buf1));
                av_channel_layout_describe(&dec_channel_layout, buf2, sizeof(buf2));

                av_log(nullptr,
                       AV_LOG_DEBUG,
                       "Audio frame changed from rate:%d ch:%d fmt:%s layout:%s "
                       "serial:%d to rate:%d ch:%d fmt:%s layout:%s serial:%d\n",
                       is->audio_filter_src.freq,
                       is->audio_filter_src.ch_layout.nb_channels,
                       av_get_sample_fmt_name(is->audio_filter_src.fmt),
                       buf1,
                       last_serial,
                       frame->sample_rate,
                       frame->ch_layout.nb_channels,
                       av_get_sample_fmt_name(AVSampleFormat(frame->format)),
                       buf2,
                       is->auddec.pkt_serial);

                is->audio_filter_src.fmt = (AVSampleFormat) frame->format;
                ret = av_channel_layout_copy(&is->audio_filter_src.ch_layout, &frame->ch_layout);
                if (ret < 0)
                    goto the_end;
                is->audio_filter_src.freq = frame->sample_rate;
                last_serial = is->auddec.pkt_serial;

                ret = configure_audio_filters(is, is->afilters, 1);
                if (ret < 0)
                    goto the_end;
                is->req_afilter_reconfigure = 0;
            }

            qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] adding frame to audio filter";
            ret = av_buffersrc_add_frame(is->in_audio_filter, frame);
            if (ret < 0) {
                qCWarning(playerControllerAudioDecodeThread) << "[AudioDecodeThread] av_buffersrc_add_frame failed, ret:" << ret;
                goto the_end;
            }

            while ((ret = av_buffersink_get_frame_flags(is->out_audio_filter, frame, 0)) >= 0) {
                qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] got filtered audio frame";
                tb = av_buffersink_get_time_base(is->out_audio_filter);
#endif

                if (!(af = frame_queue_peek_writable(&is->sampq)))
                    goto the_end;

                af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
                af->pos = frame->pkt_pos;
                af->serial = is->auddec.pkt_serial;
                af->duration = av_q2d(AVRational{frame->nb_samples, frame->sample_rate});

                qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] pushing audio frame to queue, pts:" << af->pts
                                             << ", duration:" << af->duration << ", serial:" << af->serial;

                av_frame_move_ref(af->frame, frame);
                frame_queue_push(&is->sampq);

#if USE_AVFILTER_AUDIO
                if (is->audioq.serial != is->auddec.pkt_serial)
                    break;
            }
            if (ret == AVERROR_EOF) {
                qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] audio filter EOF, marking decoder finished";
                is->auddec.finished = is->auddec.pkt_serial;
            }
#endif
        } else {
            qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] no frame decoded, continue.";
        }
    } while (got_frame);

the_end:

#if USE_AVFILTER_AUDIO
    avfilter_graph_free(&is->agraph);
    if (is->afilters) {
        av_free(is->afilters);
        is->afilters = nullptr;
    }
#endif

    av_frame_free(&frame);
    qCDebug(playerControllerAudioDecodeThread) << "[AudioDecodeThread] run end, abort_request:"
                                 << is->abort_request
                                 << ", m_exit:" << (m_exit ? m_exit->load() : -1)
                                 << ", total frames processed in this session";
    return;
}
