/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* CVS tag name for identification */
const char tagName[256] = "$Name: FIRST_ANDROID_COPYRIGHT $";

#include "H264SwDecApi.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DEBUG(argv) printf argv

/* _NO_OUT disables output file writing */
#ifdef __arm
#define _NO_OUT
#endif

/*------------------------------------------------------------------------------

------------------------------------------------------------------------------*/
void WriteOutput(FILE *fid, u8 *data, u32 picSize);

u32 CropPicture(u8 *pOutImage, u8 *pInImage,
    u32 picWidth, u32 picHeight, CropParams *pCropParams);

void CropWriteOutput(FILE *fid, u8 *imageData, u32 cropDisplay,
        H264SwDecInfo *decInfo);

typedef struct
{
    H264SwDecInst decInst;
    H264SwDecInput decInput;
    H264SwDecOutput decOutput;
    H264SwDecPicture decPicture;
    H264SwDecInfo decInfo;
    FILE *foutput;
    char outFileName[256];
    u8 *byteStrmStart;
    u32 picNumber;
} Decoder;


/*------------------------------------------------------------------------------

------------------------------------------------------------------------------*/
int main(int argc, char **argv)
{

    i32 instCount, instRunning;
    i32 i;
    u32 maxNumPics;
    u32 strmLen;
    H264SwDecRet ret;
    u32 numErrors = 0;
    u32 cropDisplay = 0;
    u32 disableOutputReordering = 0;
    FILE *finput;
    Decoder **decoder;
    char outFileName[256] = "out.yuv";


    if ( argc > 1 && strcmp(argv[1], "-T") == 0 )
    {
        fprintf(stderr, "%s\n", tagName);
        return 0;
    }

    if (argc < 2)
    {
        DEBUG((
            "Usage: %s [-Nn] [-Ooutfile] [-P] [-U] [-C] [-R] [-T] file1.264 [file2.264] .. [fileN.264]\n",
            argv[0]));
        DEBUG(("\t-Nn forces decoding to stop after n pictures\n"));
#if defined(_NO_OUT)
        DEBUG(("\t-Ooutfile output writing disabled at compile time\n"));
#else
        DEBUG(("\t-Ooutfile write output to \"outfile\" (default out.yuv)\n"));
        DEBUG(("\t-Onone does not write output\n"));
#endif
        DEBUG(("\t-C display cropped image (default decoded image)\n"));
        DEBUG(("\t-R disable DPB output reordering\n"));
        DEBUG(("\t-T to print tag name and exit\n"));
        exit(100);
    }

    instCount = argc - 1;

    /* read command line arguments */
    maxNumPics = 0;
    for (i = 1; i < (argc-1); i++)
    {
        if ( strncmp(argv[i], "-N", 2) == 0 )
        {
            maxNumPics = (u32)atoi(argv[i]+2);
            instCount--;
        }
        else if ( strncmp(argv[i], "-O", 2) == 0 )
        {
            strcpy(outFileName, argv[i]+2);
            instCount--;
        }
        else if ( strcmp(argv[i], "-C") == 0 )
        {
            cropDisplay = 1;
            instCount--;
        }
        else if ( strcmp(argv[i], "-R") == 0 )
        {
            disableOutputReordering = 1;
            instCount--;
        }
    }

    if (instCount < 1)
    {
        DEBUG(("No input files\n"));
        exit(100);
    }

    /* allocate memory for multiple decoder instances
     * one instance for every stream file */
    decoder = (Decoder **)malloc(sizeof(Decoder*)*(u32)instCount);
    if (decoder == NULL)
    {
        DEBUG(("Unable to allocate memory\n"));
        exit(100);
    }

    /* prepare each decoder instance */
    for (i = 0; i < instCount; i++)
    {
        decoder[i] = (Decoder *)calloc(1, sizeof(Decoder));

        /* open input file */
        finput = fopen(argv[argc-instCount+i],"rb");
        if (finput == NULL)
        {
            DEBUG(("Unable to open input file <%s>\n", argv[argc-instCount+i]));
            exit(100);
        }

        DEBUG(("Reading input file[%d] %s\n", i, argv[argc-instCount+i]));

        /* read input stream to buffer */
        fseek(finput,0L,SEEK_END);
        strmLen = (u32)ftell(finput);
        rewind(finput);
        decoder[i]->byteStrmStart = (u8 *)malloc(sizeof(u8)*strmLen);
        if (decoder[i]->byteStrmStart == NULL)
        {
            DEBUG(("Unable to allocate memory\n"));
            exit(100);
        }
        fread(decoder[i]->byteStrmStart, sizeof(u8), strmLen, finput);
        fclose(finput);

        /* open output file */
        if (strcmp(outFileName, "none") != 0)
        {
#if defined(_NO_OUT)
            decoder[i]->foutput = NULL;
#else
            sprintf(decoder[i]->outFileName, "%s%i", outFileName, i);
            decoder[i]->foutput = fopen(decoder[i]->outFileName, "wb");
            if (decoder[i]->foutput == NULL)
            {
                DEBUG(("Unable to open output file\n"));
                exit(100);
            }
#endif
        }

        ret = H264SwDecInit(&(decoder[i]->decInst), disableOutputReordering);

        if (ret != H264SWDEC_OK)
        {
            DEBUG(("Init failed %d\n", ret));
            exit(100);
        }

        decoder[i]->decInput.pStream = decoder[i]->byteStrmStart;
        decoder[i]->decInput.dataLen = strmLen;
        decoder[i]->decInput.intraConcealmentMethod = 0;

    }

    /* main decoding loop */
    do
    {
        /* decode once using each instance */
        for (i = 0; i < instCount; i++)
        {
            ret = H264SwDecDecode(decoder[i]->decInst,
                                &(decoder[i]->decInput),
                                &(decoder[i]->decOutput));

            switch(ret)
            {

                case H264SWDEC_HDRS_RDY_BUFF_NOT_EMPTY:

                    ret = H264SwDecGetInfo(decoder[i]->decInst,
                            &(decoder[i]->decInfo));
                    if (ret != H264SWDEC_OK)
                        exit(1);

                    if (cropDisplay && decoder[i]->decInfo.croppingFlag)
                    {
                        DEBUG(("Decoder[%d] Cropping params: (%d, %d) %dx%d\n",
                            i,
                            decoder[i]->decInfo.cropParams.cropLeftOffset,
                            decoder[i]->decInfo.cropParams.cropTopOffset,
                            decoder[i]->decInfo.cropParams.cropOutWidth,
                            decoder[i]->decInfo.cropParams.cropOutHeight));
                    }

                    DEBUG(("Decoder[%d] Width %d Height %d\n", i,
                        decoder[i]->decInfo.picWidth,
                        decoder[i]->decInfo.picHeight));

                    DEBUG(("Decoder[%d] videoRange %d, matricCoefficients %d\n",
                        i, decoder[i]->decInfo.videoRange,
                        decoder[i]->decInfo.matrixCoefficients));
                    decoder[i]->decInput.dataLen -=
                        (u32)(decoder[i]->decOutput.pStrmCurrPos -
                              decoder[i]->decInput.pStream);
                    decoder[i]->decInput.pStream =
                        decoder[i]->decOutput.pStrmCurrPos;
                    break;

                case H264SWDEC_PIC_RDY_BUFF_NOT_EMPTY:
                    decoder[i]->decInput.dataLen -=
                        (u32)(decoder[i]->decOutput.pStrmCurrPos -
                              decoder[i]->decInput.pStream);
                    decoder[i]->decInput.pStream =
                        decoder[i]->decOutput.pStrmCurrPos;
                    /* fall through */
                case H264SWDEC_PIC_RDY:
                    if (ret == H264SWDEC_PIC_RDY)
                        decoder[i]->decInput.dataLen = 0;

                    ret = H264SwDecGetInfo(decoder[i]->decInst,
                            &(decoder[i]->decInfo));
                    if (ret != H264SWDEC_OK)
                        exit(1);

                    while (H264SwDecNextPicture(decoder[i]->decInst,
                            &(decoder[i]->decPicture), 0) == H264SWDEC_PIC_RDY)
                    {
                        decoder[i]->picNumber++;

                        numErrors += decoder[i]->decPicture.nbrOfErrMBs;

                        DEBUG(("Decoder[%d] PIC %d, type %s, concealed %d\n",
                            i, decoder[i]->picNumber,
                            decoder[i]->decPicture.isIdrPicture
                                ? "IDR" : "NON-IDR",
                            decoder[i]->decPicture.nbrOfErrMBs));
                        fflush(stdout);

                        CropWriteOutput(decoder[i]->foutput,
                                (u8*)decoder[i]->decPicture.pOutputPicture,
                                cropDisplay, &(decoder[i]->decInfo));
                    }

                    if (maxNumPics && decoder[i]->picNumber == maxNumPics)
                        decoder[i]->decInput.dataLen = 0;
                    break;

                case H264SWDEC_STRM_PROCESSED:
                case H264SWDEC_STRM_ERR:
                case H264SWDEC_PARAM_ERR:
                    decoder[i]->decInput.dataLen = 0;
                    break;

                default:
                    DEBUG(("Decoder[%d] FATAL ERROR\n", i));
                    exit(10);
                    break;

            }
        }

        /* check if any of the instances is still running (=has more data) */
        instRunning = instCount;
        for (i = 0; i < instCount; i++)
        {
            if (decoder[i]->decInput.dataLen == 0)
                instRunning--;
        }

    } while (instRunning);


    /* get last frames and close each instance */
    for (i = 0; i < instCount; i++)
    {
        while (H264SwDecNextPicture(decoder[i]->decInst,
                &(decoder[i]->decPicture), 1) == H264SWDEC_PIC_RDY)
        {
            decoder[i]->picNumber++;

            DEBUG(("Decoder[%d] PIC %d, type %s, concealed %d\n",
                i, decoder[i]->picNumber,
                decoder[i]->decPicture.isIdrPicture
                    ? "IDR" : "NON-IDR",
                decoder[i]->decPicture.nbrOfErrMBs));
            fflush(stdout);

            CropWriteOutput(decoder[i]->foutput,
                    (u8*)decoder[i]->decPicture.pOutputPicture,
                    cropDisplay, &(decoder[i]->decInfo));
        }

        H264SwDecRelease(decoder[i]->decInst);

        if (decoder[i]->foutput)
            fclose(decoder[i]->foutput);

        free(decoder[i]->byteStrmStart);

        free(decoder[i]);
    }

    free(decoder);

    if (numErrors)
        return 1;
    else
        return 0;

}

/*------------------------------------------------------------------------------

------------------------------------------------------------------------------*/
void CropWriteOutput(FILE *foutput, u8 *imageData, u32 cropDisplay,
        H264SwDecInfo *decInfo)
{
    u8 *tmpImage = NULL;
    u32 tmp, picSize;

    if (cropDisplay && decInfo->croppingFlag)
    {
        picSize = decInfo->cropParams.cropOutWidth *
                  decInfo->cropParams.cropOutHeight;
        picSize = (3 * picSize)/2;
        tmpImage = malloc(picSize);
        if (tmpImage == NULL)
            exit(1);
        tmp = CropPicture(tmpImage, imageData,
            decInfo->picWidth, decInfo->picHeight,
            &(decInfo->cropParams));
        if (tmp)
            exit(1);
        WriteOutput(foutput, tmpImage, picSize);
        free(tmpImage);
    }
    else
    {
        picSize = decInfo->picWidth * decInfo->picHeight;
        picSize = (3 * picSize)/2;
        WriteOutput(foutput, imageData, picSize);
    }

}

/*------------------------------------------------------------------------------

------------------------------------------------------------------------------*/
void WriteOutput(FILE *fid, u8 *data, u32 picSize)
{
    if (fid)
        fwrite(data, 1, picSize, fid);
}

/*------------------------------------------------------------------------------

    Function name:  H264SwDecTrace

------------------------------------------------------------------------------*/
void H264SwDecTrace(char *string)
{
    FILE *fp;

    fp = fopen("dec_api.trc", "at");

    if (!fp)
        return;

    fwrite(string, 1, strlen(string), fp);
    fwrite("\n", 1,1, fp);

    fclose(fp);
}

/*------------------------------------------------------------------------------

    Function name:  H264SwDecmalloc

------------------------------------------------------------------------------*/
void* H264SwDecMalloc(u32 size, u32 num)
{
    if (size > UINT32_MAX / num) {
        return NULL;
    }
    return malloc(size * num);
}

/*------------------------------------------------------------------------------

    Function name:  H264SwDecFree

------------------------------------------------------------------------------*/
void H264SwDecFree(void *ptr)
{
    free(ptr);
}

/*------------------------------------------------------------------------------

    Function name:  H264SwDecMemcpy

------------------------------------------------------------------------------*/
void H264SwDecMemcpy(void *dest, void *src, u32 count)
{
    memcpy(dest, src, count);
}

/*------------------------------------------------------------------------------

    Function name:  H264SwDecMemset

------------------------------------------------------------------------------*/
void H264SwDecMemset(void *ptr, i32 value, u32 count)
{
    memset(ptr, value, count);
}

/*------------------------------------------------------------------------------

    Function name: CropPicture

------------------------------------------------------------------------------*/
u32 CropPicture(u8 *pOutImage, u8 *pInImage,
    u32 picWidth, u32 picHeight, CropParams *pCropParams)
{

    u32 i, j;
    u32 outWidth, outHeight;
    u8 *pOut, *pIn;

    if (pOutImage == NULL || pInImage == NULL || pCropParams == NULL ||
        !picWidth || !picHeight)
    {
        /* due to lint warning */
        free(pOutImage);
        return(1);
    }

    if ( ((pCropParams->cropLeftOffset + pCropParams->cropOutWidth) >
           picWidth ) ||
         ((pCropParams->cropTopOffset + pCropParams->cropOutHeight) >
           picHeight ) )
    {
        /* due to lint warning */
        free(pOutImage);
        return(1);
    }

    outWidth = pCropParams->cropOutWidth;
    outHeight = pCropParams->cropOutHeight;

    pIn = pInImage + pCropParams->cropTopOffset*picWidth +
        pCropParams->cropLeftOffset;
    pOut = pOutImage;

    /* luma */
    for (i = outHeight; i; i--)
    {
        for (j = outWidth; j; j--)
        {
            *pOut++ = *pIn++;
        }
        pIn += picWidth - outWidth;
    }

    outWidth >>= 1;
    outHeight >>= 1;

    pIn = pInImage + picWidth*picHeight +
        pCropParams->cropTopOffset*picWidth/4 + pCropParams->cropLeftOffset/2;

    /* cb */
    for (i = outHeight; i; i--)
    {
        for (j = outWidth; j; j--)
        {
            *pOut++ = *pIn++;
        }
        pIn += picWidth/2 - outWidth;
    }

    pIn = pInImage + 5*picWidth*picHeight/4 +
        pCropParams->cropTopOffset*picWidth/4 + pCropParams->cropLeftOffset/2;

    /* cr */
    for (i = outHeight; i; i--)
    {
        for (j = outWidth; j; j--)
        {
            *pOut++ = *pIn++;
        }
        pIn += picWidth/2 - outWidth;
    }

    return (0);

}

