/*===========================================================================*
 * frametype.c								     *
 *									     *
 *	procedures to keep track of frame types (I, P, B)		     *
 *									     *
 * EXPORTED PROCEDURES:							     *
 *	FType_Type						             *
 *	FType_FutureRef						             *
 *	FType_PastRef						             *
 *									     *
 * SYNOPSIS								     *
 *	FType_Type	returns the type of the given numbered frame	     *
 *	FType_FutureRef	returns the number of the future reference frame     *
 *	FType_PastRef	returns the number of the past reference frame	     *
 *									     *
 * 00.12.07 change malloc from frameTable to calloc to fix bug 
 *===========================================================================*/

/*
 * Copyright (c) 1995 The Regents of the University of California.
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without written agreement is
 * hereby granted, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */


/*==============*
 * HEADER FILES *
 *==============*/

#include "mallocvar.h"
#include "all.h"
#include "frames.h"
#include "frame.h"
#include "param.h"
#include "specifics.h"
#include "frametype.h"


static FrameTable *frameTable=NULL;
static boolean use_cache = FALSE;
static int firstI = 0;
static int numFrames;

/*==================*
 * GLOBAL VARIABLES *
 *==================*/

extern int framePatternLen;
extern char * framePattern;


/*=====================*
 * EXPORTED PROCEDURES *
 *=====================*/

/*===========================================================================*
 *
 * FType_Type
 *
 *	returns the type of the given numbered frame
 *
 * RETURNS:	the type
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
char
FType_Type(unsigned int const frameNum) {

    char const patternedType = framePattern[frameNum % framePatternLen];

    char retval;

    if (use_cache) 
        return frameTable[frameNum].typ;
  
    if (frameNum+1 == numFrames) {
        /* It's the last frame in the sequence.  If the pattern says it's
           a B, we convert it to I because a B frame makes no sense as the
           last frame of a sequence.
        */
        if (patternedType == 'b') 
            retval = 'i';
        else 
            retval = patternedType;
    } else {
        if (specificsOn) {
            static int lastI = -1;
            int newtype;
      
            if (lastI > frameNum) 
                lastI = -1;
            newtype = SpecTypeLookup(frameNum);
            switch (newtype) {
            case 1:
                lastI = frameNum;
                retval = 'i';
                break;
            case 2:
                retval = 'p';
                break;
            case 3:
                retval = 'b';
                break;
            default:
                if (lastI != -1) {
                    unsigned int const pretendFrameNumber = 
                        (frameNum - lastI + firstI) % framePatternLen;
                    retval = framePattern[pretendFrameNumber];
                } else 
                    retval = patternedType;
            }
        } else 
            retval = patternedType;
    }
    return retval;
}



unsigned int
FType_FutureRef(unsigned int const currFrameNum) {
/*----------------------------------------------------------------------------
  Return the number of the future reference frame for the B frame
  'currentFrameNum'.
-----------------------------------------------------------------------------*/
    unsigned int retval;

    if (use_cache) {
        retval = frameTable[currFrameNum].next->number;
    } else {
        int const index = currFrameNum % framePatternLen;
        int const futureIndex = frameTable[index].next->number;
        unsigned int const patternedFutureRef =
            currFrameNum +
            (((futureIndex-index)+framePatternLen) % framePatternLen);

        retval = MIN(patternedFutureRef, numFrames-1);
    }
    return retval;
}



/*===========================================================================*
 *
 * FType_PastRef
 *
 *	returns the number of the past reference frame
 *
 * RETURNS:	the number
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
int
FType_PastRef(currFrameNum)
    int currFrameNum;
{
    int	    index;
    int	    pastIndex;

    if (use_cache) {
      return frameTable[currFrameNum].prev->number;
    } else {
      index = currFrameNum % framePatternLen;
      pastIndex = frameTable[index].prev->number;
      
      return currFrameNum -
	(((index-pastIndex)+framePatternLen) % framePatternLen);
    }
}


/*===========================================================================*
 *
 * SetFramePattern
 *
 *	set the IPB pattern; calls ComputeFrameTable to set up table
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    framePattern, framePatternLen, frameTable
 *
 *===========================================================================*/
#define SIMPLE_ASCII_UPPER(x)  (((x)>='a') ? ((x)-'a'+'A') : (x))
void
SetFramePattern(const char * const pattern) {
    unsigned int const len = strlen(pattern);

    char *buf;
    unsigned int index;

    if (!pattern)
        pm_error("INTERNAL ERROR: pattern cannot be NULL "
            "in SetFramePattern");

    if (SIMPLE_ASCII_UPPER(pattern[0]) != 'I') {
        unsigned int index;
        for (index = 0; index < len; ++index) {
            if (SIMPLE_ASCII_UPPER(pattern[index]) == 'I') {
                break;
            } else if (SIMPLE_ASCII_UPPER(pattern[index]) == 'P')
                pm_error("first reference frame must be 'i', not '%c'",
                         pattern[index]);
        }
    }

    buf = (char *)malloc(sizeof(char)*(len+1));
    ERRCHK(buf, "malloc");

    firstI = -1;
    for (index = 0; index < len; index++) {
        switch( SIMPLE_ASCII_UPPER(pattern[index]) ) {
        case 'I':	
            buf[index] = 'i';
            if (firstI == -1) firstI = index;
            break;
        case 'P':	
            buf[index] = 'p'; 
            break;
        case 'B':	
            buf[index] = 'b';
            break;
        default:
            pm_error("Invalid MPEG Frame type '%c'.", pattern[index]);
        }
    }
    buf[len] = 0;

    if (firstI == -1)
        pm_error("Must have an I-frame in PATTERN");

    framePattern = buf;
    framePatternLen = len;
    
    /* Used to ComputeFrameTable(), but now must wait until param
       parsed. (STDIN or not)
    */
}



void
ComputeFrameTable(unsigned int const numFramesArg) {
/*----------------------------------------------------------------------------
  Compute a table of I, P, B frames to help in determining dependencie

  'numFrames' == 0 means number of frames is not known at this time.
-----------------------------------------------------------------------------*/
    int index;
    FrameTable	*lastI, *lastIP, *firstB, *secondIP;
    FrameTable	*ptr;
    char typ;
    int table_size;

    numFrames = numFramesArg;

    if (numFrames)
        table_size = numFrames;
    else
        table_size = framePatternLen;

    MALLOCARRAY_NOFAIL(frameTable, 1 + table_size);

    lastI = NULL;
    lastIP = NULL;
    firstB = NULL;
    secondIP = NULL;
    for ( index = 0; index < table_size; index++ ) {
        frameTable[index].number = index;
        typ = FType_Type(index);
        frameTable[index].typ = typ;
        switch( typ ) {
	    case 'i':
            ptr = firstB;
            while ( ptr != NULL ) {
                ptr->next = &(frameTable[index]);
                ptr = ptr->nextOutput;
            }
            frameTable[index].nextOutput = firstB;
            frameTable[index].prev = lastIP;	/* for freeing */
            if ( lastIP != NULL ) {
                lastIP->next = &(frameTable[index]);
                if ( secondIP == NULL ) {
                    secondIP = &(frameTable[index]);
                }
            }
            lastIP = &(frameTable[index]);
            firstB = NULL;
            break;
	    case 'p':
            ptr = firstB;
            while ( ptr != NULL ) {
                ptr->next = &(frameTable[index]);
                ptr = ptr->nextOutput;
            }
            frameTable[index].nextOutput = firstB;
            frameTable[index].prev = lastIP;
            if ( lastIP != NULL ) {
                lastIP->next = &(frameTable[index]);
                if ( secondIP == NULL ) {
                    secondIP = &(frameTable[index]);
                }
            }
            lastIP = &(frameTable[index]);
            firstB = NULL;
            break;
	    case 'b':
            if ( (index+1 == framePatternLen) ||
                 (FType_Type(index+1) != 'b') ) {
                frameTable[index].nextOutput = NULL;
            } else {
                frameTable[index].nextOutput = &(frameTable[index+1]);
            }
            frameTable[index].prev = lastIP;
            if ( firstB == NULL ) {
                firstB = &(frameTable[index]);
            }
            break;
	    default:
	        fprintf(stderr, "Programmer Error in ComputeFrameTable (%d)\n",
                    framePattern[index]);
	        exit(1);
	        break;
        }
    }
    
    /* why? SRS */
    frameTable[table_size].number = framePatternLen;
    ptr = firstB;
    while ( ptr != NULL ) {
        ptr->next = &(frameTable[table_size]);
        ptr = ptr->nextOutput;
    }
    frameTable[table_size].nextOutput = firstB;
    frameTable[table_size].prev = lastIP;
    if ( secondIP == NULL )
        frameTable[table_size].next = &(frameTable[0]);
    else
        frameTable[table_size].next = secondIP;

    frameTable[0].prev = lastIP;
    if ( lastIP != NULL ) {
        lastIP->next = &(frameTable[table_size]);
    }

    if (numFrames)
        use_cache = TRUE;
}