/*------------------------------------------------------------------------
 *
 * Copyright (c) 1997-1998 by Cornell University.
 * 
 * See the file "license.txt" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * This is a simple example demonstrating how to encode a short sequence
 * of PPM files into I and P frames with: 1 Sequence, 2 GOPs, 4 Picture
 * frames per GOP, 1 Slice per frame, 1 quantization scale.  The P frames 
 * are encoded using the original I frames.  The motion vector search is 
 * done with full pel units.  The frame pattern is IPIPIP...
 *
 * Note: the input files should be named <prefix>000.ppm, <prefix>001.ppm ...
 *------------------------------------------------------------------------
 */

#include <sys/stat.h>
#include <dvmbasic.h>
#include <dvmmpeg.h>
#include <dvmpnm.h>
#include <dvmcolor.h>

#define PREFIX_SIZE         100     /* prefix to the input files including directory */
#define BUFFER_SIZE         50000   /* large enough to hold 2 frames and some headers */
#define FRAMES_PER_SECOND   30
#define FORWARD_F_CODE      3
#define WIDTH               176     /* width and height of each frame in pixels */
#define HEIGHT              120

int fsize (char *name)
{
#ifdef __WIN32__
     struct _stat s;
#else
     struct stat s;
#endif
    int result;

    result = stat(name, &s);
    return s.st_size;
}

int pictures = 0;
int seconds = 0;
int minutes = 0;
int hours = 0;

/*
 *------------------------------------------------------------------------
 * Use this to keep track of how many picture frames, seconds, minutes,
 * hours.  This information is encoded in the GOP headers.
 *------------------------------------------------------------------------
 */
void
IncrementTime()
{
    pictures++;
    if ( pictures == FRAMES_PER_SECOND ) {
        pictures = 0;
        seconds++;
        if ( seconds == 60 ) {
            seconds = 0;
            minutes++;
            if ( minutes == 60 ) {
                minutes = 0;
                hours++;
            }
        }
    }
}


/*
 *------------------------------------------------------------------------
 * Swap two ByteImage pointers
 *------------------------------------------------------------------------
 */

void Swap(a, b)
    ByteImage **a, **b;
{
    ByteImage *temp;
    temp = *a;
    *a = *b;
    *b = temp;
}


/*
 *------------------------------------------------------------------------
 * Read in a PPM file into r, g, b ByteImages, then convert it to y, u, v
 * with 4:2:0 sampling
 *------------------------------------------------------------------------
 */

#define READ_TO_YUV(bp, bs, r, g, b, y, u, v) {\
    sprintf(fileName, "%s%03d.ppm", namePrefix, pictures);\
    printf("Processing %s\n", fileName);\
    inFile = fopen(fileName, "rb");\
    if (inFile == NULL) {\
	fprintf(stderr, "unable to open %s for reading.\n", fileName);\
	exit(1);\
    }\
    BitParserWrap(bp, bs);\
    BitStreamFileRead(bs, inFile, 0);\
    PnmHdrParse(bp, pnmHdr);\
    PpmParse(bp, r, g, b);\
    fclose(inFile);\
    RgbToYuv420(r, g, b, y, u, v);\
}


int main(int argc, char *argv[])
{
    FILE *inFile, *outFile;
    char fileName[PREFIX_SIZE+3];
    char namePrefix[PREFIX_SIZE];
    BitParser *bp  = BitParserNew();        /* input bitstream */
    BitStream *bs;
    BitParser *obp = BitParserNew();        /* output bitstream */
    BitStream *obs = BitStreamNew(BUFFER_SIZE);

    ByteImage *r, *g, *b, *y, *u, *v;
    ByteImage *prevY, *prevU, *prevV;
    ByteImage *qScale;
    ScImage *scY, *scU, *scV;
    VectorImage *fmv;                       /* forward motion vector */

    PnmHdr *pnmHdr = PnmHdrNew();
    MpegSeqHdr *seqHdr = MpegSeqHdrNew();
    MpegGopHdr *gopHdr = MpegGopHdrNew();
    MpegPicHdr *picHdr = MpegPicHdrNew();

    int w = WIDTH, h = HEIGHT;
    int halfw = w/2;
    int halfh = h/2;
    int mbw = (w + 15) / 16;
    int mbh = (h + 15) / 16;

    int sliceInfo[] = {1000};       /* this should be enough for one frame */
    int sliceInfoLen = 1;
    int gop, pic, temporalRef;

    r       = ByteNew(w, h);
    g       = ByteNew(w, h);
    b	    = ByteNew(w, h);
    y       = ByteNew(w, h);
    prevY   = ByteNew(w, h);
    u       = ByteNew(halfw, halfh);
    prevU   = ByteNew(halfw, halfh);
    v       = ByteNew(halfw, halfh);
    prevV   = ByteNew(halfw, halfh);
    qScale  = ByteNew(mbw, mbh);
    scY     = ScNew(mbw*2, mbh*2);
    scU     = ScNew(mbw, mbh);
    scV     = ScNew(mbw, mbh);
    fmv     = VectorNew(mbw, mbh);

    if (argc < 2) {
	fprintf(stderr, "Not enough parameters : %s <input file prefix> <output file name>\n", argv[0]);
	exit(1);
    }

    /* Get input file size */
    strcpy(namePrefix, argv[1]);
    sprintf(fileName, "%s000.ppm", namePrefix);
    inFile = fopen(fileName, "rb");
    if (inFile == NULL) {
	fprintf(stderr, "unable to open %s for reading.\n", fileName);
	exit(1);
    }
    bs  = BitStreamNew(fsize(fileName));
    fclose(inFile);

    /* Create outfile file */
    outFile = fopen(argv[2], "wb");
    if (outFile == NULL) {
	fprintf(stderr, "unable to open %s for writing.\n", argv[2]);
	exit(1);
    }
    BitParserWrap(obp, obs);

    ByteSet(qScale, 4);

    MpegSeqHdrSetWidth(seqHdr, w);
    MpegSeqHdrSetHeight(seqHdr, h);
    MpegSeqHdrSetAspectRatio(seqHdr, 1.000);
    MpegSeqHdrSetPicRate(seqHdr, FRAMES_PER_SECOND);
    MpegSeqHdrSetBitRate(seqHdr, -1);
    MpegSeqHdrSetBufferSize(seqHdr, 16);
    MpegSeqHdrSetConstrained(seqHdr, 0);
    MpegSeqHdrSetDefaultIQT(seqHdr);
    MpegSeqHdrSetDefaultNIQT(seqHdr);

    MpegSeqHdrEncode(seqHdr, obp);

    /* we are not going to change these */
    MpegGopHdrSetDropFrameFlag(gopHdr, 0);
    MpegGopHdrSetClosedGop(gopHdr, 0);
    MpegGopHdrSetBrokenLink(gopHdr, 0);

    MpegPicHdrSetVBVDelay(picHdr, 0);
    MpegPicHdrSetFullPelBackward(picHdr, 0);
    MpegPicHdrSetBackwardFCode(picHdr, 0);

    for (gop = 0; gop < 2; gop++) {
        MpegGopHdrSetHours(gopHdr, hours);
        MpegGopHdrSetMinutes(gopHdr, minutes);
        MpegGopHdrSetSeconds(gopHdr, seconds);
        MpegGopHdrSetPictures(gopHdr, pictures);

        MpegGopHdrEncode(gopHdr, obp);
        temporalRef = 0;
        for (pic = 0; pic < 2; pic++) {

            /* I FRAME */
            READ_TO_YUV(bp, bs, r, g, b, y, u, v);

            MpegPicHdrSetTemporalRef(picHdr, temporalRef);
            MpegPicHdrSetType(picHdr, I_FRAME);
            MpegPicHdrSetFullPelForward(picHdr, 0);
            MpegPicHdrSetForwardFCode(picHdr, 0);
            MpegPicHdrEncode (picHdr, obp);

            ByteToScI(y, qScale, MPEG_INTRA, scY); /* MPEG_INTRA is the default quantizer table */
            ByteToScI(u, qScale, MPEG_INTRA, scU); 
            ByteToScI(v, qScale, MPEG_INTRA, scV);
            MpegPicIEncode (picHdr, scY, scU, scV, qScale, sliceInfo, sliceInfoLen, obp);

            IncrementTime();
            temporalRef++;
            Swap(&y, &prevY);
            Swap(&u, &prevU);
            Swap(&v, &prevV);

            /* P FRAME */
            READ_TO_YUV(bp, bs, r, g, b, y, u, v);

            MpegPicHdrSetTemporalRef(picHdr, temporalRef);
            MpegPicHdrSetType(picHdr, P_FRAME);
            MpegPicHdrSetFullPelForward(picHdr, 1);
            MpegPicHdrSetForwardFCode(picHdr, FORWARD_F_CODE);
            MpegPicHdrEncode (picHdr, obp);

            BytePMotionVecSearch(picHdr, y, prevY, NULL, fmv);
            ByteYToScP (y, prevY, fmv, qScale, MPEG_INTRA, MPEG_NON_INTRA, scY);
            ByteUVToScP(u, prevU, fmv, qScale, MPEG_INTRA, MPEG_NON_INTRA, scU);
            ByteUVToScP(v, prevV, fmv, qScale, MPEG_INTRA, MPEG_NON_INTRA, scV);
            MpegPicPEncode (picHdr, scY, scU, scV, fmv, qScale, sliceInfo, sliceInfoLen, obp);

            IncrementTime();
            temporalRef++;

            /* write two frames to the bitstream */
            printf("Writing to file...\n");
            BitStreamFileWriteSegment(obs, outFile, 0, BitParserTell(obp));
            BitParserWrap(obp, obs);
        }
    }
    /* done forget this */
    MpegSeqEndCodeEncode(obp);
    BitStreamFileWriteSegment(obs, outFile, 0, BitParserTell(obp));
 
 
    /* lots of things to free up */
    BitStreamFree(bs);
    BitParserFree(bp);
    BitStreamFree(obs);
    BitParserFree(obp);

    MpegSeqHdrFree(seqHdr);
    MpegGopHdrFree(gopHdr);
    MpegPicHdrFree(picHdr);
    PnmHdrFree(pnmHdr);

    ByteFree(r);
    ByteFree(g);
    ByteFree(b);
    ByteFree(y);
    ByteFree(u);
    ByteFree(v);
    ByteFree(prevY);
    ByteFree(prevU);
    ByteFree(prevV);
    ByteFree(qScale);
    ScFree(scY);
    ScFree(scU);
    ScFree(scV);
    VectorFree(fmv);
    return 0;
}