// (c) 2003-2006 Criterion Software Limited and its licensors. All Rights Reserved

/* ChunktoAVI.cpp
    Written by: Ali Emami

    Converts any chunk style file format to an AVI video file.

    Formats understood internally are MAD and Streaming Shapes 2.
    For these formats no width/height need be specified on the command
    line.

    For other chunk based formats, a width and a height must be specified
    for the format.



    Usage:  ChunkToAVI "inputfile" "outputfile" <width> <height>

            " " : required
            < > : depends on format

*/


//#include <stdio.h>
//#include <string.h>
#include "stdgimex.h"
//#include "stdgimex.h"
#include "chunktoavi.h"

int WriteAVIIndex(GSTREAM* writeStream,AVIHStruct* aviheader)
{

    char buf[16];
    int indexSize=0;
    int ok = 1;
    GPOS fileSize = glen(writeStream);
    int frames = aviheader->TotalFrames;

    gputm(buf,GMAKEID('i','d','x','1'),4);
    gputi(buf+4,frames*sizeof(AVIINDXStruct),4);
    ok = gwrite(writeStream,buf,8);
    indexSize +=8;

    AVIINDXStruct* index = (AVIINDXStruct*) galloc(frames*sizeof(AVIINDXStruct));

    // seek to beginning
    GPOS currentPos = gtell(writeStream);

    gseek(writeStream,0xe0);    // we can read from the writeStream
                                // 0xe0 is constant offset of where movie data starts

    int offsetFromMovi = 4;

    for(int i=0; i<frames && ok;i++)
    {

        ok = gread(writeStream,buf,8);

        index[i].Identifier = ggeti(buf,4);
        index[i].Length = ggeti(buf+4,4);
        index[i].Offset = offsetFromMovi;

        offsetFromMovi += index[i].Length + 8;

        if(index[i].Length>=12)
        {
            ok = gread(writeStream,buf,4);
            ok = gseek(writeStream,(gtell(writeStream)-4) + index[i].Length );

            if(ggetm(buf,4) == GMAKEID('M','A','D','k')  )
            {
                index[i].Flags = AVI_KEYFRAME;
            }
            else if(ggetm(buf,3) == GMAKEID(0,'S','s','f'))
            {
                index[i].Flags = AVI_KEYFRAME;
            }
            else
            {
                index[i].Flags = 0;
            }

        }
        else
        {
            gseek(writeStream,gtell(writeStream)+index[i].Length);
            index[i].Flags = 0;
        }

        indexSize +=16;


    }

    ok = gseek(writeStream,currentPos);

    ok = gwrite(writeStream,index,indexSize-8); // -8 because already included 'idx1' and size

    return indexSize;

}




int WriteMovieDataChunks(GSTREAM* readStream,GSTREAM* writeStream,AVIHStruct* aviheader,AVISTRHStruct *streamHeader)
{

    char buf[16];
    int ok=1;
    GPOS fileSize = glen(readStream);
    unsigned char *chunkBuffer;
    int chunkSize;
    int framecount = 0;
    int audChunks=0;
    int maxChunkSize=0;
    int currentChunkSize=0;
    int totalDataSize=0;

    // seek to beginning of input file
    gseek(readStream,0);

    while(ok && gtell(readStream) < (fileSize-8) )
    {
        ok = gread(readStream,buf,8);

        chunkSize = ggeti(buf+4,4);
        if(chunkSize & 0x01)
        {
            chunkSize++;
        }

        if(ok && ggetm(buf,2) != 'SC' && ggetm(buf,4)!='MVhd') // write the chunk to file since not dealing with an audio chunk
        {
            // write the stream identifier
            gputm(buf,GMAKEID('0','0','d','c'),4);
            ok = gwrite(writeStream,buf,4);
            totalDataSize+=4;

			chunkSize -= 8;
            ok = gwrite(writeStream,&chunkSize,4);

            totalDataSize+=4;

            chunkBuffer = (unsigned char*) galloc(chunkSize+1);

            if(chunkBuffer)
            {
//                gseek(readStream,gtell(readStream)-8);
                gread(readStream,chunkBuffer,chunkSize);   // read in the chunk


            }
            else
            {
                gprintf("Out of memory\n");
                return 0;
            }
            if(ok)
            {
                gwrite(writeStream,chunkBuffer,chunkSize);
                totalDataSize+=chunkSize;
                if(chunkSize>maxChunkSize)
                {
                    maxChunkSize = chunkSize;
                }

                framecount++;
                gfree(chunkBuffer);
            }
        }
        else
        {
            gseek(readStream,(gtell(readStream)-8)+chunkSize);


        }

    } // end while

    aviheader->TotalFrames = framecount;
    aviheader->SuggestedBufferSize = maxChunkSize; //[AE] correct? compressed size?
    aviheader->MaxBps = (int)( maxChunkSize * (1.0f/aviheader->TimeBetweenFrames)*1000000);

    streamHeader->DataLength = framecount;
    streamHeader->SuggestedBufferSize = maxChunkSize;

    /* write the new information to file */
    GPOS currentPos = gtell(writeStream);


    //MaxBps in AVI header
    ok = gseek(writeStream,0x24);
    ok = gwrite(writeStream,&aviheader->MaxBps,4);

    //framecount in AVI header
    ok = gseek(writeStream,0x30);
    ok = gwrite(writeStream,&aviheader->TotalFrames,4);

    //SuggestedBufferSize in AVI header
    ok = gseek(writeStream,0x3c);
    ok = gwrite(writeStream,&aviheader->SuggestedBufferSize,4);

    //DataLength in AVI header
    ok = gseek(writeStream,0x54);
    ok = gwrite(writeStream,&aviheader->TotalFrames,4);

    //DataLength in stream Header
    ok = gseek(writeStream,0x8c);
    ok = gwrite(writeStream,&streamHeader->DataLength,4);

    //SuggestedBufferSize in stream Header
    ok = gseek(writeStream,0x90);
    ok = gwrite(writeStream,&streamHeader->SuggestedBufferSize,4);


    gseek(writeStream,currentPos);

    return totalDataSize;

}


int WriteVideoStreamHeader(AVISTRHStruct *videoStreamHeader,AVIHStruct* aviheader,GSTREAM *writeStream, int fourCC)
{

    int ok = 0;

	if (fourCC=='06PV' || fourCC=='VP60')
		fourCC = 'vp60'; // compressor = dll

    gputm(&videoStreamHeader->DataType, GMAKEID('v','i','d','s'), 4); /* DataType */
    gputm(&videoStreamHeader->DataHandler, fourCC, 4);       /* DataHandler, optional */
    gputm(&videoStreamHeader->Flags,  0x00000000, 4);       /* Flags */
    gputi(&videoStreamHeader->Priority, 0, 4);                /* Priority */
    gputi(&videoStreamHeader->InitialFrames, 0, 4);                /* InitialFrames */
    gputi(&videoStreamHeader->TimeScale, aviheader->TimeBetweenFrames, 4);/* TimeScale */
    gputi(&videoStreamHeader->DataRate, 0x000f4240, 4);       /* DataRate */
    gputi(&videoStreamHeader->StartTime, 0, 4);                /* StartTime */
    gputi(&videoStreamHeader->DataLength, 0, 4);       /* DataLength, fill later with number of frames */
    gputi(&videoStreamHeader->SuggestedBufferSize, 0x00080000, 4);       /* SuggestedBufferSize, modify later, */
    gputi(&videoStreamHeader->Quality, 10000, 4);     /* [AE], no default, Quality (10000 Max) */
    gputi(&videoStreamHeader->SampleSize, 0, 4);                /* SampleSize */
    gputi((char*)videoStreamHeader + 48, 0, 4);                /* Reserved */
    gputi((char*)videoStreamHeader + 52, 0, 4);                /* Reserved */

    ok = gwrite(writeStream,videoStreamHeader,56);

    return ok;
}

int WriteBmiHeader(AVIbmiStruct *bmiHeader,AVIHStruct* aviHeader,int fourCC,GSTREAM* writeStream)
{
    int ok = 0;

	if (fourCC=='06PV' || fourCC=='vp60')
		fourCC = 'VP60';  // format is codec within dll

    gputi(&bmiHeader->Size,40,4);                                           /* Size */
    gputi(&bmiHeader->Width, aviHeader->Width, 4);                          /* Width */
    gputi(&bmiHeader->Height, aviHeader->Height, 4);                        /* Height */
    gputi(&bmiHeader->Planes, 1, 2);                                        /* Planes */
    gputi(&bmiHeader->BitCount,32,2);                                       /* BitCount */
    gputm(&bmiHeader->Compression, fourCC, 4);                              /* Compression (RGB) */
    gputi(&bmiHeader->ImageSize, aviHeader->Width*aviHeader->Height*32,4);  /* ImageSize */ /* KAZ */
    gputi(&bmiHeader->XpelsMeter, 0, 4);                                    /* XpelsMeter */
    gputi(&bmiHeader->YpelsMeter, 0, 4);                                    /* YpelsMeter */
    gputi(&bmiHeader->NumberOfColours, 0, 4);                               /* NumberOfColours */
    gputi(&bmiHeader->ImportantColours, 0, 4);                              /* ImportantColours */

    ok = gwrite(writeStream,bmiHeader,40);
    return ok;

}



int WriteAVIMovieHeader(AVIHStruct* header, GSTREAM* readStream, GSTREAM* writeStream, int *fourCC)
{
    char buf[56];
    int ok=1;

    /* Let's get some information from the FIRST VIDEO CHUNK header( TimeBetweenFrames, Width, Height)
    For now, we ignore all chunks that have an identifier that start with 'SC' ( ie. the audio chunks)*/

    int TimeBetweenFrames;
    int width=0;
    int height=0;
    GPOS fileLength = glen(readStream);
    bool found = false;

    while( ok && !found && (gtell(readStream) <= fileLength-8 ) )
    {
        ok = gread(readStream,buf,8);
        int chunkSize = ggeti(buf+4,4);

        if(ok && ggetm(buf,2) != 'SC')  // we're not dealing with an audio chunk
        {

#if 0
struct ChunkedAviHeader
{
  char   mId[4];                    // 'AVhd' FourCC
  char   mLength[4];                // Length of 'AVhd' chunk
  char   mCompressor[4];            // Compressor FourCC 
  char   mWidth[2];
  char   mHeight[2];
  char   mTotalFrames[4];           // Total number of frames in the file. This should always be accurate
  char   mSuggestedBufferSize[4];   // Recommended decode buffer size 
  char   mFpsNumerator[4];          // FPS = Numerator/Denominator
  char   mFpsDenominator[2];        // Eg. 30000/1001=29.97
  char   mFlags[2];                 // Eg. uses B-frames
};
#endif

            if(ggetm(buf,4)=='MVhd')     // dealing with MPV/VP6 header chunk, get info from header
            {
                ok = gread(readStream,buf+8,32-8);
                if(ok)
                {
                    width = ggeti(buf+12,2);
                    height = ggeti(buf+14,2);

					
                    int mFpsNumerator = ggeti(buf+24,4);
                    int mFpsDenominator = ggeti(buf+28,2);

                    TimeBetweenFrames = (int) (mFpsDenominator * 1000000.0f / mFpsNumerator);  // need it in microseconds

                    *fourCC = ggetm(buf+8,4);
                    found = true;
                }
            }
            else if (ggetm(buf,3)=='MAD')     // dealing with MAD chunk, get info from header
            {
                ok = gread(readStream,buf+8,16);
                if(ok)
                {
                    width = ggeti(buf+16,2);
                    height = ggeti(buf+18,2);


                    int fraction = (ggeti(buf+12,4) & 0xFFFF) ; // in MAD time between frames is a 16.16 fixed number in millliseconds
                    int whole    = ggeti(buf+12,4) >> 16;
                    float timeInMilliSeconds  = ( (float)whole + ((float)fraction / 65536.0f) );
                    TimeBetweenFrames = (int) (timeInMilliSeconds * 1000);  // need it in microseconds
                    *fourCC = ggetm(buf,4);
                    found = true;
                }
            }

            else if(ok && ggetm(buf,3)=='Ssf')  // dealing with a streaming shape
            {
                ok = gread(readStream,buf+8,32);
                if(ok)
                {
                    width = ggeti(buf+32,4);
                    height = ggeti(buf+36,4);

                    TimeBetweenFrames = 33333; // microsecs., 1/30fps
                    *fourCC = ggetm(buf,4);
                    found = true;

                }
            }
            else    // dealing with a video chunk that we don't know about
            {
                if(header->Width)
                    width = header->Width;
                else
                    width = 640;    // default width

                if(header->Height)
                    height = header->Height;
                else
                    height = 480;   // default height

                TimeBetweenFrames = 33333; // microsecs., 1/30fps
                *fourCC = ggetm(buf,4);
                found = true;



            }
        }
        else    // we're dealing with an 'SC' audio chunk, skip it
        {
            ok = gseek(readStream, (gtell(readStream)-8) + chunkSize);

        }

    }

    if(!ok)
    {
        gprintf("Error reading file");
        return ok;
    }
    if(!found )
    {
        gprintf("Input file has no video chunks");
        return 0;
    }

    gputi(&header->TimeBetweenFrames, TimeBetweenFrames,4);/* TimeBetweenFrames (1000000/15fps) */
    gputi(&header->MaxBps,0,4);                             /* MaxBps (Data size * fps, Added later) Need max frame size */
    gputi(&header->PaddingGranularity,0,4);     /* PaddingGranularity */
    gputi(&header->Flags,AVIHASINDEX,4);        /* Flags (Index exists) */
    gputi(&header->TotalFrames,0,4);            /* TotalFrames, fill later, need frame count*/
    gputi(&header->InitFrames,0,4);             /* [AE] InitFrames, different for interleaved files? */
    gputi(&header->Streams,1,4);                 /* [AE} Streams, different w/ more streams */
    gputi(&header->SuggestedBufferSize,0,4);     /*  SuggestedBufferSize (Max frame chunk size, Modified later) */
    gputi(&header->Width,width,4);              /* Width */
    gputi(&header->Height,height,4);            /* Height */
    gputi(&header->TimeScale,TimeBetweenFrames,4);  /* TimeScale */
    gputi(&header->DataRate,0,4);                   /* DataRate */
    gputi(&header->StartTime,0,4);                   /* StartTime */
    gputi(&header->DataLength,0,4);                 /* DataLength */

    ok = gwrite(writeStream,header,56);

    return ok;
}


int WriteAVIHeaders(GSTREAM* readStream, GSTREAM* writeStream,AVIHStruct* aviheader)
{
    char buf[56];
    int ok=0;
    int aviHeaderSize = 192; // since we have no pallete this is always 192
    int fourCC;
    int totalFileSize=0;


    /* output the file chunk headers */
    gputm(buf,GMAKEID('R','I','F','F'),4);
    gputm(buf+4,0,4);   // [AE] leave 4 bytes for the RIFF chunk size
    gputm(buf+8,GMAKEID('A','V','I',' '),4);
    ok = gwrite(writeStream,buf,12);
    totalFileSize+=12;

    /* Output AVI header chunk header */
    gputm(buf,GMAKEID('L','I','S','T'),4);
    gputi(buf+4,aviHeaderSize,4);
    gputm(buf+8,GMAKEID('h','d','r','l'),4);
    ok = gwrite(writeStream,buf,12);
    totalFileSize+=12;

     /* Output AVI header (56 bytes) */
    gputm(buf,GMAKEID('a','v','i','h'),4);
    gputi(buf+4,56,4);
    ok = gwrite(writeStream,buf,8);
    totalFileSize+=8;

    /* Fill in Avi header struct and write to file*/
    ok = WriteAVIMovieHeader(aviheader,readStream,writeStream,&fourCC);
    totalFileSize+=ok;

     /* Output video stream header chunk header */
    aviHeaderSize -= 76;
    gputm(buf,GMAKEID('L','I','S','T'),4);
    gputi(buf+4,aviHeaderSize,4);
    gputm(buf+8,GMAKEID('s','t','r','l'),4);
    ok = gwrite(writeStream,buf,12);
    totalFileSize+=12;

     /* Output video stream header */
    gputm(buf,GMAKEID('s','t','r','h'),4);
    gputi(buf+4,56,4);
    ok = gwrite(writeStream,buf,8);
    totalFileSize+=8;

    AVISTRHStruct *videoStreamHeader = (AVISTRHStruct *) galloc(56);
    ok = WriteVideoStreamHeader(videoStreamHeader,aviheader,writeStream,fourCC);
    totalFileSize+=ok;

     /* Output Bitmap info header */
    int size = 40;
    gputm(buf,GMAKEID('s','t','r','f'),4);
    gputi(buf+4,size,4);
    ok = gwrite(writeStream,buf,8);
    totalFileSize+=8;

    AVIbmiStruct bmiHeader;
    ok = WriteBmiHeader(&bmiHeader,aviheader,fourCC,writeStream);
    totalFileSize+=ok;

     /* Output movie chunk header */
    gputm(buf,GMAKEID('L','I','S','T'),4);
    gputi(buf+4,0,4); /* [AE] Correct size to be given later  */
    gputm(buf+8,GMAKEID('m','o','v','i'),4);
    ok = gwrite(writeStream,buf,12);
    totalFileSize+=12;

    /* Output the movie data chunks*/
    ok = WriteMovieDataChunks(readStream,writeStream,aviheader,videoStreamHeader);
    totalFileSize+=ok;


    int movieChunkSize = ok +4; // add 4 for 'movi'
    GPOS currentWritePos = gtell(writeStream);

    /* output LIST:movi chunk size*/
    ok = gseek(writeStream,0xd8);
    ok = gwrite(writeStream,&movieChunkSize,4);

    ok = gseek(writeStream,currentWritePos);

    /* write in index */
    ok = WriteAVIIndex(writeStream,aviheader);
    totalFileSize+=ok;


    /* fourCC reverse */


    /* output RIFF file chunk size */
    totalFileSize -= 8; // already counted 'RIFF', and size for RIFF chunk
    ok = gseek(writeStream,4);
    ok = gwrite(writeStream,&totalFileSize,4);


    gfree(videoStreamHeader);


    return ok;

}

int main(int argc, char* argv[])
{

    char inputFile[64];
    char outputFile[64];
    int curArg = 1;
    int ok = 0;
    AVIHStruct aviheader;
    memset(&aviheader,0,sizeof(AVIHStruct));

    if(argc < 3)
    {
        gprintf("Usage: ChunktoAVI 'input filename' 'output filename' <width> <height>");
        return 0;
    }

    if(argc == 4)
    {
        gprintf("width specified without height");
        return 0;
    }



    strcpy(inputFile,argv[curArg]); curArg++;
    strcpy(outputFile,argv[curArg]); curArg++;
    GSTREAM* readStream = gopen(inputFile);
    GSTREAM* writeStream = gwopen(outputFile);

    if(!readStream || !writeStream)
    {
        gprintf("Error opening files. Check if files exist");
        return 0;
    }

    /* if the user specified a width and height, we use this width
        and height to fill in the AVI headers.

        For now, we can read width/height directly from MAD and streaming shapes,
        but for MPC and any other chunk format, the user needs to specify them
        */
    if(argc == 5)
    {
        aviheader.Width = atoi(argv[curArg]); curArg++;
        aviheader.Height = atoi(argv[curArg]); curArg++;
        aviheader.TimeBetweenFrames = 33333;    // in microseconds: 1/30fps
    }




    ok = WriteAVIHeaders(readStream,writeStream,&aviheader);

    return ok;
}

