#include "VideoDecoder.h"
#include "VideoEncoder.h"
#include "V4l2Device.h"

#include <QDebug>
#include <QDateTime>
#include <turbojpeg.h>
#include <libyuv/convert_argb.h>
#include <libyuv/convert_from.h>
#include <libyuv.h>

#include "CCycleBuffer.h"
//#include "cameraLog.h"
#include "kylinlogger_cpp.h"

#include <unistd.h>
#include <sys/prctl.h>
#include <sys/syscall.h>




VideoDecoder::VideoDecoder(CCycleBuffer *m_capture, CCycleBuffer *m_encoder, int width, int height, int size,
                           int format) {
    m_bFirst = true;
    m_capture_buf = m_capture;
    m_encoder_buf = m_encoder;

    m_dstWidth = m_width = width;
    m_dstHeight = m_height = height;

    m_rgb_size = m_width * m_height * 3;
    m_rgb_buf = new char[m_rgb_size];

    m_yuv_size = m_width * m_height * 2;
    m_yuv_buf[0] = new char[m_yuv_size];
    m_yuv_buf[1] = new char[m_yuv_size];

    m_frame_buf[0] = new char[width * height * 2];
    m_frame_buf[1] = new char[width * height * 2];

    m_jpeg_buf = new char[m_yuv_size];

    m_buf_size = size;
    m_buf = (uint8_t *) new char[size];

    m_frame[0] = QImage(m_width, m_height, QImage::Format_RGB888);
    m_frame[1] = QImage(m_width, m_height, QImage::Format_RGB888);
    m_frame[0].fill(0);
    m_frame[1].fill(0);
    m_frame_flag = 0;

    m_format = format;
    m_stopped = false;
    m_run_stopped = false;

    //m_fp = fopen("yuv1920x1080.yuv", "wb");
    m_photo_clicked = false;
    m_video_clicked = false;

    m_display_mirror = false;
    char name[30] = "kylin.jpg";
    memcpy(m_photo_name, name, strnlen(name,sizeof(name)));
}

VideoDecoder::~VideoDecoder() {
    requestInterruption();
    quit();
    wait();

    if (m_rgb_buf)
        delete []m_rgb_buf;

    if (m_yuv_buf[0])
        delete []m_yuv_buf[0];

    if (m_yuv_buf[1])
        delete []m_yuv_buf[1];

    if (m_jpeg_buf)
        delete []m_jpeg_buf;

    if (m_frame_buf[0])
        delete []m_frame_buf[0];
    if (m_frame_buf[1])
        delete []m_frame_buf[1];

    if (m_buf)
        delete []m_buf;
}

int VideoDecoder::decoder_init(CCycleBuffer *buf, int format, int width, int height, int size) {
    m_capture_buf = buf;
    m_dstWidth = m_width = width;
    m_dstHeight = m_height = height;

    m_rgb_size = m_width * m_height * 3;
    m_rgb_buf = new char[m_rgb_size];

    m_yuv_size = m_width * m_height * 2;
    m_yuv_buf[0] = new char[m_yuv_size];

    m_yuv_buf[1] = new char[m_yuv_size];

    //m_frame_buf[0] = new char[m_yuv_size];
    //m_frame_buf[1] = new char[m_yuv_size];

    m_jpeg_buf = new char[m_yuv_size];

    m_buf_size = size;
    m_buf = (uint8_t *) new char[size];

    m_frame[0] = QImage(m_width, m_height, QImage::Format_RGB888);
    m_frame[1] = QImage(m_width, m_height, QImage::Format_RGB888);
    m_frame[0].fill(0);
    m_frame[1].fill(0);
    m_frame_flag = 0;

    m_format = format;

    return 0;
}

int VideoDecoder::decoder_deinit() {
    if (m_rgb_buf)
        delete []m_rgb_buf;

    if (m_yuv_buf[0])
        delete []m_yuv_buf[0];

    if (m_yuv_buf[1])
        delete []m_yuv_buf[1];

    if (m_jpeg_buf)
        delete []m_jpeg_buf;

    //if(m_frame_buf[0])
    //delete []m_frame_buf[0];

    //if(m_frame_buf[1])
    //delete []m_frame_buf[1];

    if (m_buf)
        delete []m_buf;

    return 0;
}

int VideoDecoder::yuv2Rgb(char *pRGB, char *pYUV, int size) {
    unsigned char y, u, v, y1;
    int r, g, b;
    unsigned int loop = size >> 2;

    while (loop-- > 0) {
        y = *pYUV;
        pYUV++;
        u = *pYUV;
        pYUV++;
        y1 = *pYUV;
        pYUV++;
        v = *pYUV;
        pYUV++;

        r = 1.164 * (y - 16) + 1.596 * (v - 128);
        g = 1.164 * (y - 16) - 0.813 * (v - 128) - 0.392 * (u - 128);
        b = 1.164 * (y - 16) + 2.017 * (u - 128);

        *pRGB = TUNE(r);
        pRGB++;
        *pRGB = TUNE(g);
        pRGB++;
        *pRGB = TUNE(b);
        pRGB++;

        r = 1.164 * (y1 - 16) + 1.596 * (v - 128);
        g = 1.164 * (y1 - 16) - 0.813 * (v - 128) - 0.392 * (u - 128);
        b = 1.164 * (y1 - 16) + 2.017 * (u - 128);
        *pRGB = TUNE(r);
        pRGB++;
        *pRGB = TUNE(g);
        pRGB++;
        *pRGB = TUNE(b);
        pRGB++;
    }
    return 0;
}
#if 0
void VideoDecoder::YUYV_To_RGB(uint8_t *yuv, uint8_t *rgb, int width, int height)
{

    libyuv::YUY2ToI420(yuv, width*2, (uint8_t *)m_420_y, width, (uint8_t *)m_420_u, width/2,
                       (uint8_t *)m_420_v, width/2,width, height);

    libyuv::I420ToRGB24((uint8_t *)m_420_y, width, (uint8_t *)m_420_v, width / 2, (uint8_t *)m_420_u, width / 2,
                        rgb, width * 3, width, height);

}

void VideoDecoder::I420_To_RGB24(uint8_t *yuv, uint8_t *rgb, int width, int height)
{
    static int i=0;
    uint8_t *ybase = yuv;
    uint8_t *ubase = &yuv[width * height];
    uint8_t *vbase = &yuv[width * height * 3 / 2];


    libyuv::I422ToI420(ybase, width, ubase, width / 2, vbase, width / 2,
                       (uint8_t *)m_420_y, width, (uint8_t *)m_420_u, width/2, (uint8_t *)m_420_v, width/2,
                       width, height);


    libyuv::I420ToRGB24((uint8_t *)m_420_y, width, (uint8_t *)m_420_v, width / 2, (uint8_t *)m_420_u, width / 2,
                        rgb, width * 3, width, height);


    if(i++ == 5)
    {
        fwrite(rgb, 1, width*height*3, m_fp);
    }
}
#endif

void VideoDecoder::YUYV_To_I420(uint8_t *src, uint8_t *dst, int width, int height) {
    uint8_t *y = dst;
    uint8_t *u = &dst[width * height];
    uint8_t *v = &dst[width * height / 4 * 5];

    libyuv::YUY2ToI420(src, width * 2, (uint8_t *) y, width, (uint8_t *) u, width / 2,
                       (uint8_t *) v, width / 2, width, height);
}

void VideoDecoder::UYVY_To_I420(uint8_t *src, uint8_t *dst, int width, int height) {
    uint8_t *y = dst;
    uint8_t *u = &dst[width * height];
    uint8_t *v = &dst[width * height / 4 * 5];

    libyuv::UYVYToI420(src, width * 2, (uint8_t *) y, width, (uint8_t *) u, width / 2,
                       (uint8_t *) v, width / 2, width, height);
}

void VideoDecoder::I422_To_I420(uint8_t *src, uint8_t *dst, int width, int height) {
    uint8_t *y = src;
    uint8_t *u = &src[width * height];
    uint8_t *v = &src[width * height * 3 / 2];

    uint8_t *dy = dst;
    uint8_t *du = &dst[width * height];
    uint8_t *dv = &dst[width * height / 4 * 5];

    libyuv::I422ToI420(y, width, u, width / 2, v, width / 2,
                       (uint8_t *) dy, width, (uint8_t *) du, width / 2, (uint8_t *) dv, width / 2,
                       width, height);
}

void VideoDecoder::I420_To_NV12(uint8_t *src, uint8_t *dst, int width, int height) {
    uint8_t *y = src;
    uint8_t *u = &src[width * height];
    uint8_t *v = &src[width * height / 4 * 5];

    uint8_t *dy = dst;
    uint8_t *duv = &dst[width * height];

    libyuv::I420ToNV12(y, width, u, width / 2, v, width / 2,
                       (uint8_t *) dy, width, (uint8_t *) duv, width, width, height);
}

void VideoDecoder::I420_To_RGB(uint8_t *src, uint8_t *dst, int width, int height) {
    uint8_t *y = src;
    uint8_t *u = &src[width * height];
    uint8_t *v = &src[width * height / 4 * 5];
    libyuv::I420ToRGB24((uint8_t *) y, width, (uint8_t *) v, width / 2, (uint8_t *) u, width / 2,
                        dst, width * 3, width, height);
}

void VideoDecoder::mirrorI420(uint8_t *src, uint8_t *dst, int width, int height) {
    uint8_t *y = src;
    uint8_t *u = &src[width * height];
    uint8_t *v = &src[width * height / 4 * 5];

    uint8_t *dy = dst;
    uint8_t *du = &dst[width * height];
    uint8_t *dv = &dst[width * height / 4 * 5];

    libyuv::I420Mirror(y, width, u, width >> 1,
                       v, width >> 1, dy, width,
                       du, width >> 1, dv, width >> 1,
                       width, height);
}

void VideoDecoder::mirrorjpeg(uint8_t *src, int jpeg_size, uint8_t **dst, long unsigned int *dst_size, int width,
                              int height) {
    tjhandle handle = NULL;
    int flags = 0;
    int ret = -1;

    tjtransform xform; //指定transform的参数，如裁剪区域
    memset(&xform, 0, sizeof(tjtransform));
    xform.op = TJXOP_NONE;
    //xform.options=TJXOPT_PERFECT;         //解决420*240照片镜像报错问题
    xform.op |= TJXOP_HFLIP; //水平翻转
    xform.options |= TJXOPT_TRIM;
    xform.customFilter = NULL;

    handle = tjInitTransform();

    ret = tjTransform(handle, src,
                      jpeg_size, 1,
                      dst, (unsigned long *) dst_size,
                      &xform, flags);
    if (ret < 0)
        LOG(CAMERA_LOG, WARN) << "transform jpeg failed " << tjGetErrorStr();

    tjDestroy(handle);
}


int VideoDecoder::jpeg2Yuv2(uint8_t *jpeg_buffer, int jpeg_size, uint8_t *yuv_buffer, int &des_width, int &des_height,
                            int *yuv_size, int *yuv_type)
{
    tjhandle handle = NULL;
    int subsample, colorspace, width, height;
    // int flags = 0;
    int flags = TJFLAG_FASTDCT | TJFLAG_FASTUPSAMPLE;
    int padding = 1;
    int ret = -1;

    handle = tjInitDecompress();
    if (handle == NULL)
    {
        qDebug() << "JPEG decompress error";
        return ret;
    }
    ret = tjDecompressHeader3(handle, jpeg_buffer, jpeg_size, &width, &height, &subsample, &colorspace);
    if (ret < 0)
    {
        qDebug() << "JPEG decompress header error ";
        tjDestroy(handle);
        return ret;
    }
    // qDebug() << "w: " << width << "h: " << height << "subsample " << subsample << "color " << colorspace;

    if (width > 0 && width < 1080 * 8 && height > 0 && height < 1080 * 8 && subsample < 8)
    {
        flags |= 0;

        *yuv_type = subsample;

        *yuv_size = tjBufSizeYUV2(width, padding, height, subsample);
        if (*yuv_size > m_yuv_size)
        {
            qDebug() << "yuv_size =" << *yuv_size << " myuv_size=" << m_yuv_size;
            tjDestroy(handle);
            return -1;
        }
        if (yuv_buffer == NULL)
        {
            LOG(CAMERA_LOG, NOTICE) << "yuv_buffer is NULL";
            // return -1;
        }
        // ret = tjDecompressToYUV2(handle, jpeg_buffer, jpeg_size, yuv_buffer, width, padding, height, flags);
        ret = tjDecompressToYUV2(handle, jpeg_buffer, jpeg_size, yuv_buffer, width, padding, height, flags);


        if (ret < 0)
            LOG(CAMERA_LOG, WARN) << " decompress to yuv failed "<< tjGetErrorStr();
    }
    else
    {
        qDebug() << "w: " << width << "h: " << height << "subsample " << subsample << "color " << colorspace;
    }

    tjDestroy(handle);
    des_width = width;
    des_height = height;
    return ret;
}



int VideoDecoder::jpeg2Yuv(uint8_t *jpeg_buffer, int jpeg_size, uint8_t *yuv_buffer, int *yuv_size, int *yuv_type) {
    tjhandle handle = NULL;
    int width, height, subsample, colorspace;
    int flags = 0;
    int padding = 1;
    int ret = -1;

    handle = tjInitDecompress();
    if (handle == NULL) {
        qDebug() << "JPEG decompress error";
        return ret;
    }
    ret = tjDecompressHeader3(handle, jpeg_buffer, jpeg_size, &width, &height, &subsample, &colorspace);
    if (ret < 0) {
        qDebug() << "JPEG decompress header error " << tjGetErrorCode(handle) << "-"<<tjGetErrorStr();
        tjDestroy(handle);
        return ret;
    }
    //qDebug() << "w: " << width << "h: " << height << "subsample " << subsample << "color " << colorspace;

    if (width > 0 && width < 1080 * 8 && height > 0 && height < 1080 * 8 && subsample < 8) {
        flags |= 0;

        *yuv_type = subsample;

        *yuv_size = tjBufSizeYUV2(width, padding, height, subsample);
        if (*yuv_size > m_yuv_size) {
            qDebug() << "yuv_size =" << *yuv_size << " myuv_size=" << m_yuv_size;
            tjDestroy(handle);
            return -1;
        }
        if (yuv_buffer == NULL) {
            LOG(CAMERA_LOG, NOTICE) << "yuv_buffer is NULL";
            //return -1;
        }
        ret = tjDecompressToYUV2(handle, jpeg_buffer, jpeg_size, yuv_buffer, width,
                                 padding, height, flags);
        if (ret < 0)
            LOG(CAMERA_LOG, WARN) << " decompress to yuv failed " << tjGetErrorStr();
    } else {
        qDebug() << "w: " << width << "h: " << height << "subsample " << subsample << "color " << colorspace;
    }

    tjDestroy(handle);

    return ret;
}

int VideoDecoder::yuv2jpeg(uint8_t *yuv_buffre, int yuv_size, int width, int height, int subsample,
                           uint8_t **jpeg_buffer, unsigned long *jpeg_size, int quality) {
    tjhandle handle = NULL;
    int flags = 0;
    int padding = 1;
    int need_size = 0;
    int ret = 0;

    handle = tjInitCompress();

    flags |= 0;

    need_size = tjBufSizeYUV2(width, padding, height, subsample);
    if (need_size != yuv_size) {
        LOG(CAMERA_LOG, NOTICE) << "need_size " << need_size << "yuv_size " << yuv_size;
        return 0;
    }

    ret = tjCompressFromYUV(handle, yuv_buffre, width, padding, height,
                            subsample, jpeg_buffer, jpeg_size, quality, flags);
    if (ret < 0) {
        LOG(CAMERA_LOG, NOTICE) << "compress to jpeg failed " << tjGetErrorStr();
    }

    tjDestroy(handle);
    return ret;
}

void VideoDecoder::takePhoto(uint8_t *buf, int size, int format) {
    FILE *fp = NULL;
    unsigned long jpeg_size;
    LOG(CAMERA_LOG, NOTICE) << "take photo";

    yuv2jpeg((uint8_t *) buf, size, m_dstWidth, m_dstHeight, TJSAMP_420, (uint8_t **) &m_jpeg_buf, &jpeg_size, 100);
    fp = fopen(m_photo_name, "wb");
    if (fp) {
        fwrite(m_jpeg_buf, 1, jpeg_size, fp);
        fclose(fp);
    } else {
        LOG(CAMERA_LOG, ERROR) << "take photo open file error";
        emit photoError();
        return;
    }
}

void VideoDecoder::run() {
    int ret = 0;
    int decode_size = 0;
    uint8_t *yuv_buf;
    int index;
    int yuv420_size = m_width * m_height * 3 / 2;
    m_dstWidth = m_width;
    m_dstHeight = m_height;
    while (!isInterruptionRequested()) {
        m_mutex.lock();
        if (m_quit == false && m_capture_buf->isEmpty() == false) {
            index = 0;
            yuv_buf = (uint8_t *) m_yuv_buf[index];

            //qDebug() << "m_capture_buf size " << m_capture_buf->GetLength();
            m_capture_buf->Read(m_buf, m_buf_size);
            if (m_format == V4L2_PIX_FMT_YUYV) {
                YUYV_To_I420((uint8_t *) m_buf, (uint8_t *) yuv_buf, m_dstWidth, m_dstHeight);
            } else if (m_format == V4L2_PIX_FMT_UYVY) {
                UYVY_To_I420((uint8_t *) m_buf, (uint8_t *) yuv_buf, m_dstWidth, m_dstHeight);
            } else if (m_format == V4L2_PIX_FMT_MJPEG) {
                ret = jpeg2Yuv2((uint8_t *) m_buf, m_buf_size, (uint8_t *) yuv_buf, m_dstWidth, m_dstHeight,
                                &decode_size, &m_yuv_type);
                if (ret < 0 ) {m_mutex.unlock();continue;}
                if (m_yuv_type == TJSAMP_422) {
                    index = !index;
                    I422_To_I420((uint8_t *) yuv_buf, (uint8_t *) m_yuv_buf[index], m_dstWidth, m_dstHeight);
                    yuv_buf = (uint8_t *) m_yuv_buf[index];
                }
            }else if (m_format == V4L2_PIX_FMT_GREY){
                memset(yuv_buf, 128, m_yuv_size);
                memcpy(yuv_buf, m_buf, m_buf_size);
            }
            yuv420_size = m_dstWidth * m_dstHeight * 3 / 2;
            if (m_bFirst == true){
                if (m_dstWidth != m_width || m_dstHeight != m_height){
                    emit format_fix(m_dstWidth, m_dstHeight);
                }
                m_bFirst = false;
            }
            if (m_display_mirror == true) {
                index = !index;
                mirrorI420((uint8_t *) yuv_buf, (uint8_t *) m_yuv_buf[index], m_dstWidth, m_dstHeight);
                yuv_buf = (uint8_t *) m_yuv_buf[index];
            }

            //if(ret >= 0)
            {
                //I420_To_RGB((uint8_t *)yuv_buf, (uint8_t *)m_rgb_buf, m_width, m_height);

                m_frame_flag ^= 1;
                //qDebug() << "m_frame_flag = " << m_frame_flag;

                memcpy(m_frame_buf[m_frame_flag], yuv_buf, m_yuv_size);
                //I420_To_RGB((uint8_t *)yuv_buf, (uint8_t *)m_frame[m_frame_flag].bits(), m_width, m_height);
                //QImage frame((unsigned char*)m_rgb_buf, m_width, m_height, QImage::Format_RGB888);
                //m_pixmap = QPixmap::fromImage(m_frame);

                emit frameReceived((uchar *) m_frame_buf[m_frame_flag], m_dstWidth, m_dstHeight);

                if (m_photo_clicked) {
                    takePhoto((uint8_t *) yuv_buf, yuv420_size, m_format);
                    m_photo_clicked = false;
                }

                if (m_video_clicked) {
                    index = !index;
                    I420_To_NV12((uint8_t *) yuv_buf, (uint8_t *) m_yuv_buf[index], m_dstWidth, m_dstHeight);
                    yuv_buf = (uint8_t *) m_yuv_buf[index];
                    if (m_encoder_buf->GetFreeLength() > yuv420_size)
                        m_encoder_buf->Write((uint8_t *) yuv_buf, yuv420_size);
                    else
                        LOG(CAMERA_LOG, NOTICE) << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:MM:ss:zzz")<<" "<< __func__ <<"m_encoder_buf length="<<m_encoder_buf->GetLength() << " free_length="<<m_encoder_buf->GetFreeLength();
                    m_video_start = true;
                }
                //qDebug() << "size " << m_encoder_buf->GetLength() << "m_420_size " << m_420_size;
            }
        }
        if (m_stopped == false)
            m_mutex.unlock();
        else {
            m_run_stopped = true;
            emit runingStop();
        }

        usleep(1 * 1000);
    }
}
