/*
 * =====================================================================================
 *
 *       Filename:  CdcFileSink.c
 *
 *    Description:
 *
 *        Version:  1.0
 *        Created:  2020年10月22日 14时31分53秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  jilinglin
 *        Company:  allwinnertech.com
 *
 * =====================================================================================
 */
#include "CdcPicSink.h"
#include "cdc_log.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

#include "SinkMd5.h"

#define PIC_ALIGN(value, n) (((value) + (n) - 1) & ~((n) - 1))
#define MAX_FILE_NAME_LEN 100

typedef struct CdcPicSink {
    CdcSinkInterface mInterFace;
    char mFileName[MAX_FILE_NAME_LEN];
    FILE* pFile;
    int mStartNum;
    int mTotalNum;
    int mPicCount;
    int bMd5Open;
    SinkMD5 mMd5Ctx;
} CdcPicSink;

int PicSinkWriteBS(CdcSinkInterface* pSelf, CdcBitStream* pStream)
{
    (void)pSelf;
    (void)pStream;
    loge("not support");
    return -1;
}

static int InterUpdateMd5Data(CdcPicSink* pContext, CdcSinkPicture* pPic, u8 mMd5Ret[16])
{
    int mTopOffset    = pPic->nTop;
    int mLeftOffset   = pPic->nLeft;
    int mBottomOffset = pPic->nBottom;
    int mRightOffset  = pPic->nRight;
    int mCopyHeight   = mBottomOffset - mTopOffset;
    int mCopyWidth    = mRightOffset - mLeftOffset;
    int mStride       = pPic->nLineStride;
    u8* pSrc = (u8*)pPic->pData[0] + (mStride * mTopOffset + mLeftOffset);
    int i = 0;
    SinkMD5* pMd5Ctx = &(pContext->mMd5Ctx);

    logd("md5 cul top:%d bot:%d left:%d right:%d",
            mTopOffset,mBottomOffset,mLeftOffset,mRightOffset);
    SinkMd5Init(pMd5Ctx);
    //data[0], for all
    for(i=0; i<mCopyHeight; i++)
    {
        SinkMd5Update(pMd5Ctx, (void*)pSrc, mCopyWidth);
        pSrc += mStride;
    }

    //data[1]
    if(pPic->nFormat == CDC_SINK_PIC_FORMAT_NV12 || pPic->nFormat == CDC_SINK_PIC_FORMAT_NV21)
    {
        pSrc =((u8*)pPic->pData[1]) + mStride * mTopOffset / 2 + mLeftOffset;
        for(i=0; i<mCopyHeight/2 ; i++)
        {
            SinkMd5Update(pMd5Ctx, pSrc, mCopyWidth);
            pSrc += mStride;
        }
        SinkMd5Final(pMd5Ctx, mMd5Ret);
        return 0;
    }

    if(pPic->nFormat == CDC_SINK_PIC_FORMAT_YU12 || pPic->nFormat == CDC_SINK_PIC_FORMAT_YV12)
    {
        pSrc =((u8*)pPic->pData[1]) + mStride/2 * mTopOffset / 2 + mLeftOffset / 2;
        mStride /= 2;
        mCopyHeight /= 2;
        mCopyWidth /= 2;
    }
    else
    {
        pSrc =((u8*)pPic->pData[1]) + mStride * mTopOffset + mLeftOffset;
    }

    for(i=0; i<mCopyHeight; i++)
    {
        SinkMd5Update(pMd5Ctx, pSrc, mCopyWidth);
        pSrc += mStride;
    }

    //for data[2]
    if(pPic->nFormat == CDC_SINK_PIC_FORMAT_YU12 || pPic->nFormat == CDC_SINK_PIC_FORMAT_YV12)
    {
        pSrc =((u8*)pPic->pData[2]) + mStride/2 * mTopOffset / 2 + mLeftOffset / 2;
        for(i=0; i<mCopyHeight; i++)
        {
            SinkMd5Update(pMd5Ctx, pSrc, mCopyWidth);
            pSrc += mStride;
        }
        SinkMd5Final(pMd5Ctx, mMd5Ret);
        return 0;
    }
    else
    {
        pSrc =((u8*)pPic->pData[2]) + mStride * mTopOffset + mLeftOffset;
        mStride *= 2;
        mCopyHeight *= 2;
        mCopyWidth *= 2;
        for(i=0; i<mCopyHeight; i++)
        {
            SinkMd5Update(pMd5Ctx, pSrc, mCopyWidth);
            pSrc += mStride;
        }
    }

    //for data[3]
    pSrc =((u8*)pPic->pData[3]) + mStride * mTopOffset + mLeftOffset;
    for(i=0; i<mCopyHeight; i++)
    {
        SinkMd5Update(pMd5Ctx, pSrc, mCopyWidth);
        pSrc += mStride;
    }
    SinkMd5Final(pMd5Ctx, mMd5Ret);
    return 0;
}

static int InterPicSaveMd5(CdcPicSink* pContext, CdcSinkPicture* pPic)
{
    SinkMD5* pMd5Ctx = NULL;
    u8 mMd5Ret[16];
    int i;
    int nBpp10_size = 0;
    pMd5Ctx = &(pContext->mMd5Ctx);

    if(pPic->nAfbc)
    {
        SinkMd5Init(pMd5Ctx);
        int nHeaderSize = 16 * ((pPic->nWidth + 15) & ~15) * ((pPic->nHeight + 15) & ~15);
        int nHeaderSizeAlign = (nHeaderSize + 1023) & ~1023;
        SinkMd5Update(pMd5Ctx, (u8 *)pPic->pData[0] + nHeaderSizeAlign, pPic->nAfbcSize - nHeaderSizeAlign);
        SinkMd5Final(pMd5Ctx, mMd5Ret);
    }
#if VE_SUPPORT_DECODER_LBC_MODE
    else if(pPic->nLbcLossyComMod)
    {
        SinkMd5Init(pMd5Ctx);
        SinkMd5Update(pMd5Ctx, (u8 *)pPic->pData[0], pPic->nLbcSize);
        SinkMd5Final(pMd5Ctx, mMd5Ret);
    }
#endif
    else
    {
        if(pPic->nBpp == 10)
        {
            nBpp10_size = pPic->nHeight*pPic->nWidth*3;
            SinkMd5Init(pMd5Ctx);
            SinkMd5Update(pMd5Ctx, pPic->pData[0], nBpp10_size);
            SinkMd5Final(pMd5Ctx, mMd5Ret);
        }
        else
        {
            InterUpdateMd5Data(pContext, pPic, mMd5Ret);
        }
    }

    for(i = 0; i < sizeof(mMd5Ret); i++) {
        fprintf(pContext->pFile, "%02X", mMd5Ret[i]);
    }
    fprintf(pContext->pFile, "\n");
    fflush(pContext->pFile);
    logd("pic:%p data:%x %x %x %x %x %x %x %x",
        (u8*)pPic->pData[0],mMd5Ret[0],mMd5Ret[1],mMd5Ret[2],mMd5Ret[3],mMd5Ret[4],mMd5Ret[5],mMd5Ret[6],mMd5Ret[7]);
    return 0;
}

int PicSinkWritePic(CdcSinkInterface* pSelf, CdcSinkPicture* pPic)
{
    int nLen;

    CdcPicSink* pContext = (CdcPicSink*)pSelf;
    if(pPic == NULL || pContext->pFile == NULL)
    {
        loge("pointer error pic:%p file:%p", pPic,pContext->pFile);
        return -1;
    }

    if(pContext->mPicCount < pContext->mStartNum ||
       pContext->mPicCount >= pContext->mStartNum + pContext->mTotalNum)
    {
        pContext->mPicCount++;
        return 0;
    }

    if(pContext->bMd5Open)
    {
        InterPicSaveMd5(pContext, pPic);
        goto EXIT;
    }
    if(pPic->nAfbc)
    {
        fwrite(pPic->pData[0], 1, pPic->nAfbcSize, pContext->pFile);
        goto EXIT;
    }

    nLen = pPic->nLineStride*pPic->nHeight;

    switch(pPic->nFormat)
    {
        case CDC_SINK_PIC_FORMAT_NV12:
        case CDC_SINK_PIC_FORMAT_NV21:
            if(pPic->nBpp == 10)
            {
                logw("allwinner 10bit format, we just save 8bit data");
            }
            fwrite(pPic->pData[0], 1, nLen, pContext->pFile);
            fwrite(pPic->pData[1], 1, nLen/2, pContext->pFile);
            break;
        case CDC_SINK_PIC_FORMAT_YU12:
        case CDC_SINK_PIC_FORMAT_YV12:
            if(pPic->nBpp == 10)
            {
                logw("allwinner 10bit format, we just save 8bit data");
            }

            fwrite(pPic->pData[0], 1, nLen, pContext->pFile);
            fwrite(pPic->pData[1], 1, nLen/4, pContext->pFile);
            fwrite(pPic->pData[2], 1, nLen/4, pContext->pFile);
            break;
        case CDC_SINK_PIC_FORMAT_P010:
            nLen *= 2;
            fwrite(pPic->pData[0], 1, nLen, pContext->pFile);
            fwrite(pPic->pData[1], 1, nLen/2, pContext->pFile);
            break;
        case CDC_SINK_PIC_FORMAT_ARGB:
            fwrite(pPic->pData[0], 1, nLen, pContext->pFile);
            fwrite(pPic->pData[1], 1, nLen, pContext->pFile);
            fwrite(pPic->pData[2], 1, nLen, pContext->pFile);
            fwrite(pPic->pData[3], 1, nLen, pContext->pFile);
            break;
        default:
            loge("picture format not support");
    }

EXIT:
    logd("save pic width:%d height:%d format:%d bpp:%d", pPic->nWidth, pPic->nHeight, pPic->nFormat, pPic->nBpp);
    pContext->mPicCount++;

    return 0;
}

int PicSinkWriteSinglePic(CdcSinkInterface* pSelf, CdcSinkPicture* pPic)
{
    (void)pSelf;
    (void)pPic;
    logd("not support");
    return 0;
}

int PicSinkSetParam(CdcSinkInterface* pSelf, CdcSinkParam* pParam)
{
    CdcPicSink* pContext = (CdcPicSink*)pSelf;
    if(pParam == NULL)
    {
        loge("param is null");
        return -1;
    }

    memcpy(pContext->mFileName, pParam->pFileName, pParam->nLen);
    pContext->mStartNum = pParam->nStartNum;
    pContext->mTotalNum = pParam->nToatalNum;
    pContext->bMd5Open = pParam->bMd5Open;
    pContext->pFile = fopen(pContext->mFileName, "wb");
    logd("start num:%d total num:%d filename:%s",
            pContext->mStartNum,pContext->mTotalNum,pContext->mFileName);
    if(pContext->pFile == NULL)
    {
        loge("open file failure, name:%s", pContext->mFileName);
        return -1;
    }
    return 0;
}

static int CdcGetFileMd5(CdcPicSink* pContext,char* filename,u8 * mMd5Ret)
{
    FILE *fp = fopen(filename,"rb");
    SinkMD5* pMd5Ctx = NULL;
    int i;
    u8 mMd5Temp[16];
    char temp[8];
    memset(mMd5Temp, 0, 16);
    memset(temp, 0, 8);
    pMd5Ctx = &(pContext->mMd5Ctx);
    if(fp != NULL)
    {
        fseek(fp, 0, SEEK_END);
        int64_t flen = ftell(fp);
        u8* p = NULL;
        p = (u8*)malloc(flen);
        if(p != NULL)
        {
            fseek(fp, 0, SEEK_SET);
            fread(p, flen, 1, fp);
            SinkMd5Init(pMd5Ctx);
            SinkMd5Update(pMd5Ctx, p, flen);
            SinkMd5Final(pMd5Ctx, mMd5Temp);
            for(i = 0; i < sizeof(mMd5Temp); i++)
            {
                sprintf(temp, "%02X ", mMd5Temp[i]);
                strcat((char *)mMd5Ret, temp);
            }
            logv("mMd5Ret = %s", mMd5Ret);
            free(p);
        }
        fclose(fp);
    }
    return 0;
}

CdcSinkInterface* CdcPicSinkCreate(void)
{
    CdcPicSink* pContext = NULL;
    pContext = (CdcPicSink*)malloc(sizeof(CdcPicSink));
    if(pContext == NULL)
    {
        loge("malloc failure");
        return NULL;
    }
    memset(pContext, 0, sizeof(CdcPicSink));

    pContext->mInterFace.pWritePicture = PicSinkWritePic;
    pContext->mInterFace.pWriteSinglePicture = PicSinkWriteSinglePic;
    pContext->mInterFace.pWriteBitStream = PicSinkWriteBS;
    pContext->mInterFace.pSetParameter = PicSinkSetParam;

    return (CdcSinkInterface*)(pContext);
}


void CdcPicSinkDestory(CdcSinkInterface* pSelf)
{
    CdcPicSink* pContext = (CdcPicSink*)pSelf;
    if(pContext->bMd5Open && (access("/mnt/md5_compare.dat",F_OK))== 0)
    {
        u8 Md5Ret1[128];
        u8 Md5Ret2[128];
        memset(Md5Ret1, 0, 128);
        memset(Md5Ret2, 0, 128);
        CdcGetFileMd5(pContext,"/mnt/md5.dat",Md5Ret1);
        CdcGetFileMd5(pContext,"/mnt/md5_compare.dat",Md5Ret2);
        if(strcmp((char*)Md5Ret1,(char*)Md5Ret2)== 0)
        {
            loge("******* calculate_md5 : the md5 of the two file are equal *******");
            remove("/mnt/md5_compare.dat");
        }
        else{
            loge("******* calculate_md5 : the md5 of the two file is not equal *******");
            rename("/mnt/md5_compare.dat", "/mnt/md5_compare_error.dat");
        }
    }
    if(pContext->pFile != NULL)
        fclose(pContext->pFile);
    free(pContext);
}
