
/*  (C) Copyright 1999 Rick Dean. */
/*  <vobTools@fdd.com>  http://fdd.com/vobTools */
/*  Released under version 2 of the GNU Public License. */                                                                         

/* See bitFile.h for longer comment. */

#include <stdio.h>
#include <stdlib.h>
#include "bitfile.h"
#include <assert.h>

#define ALARM_BIT_NOT_USED (0x7fffffff)

struct bitFileStruct {
   enum BitFileType {
      not_open = 63, /* arbitrary number to minimize matches with uninitialized data */
      read,
      write,
   } bitFileType;
   FILE *bitFILEPtr;
   FILE *traceFILEPtr;  /* == NULL means no tracefile */
   unsigned int buffer;  /* undefined buffer bit must be zero */
   unsigned int peekBuffer;
   int peekBufferLoadedFlag;
   int eofFlag;
   int cursorBitsFromLeft;
   int totalBitsProcessed;
   int alarmBit;
   jmp_buf *alarmJmpBufPtr;
};

BitFile *NewBitFile(void)
{
   BitFile *bitFilePtr;
   
   bitFilePtr = (BitFile *) malloc(sizeof(BitFile));
   if(bitFilePtr == NULL) {
      fprintf(stderr,"ERROR: Could not allocate memory for BitFile structure.\n");
      exit(-1);
   };
   bitFilePtr->bitFileType = not_open;
   BitFileAlarmOff(bitFilePtr);
   return bitFilePtr;
}

void FreeBitFile(BitFile **bitFileHdl)
{
   if(*bitFileHdl == NULL)
      return;
   assert((*bitFileHdl)->bitFileType == not_open);
   free(*bitFileHdl);
   *bitFileHdl = NULL;
}

void OpenReadBitFile(BitFile *bitFilePtr,char *bitFilename,char *traceFilename)
{
   assert(bitFilePtr != NULL);
   assert(bitFilePtr->bitFileType == not_open);
   bitFilePtr->bitFileType = read;
   if(bitFilename == NULL) 
      bitFilePtr->bitFILEPtr = NULL;
   else if(strcmp(bitFilename,"-") == 0) 
      bitFilePtr->bitFILEPtr = stdin;
   else 
      if((bitFilePtr->bitFILEPtr = fopen(bitFilename,"rb")) == NULL) {
         fprintf(stderr,"ERROR: Could not open BitFile \"%s\" for reading.\n",bitFilename);
         exit(-1);
      };
   if(traceFilename == NULL)
      bitFilePtr->traceFILEPtr = NULL;
   else if(strcmp(traceFilename,"-") == 0)
      bitFilePtr->traceFILEPtr = stdout;
   else  
      if((bitFilePtr->traceFILEPtr = fopen(traceFilename,"w+")) == NULL) {
         fprintf(stderr,"ERROR: Could not open BitFile trace file \"%s\" for writing.\n",traceFilename);
         exit(-1);
      };
   bitFilePtr->cursorBitsFromLeft = 32;  /* declare entire buffer as invalid */
   bitFilePtr->totalBitsProcessed = 0;
   bitFilePtr->eofFlag = 0;
   bitFilePtr->peekBufferLoadedFlag = 0;
}

void OpenWriteBitFile(BitFile *bitFilePtr,char *bitFilename,char *traceFilename)
{
   assert(bitFilePtr != NULL);
   assert(bitFilePtr->bitFileType == not_open);
   bitFilePtr->bitFileType = write;
   if(bitFilename == NULL)
      bitFilePtr->bitFILEPtr = NULL;
   else if(strcmp(bitFilename,"-") == 0)
      bitFilePtr->bitFILEPtr = stdout;
   else
      if((bitFilePtr->bitFILEPtr = fopen(bitFilename,"w+b")) == NULL) {
         fprintf(stderr,"ERROR: Could not open BitFile %s for writing.\n",bitFilename);
         exit(-1);
      };
   if(traceFilename == NULL)
      bitFilePtr->traceFILEPtr = NULL;
   else if(strcmp(traceFilename,"-") == 0)
      bitFilePtr->traceFILEPtr = stdout;
   else 
      if((bitFilePtr->traceFILEPtr = fopen(traceFilename,"w+")) == NULL) {
         fprintf(stderr,"ERROR: Could not open BitFile trace file %s for writing.\n",traceFilename);
         exit(-1);
      };
   bitFilePtr->buffer = 0;
   bitFilePtr->eofFlag = 0;
   bitFilePtr->cursorBitsFromLeft = 0;  /* declare entire buffer as unused */
   bitFilePtr->totalBitsProcessed = 0;
}

static void WriteBitFileBuffer(BitFile *bitFilePtr)
{
   if(fwrite(&bitFilePtr->buffer,4,1,bitFilePtr->bitFILEPtr) != 1) {
      fprintf(stderr,"ERROR: Could not write to BitFile.\n");
      exit(-1);
   };
   bitFilePtr->cursorBitsFromLeft = 0;
   bitFilePtr->buffer = 0;
}
      
static void MaybeWriteBitFileBuffer(BitFile *bitFilePtr)
{
   assert(bitFilePtr->cursorBitsFromLeft <= 32 && bitFilePtr->cursorBitsFromLeft >= 0);
   if(bitFilePtr->cursorBitsFromLeft != 32)
      return;
   WriteBitFileBuffer(bitFilePtr);
}

/*  The comment does not need to include a newline. */
/*  A null comment is different than a comment of zero length. */
static void WriteComment(BitFile *bitFilePtr,unsigned int bits,int bitCount,char *comment)
{  
   if(bitFilePtr->traceFILEPtr == NULL || comment == NULL)  /* if the trace file is not open */
      return;
   if(fprintf(bitFilePtr->traceFILEPtr,"%6d.%d %2d %8x %s\n",bitFilePtr->totalBitsProcessed / 8,
              bitFilePtr->totalBitsProcessed % 8,bitCount,bits,comment) < 0) {
      fprintf(stderr,"ERROR: Error while writing BitFile trace file.\n");
      exit(-1);
   };
   fflush(bitFilePtr->traceFILEPtr);
}

/* bit patter to be written should be right aligned. */
/* The return value is bitCount */
int BitFileWriteBits(BitFile *bitFilePtr,int bitCount,unsigned int bits,char *comment)
{
   int firstHalfLeftShift;
  
   assert(bitFilePtr != NULL && bitFilePtr->bitFileType == write); 
   assert(bitCount > 0 && bitCount <= 32);
   assert(bitCount == 32 || (bits >> bitCount) == 0);
   firstHalfLeftShift = 32 - bitCount - bitFilePtr->cursorBitsFromLeft;
   if(firstHalfLeftShift >= 0) {  /* if we have enough bits in the current buffer */
      bitFilePtr->buffer |= bits << firstHalfLeftShift;  /* keep all the bits */
      bitFilePtr->cursorBitsFromLeft += bitCount;
      MaybeWriteBitFileBuffer(bitFilePtr);
   } else {  /* we don't have enough bits in the current buffer */
      bitFilePtr->buffer |= bits >> -firstHalfLeftShift;  /* keep left half */
      WriteBitFileBuffer(bitFilePtr);
      bitFilePtr->buffer = bits << (firstHalfLeftShift + 32);  /* keep right half */
      bitFilePtr->cursorBitsFromLeft = -firstHalfLeftShift;
   };
   WriteComment(bitFilePtr,bits,bitCount,comment);
   bitFilePtr->totalBitsProcessed += bitCount;
   return bitCount;
}

static unsigned int PeekNextBitFileBuffer(BitFile *bitFilePtr)
{
   int bytesRead;
   int bytesToGo;
   unsigned char readChar;
   
   if(bitFilePtr->peekBufferLoadedFlag != 1) {
      bitFilePtr->peekBuffer = 0;
      bytesRead = 0;
      for(bytesToGo=4;bytesToGo;bytesToGo--) {
         bytesRead += fread(&readChar,1,1,bitFilePtr->bitFILEPtr); /* do it this slow way for endian issues. */
         bitFilePtr->peekBuffer = (bitFilePtr->peekBuffer<<8)|readChar;
      };
      if(bytesRead > 0)
         bitFilePtr->peekBufferLoadedFlag = 1;
      if(bytesRead < 4) 
         if(!feof(bitFilePtr->bitFILEPtr)) {
            fprintf(stderr,"ERROR: Could not read from bit file. %d != 4\n",bytesRead);
            fflush(NULL);
            exit(5);
         };
   };
   return bitFilePtr->peekBuffer;
}

static void ReadToBitFileBuffer(BitFile *bitFilePtr)
{
   bitFilePtr->buffer = PeekNextBitFileBuffer(bitFilePtr);
   if(bitFilePtr->peekBufferLoadedFlag == 0)
      bitFilePtr->eofFlag = 1;
   bitFilePtr->peekBufferLoadedFlag = 0;
   bitFilePtr->cursorBitsFromLeft = 0;
}

/* This fills the buffer if it is completely empty. */
/* This function is called to ensure that there are at least some bits in the buffer. */
static void MaybeReadToBitFileBuffer(BitFile *bitFilePtr)
{
   if(bitFilePtr->cursorBitsFromLeft != 32)
      return;
   ReadToBitFileBuffer(bitFilePtr);
}

/* This function writes the remaining 32 bit word with zeros. */
void CloseBitFile(BitFile *bitFilePtr)
{
   assert(bitFilePtr != NULL);
   assert(bitFilePtr->bitFileType != not_open);
   if(bitFilePtr->bitFileType == write && bitFilePtr->bitFILEPtr != NULL && bitFilePtr->cursorBitsFromLeft > 0) {
      WriteBitFileBuffer(bitFilePtr);
   };
   if(fclose(bitFilePtr->bitFILEPtr) == EOF)
      fprintf(stderr,"ERROR: Error while closing BitFile.\n");
   if(bitFilePtr->traceFILEPtr != NULL)
      if(fclose(bitFilePtr->traceFILEPtr) == EOF)
         fprintf(stderr,"ERROR: Error while closing BitFile trace file.\n");
   bitFilePtr->bitFileType = not_open;
   BitFileAlarmOff(bitFilePtr);
}

/*  This does not treat the tracefile well. */
void CloseBitFileWithAppend(BitFile *closingBitFilePtr,BitFile *appendingBitFilePtr)
{
   int bitCount;
   char lineBufferStr[200];
   
   assert(closingBitFilePtr != NULL && closingBitFilePtr->bitFileType == write);
   assert(appendingBitFilePtr != NULL && appendingBitFilePtr->bitFileType == write);
   bitCount = BitFileFTell(closingBitFilePtr);
   WriteComment(appendingBitFilePtr,0,bitCount,"appending file");
   if(closingBitFilePtr->traceFILEPtr != NULL && appendingBitFilePtr->traceFILEPtr != NULL) {
      fseek(closingBitFilePtr->traceFILEPtr,0,SEEK_SET);
      while(fgets(lineBufferStr,200,closingBitFilePtr->traceFILEPtr) != NULL) /* copy over tracefile */
         fputs(lineBufferStr,appendingBitFilePtr->traceFILEPtr);
   };
   WriteBitFileBuffer(closingBitFilePtr);
   fseek(closingBitFilePtr->bitFILEPtr,0,SEEK_SET);
   closingBitFilePtr->bitFileType = read; 
   ReadToBitFileBuffer(closingBitFilePtr);
   for(;bitCount > 31;bitCount-=32)
      BitFileWriteBits(appendingBitFilePtr,32,BitFileReadBits(closingBitFilePtr,32,NULL),"wb");
   if(bitCount > 0)
      BitFileWriteBits(appendingBitFilePtr,bitCount,BitFileReadBits(closingBitFilePtr,bitCount,NULL),NULL);
   CloseBitFile(closingBitFilePtr);
}

/* We construct the bits to the left and then shift right. */
unsigned int BitFileReadBits(BitFile *bitFilePtr,int bitCount,char *comment)
{
   unsigned int bits;
   int shortfall;
    
   assert(bitCount > 0 && bitCount <= 32);
   assert(bitFilePtr != NULL && bitFilePtr->bitFileType == read);
   if(bitFilePtr->totalBitsProcessed + bitCount > bitFilePtr->alarmBit) {
      printf("Attempt to read past alarm bit (%d > %d).\n",bitFilePtr->totalBitsProcessed + bitCount,
             bitFilePtr->alarmBit);
      longjmp(*bitFilePtr->alarmJmpBufPtr,1);
   };
   MaybeReadToBitFileBuffer(bitFilePtr);
   bits = bitFilePtr->buffer << bitFilePtr->cursorBitsFromLeft;
   shortfall = bitFilePtr->cursorBitsFromLeft + bitCount - 32;
   if(shortfall > 0) { /* if not enough bits in buffer */
      ReadToBitFileBuffer(bitFilePtr);
      bits |= bitFilePtr->buffer >> (bitCount - shortfall) ;   
      bitFilePtr->cursorBitsFromLeft = shortfall;
   } else 
      bitFilePtr->cursorBitsFromLeft += bitCount;
   bits >>= 32 - bitCount;
   WriteComment(bitFilePtr,bits,bitCount,comment);
   bitFilePtr->totalBitsProcessed += bitCount;
   return bits;   
}

/* This function is just like read, but it does not advance the file pointer. */
/* We construct the bits to the left and then shift right. */
unsigned int BitFilePeekBits(BitFile *bitFilePtr,int bitCount,char *commentStr)
{
   unsigned int bits;
   int shortfall;
   
   assert(bitCount > 0 || bitCount <= 32);
   assert(bitFilePtr != NULL && bitFilePtr->bitFileType == read);
   MaybeReadToBitFileBuffer(bitFilePtr);
   bits = bitFilePtr->buffer << bitFilePtr->cursorBitsFromLeft;
   shortfall = bitFilePtr->cursorBitsFromLeft + bitCount - 32;
   assert(bitCount - shortfall > 0);
   assert(shortfall <= 0 || bitCount - shortfall < 32);
   if(shortfall > 0) /* if not enough bits in buffer */
      bits |= PeekNextBitFileBuffer(bitFilePtr) >> (bitCount - shortfall);   
   bits >>= 32 - bitCount;
   WriteComment(bitFilePtr,bits,bitCount,commentStr);
   return bits;
}

unsigned int BitFileReadAlignBits(BitFile *bitFilePtr,int bitCount,char *commentStr)
{
   int bitsToSkip; 
   
   assert(bitCount > 0 || bitCount <= 32);
   bitsToSkip = (32-bitFilePtr->cursorBitsFromLeft) % 8;
   if(bitsToSkip)
      BitFileReadBits(bitFilePtr,bitsToSkip,"byte aligned skip");
   return BitFileReadBits(bitFilePtr,bitCount,commentStr);
}

unsigned int BitFilePeekAlignBits(BitFile *bitFilePtr,int bitCount,char *commentStr)
{
   int stuffingCount,uneededBits;
   
   stuffingCount = (32-bitFilePtr->cursorBitsFromLeft) % 8 ;
   if(stuffingCount + bitCount > 32) {
      fprintf(stderr,"ERROR: Attempt to peek too many aligned bits (%d<24).\n",bitCount);
      exit(-1);  
   }
   uneededBits = 32 - bitCount;
   return (BitFilePeekBits(bitFilePtr,stuffingCount+bitCount,commentStr)<<uneededBits) >>uneededBits;
}

unsigned int BitFileReadTilByte(BitFile* bitFilePtr,char *commentStr)
{
   return BitFileReadBits(bitFilePtr,8-bitFilePtr->totalBitsProcessed%8,commentStr);
}

int BitFileFTell(BitFile *bitFilePtr)
{
   assert(bitFilePtr != NULL && bitFilePtr->bitFileType != not_open);
   return bitFilePtr->totalBitsProcessed;
}

void PrintErrorWithClose(BitFile* bitFilePtr,int bitCount,unsigned int bits,
                         char* commentStr,char* errorStr)
{
   fprintf(stderr,errorStr);
   fprintf(stderr,"commentStr=%s\n",commentStr);
   WriteComment(bitFilePtr,bits,bitCount,commentStr);
   CloseBitFile(bitFilePtr);
   exit(-1);
}  
   
void BitFileFSeek(BitFile* bitFilePtr,int bitsFromStart,char *commentStr)
{
   assert(bitFilePtr != NULL && bitFilePtr->bitFileType == read);
   assert(bitsFromStart >= 0);
   if(commentStr != NULL)
      WriteComment(bitFilePtr,0,bitsFromStart,commentStr);
   fseek(bitFilePtr->bitFILEPtr,(bitsFromStart/32)*4,SEEK_SET);
   bitFilePtr->peekBufferLoadedFlag = 0;
   ReadToBitFileBuffer(bitFilePtr);  
   bitFilePtr->totalBitsProcessed = bitsFromStart;
   bitFilePtr->eofFlag = 0;
   bitFilePtr->cursorBitsFromLeft = bitsFromStart % 32;
}

int BitFileIsTraceOpen(BitFile* bitFilePtr)
{
   return (int) bitFilePtr->traceFILEPtr;
}
   
int BitFileEOF(BitFile* bitFilePtr)
{
   return bitFilePtr->eofFlag;   
}

void BitFileFFlush(BitFile* bitFilePtr)
{
   fflush(bitFilePtr->bitFILEPtr);
}

/* This sets the alarm bit to a specific number of bits into the file. */
/* If a bit file read (bit not a peek) attempts to go past the alarm, then */
/* a longjmp to the alarm routine is made. */
void BitFileSetAlarm(BitFile *bitFilePtr,int alarmBit,jmp_buf *alarmJmpBufPtr)
{
   assert(alarmBit > 0);
   bitFilePtr->alarmJmpBufPtr = alarmJmpBufPtr;
   bitFilePtr->alarmBit = alarmBit;
}

void BitFileAlarmOff(BitFile *bitFilePtr)
{
   bitFilePtr->alarmBit = ALARM_BIT_NOT_USED;
}




