#include "qthcamera.h"

QThCamera::QThCamera(QObject *parent) : QThread(parent) {
    MainForm= (QFMainForm*)parent;
    DoStart= true;
}

QThCamera::~QThCamera() {
    DoStart= false;
    while (this->isRunning()) msleep(10);
    qDebug() << "QThCamera::~QThCamera()";
}

void QThCamera::IplImageToAVFrame(IplImage* iplImage, AVFrame* avFrame, int Width, int Height, enum PixelFormat pix_fmt) {
    int linesize[4]= {0, 0, 0, 0};
    struct SwsContext* img_convert_ctx= sws_getContext(iplImage->width, iplImage->height, PIX_FMT_BGR24, Width, Height, pix_fmt, SWS_BICUBIC, 0, 0, 0);
    if (img_convert_ctx!= 0) {
        linesize[0]= 3 * iplImage->width;
        sws_scale(img_convert_ctx, (uint8_t**)&iplImage->imageData, linesize, 0, iplImage->height, avFrame->data, avFrame->linesize);
        sws_freeContext(img_convert_ctx);
    }
}

typedef struct DynBuffer {
    int pos, size, allocated_size;
    uint8_t *buffer;
    int io_buffer_size;
    uint8_t io_buffer[1];
} DynBuffer;

void QThCamera::run() {
    qDebug() << "QThCamera::run() start";
    QString MPGFileName= "a.mp4";

    CvCapture *Capture= NULL;
    Capture= cvCreateCameraCapture(Index, Width, Height);
    if (!Capture) qDebug() << "cvCreateCameraCapture Error!";
    else {
        if (ExternalFrame) cvNamedWindow("Frame", CV_WINDOW_AUTOSIZE);
        IplImage *frame= 0;
        CvFont font;
        cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX, 0.5f, 0.5f);

        AVOutputFormat *pOutputFormat= av_guess_format("mp4", NULL, NULL); // CODEC_ID_H264 -> mp4, rawvideo; CODEC_ID_THEORA -> ogg; CODEC_ID_MPEG4 -> mpegts, rawvideo; CODEC_ID_VP8 -> webm
        if (!pOutputFormat) {
            qDebug() << "Could not set output format, using MPEG.";
            pOutputFormat= av_guess_format("mpeg", NULL, NULL);
        }
        if (!pOutputFormat) qDebug() << "av_guess_format Error!";
        else {
            AVFormatContext *pFormatCtx;
            if (avformat_alloc_output_context2(&pFormatCtx, pOutputFormat, NULL, NULL)< 0) qDebug() << "avformat_alloc_output_context2 Error!";
            else {
                AVStream *pVideoStream= av_new_stream(pFormatCtx, 0);
                if (!pVideoStream) qDebug() << "av_new_stream Error!";
                else {
                    AVCodecContext *pCodecCtx= pVideoStream->codec;
                    pCodecCtx->codec_id= CODEC_ID_H264;
                    pCodecCtx->codec_type= AVMEDIA_TYPE_VIDEO;
                    pCodecCtx->bit_rate= 40000;
                    pCodecCtx->width= Width;
                    pCodecCtx->height= Height;
                    pCodecCtx->time_base.den= 25;
                    pCodecCtx->time_base.num= 1;
                    pCodecCtx->gop_size= 10;
                    pCodecCtx->pix_fmt= PIX_FMT_YUV420P;
                    if (pFormatCtx->oformat->flags & AVFMT_GLOBALHEADER) pCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
                    av_dump_format(pFormatCtx, 0, NULL, 1);
                    AVCodec *pCodec= avcodec_find_encoder(pCodecCtx->codec_id);
                    if (!pCodec) qDebug() << "avcodec_find_encoder Error!";
                    else {
                        if (avcodec_open(pCodecCtx, pCodec)< 0) qDebug() << "avcodec_open Error!";
                        else {
                            QFile QFMPGFileOut;
                            QFMPGFileOut.setFileName(MPGFileName);
                            if (QFMPGFileOut.open(QIODevice::WriteOnly)) {

                                if (avio_open_dyn_buf(&pFormatCtx->pb)< 0) qDebug() << "avio_open Error!";
                                else {
                                    if (avformat_write_header(pFormatCtx, NULL)!= 0) qDebug() << "av_write_header Error!";
                                    int BYTEPIC= Width * Height * 3;
                                    uint8_t *pOutBuffer= (uint8_t*)malloc(BYTEPIC); {
                                        int Frames= 0;
                                        QDateTime QDTStart= QDateTime::currentDateTime();
                                        while (DoStart) {
                                            if (!cvSetChannel(Capture, Channel)) qDebug() << "cvSetChannel Error!";
                                            frame= cvQueryFrame(Capture);
                                            if (frame) {
                                                if (MainForm->ui->QCBAutoBrightness->isChecked()) {
                                                    int Brightness= BrightnessOfAPixel(frame);
                                                    Brightness= ((Brightness * 200 / 256) - 100) * -1;
                                                    IplImage *frame2= ContrastBrightness(frame, 0, Brightness); {
                                                        cvCopyImage(frame2, frame);
                                                    }{
                                                        cvReleaseImage(&frame2);
                                                    }
                                                } else {
                                                    IplImage *frame2= ContrastBrightness(frame, MainForm->ui->QHSContrast->value(), MainForm->ui->QHSBrightness->value()); {
                                                        cvCopyImage(frame2, frame);
                                                    }{
                                                        cvReleaseImage(&frame2);
                                                    }
                                                }
                                                if (MainForm->ui->QCBShowDateTime->isChecked()) cvPutText(frame, QDateTime::currentDateTime().toString("dd-MM-yyyy hh:mm:ss").toAscii(), cvPoint(10, 20), &font, cvScalar(255, 255, 255));
                                                if (MainForm->ui->QRBRotate90->isChecked()) {
                                                    IplImage *frame2= Rotate(frame, 90); {
                                                        cvCopyImage(frame2, frame);
                                                    }{
                                                        cvReleaseImage(&frame2);
                                                    }
                                                } else if (MainForm->ui->QRBRotate180->isChecked()) {
                                                    IplImage *frame2= Rotate(frame, 180); {
                                                        cvCopyImage(frame2, frame);
                                                    }{
                                                        cvReleaseImage(&frame2);
                                                    }
                                                } else if (MainForm->ui->QRBRotate270->isChecked()) {
                                                    IplImage *frame2= Rotate(frame, 270); {
                                                        cvCopyImage(frame2, frame);
                                                    }{
                                                        cvReleaseImage(&frame2);
                                                    }
                                                }
                                                if (ExternalFrame) cvShowImage("Frame", frame);
                                                QImage Image(frame->width, frame->height, QImage::Format_RGB888);
                                                const unsigned char *data= NULL;
                                                data= (unsigned char*)(frame->imageData);
                                                Image= QImage(data, frame->width, frame->height, frame->widthStep, QImage::Format_RGB888);
                                                if (DoStart) emit SendNewImage(Image);

                                                AVFrame *pAVFrame= avcodec_alloc_frame();
                                                uint8_t *pBuffer= (uint8_t*)malloc(avpicture_get_size(PIX_FMT_YUV420P, Width, Height)); {
                                                    avpicture_fill((AVPicture*)pAVFrame, pBuffer, PIX_FMT_YUV420P, Width, Height);
                                                    IplImageToAVFrame(frame, pAVFrame, Width, Height, PIX_FMT_YUV420P);
                                                    pAVFrame->pts= Frames;
                                                    int OutSize= avcodec_encode_video(pCodecCtx, pOutBuffer, BYTEPIC, pAVFrame);
                                                    if (OutSize> 0) {
                                                        AVPacket Packet;
                                                        av_init_packet(&Packet);
                                                        //Packet.pts= Frames;
                                                        if (pCodecCtx->coded_frame->pts != AV_NOPTS_VALUE) {
                                                            Packet.pts= av_rescale_q(pCodecCtx->coded_frame->pts, pCodecCtx->time_base, pVideoStream->time_base);
                                                        }
                                                        if (pCodecCtx->coded_frame->key_frame) Packet.flags |= AV_PKT_FLAG_KEY;
                                                        Packet.stream_index= pVideoStream->index;
                                                        Packet.data= pOutBuffer;
                                                        Packet.size= OutSize;
                                                        if (av_interleaved_write_frame(pFormatCtx, &Packet)!= 0) qDebug() << "av_interleaved_write_frame Error!";


                                                        DynBuffer *d= (DynBuffer*)pFormatCtx->pb->opaque;
                                                        qDebug() << d->buffer << pFormatCtx->pb->buffer << pFormatCtx->pb->pos;
                                                        /*QByteArray QBAByteIn((const char*)d->buffer, d->pos);
                                                        QFMPGFileOut.write(QBAByteIn);
                                                        d->pos= d->size= 0;*/


                                                    }
                                                    Frames++;
                                                    if (Frames> pCodecCtx->time_base.den / pCodecCtx->time_base.num * 5) break;
                                                }{
                                                    av_free(pAVFrame);
                                                    free(pBuffer);
                                                }


                                            } else qDebug() << "QThCamera::run() frame= false";
                                        }
                                        qDebug() << Frames / QDTStart.secsTo(QDateTime::currentDateTime());
                                    }{
                                        free(pOutBuffer);
                                    }
                                    if (av_write_trailer(pFormatCtx)!= 0) qDebug() << "av_write_trailer Error!"; // Crash with H264!!!
                                    uint8_t *pDynBuffer; {
                                        DynBuffer *d= (DynBuffer*)pFormatCtx->pb->opaque;
                                        qDebug() << d->buffer << pFormatCtx->pb->opaque << d->allocated_size;
                                        //QByteArray QBAByteIn((const char*)d->buffer, d->pos);
                                        //d->pos= d->size= 0;*/
                                        int pDynBufferLen= avio_close_dyn_buf(pFormatCtx->pb, &pDynBuffer);
                                        qDebug() << pDynBufferLen << pDynBuffer;
                                        //QByteArray QBAByteIn((const char*)pDynBuffer, pDynBufferLen);
                                        //QFMPGFileOut.write(QBAByteIn);
                                    }{
                                        av_free(pDynBuffer);
                                    }
                                }
                                QFMPGFileOut.close();
                            } else qDebug() << "QFMPGFileOut.open() Error!";
                            avcodec_close(pVideoStream->codec);
                        }
                    }
                    for (int count= 0; (unsigned)count< pFormatCtx->nb_streams; count++) {
                        av_freep(&pFormatCtx->streams[count]->codec);
                        av_freep(&pFormatCtx->streams[count]);
                    }
                }
                av_free(pFormatCtx);
            }
        }
        cvReleaseCapture(&Capture);
        if (ExternalFrame) cvDestroyWindow("Frame");
    }
    qDebug() << "QThCamera::run() stop";
}

IplImage* QThCamera::Rotate(IplImage *src, int angle) {
    IplImage *dest= cvCloneImage(src);
    CvPoint2D32f center;
    center.x= dest->width / 2;
    center.y= dest->height / 2;
    CvMat *mapMatrix= cvCreateMat(2, 3, CV_32FC1); {
        cv2DRotationMatrix(center, angle, 1.0, mapMatrix);
        cvWarpAffine(src, dest, mapMatrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvScalarAll(0));
    }{
        cvReleaseMat(&mapMatrix);
    }
    return dest;
}

/*IplImage* QThCamera::Rotate(const IplImage *img, int angle) {
    IplImage *newImg;
    int newWidth, newHeight;
    int rectX, rectY;
    if (angle == -45 || angle == 45) {
        newWidth= (int) ((img->width + img->height) / sqrt(2.0));
        newHeight= (int) ((img->width + img->height) / sqrt(2.0));
    } else if (angle == -90 || angle == 90) {
        if (img->width > img->height) {
            newWidth = img->width;
            newHeight = img->width;
        } else {
            newWidth= img->height;
            newHeight= img->height;
        }
    } else {
        newWidth = img->width;
        newHeight = img->height;
    }
    newImg= cvCreateImage(cvSize(newWidth, newHeight), img->depth, img->nChannels);
    cvSetZero(newImg);
    rectX = (int) ((newWidth - img->width) / 2);
    rectY = (int) ((newHeight - img->height) / 2);
    CvRect rect = cvRect(rectX, rectY, img->width, img->height);
    cvSetImageROI(newImg, rect);
    cvResize(img, newImg);
    cvResetImageROI(newImg);
    IplImage *rotatedImg = cvCreateImage(cvGetSize(newImg), IPL_DEPTH_8U, img -> nChannels);
    CvPoint2D32f center;
    int xPos, yPos;
    xPos = (int) newWidth / 2;
    yPos = (int) newHeight / 2;
    CvMat *mapMatrix = cvCreateMat(2, 3, CV_32FC1);
    center.x = xPos;
    center.y = yPos;
    cv2DRotationMatrix(center, angle, 1.0, mapMatrix);
    cvWarpAffine(newImg, rotatedImg, mapMatrix);
    return rotatedImg;
}*/

int QThCamera::BrightnessOfAPixel(IplImage *src) {
    int Result= -1;
    IplImage *hsv= cvCloneImage(src); {
        cvCvtColor(src, hsv, CV_BGR2HSV);
        int x= 0, y= 0;
        QVector <int>QVResults;
        if (src->nChannels== 3) {
            IplImage *ThirthChannelImage= cvCreateImage(cvGetSize(src), 8, 1); {
                cvSplit(hsv, NULL, NULL, ThirthChannelImage, NULL);
                while (true) {
                    if (x< cvGetSize(src).width && y< cvGetSize(src).height) {
                        QVResults.append(cvRound(cvGetReal2D(ThirthChannelImage, x, y)));
                        x= x+ 32;
                        y= y+ 32;
                    } else break;
                }
            }{
                cvReleaseImage(&ThirthChannelImage);
            }
        } else {
            while (true) {
                if (x< cvGetSize(src).width && y< cvGetSize(src).height) {
                    QVResults.append(cvRound(cvGetReal2D(hsv, x, y)));
                    x= x+ 32;
                    y= y+ 32;
                } else break;
            }
        }
        if (QVResults.count()> 0) Result= QVResults[0];
        if (QVResults.count()> 1) {
            for (int count= 1; count< QVResults.count(); count++) Result+= QVResults[count];
            Result= Result / QVResults.count();
        }
    } {
        cvReleaseImage(&hsv);
    }
    return Result;
}

IplImage* QThCamera::ContrastBrightness(IplImage *src, int Contrast, int Brightness) {
    if(Contrast> 100) Contrast= 100;
    if(Contrast< -100) Contrast= -100;
    if(Brightness> 100) Brightness= 100;
    if(Brightness< -100) Brightness= -100;
    uchar UserData[256];
    CvMat* MultiChannel2DMatrix;
    IplImage *dest= cvCloneImage(src);
    IplImage *gray= cvCreateImage(cvGetSize(src), src->depth, 1); {
        MultiChannel2DMatrix= cvCreateMatHeader(1, 256, CV_8UC1);
        cvSetData(MultiChannel2DMatrix, UserData, 0);
        if(Contrast> 0) {
            double delta= 127. * Contrast / 100;
            double a= 255. / (255. - delta * 2);
            double b= a * (Brightness - delta);
            for(int count= 0; count< 256; count++) {
                int v= cvRound(a * count + b);
                if (v< 0) v= 0;
                if (v> 255) v= 255;
                UserData[count]= v;
            }
        } else {
            double delta= -128. * Contrast / 100;
            double a= (256. - delta * 2) / 255.;
            double b= a * Brightness + delta;
            for (int count= 0; count< 256; count++) {
                int v= cvRound(a * count + b);
                if (v< 0) v= 0;
                if (v> 255) v= 255;
                UserData[count]= v;
            }
        }
        if (src->nChannels== 3) {
            IplImage *R= cvCreateImage(cvGetSize(src), src->depth, 1);
            IplImage *G= cvCreateImage(cvGetSize(src), src->depth, 1);
            IplImage *B= cvCreateImage(cvGetSize(src), src->depth, 1); {
                cvCvtPixToPlane(src, R, G, B, NULL);
                cvLUT(R, R, MultiChannel2DMatrix);
                cvLUT(G, G, MultiChannel2DMatrix);
                cvLUT(B, B, MultiChannel2DMatrix);
                cvCvtPlaneToPix(R, G, B, NULL, dest);
            }{
                cvReleaseImage(&R);
                cvReleaseImage(&G);
                cvReleaseImage(&B);
            }
        } else cvLUT(gray, dest, MultiChannel2DMatrix);
    }{
        cvReleaseImage(&gray);
        cvReleaseMat(&MultiChannel2DMatrix);
    }
    return dest;
}

