/*
 * Kylin-video
 *
 * Copyright (C) 2021, Tianjin KYLIN Information Technology Co., Ltd.
 *
 * 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 3 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, see <https://www.gnu.org/licenses/>.
 *
 * Authors: Liu Cong <liucong1@kylinos.cn>
 *
 */

#include "ffutil.h"

#include <QDateTime>
#include <QProcess>
#include <QDebug>
#include <QImage>

#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h> //封装格式
#include <libavcodec/avcodec.h>   //解码
#include <libswscale/swscale.h>   //缩放
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
}
#endif

#include "elog.h"
#define LOG_TAG "ffutil"

FFUtil::FFUtil(QObject *parent) : QObject(parent)
{
    m_videoTbr = new VideoThumbnailer;
    m_videoTbr->setThumbnailSize(176);
    pFormatCtx = nullptr;
    pCodecCtx = nullptr;
}

FFUtil::~FFUtil()
{
    close();
}

int FFUtil::open(QString _file)
{
    m_fileName = _file;

    if(_file.length() == 0)
        return -1;
    if(pFormatCtx)
        avformat_close_input(&pFormatCtx);
    if(pCodecCtx)
        avcodec_free_context(&pCodecCtx);

    videoDuration   = 0;
    videoStream     = -1;
    pFormatCtx      = nullptr;
    pCodecCtx       = nullptr;
    AVDictionary *opts = nullptr;
    int t_seekTime = 0;
    if (avformat_open_input(&pFormatCtx, _file.toStdString().c_str(), 0, &opts) != 0)
    {
        log_e("Can't open the file.");
        printf("can't open the file.");
        return -1;
    }

    // 找流信息
    if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
    {
        log_e("Couldn't find stream information.");
        printf("Couldn't find stream information.");
        return -1;
    }

    videoStream = -1;
    videoStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (videoStream < 0)
    {
        log_e("Can't find a video stream.");
        return -1;
    }

    pCodec = avcodec_find_decoder_by_name(avcodec_get_name(pFormatCtx->streams[videoStream]->codecpar->codec_id));
    if(!pCodec) {
        pCodec = avcodec_find_decoder(pFormatCtx->streams[videoStream]->codecpar->codec_id);
        if(!pCodec)
        {
            log_e("find decodec error");
            return -1;
        }
    }

    pCodecCtx = avcodec_alloc_context3(pCodec);
    if(!pCodecCtx)
    {
        log_e("Codec context alloc error.");
        return -1;
    }

    avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar);

    int re = avcodec_open2(pCodecCtx, pCodec, 0);
    if (re != 0)
    {
        log_e("Open decodec error.");
        avcodec_free_context(&pCodecCtx);
        return -1;
    }

    // 可能前几帧是黑屏，所以向后跳转
    videoDuration = pFormatCtx->duration/1000000;
    t_seekTime = fitTime(videoDuration);
    if(t_seekTime > 0)
    {
        int re = av_seek_frame(pFormatCtx, -1, t_seekTime*AV_TIME_BASE, 0);
        if(re < 0) {
            log_e("get view seek error.");
        }
        avcodec_flush_buffers(pCodecCtx);
    }

    return 0;
}

int FFUtil::getDuration()
{
    if(!pFormatCtx)
        return 0;
    return pFormatCtx->duration/1000000;
}

void FFUtil::close()
{
    if(pCodecCtx)
    {
        avcodec_free_context(&pCodecCtx);
    }
}

void FFUtil::saveIFrame(QString _savePath)
{
    // 如果用接口的话没有视频流会导致崩溃
//    m_videoTbr->generateThumbnail(m_fileName.toStdString(), Png, _savePath.toStdString());

    QProcess p;
    p.start(QString("ffmpegthumbnailer -i %1 -o %2").arg("\"" + m_fileName + "\"").arg(_savePath));
    p.waitForFinished();
    close();
    return;

    AVFrame *pFrame     = nullptr;
    AVFrame *pFrameRGB  = nullptr;
    uint8_t *outBuffer  = nullptr;
    AVPacket *packet    = nullptr;
    int numBytes = 0;

    if(videoStream < 0)
        return;

    if(!pCodecCtx)
    {
        qDebug() << "codec context is nullptr!";
        return;
    }
    pFrame      = av_frame_alloc();
    pFrameRGB   = av_frame_alloc();

    numBytes    = av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height, 16);

    outBuffer   = (uint8_t *)av_malloc(numBytes);

    av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, outBuffer, AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1);

    packet = av_packet_alloc();
    av_init_packet(packet);
    int tryNum = 0;
    while (true) {
        if (av_read_frame(pFormatCtx, packet) < 0)//从流中读取读取数据到Packet中
        {
            log_e("read end, but no frame get");
            break; //这里认为视频读取完了
        }
        if (packet->stream_index != videoStream) {
            av_packet_unref(packet);
            continue;
        }
//        if (packet->stream_index == videoStream)
        {
            // 解码
            avcodec_send_packet(pCodecCtx, packet);
            // 获取解码数据
            int ret = avcodec_receive_frame(pCodecCtx, pFrame);
            if (ret < 0 || !(pFrame->flags & AV_FRAME_FLAG_KEY))
            {
                if(tryNum > 2000)
                {
                    break;
                }
                // 最多尝试解码 200 帧，如果没获取到正确的帧就不要预览图了，否则解码占用时间过长，后面放到线程里面去就可以一直解码了。
                log_e("get view frame error, try %d", tryNum);
                tryNum++;
                continue;
            }
            SwsContext *img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
                                                        (AVPixelFormat)pFrame->format, pCodecCtx->width, pCodecCtx->height,
                                                        AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);
            sws_scale(img_convert_ctx, (const uint8_t *const*)pFrame->data,
                      pFrame->linesize, 0, pCodecCtx->height,
                      pFrameRGB->data, pFrameRGB->linesize);
            sws_freeContext(img_convert_ctx);
            {
                QImage img((uchar *)pFrameRGB->data[0], pCodecCtx->width, pCodecCtx->height, QImage::Format_RGB32);
                img.save(_savePath);
                log_i("Save preview image ok. [%s:%s]", pFormatCtx->url, _savePath);
                break;
            }
        }
    }
    free(outBuffer);
    av_packet_free(&packet);
    av_frame_free(&pFrame);
    av_frame_free(&pFrameRGB);
    avformat_flush(pFormatCtx);
    avcodec_flush_buffers(pCodecCtx);
}

int FFUtil::fitTime(int _duration)
{
    if(_duration < 1)
        return 0;
    else if(_duration < 5)
        return 1;
    else
        return 3;
}
