#include <stdio.h>

#include <log.h>
#include <vdecoder.h>
#include "memoryAdapter.h"
#include <errno.h>

#define SAVE_RGB (1)
typedef struct VideoFrame
{
    // Intentional public access modifier:
    uint32_t mWidth;
    uint32_t mHeight;
    uint32_t mDisplayWidth;
    uint32_t mDisplayHeight;
    uint32_t mSize;            // Number of bytes in mData
    uint8_t* mData;            // Actual binary data
    int32_t  mRotationAngle;   // rotation angle, clockwise
}VideoFrame;

#if SAVE_RGB
//-------------------------------------------------------------------
char* gOutPutPath = NULL;
 /*
  位图文件的组成
          结构名称 符 号
	位图文件头 (bitmap-file header) BITMAPFILEHEADER bmfh
	位图信息头 (bitmap-information header) BITMAPINFOHEADER bmih
	彩色表 (color table) RGBQUAD aColors[]
	图象数据阵列字节 BYTE aBitmapBits[]
  */
typedef struct bmp_header
{
	short twobyte           ;//两个字节,用来保证下面成员紧凑排列,这两个字符不能写到文件中
	     //14B
	char bfType[2]          ;//!文件的类型,该值必需是0x4D42,也就是字符'BM'
	unsigned int bfSize     ;//!说明文件的大小,用字节为单位
	unsigned int bfReserved1;//保留,必须设置为0
	unsigned int bfOffBits  ;//!说明从文件头开始到实际的图象数据之间的字节的偏移量,这里为14B+sizeof(BMPINFO)
}BMPHEADER;

typedef struct bmp_info
{
	     //40B
	 unsigned int biSize         ;//!BMPINFO结构所需要的字数
	 int biWidth                 ;//!图象的宽度,以象素为单位
	 int biHeight                ;//!图象的宽度,以象素为单位,如果该值是正数,说明图像是倒向的,如果该值是负数,则是正向的
	 unsigned short biPlanes     ;//!目标设备说明位面数,其值将总是被设为1
	 unsigned short biBitCount   ;//!比特数/象素,其值为1、4、8、16、24、或32
	 unsigned int biCompression  ;//说明图象数据压缩的类型
	 #define BI_RGB        0L    //没有压缩
	 #define BI_RLE8       1L    //每个象素8比特的RLE压缩编码,压缩格式由2字节组成(重复象素计数和颜色索引);
	 #define BI_RLE4       2L    //每个象素4比特的RLE压缩编码,压缩格式由2字节组成
	 #define BI_BITFIELDS  3L    //每个象素的比特由指定的掩码决定。
	 unsigned int biSizeImage    ;//图象的大小,以字节为单位。当用BI_RGB格式时,可设置为0
	 int biXPelsPerMeter         ;//水平分辨率,用象素/米表示
	 int biYPelsPerMeter         ;//垂直分辨率,用象素/米表示
	 unsigned int biClrUsed      ;//位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)
	 unsigned int biClrImportant ;//对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。
}BMPINFO;

typedef struct tagRGBQUAD
{
	unsigned char rgbBlue;
	unsigned char rgbGreen;
	unsigned char rgbRed;
	unsigned char rgbReserved;
} RGBQUAD;

typedef struct tagBITMAPINFO
{
	BMPINFO    bmiHeader;
	//RGBQUAD    bmiColors[1];
	unsigned int rgb[3];
} BITMAPINFO;


static int get_rgb565_header(int w, int h, BMPHEADER * head, BITMAPINFO * info)
{
    int size = 0;
    if (head && info)
    {
        size = w * h * 2;
        memset(head, 0, sizeof(* head));
        memset(info, 0, sizeof(* info));
         head->bfType[0] = 'B';
         head->bfType[1] = 'M';
         head->bfOffBits = 14 + sizeof(* info);
         head->bfSize = head->bfOffBits + size;
         head->bfSize = (head->bfSize + 3) & ~3;
         size = head->bfSize - head->bfOffBits;

         info->bmiHeader.biSize = sizeof(info->bmiHeader);
         info->bmiHeader.biWidth = w;
         info->bmiHeader.biHeight = -h;
         info->bmiHeader.biPlanes = 1;
         info->bmiHeader.biBitCount = 16;
         info->bmiHeader.biCompression = BI_BITFIELDS;
         info->bmiHeader.biSizeImage = size;

         info->rgb[0] = 0xF800;
         info->rgb[1] = 0x07E0;
         info->rgb[2] = 0x001F;

         logd("rgb565:%dbit,%d*%d,%d\n", info->bmiHeader.biBitCount, w, h, head->bfSize);
     }
     return size;
 }


static int save_bmp_rgb565(FILE* fp, int width, int height, unsigned char* pData)
{
	int success = 0;
	int size = 0;
	BMPHEADER head;
	BITMAPINFO info;
	size = get_rgb565_header(width, height, &head, &info);
	if(size > 0)
	{
        fwrite(head.bfType,1,2,fp);
        fwrite(&head.bfSize,1,4,fp);
        fwrite(&head.bfReserved1,1,4,fp);
        fwrite(&head.bfOffBits,1,4,fp);

		fwrite(&info,1,sizeof(info), fp);
		fwrite(pData,1,size, fp);
		success = 1;
	}
	logd("*****success=%d\n", success);
	return success;
}



#endif

#if 0
static int get_rgb888_header(int w, int h, BMPHEADER * head, BITMAPINFO * info)
{
    int size = 0;
    if (head && info)
    {
        size = w * h * 3;
        memset(head, 0, sizeof(* head));
        memset(info, 0, sizeof(* info));
         head->bfType[0] = 'B';
         head->bfType[1] = 'M';
         head->bfOffBits = 14 + sizeof(* info);
         head->bfSize = head->bfOffBits + size;
         head->bfSize = (head->bfSize + 3) & ~3;
         size = head->bfSize - head->bfOffBits;

         info->bmiHeader.biSize = sizeof(info->bmiHeader);
         info->bmiHeader.biWidth = w;
         info->bmiHeader.biHeight = -h;
         info->bmiHeader.biPlanes = 1;
         info->bmiHeader.biBitCount = 24;
         info->bmiHeader.biCompression = BI_RGB;
         info->bmiHeader.biSizeImage = size;

         logd("rgb888:%dbit,%d*%d,%d\n", info->bmiHeader.biBitCount, w, h, head->bfSize);
     }
     return size;
 }


static int save_bmp_rgb888(FILE* fp, int width, int height, unsigned char* pData)
{
	int success = 0;
	int size = 0;
	BMPHEADER head;
	BITMAPINFO info;
	size = get_rgb888_header(width, height, &head, &info);
	if(size > 0)
	{
		fwrite(head.bfType,1,14,fp);
		fwrite(&info,1,sizeof(info), fp);
		fwrite(pData,1,size, fp);
		success = 1;
	}
	logd("*****success=%d\n", success);
	return success;
}

static int transformPictureMb32ToRGB888(VideoPicture* pPicture, unsigned char* pData, int nWidth, int nHeight)
{
    unsigned char*   pClipTable;
    unsigned char*   pClip;
    static const int nClipMin = -278;
    static const int nClipMax = 535;

    unsigned short*  pDst = NULL;
    unsigned char*   pSrcY = NULL;
    unsigned char*   pSrcVU = NULL;

    int x = 0;
    int y = 0;
    int nMbWidth = 0;
    int nMbHeight = 0;
    int nVMb = 0;
    int nHMb = 0;
    int yPos = 0;
    int pos = 0;
    int uvPos = 0;

    //* initialize the clip table.
    pClipTable = (unsigned char*)malloc(nClipMax - nClipMin + 1);
    if(pClipTable == NULL)
    {
        loge("can not allocate memory for the clip table, quit.");
        return -1;
    }
    for(x=nClipMin; x<=nClipMax; x++)
    {
        pClipTable[x-nClipMin] = (x<0) ? 0 : (x>255) ? 255 : x;
    }
    pClip = &pClipTable[-nClipMin];

    //* flush cache.
    MemAdapterFlushCache(pPicture->pData0, pPicture->nWidth*pPicture->nHeight);
    MemAdapterFlushCache(pPicture->pData1, pPicture->nHeight*pPicture->nHeight/2);

    pDst  = (unsigned short*)pData;
    logd("+++++ pDst: %p", pDst);
    pSrcY = (unsigned char*)pPicture->pData0;
    pSrcVU  = (unsigned char*)pPicture->pData1;

    nMbWidth = pPicture->nWidth/32;
    nMbHeight = pPicture->nHeight/32;

	for(nVMb=0; nVMb<nMbHeight;nVMb++)
	{
		for(nHMb=0; nHMb<nMbWidth; nHMb++)
		{
			#if 1
			pos = 3*(nVMb*pPicture->nWidth*32+nHMb*32);
			#else
			pos = nVMb*pPicture->nWidth*32+nHMb*32;
			#endif

			for(y=0; y<32; y++)
			{
				yPos = (nVMb*nMbWidth+nHMb)*1024+y*32;
				uvPos = ((nVMb/2)*nMbWidth*1024)+nHMb*1024+(y/2)*32+ (((nVMb%2)==1) ? 512 : 0);
				for(x=0; x<32; x+=2)
				{
					signed y1 = (signed)pSrcY[yPos+x+0] - 16;
					signed y2 = (signed)pSrcY[yPos+x+1] - 16;
					signed u  = (signed)pSrcVU[uvPos+x+0] - 128;
					signed v  = (signed)pSrcVU[uvPos+x+1] - 128;
					signed u_b = u * 517;
					signed u_g = -u * 100;
					signed v_g = -v * 208;
					signed v_r = v * 409;
					signed tmp1 = y1 * 298;
					signed b1 = (tmp1 + u_b) / 256;
					signed g1 = (tmp1 + v_g + u_g) / 256;
					signed r1 = (tmp1 + v_r) / 256;
					signed tmp2 = y2 * 298;
					signed b2 = (tmp2 + u_b) / 256;
					signed g2 = (tmp2 + v_g + u_g) / 256;
					signed r2 = (tmp2 + v_r) / 256;

				#if 1

					pDst[pos+0] = pClip[r1];
                    pDst[pos+1] = pClip[g1];
                    pDst[pos+2] = pClip[b1];
                    pDst[pos+3] = pClip[r2];
                    pDst[pos+4] = pClip[g2];
                    pDst[pos+5] = pClip[b2];
                    pos += 6;

				}
				pos += 3*(nMbWidth-1)*32;

				#else
					unsigned int rgb1 = ((pClip[r1] >> 3) << 11) |
										((pClip[g1] >> 2) << 5)  |
										(pClip[b1] >> 3);

					unsigned int rgb2 = ((pClip[r2] >> 3) << 11) |
										((pClip[g2] >> 2) << 5)  |
										(pClip[b2] >> 3);
					*(unsigned int *)(&pDst[pos]) = (rgb2 << 16) | rgb1;
					pos += 2;
				}
				pos += (nMbWidth-1)*32;
				#endif
			}
		}
	}

	logd("pos: %d", pos);
	pDst  = (unsigned short*)pData;
	for(y=0; y<pPicture->nTopOffset; y++)
	{
		memset(pDst+y*nWidth, 0, 2*nWidth);
	}
	for(y=pPicture->nBottomOffset; y<nHeight; y++)
	{
		memset(pDst+y*nWidth, 0, 2*nWidth);
	}

	for(y=pPicture->nTopOffset; y<pPicture->nBottomOffset; y++)
	{
		memset(pDst+y*nWidth, 0, 2*pPicture->nLeftOffset);
		memset(pDst+y*nWidth+pPicture->nRightOffset, 0, 2*(nWidth-pPicture->nRightOffset));
	}

#if 1
    FILE* outFp = fopen("/mnt/UDISK/rgb.data", "wb");
    if(outFp != NULL)
    {
	logd("************save_bmp_rgb565\n");
	save_bmp_rgb888(outFp, nWidth, nHeight, pData);
	fwrite(pDst, 1, nWidth*nHeight*3, outFp);
	fclose(outFp);
    }
#endif

    free(pClipTable);

    return 0;
}
#endif

static int transformPictureMb32ToRGB(struct ScMemOpsS* memops, VideoPicture* pPicture, unsigned char* pData, int nWidth, int nHeight)
{
    unsigned char*   pClipTable;
    unsigned char*   pClip;
    static const int nClipMin = -278;
    static const int nClipMax = 535;

    unsigned short*  pDst = NULL;
    unsigned char*   pSrcY = NULL;
    unsigned char*   pSrcVU = NULL;

    int x = 0;
    int y = 0;
    int nMbWidth = 0;
    int nMbHeight = 0;
    int nVMb = 0;
    int nHMb = 0;
    int yPos = 0;
    int pos = 0;
    int uvPos = 0;

    //* initialize the clip table.
    pClipTable = (unsigned char*)malloc(nClipMax - nClipMin + 1);
    if(pClipTable == NULL)
    {
        loge("can not allocate memory for the clip table, quit.");
        return -1;
    }
    for(x=nClipMin; x<=nClipMax; x++)
    {
        pClipTable[x-nClipMin] = (x<0) ? 0 : (x>255) ? 255 : x;
    }
    pClip = &pClipTable[-nClipMin];

    //* flush cache.
    CdcMemFlushCache(memops, pPicture->pData0, pPicture->nWidth*pPicture->nHeight);
    CdcMemFlushCache(memops, pPicture->pData1, pPicture->nHeight*pPicture->nHeight/2);

    pDst  = (unsigned short*)pData;
    logd("+++++ pDst: %p", pDst);
    pSrcY = (unsigned char*)pPicture->pData0;
    pSrcVU  = (unsigned char*)pPicture->pData1;

    nMbWidth = pPicture->nWidth/32;
    nMbHeight = pPicture->nHeight/32;

	for(nVMb=0; nVMb<nMbHeight;nVMb++)
	{
		for(nHMb=0; nHMb<nMbWidth; nHMb++)
		{
			pos = nVMb*pPicture->nWidth*32+nHMb*32;
			for(y=0; y<32; y++)
			{
				yPos = (nVMb*nMbWidth+nHMb)*1024+y*32;
				uvPos = ((nVMb/2)*nMbWidth*1024)+nHMb*1024+(y/2)*32+ (((nVMb%2)==1) ? 512 : 0);
				for(x=0; x<32; x+=2)
				{
					signed y1 = (signed)pSrcY[yPos+x+0] - 16;
					signed y2 = (signed)pSrcY[yPos+x+1] - 16;
					signed u  = (signed)pSrcVU[uvPos+x+0] - 128;
					signed v  = (signed)pSrcVU[uvPos+x+1] - 128;
					signed u_b = u * 517;
					signed u_g = -u * 100;
					signed v_g = -v * 208;
					signed v_r = v * 409;
					signed tmp1 = y1 * 298;
					signed b1 = (tmp1 + u_b) / 256;
					signed g1 = (tmp1 + v_g + u_g) / 256;
					signed r1 = (tmp1 + v_r) / 256;
					signed tmp2 = y2 * 298;
					signed b2 = (tmp2 + u_b) / 256;
					signed g2 = (tmp2 + v_g + u_g) / 256;
					signed r2 = (tmp2 + v_r) / 256;
					unsigned int rgb1 = ((pClip[r1] >> 3) << 11) |
										((pClip[g1] >> 2) << 5)  |
										(pClip[b1] >> 3);

					unsigned int rgb2 = ((pClip[r2] >> 3) << 11) |
										((pClip[g2] >> 2) << 5)  |
										(pClip[b2] >> 3);
					*(unsigned int *)(&pDst[pos]) = (rgb2 << 16) | rgb1;
					pos += 2;
				}
				pos += (nMbWidth-1)*32;
			}
		}
	}

	logd("pos: %d", pos);
	pDst  = (unsigned short*)pData;
	for(y=0; y<pPicture->nTopOffset; y++)
	{
		memset(pDst+y*nWidth, 0, 2*nWidth);
	}
	for(y=pPicture->nBottomOffset; y<nHeight; y++)
	{
		memset(pDst+y*nWidth, 0, 2*nWidth);
	}

	for(y=pPicture->nTopOffset; y<pPicture->nBottomOffset; y++)
	{
		memset(pDst+y*nWidth, 0, 2*pPicture->nLeftOffset);
		memset(pDst+y*nWidth+pPicture->nRightOffset, 0, 2*(nWidth-pPicture->nRightOffset));
	}

#if SAVE_RGB
	FILE* outFp = NULL;
	if(gOutPutPath != NULL){
		outFp = fopen(gOutPutPath, "wb");
	}else{
		outFp = fopen("/tmp/rgb.bmp", "wb");
	}
    if(outFp != NULL){
		logd("************save_bmp_rgb565\n");
		save_bmp_rgb565(outFp, nWidth, nHeight, pData);
		fclose(outFp);
    }else{
		loge("fopen output path fail\n");
	}
#endif

    free(pClipTable);

    return 0;
}


static char * readJpegData(char *path, int *pLen)
{
    FILE *fp = NULL;
    int ret = 0;
    char *data = NULL;

    fp = fopen(path, "rb");
    if(fp == NULL)
    {
        loge("read jpeg file error, errno(%d)", errno);
        return NULL;
    }

    fseek(fp,0,SEEK_END);
    *pLen = ftell(fp);
    rewind(fp);
    data = (char *) malloc (sizeof(char)*(*pLen));

    if(data == NULL)
      {
          loge("malloc memory fail");
          fclose(fp);
          return NULL;
      }

    ret = fread (data,1,*pLen,fp);
    if (ret != *pLen)
    {
        loge("read file fail");
        fclose(fp);
        free(data);
        return NULL;
    }

    if(fp != NULL)
    {
        fclose(fp);
    }
    return data;
}

static int dumpData(char *path, uint8_t *data, int len)
{
    FILE *fp;
    fp = fopen(path, "a+");

    if(fp != NULL)
    {
        logd("dump data '%d'", len);
        fwrite(data, 1, len, fp);
        fclose(fp);
    }
    else
    {
        loge("saving picture open file error, errno(%d)", errno);
        return -1;
    }
    return 0;
}


int main(int argc, char** argv)
{
    int ret;

    VConfig vConfig;
    char *jpegData = NULL;
    int dataLen =0;
    char * uri= NULL;
    VideoDecoder *pVideo;
    VideoPicture *videoPicture = NULL;

	struct ScMemOpsS* memops = MemAdapterGetOpsS();
	if(memops == NULL)
	{
		return -1;
	}
	CdcMemOpen(memops);
	AddVDPlugin();

    if (argc != 3)
    {
        logd("argc must be '3'");
		logd("usage:");
		logd("jpegdecodedemo /mnt/UDISK/test.jpg /mnt/UDISK/save.bmp");
        return 0;
    }
    uri = argv[1];
#if SAVE_RGB
	gOutPutPath = argv[2];
#endif
    jpegData = readJpegData(uri , &dataLen);
	logd("dataLen = %d",dataLen);
    if (dataLen <= 0  || jpegData == NULL)
   {
        loge("read file fail");
        return 0;
    }

    memset(&vConfig, 0x00, sizeof(VConfig));
    vConfig.bDisable3D = 0;
    vConfig.bDispErrorFrame = 0;
    vConfig.bNoBFrames = 0;
    vConfig.bRotationEn = 0;
    vConfig.bScaleDownEn = 0;
    vConfig.nHorizonScaleDownRatio = 0;
    vConfig.nVerticalScaleDownRatio = 0;
    vConfig.eOutputPixelFormat =PIXEL_FORMAT_YUV_MB32_420;
    vConfig.nDeInterlaceHoldingFrameBufferNum = 0;
    vConfig.nDisplayHoldingFrameBufferNum = 0;
    vConfig.nRotateHoldingFrameBufferNum = 0;
    vConfig.nDecodeSmoothFrameBufferNum = 1;
	if(dataLen < 2*1024*1024){
		vConfig.nVbvBufferSize = 2*1024*1024;
	}else{
		int cnt = dataLen/(4*1024);
		logd("cnt = %d",cnt);
		vConfig.nVbvBufferSize = (cnt+1)*4*1024;
	}
	logd("vConfig.nVbvBufferSize = %d",vConfig.nVbvBufferSize);
    vConfig.bThumbnailMode = 0;
    vConfig.memops = memops;
    VideoStreamInfo videoInfo;
    memset(&videoInfo, 0x00, sizeof(VideoStreamInfo));
    videoInfo.eCodecFormat = VIDEO_CODEC_FORMAT_MJPEG;

    pVideo = CreateVideoDecoder();
    if(!pVideo)
    {
        loge("create video decoder failed\n");
        return 0;
    }
    logd("create video decoder ok\n");


    if ((InitializeVideoDecoder(pVideo, &videoInfo, &vConfig)) != 0)
    {
        loge("InitializeVideoDecoder  failed !\n");
        return 0;
    }
    logd("Initialize video decoder ok\n");

    char *buf, *ringBuf;
    int buflen, ringBufLen;
	if((RequestVideoStreamBuffer(pVideo,
                                dataLen,
                                (char**)&buf,
                                &buflen,
                                (char**)&ringBuf,
                                &ringBufLen,
                                0)) != 0){
		loge("Request Video Stream Buffer failed\n");
        return 0;
	}
    logd("Request Video Stream Buffer ok\n");

    if(buflen + ringBufLen < dataLen)
    {
        loge("#####Error: request buffer failed, buffer is not enough\n");
        return 0;
    }

    logd("goto to copy Video Stream Data ok!\n");
    // copy stream to video decoder SBM
    if(buflen >= dataLen)
    {
        memcpy(buf,jpegData,dataLen);
    }
    else
    {
        memcpy(buf,jpegData,buflen);
        memcpy(ringBuf,jpegData+buflen,dataLen-buflen);
    }
    logd("Copy Video Stream Data ok!\n");

    VideoStreamDataInfo DataInfo;
    memset(&DataInfo, 0, sizeof(DataInfo));
    DataInfo.pData = buf;
    DataInfo.nLength = dataLen;
    DataInfo.bIsFirstPart = 1;
    DataInfo.bIsLastPart = 1;

    if (SubmitVideoStreamData(pVideo, &DataInfo, 0))
    {
        loge("#####Error: Submit Video Stream Data failed!\n");
        return 0;
    }
    logd("Submit Video Stream Data ok!\n");

    // step : decode stream now
    int endofstream = 0;
    int dropBFrameifdelay = 0;
    int64_t currenttimeus = 0;
    int decodekeyframeonly = 0;

    ret = DecodeVideoStream(pVideo, endofstream, decodekeyframeonly,dropBFrameifdelay, currenttimeus);
    logd("decoder ret is %d",ret);
    switch (ret)
    {
        case VDECODE_RESULT_KEYFRAME_DECODED:
        case VDECODE_RESULT_FRAME_DECODED:
        case VDECODE_RESULT_NO_FRAME_BUFFER:
        {
            ret = ValidPictureNum(pVideo, 0);
            if (ret>= 0)
            {
                videoPicture = RequestPicture(pVideo, 0);
                if (videoPicture == NULL){
                    loge("decoder fail");
                    return 0;
                }
                logd("decoder one pic...");
                logd("pic nWidth is %d,nHeight is %d",videoPicture->nWidth,videoPicture->nHeight);

                VideoFrame jpegData;
                jpegData.mWidth = videoPicture->nWidth;
                jpegData.mHeight = videoPicture->nHeight;
                jpegData.mSize = jpegData.mWidth*jpegData.mWidth*2;
                jpegData.mData = (unsigned char*)malloc(jpegData.mSize);
                if(jpegData.mData == NULL)
                {
					return -1;
                }

                transformPictureMb32ToRGB(memops, videoPicture, jpegData.mData, jpegData.mWidth, jpegData.mHeight);

                char path[1024] = "./pic.rgb";
                dumpData(path, (uint8_t *)jpegData.mData, jpegData.mWidth * jpegData.mHeight * 2);
                sync();
            }
            else
            {
               logd("no ValidPictureNum ret is %d",ret);
            }

            break;
        }

        case VDECODE_RESULT_OK:
        case VDECODE_RESULT_CONTINUE:
        case VDECODE_RESULT_NO_BITSTREAM:
        case VDECODE_RESULT_RESOLUTION_CHANGE:
        case VDECODE_RESULT_UNSUPPORTED:
        default:
            loge("video decode Error: %d!\n", ret);
            return 0;
    }

	if (jpegData != NULL)
	{
		free(jpegData);
		jpegData = NULL;
	}

	CdcMemClose(memops);

    return 0;
}