#ifndef XCODEC_CPP
#define XCODEC_CPP

#include "XCodec.h"

#include <QDebug>


#define STREAM_DURATION   5.0
#define STREAM_FRAME_RATE 24
#define STREAM_NB_FRAMES  ((int)(STREAM_DURATION * STREAM_FRAME_RATE))
#define STREAM_PIX_FMT    AV_PIX_FMT_YUV420P /* default pix_fmt */
#define OUTPUT_CODEC AV_CODEC_ID_H264

int XCodec::s_frameCount = 0;

XCodec::XCodec( const char* filename ) :
    m_filename( filename ),
    m_encoder( avcodec_find_encoder( OUTPUT_CODEC ))
{
    av_log_set_level(AV_LOG_VERBOSE);

    int ret(0);


    // allocate the output media context
    ret = avformat_alloc_output_context2( &m_formatCtx, m_outputFormat, NULL, m_filename.c_str());

    if (!m_formatCtx)
        return;

    m_outputFormat = m_formatCtx->oformat;

    // Add the video stream using H264 codec
    add_stream();

    // Open video codec and allocate the necessary encode buffers
    if (m_streamOut)
        openVideoCodec();

    // Print detailed information about input and output
    av_dump_format( m_formatCtx, 0, m_filename.c_str(), 1);

    // Open the output media file, if needed
    if (!( m_outputFormat->flags & AVFMT_NOFILE))
    {
        ret = avio_open( &m_formatCtx->pb, m_filename.c_str(), AVIO_FLAG_WRITE);

        if (ret < 0)
        {
            char error[255];
            ret = av_strerror( ret, error, 255);
            fprintf(stderr, "Could not open '%s': %s\n", m_filename.c_str(), error);
            return ;
        }
    }
    else
    {
        return;
    }

    // Write media header
    ret = avformat_write_header( m_formatCtx, NULL );

    if (ret < 0)
    {
        char error[255];
        av_strerror(ret, error, 255);
        fprintf(stderr, "Error occurred when opening output file: %s\n", error);
        return;
    }

    if ( m_frame )
           m_frame->pts = 0;
}



XCodec::~XCodec()
{}

/* Add an output stream. */
void XCodec::add_stream()
{
    AVCodecID codecId = OUTPUT_CODEC;

    if (!( m_encoder ))
    {
        fprintf(stderr, "Could not find encoder for '%s'\n",
            avcodec_get_name(codecId));
        return;
    }

    // Get the stream for codec
    m_streamOut = avformat_new_stream(m_formatCtx, m_encoder);

    if (!m_streamOut) {
        fprintf(stderr, "Could not allocate stream\n");
        return;
    }

    m_streamOut->id = m_formatCtx->nb_streams - 1;

    m_codecCtx = avcodec_alloc_context3( m_encoder);

    switch (( m_encoder)->type)
    {
    case AVMEDIA_TYPE_VIDEO:
        m_streamOut->codecpar->codec_id = codecId;
        m_streamOut->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
        m_streamOut->codecpar->bit_rate = 400000;
        m_streamOut->codecpar->width = 800;
        m_streamOut->codecpar->height = 640;
        m_streamOut->codecpar->format = STREAM_PIX_FMT;
        m_streamOut->time_base = { 1, STREAM_FRAME_RATE };

        avcodec_parameters_to_context( m_codecCtx, m_streamOut->codecpar);

        m_codecCtx->gop_size = 12; /* emit one intra frame every twelve frames at most */
        m_codecCtx->max_b_frames = 1;
        m_codecCtx->time_base = { 1, STREAM_FRAME_RATE };
        m_codecCtx->framerate = { STREAM_FRAME_RATE, 1 };
        m_codecCtx->pix_fmt = STREAM_PIX_FMT;
        m_codecCtx->profile = FF_PROFILE_H264_HIGH;

        break;

    default:
        break;
    }

    if (m_streamOut->codecpar->codec_id == OUTPUT_CODEC)
    {
      av_opt_set( m_codecCtx, "preset", "ultrafast", 0 );
    }

/
//    /* Some formats want stream headers to be separate. */
    if (m_formatCtx->oformat->flags & AVFMT_GLOBALHEADER)
            m_codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;


    int ret = avcodec_parameters_from_context( m_streamOut->codecpar, m_codecCtx );

    if (ret < 0)
    {
        char error[255];
        av_strerror(ret, error, 255);
        fprintf(stderr, "avcodec_parameters_from_context returned (%d) - %s", ret, error);
        return;
    }
}


void XCodec::openVideoCodec()
{
    int ret;

    /* open the codec */
    ret = avcodec_open2(m_codecCtx, m_encoder, NULL);

    if (ret < 0)
    {
        char error[255];
        av_strerror(ret, error, 255);
        fprintf(stderr, "Could not open video codec: %s\n", error);
        return;
    }

    /* allocate and init a re-usable frame */
//    m_frame = av_frame_alloc();

}


void XCodec::encodeImage(const Image &image)
{
    // Compute video time from last added video frame
    m_timeVideo = image.timeStamp(); //(double)m_frame->pts) * av_q2d(m_streamOut->time_base);

    // Stop media if enough time
    if (!m_streamOut /*|| m_timeVideo >= STREAM_DURATION*/)
       return;


    // Add a video frame
    write_video_frame( image );

}


void XCodec::write_video_frame( const Image& image )
{
    int ret;

qDebug() << "image num " << image.uniqueImageNumber() << " " << s_frameCount;

    if ( s_frameCount >= STREAM_NB_FRAMES)
    {
        /* No more frames to compress. The codec has a latency of a few
         * frames if using B-frames, so we get the last frames by
         * passing the same picture again. */
        int p( 0 ) ;
    }
    else
    {
         createFrame( image );
    }

    // Increase frame pts according to time base
//    m_frame->pts += av_rescale_q(1, m_codecCtx->time_base, m_streamOut->time_base);
    m_frame->pts = int64_t( image.timeStamp()) ;


    if (m_formatCtx->oformat->flags & 0x0020 )
    {
        /* Raw video case - directly store the picture in the packet */
        AVPacket pkt;
        av_init_packet(&pkt);

        pkt.flags |= AV_PKT_FLAG_KEY;
        pkt.stream_index = m_streamOut->index;
        pkt.data = m_frame->data[0];
        pkt.size = sizeof(AVPicture);

//        ret = av_interleaved_write_frame(m_formatCtx, &pkt);
        ret = av_write_frame( m_formatCtx, &pkt );
    }
    else
    {
        AVPacket pkt;
        av_init_packet(&pkt);

        /* encode the image */
        ret = avcodec_send_frame(m_codecCtx, m_frame);

        if (ret < 0)
        {
            char error[255];
            av_strerror(ret, error, 255);
            fprintf(stderr, "Error encoding video frame: %s\n", error);
            return;
        }

        /* If size is zero, it means the image was buffered. */
        ret = avcodec_receive_packet(m_codecCtx, &pkt);

        if( !ret && pkt.size)
        {
qDebug() << "write frame " << m_frame->display_picture_number;
            pkt.stream_index = m_streamOut->index;

            /* Write the compressed frame to the media file. */
//            ret = av_interleaved_write_frame(m_formatCtx, &pkt);
            ret = av_write_frame( m_formatCtx, &pkt );
        }
        else
        {
            ret = 0;
        }
    }

    if (ret != 0)
    {
        char error[255];
        av_strerror(ret, error, 255);
        fprintf(stderr, "Error while writing video frame: %s\n", error);
        return;
    }

    s_frameCount++;
}


void XCodec::createFrame( const Image& image /*, AVFrame *m_frame, int frame_index, int width, int height*/)
{
    /**
     * \note allocate frame
     */
    m_frame = av_frame_alloc();
    int ret = av_frame_make_writable( m_frame );

    m_frame->format = STREAM_PIX_FMT;
    m_frame->width = image.width();
    m_frame->height = image.height();
//    m_frame->pict_type = AV_PICTURE_TYPE_I;
    m_frame->display_picture_number = image.uniqueImageNumber();

    ret = av_image_alloc(m_frame->data, m_frame->linesize, m_frame->width,  m_frame->height, STREAM_PIX_FMT, 1);

    if (ret < 0)
    {
        return;
    }

    struct SwsContext* sws_ctx = sws_getContext((int)image.width(), (int)image.height(), AV_PIX_FMT_RGB24,
                                                (int)image.width(), (int)image.height(), STREAM_PIX_FMT, 0, NULL, NULL, NULL);

    const uint8_t* rgbData[1] = { (uint8_t* )image.getData() };
    int rgbLineSize[1] = { 3 * image.width() };

    sws_scale(sws_ctx, rgbData, rgbLineSize, 0, image.height(), m_frame->data, m_frame->linesize);

//cv::Mat yuv420p( m_frame->height + m_frame->height/2, m_frame->width, CV_8UC1, m_frame->data[0]);
//cv::Mat cvmIm;
//cv::cvtColor(yuv420p,cvmIm,CV_YUV420p2BGR);
//std::ostringstream ss;
//ss << "c:\\tmp\\YUVoriginal_" << image.uniqueImageNumber() << ".png";
//cv::imwrite( ss.str().c_str(), cvmIm);
}


void XCodec::close()
{
    /* reset the framecount */
    s_frameCount = 0 ;

    int ret( 0 );

    /* flush the encoder */
    while( ret >= 0 )
        ret = avcodec_send_frame(m_codecCtx, NULL);

    // Write media trailer
    if( m_formatCtx )
        ret = av_write_trailer( m_formatCtx );

    /* Close each codec. */
    if ( m_streamOut )
    {
        if( m_frame )
        {
            av_free( m_frame->data[0]);
            av_frame_free( &m_frame );
        }

        if( m_packet )
            av_packet_free( &m_packet );
    }

    if (!( m_outputFormat->flags & AVFMT_NOFILE))
        /* Close the output file. */
        ret = avio_close( m_formatCtx->pb);


    /* free the stream */
    avformat_free_context( m_formatCtx );

    fflush( stdout );
}


#endif // XCodec_CPP
