/***************************************************************************
 *   Copyright (C) 2017-2024 by Ilya Kotov                                 *
 *   forkotov02@ya.ru                                                      *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

#include <qmmp/qmmp.h>
#include "ffvideodecoder.h"

FFVideoDecoder::FFVideoDecoder()
{}

FFVideoDecoder::~FFVideoDecoder()
{
    if(m_audioCodecContext)
        avcodec_free_context(&m_audioCodecContext);
    if(m_videoCodecContext)
        avcodec_free_context(&m_videoCodecContext);
    if(m_formatContext)
        avformat_free_context(m_formatContext);
}

bool FFVideoDecoder::initialize(const QString &path)
{
    int err = 0;
    char errbuf[AV_ERROR_MAX_STRING_SIZE] = {0};

#ifdef Q_OS_WIN
    if((err = avformat_open_input(&m_formatContext, path.toUtf8().constData(), nullptr, nullptr)) != 0)
#else
    if((err = avformat_open_input(&m_formatContext, path.toLocal8Bit().constData(), nullptr, nullptr)) != 0)
#endif
    {
        av_strerror(err, errbuf, sizeof(errbuf));
        qCWarning(plugin) << "avformat_open_input() failed:" << errbuf;
        return false;
    }
    if((err = avformat_find_stream_info(m_formatContext, nullptr)) < 0)
    {
        av_strerror(err, errbuf, sizeof(errbuf));
        qCWarning(plugin) << "avformat_find_stream_info() failed:" << errbuf;
        return false;
    }

    av_dump_format(m_formatContext,0,nullptr,0);

    m_audioIndex = av_find_best_stream(m_formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
    m_videoIndex = av_find_best_stream(m_formatContext, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);

    //select default stream for audio
    for(unsigned int i = 0; i < m_formatContext->nb_streams; ++i)
    {
        if(m_formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
                m_formatContext->streams[i]->disposition & AV_DISPOSITION_DEFAULT)
        {
            m_audioIndex = i;
            break;
        }
    }

    if((err = m_audioIndex) < 0)
    {
        av_strerror(err, errbuf, sizeof(errbuf));
        qCWarning(plugin) << "unable to find audio stream:" << errbuf;
        return false;
    }
    if((err = m_videoIndex) < 0)
    {
        av_strerror(err, errbuf, sizeof(errbuf));
        qCWarning(plugin) << "unable to find video stream:" << errbuf;
        return false;
    }

    const AVCodec *audioCodec = avcodec_find_decoder (m_formatContext->streams[m_audioIndex]->codecpar->codec_id);
    const AVCodec *videoCodec = avcodec_find_decoder (m_formatContext->streams[m_videoIndex]->codecpar->codec_id);

    if(!audioCodec || !videoCodec)
    {
        qCWarning(plugin) << "unable to find codec";
        return false;
    }

    m_audioCodecContext = avcodec_alloc_context3(nullptr);
    avcodec_parameters_to_context (m_audioCodecContext, m_formatContext->streams[m_audioIndex]->codecpar);

    m_videoCodecContext = avcodec_alloc_context3(nullptr);
    avcodec_parameters_to_context (m_videoCodecContext, m_formatContext->streams[m_videoIndex]->codecpar);

    if ((err = avcodec_open2(m_audioCodecContext, audioCodec, nullptr)) < 0)
    {
        av_strerror(err, errbuf, sizeof(errbuf));
        qCWarning(plugin) << "avcodec_open2() failed:" << errbuf;
        return false;
    }

    if ((err = avcodec_open2(m_videoCodecContext, videoCodec, nullptr)) < 0)
    {
        av_strerror(err, errbuf, sizeof(errbuf));
        qCWarning(plugin) << "avcodec_open2() failed:" << errbuf;
        return false;
    }

    m_totalTime = m_formatContext->duration * 1000 / AV_TIME_BASE;
    return true;
}

qint64 FFVideoDecoder::totalTime() const
{
    return m_totalTime;
}

void FFVideoDecoder::seek(qint64 time)
{
    int64_t timestamp = int64_t(time) * AV_TIME_BASE / 1000;

    if (m_formatContext->start_time != (qint64)AV_NOPTS_VALUE)
        timestamp += m_formatContext->start_time;

    av_seek_frame(m_formatContext, -1, timestamp, AVSEEK_FLAG_BACKWARD);
    avcodec_flush_buffers(m_audioCodecContext);
    avcodec_flush_buffers(m_videoCodecContext);
}

AVFormatContext *FFVideoDecoder::formatContext() const
{
    return m_formatContext;
}

AVCodecContext *FFVideoDecoder::audioCodecContext() const
{
    return m_audioCodecContext;
}

AVCodecContext *FFVideoDecoder::videoCodecContext() const
{
    return m_videoCodecContext;
}

int FFVideoDecoder::audioIndex() const
{
    return m_audioIndex;
}

int FFVideoDecoder::videoIndex() const
{
    return m_videoIndex;
}
