#include <QPainter>
#include <QPixmap>
#include <unistd.h>
#include <QDebug>
#include <QDateTime>

#include "VideoDisplayGL.h"
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>

#include "VideoDisplayGL.h"
#include "VideoCapture.h"
#include "VideoDecoder.h"
#include "VideoEncoder.h"
#include "CCycleBuffer.h"
#include "V4l2Capture.h"
#include "AudioCapture.h"
#include "kylinlogger_cpp.h"
#include <sys/syscall.h>
#define VERTEXIN 0
#define TEXTUREIN 1
#define gettid() syscall(SYS_gettid)

extern uint32_t current_time_msec(void);


VideoDisplayGL::VideoDisplayGL(QWidget *parent, KylinCameraInfo *device) {
    //rgb_fp = fopen("test.txt", "wb");
    auto start = std::chrono::high_resolution_clock::now();
    program = NULL;
    unsigned long enc_size = device->width * device->height * 3 * 2;
    if (enc_size < 60 * 1024 * 1024)
        enc_size = 60 * 1024 * 1024;
    m_encoder_buf = new CCycleBuffer(enc_size);
    m_audio_buf = new CCycleBuffer(64 * 1024);
    videoCapture = new VideoCapture(m_capture_buf, device);
    if (videoCapture->v4l2Capture == NULL) {
        delete m_encoder_buf;
        delete m_audio_buf;
        return;
    }
    m_rgb_buf = new char[videoCapture->m_width * videoCapture->m_height * 3];
    connect(videoCapture, SIGNAL(readout()), this, SIGNAL(readout()));
    videoCapture->start(QThread::HighPriority);
    videoEncoder = new VideoEncoder(m_encoder_buf, m_audio_buf, videoCapture->m_width, videoCapture->m_height,
                                    videoCapture->m_fps);
    //videoEncoder->encoder_audio_init(16000, 1, 16);
    videoEncoder->start();
    videoDecoder = new VideoDecoder(videoCapture->v4l2Capture->m_capture_buf, m_encoder_buf, videoCapture->m_width,
                                    videoCapture->m_height, videoCapture->m_buf_size,
                                    videoCapture->m_format);

    connect(videoDecoder, SIGNAL(frameReceived(uchar *, uint, uint)), this, SLOT(slotShowYuv(uchar *, uint, uint)));
    connect(videoDecoder , &VideoDecoder::format_fix , videoEncoder , &VideoEncoder::encode_fix);
    videoDecoder->start(QThread::HighestPriority);
    audioCapture = new AudioCapture(m_audio_buf);
    src_w_h_ratio = static_cast<double>(videoCapture->m_width) / static_cast<double>(videoCapture->m_height);
    background_color = QColor(0, 0, 0);
    fps_init();
    m_camera_mode = photo;
    m_start_status = 1;
    m_cover = 0;
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> elapsed  = end - start;

    LOG(CAMERA_LOG, NOTICE) << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:MM:ss:zzz")<<" "<< __func__ <<": totalTimes="<< elapsed.count()/1000.0 <<" done";

}

VideoDisplayGL::~VideoDisplayGL() {
    if (videoCapture->v4l2Capture) {

        m_encoder_buf->Empty();
        m_audio_buf->Empty();
        videoDecoder->m_quit = true;

        //delete audioCapture;
        delete videoCapture;
        delete videoDecoder;
        delete videoEncoder;
        delete m_audio_buf;
        delete m_encoder_buf;
        delete []m_rgb_buf;
        m_rgb_buf = NULL;
    }

    makeCurrent();
    vbo.destroy();
    if (textureY != NULL) {
        delete textureY;
        textureY = NULL;
    }
    if (textureU != NULL) {
        delete textureU;
        textureU = NULL;
    }
    if (textureV != NULL) {
        delete textureV;
        textureV = NULL;
    }

    if (program) {
        program->release();
        delete program;
        program = nullptr;
    }
    doneCurrent();
    LOG(CAMERA_LOG, NOTICE) << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:MM:ss:zzz")<<" " <<__func__<< " exit";
}

void VideoDisplayGL::slotShowYuv(uchar *ptr, uint width, uint height) {
    if (program) {
        yuvPtr = ptr;
        videoW = width;
        videoH = height;
        //qDebug() << "gl: ptr=" << ptr << "width=" << width << "height=" << height;
        update(); //调用update将执行paintEvent函数
    }
}

struct _Point {
    GLfloat x;
    GLfloat y;
};

struct _Rect {
    struct _Point p0;
    struct _Point p1;
    struct _Point p2;
    struct _Point p3;
};

struct _VerticesObj {
    struct _Rect ver; //顶点坐标
    struct _Rect tex; //纹理坐标
};

typedef struct _VerticesObj VerticesObj;

VerticesObj vertices = {
    //顶点坐标
    -1.0f, -1.0f,
    -1.0f, +1.0f,
    +1.0f, +1.0f,
    +1.0f, -1.0f,
    //纹理坐标
    0.0f, 1.0f,
    0.0f, 0.0f,
    1.0f, 0.0f,
    1.0f, 1.0f,
};

/*
GLfloat vertices[]{
    //顶点坐标
    -1.0f,-1.0f,
    -1.0f,+1.0f,
    +1.0f,+1.0f,
    +1.0f,-1.0f,
    //纹理坐标
    0.0f,1.0f,
    0.0f,0.0f,
    1.0f,0.0f,
    1.0f,1.0f,
};
*/

void VideoDisplayGL::initializeGL() {

    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);

    vbo.create();
    vbo.bind();
    vbo.allocate((GLfloat *)&vertices,sizeof(vertices));
    //初始化顶点着色器对象
    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex,this);
    //顶点着色器源码
    const char *vsrc =
   "attribute vec4 vertexIn; \
    attribute vec2 textureIn; \
    varying vec2 textureOut;  \
    void main(void)           \
    {                         \
        gl_Position = vertexIn; \
        textureOut = textureIn; \
    }";

    vshader->compileSourceCode(vsrc);	//编译顶点着色器程序

    //初始化片段着色器 功能gpu中yuv转换成rgb
    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment,this);
    //片段着色器源码
    const char *fsrc =
    "#ifdef GL_ES\n"
    "precision mediump float;\n"
    "#endif\n"
    "varying vec2 textureOut; \
    uniform sampler2D tex_y; \
    uniform sampler2D tex_u; \
    uniform sampler2D tex_v; \
    uniform float cover; \
    void main(void) \
    { \
        vec3 yuv; \
        vec3 rgb; \
        yuv.x = texture2D(tex_y, textureOut).r; \
        yuv.y = texture2D(tex_u, textureOut).r - 0.5; \
        yuv.z = texture2D(tex_v, textureOut).r - 0.5; \
        rgb = mat3( 1,       1,         1, \
                    0,       -0.39465,  2.03211, \
                    1.13983, -0.58060,  0) * yuv; \
        rgb = rgb * (1.0 - cover) + (1.0, 1.0, 1.0) * cover;\
        gl_FragColor = vec4(rgb, 1); \
    }";
    fshader->compileSourceCode(fsrc);	//将glsl源码送入编译器编译着色器程序
    //用于绘制矩形
    program = new QOpenGLShaderProgram(this);

    program->addShader(vshader);	//将顶点着色器添加到程序容器
    program->addShader(fshader);	//将片段着色器添加到程序容器
    //绑定属性vertexIn到指定位置ATTRIB_VERTEX,该属性在顶点着色源码其中有声明
    program->bindAttributeLocation("vertexIn",VERTEXIN);
    //绑定属性textureIn到指定位置ATTRIB_TEXTURE,该属性在顶点着色源码其中有声明
    program->bindAttributeLocation("textureIn",TEXTUREIN);
    program->link();	//链接所有所有添入到的着色器程序
    program->bind();	 //激活所有链接

    program->enableAttributeArray(VERTEXIN);
    program->enableAttributeArray(TEXTUREIN);
    program->setAttributeBuffer(VERTEXIN,GL_FLOAT,0,2,2*sizeof(GLfloat));
    program->setAttributeBuffer(TEXTUREIN,GL_FLOAT,8*sizeof(GLfloat),2,2*sizeof(GLfloat));

    //读取着色器中的数据变量tex_y, tex_u, tex_v的位置,这些变量的声明可以在片段着色器源码中可以看到
    textureUniformY = program->uniformLocation("tex_y");
    textureUniformU = program->uniformLocation("tex_u");
    textureUniformV = program->uniformLocation("tex_v");

    textureUniformC = program->uniformLocation("cover");

    //分别创建y,u,v纹理对象

    textureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
    textureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
    textureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
    textureY->create();
    textureU->create();
    textureV->create();

    idY = textureY->textureId();	//获取返回y分量的纹理索引值
    idU = textureU->textureId();	//获取返回u分量的纹理索引值
    idV = textureV->textureId();	//获取返回v分量的纹理索引值


    float r =  background_color.red() / (float)255;
    float g =  background_color.green() / (float)255;
    float b =  background_color.blue() / (float)255;
    glClearColor(r,g,b,1.0);	//设置背景色为黑色

}

void VideoDisplayGL::paintGL() {
    if (!(yuvPtr && videoW && videoH))
        return;

    //初始化顶点着色器对象
    //QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex,this);

    //------- 加载y数据纹理 -------
    glActiveTexture(GL_TEXTURE0); //激活纹理单元GL_TEXTURE0,系统里面的
    glBindTexture(GL_TEXTURE_2D, idY); //绑定y分量纹理对象id到激活的纹理单元
    //使用内存中的数据创建真正的y分量纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0,GL_RED, videoW, videoH, 0,GL_RED,GL_UNSIGNED_BYTE, yuvPtr);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    //------- 加载u数据纹理 -------
    glActiveTexture(GL_TEXTURE1); //激活纹理单元GL_TEXTURE1
    glBindTexture(GL_TEXTURE_2D, idU);
    //使用内存中的数据创建真正的u分量纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0,GL_RED, videoW >> 1, videoH >> 1, 0,GL_RED,GL_UNSIGNED_BYTE,
                 yuvPtr + videoW * videoH);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    //------- 加载v数据纹理 -------
    glActiveTexture(GL_TEXTURE2); //激活纹理单元GL_TEXTURE2
    glBindTexture(GL_TEXTURE_2D, idV);
    //使用内存中的数据创建真正的v分量纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, videoW >> 1, videoH >> 1, 0, GL_RED, GL_UNSIGNED_BYTE,
                 yuvPtr + videoW * videoH * 5 / 4);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    //指定y纹理要使用新值,只能用0,1,2等表示纹理单元的索引，这是opengl不人性化的地方
    glUniform1i(textureUniformY, 0); //0对应纹理单元GL_TEXTURE0,//指定y纹理要使用新值
    glUniform1i(textureUniformU, 1); //1对应纹理单元GL_TEXTURE1,//指定u纹理要使用新值
    glUniform1i(textureUniformV, 2); //2对应纹理的单元GL_TEXTURE2,指定v纹理要使用新值

    glUniform1f(textureUniformC, m_cover);
    if (m_cover > 0)
        m_cover -= 0.1;

    //使用顶点数组方式绘制图形
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    int old_fps = get_fps();
    fps_handle(current_time_msec());

    //if (fps != old_fps)
    //   qDebug() << "fps=" << fps;
}

void VideoDisplayGL::resizeGL(int w, int h) {
    des_w_h_ratio = static_cast<double>(this->width()) / static_cast<double>(this->height());

    int des_h = this->height(); //窗口高度
    int des_w = this->width(); //窗口寬度

    if (des_w_h_ratio >= src_w_h_ratio) {
        //當窗口的寬高比>圖像的寬高比時圖像縮放後的高爲窗口的高，寬需要經過縮放
        scale_h = des_h;
        scale_w = static_cast<int>(src_w_h_ratio * des_h);
    } else {
        //反之
        scale_w = des_w;
        scale_h = static_cast<int>(des_w / src_w_h_ratio);
    }

    GLfloat left = (-scale_w) / (float) des_w;
    GLfloat right = (scale_w) / (float) des_w;
    GLfloat bottom = (-scale_h) / (float) des_h;
    GLfloat top = (scale_h) / (float) des_h;

    setVboVertices(left, right, bottom, top);

    glViewport(0, 0, w, h);

    float r = background_color.red() / (float) 255;
    float g = background_color.green() / (float) 255;
    float b = background_color.blue() / (float) 255;
    glClearColor(r, g, b, 1.0);
}

void VideoDisplayGL::set_src_ratio(unsigned int width, unsigned int height) {
    src_w_h_ratio = static_cast<double>(width) / static_cast<double>(height);
}

void VideoDisplayGL::set_background_color(QColor color) {
    background_color = color;
}

void VideoDisplayGL::setVboVertices(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top) {
    vertices.ver.p0.x = left;
    vertices.ver.p0.y = bottom;

    vertices.ver.p1.x = left;
    vertices.ver.p1.y = top;

    vertices.ver.p2.x = right;
    vertices.ver.p2.y = top;

    vertices.ver.p3.x = right;
    vertices.ver.p3.y = bottom;

    vbo.release();
    vbo.create();
    vbo.bind();
    vbo.allocate((GLfloat *) &vertices, sizeof(vertices));
}

void VideoDisplayGL::set_cover(float cover) {
    if (cover <= 1 && cover >= 0)
        m_cover = cover;
}

//下面两个函数实现帧率统计
void VideoDisplayGL::fps_init(void) {
    fps_frames_pos = 0;
    memset(fps_frames, 0, sizeof(fps_frames));
}

void VideoDisplayGL::fps_handle(int time) {
    fps_frames[fps_frames_pos] = time;
    if (++fps_frames_pos == FPS_MAX_FRAMES) {
        fps_frames_pos = 0;
    }

    fps = 0;
    for (int i = 0; i < FPS_MAX_FRAMES; ++i) {
        if (abs(time - fps_frames[i] < 1000)) {
            ++fps;
        }
        //qDebug() << "time=" << time << " i=" << i << " time=" << fps_frames[i] << " d" << time - fps_frames[i] << "fps" << fps;
    }
}

int VideoDisplayGL::get_fps(void) { return fps; }

uint32_t current_time_msec(void) {
    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);
    return now.tv_sec * 1000 + now.tv_nsec / 1000000;
}
