/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

#include 
#include 
#include 
#include "loaders/mp3/mpg123.hh"
#include "loaders/mp3/tables.hh"
#include "file/file.hh"

/* max = 1728 */
#define MAXFRAMESIZE 1792

#define SKIP_JUNK 1

int tabsel_123[2][3][16] = {
  { {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,},
    {0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,},
    {0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,} },

  { {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,},
    {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,},
    {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,} }
};

long freqs[7] = { 44100, 48000, 32000, 22050, 24000, 16000 , 11025 };

static int bitindex;
static unsigned char *wordpointer;

static int fsize=0,fsizeold=0,ssize;
static unsigned char bsspace[2][MAXFRAMESIZE+512]; /* MAXFRAMESIZE */
static unsigned char *bsbuf=bsspace[1],*bsbufold;
static int bsnum=0;

extern i4_file_class *mp3_out, *mp3_in;

struct ibuf {
  struct ibuf *next;
  struct ibuf *prev;
  unsigned char *buf;
  unsigned char *pnt;
  int len;
  /* skip,time stamp */
};

struct ibuf ibufs[2];
struct ibuf *cibuf;
int ibufnum=0;

unsigned char *pcm_sample;
int pcm_point = 0;
int audiobufsize = AUDIOBUFSIZE;

#ifdef VARMODESUPPORT
/*
	 *   This is a dirty hack!  It might burn your PC and kill your cat!
	 *   When in "loaders/mp3/varmode", specially formatted layer-3 mpeg files arhe
	 *   expected as input -- it will NOT work with standard mpeg files.
	 *   The reason for this:
	 *   Varmode mpeg files enable my own GUI player to perform fast
	 *   forward and backward functions, and to jump to an arbitrary
	 *   timestamp position within the file.  This would be possible
	 *   with standard mpeg files, too, but it would be a lot harder to
	 *   implement.
	 *   A filter for converting standard mpeg to varmode mpeg is
	 *   available on request, but it's really not useful on its own.
	 *
	 *   Oliver Fromme  
	 *   Mon Mar 24 00:04:24 MET 1997
	 */
int varmode = FALSE;
int playlimit;
#endif


static unsigned long oldhead = 0;
static unsigned long firsthead=0;

static int halfphase = 0;

static char *lastdir = NULL;

void init_common(void)
{
#ifdef VARMODESUPPORT
  varmode = FALSE;
#endif
  pcm_point = 0;
  audiobufsize = AUDIOBUFSIZE;
  ibufnum=0;
  fsize=0;
  fsizeold=0;
  bsbuf=bsspace[1];
  bsnum=0;
  oldhead = 0;
  firsthead = 0;
  halfphase = 0;
  lastdir = NULL;
}

static void get_II_stuff(struct frame *fr)
{
  static int translate[3][2][16] = 
  { { { 0,2,2,2,2,2,2,0,0,0,1,1,1,1,1,0 } ,
      { 0,2,2,0,0,0,1,1,1,1,1,1,1,1,1,0 } } ,
    { { 0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0 } ,
      { 0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0 } } ,
    { { 0,3,3,3,3,3,3,0,0,0,1,1,1,1,1,0 } ,
      { 0,3,3,0,0,0,1,1,1,1,1,1,1,1,1,0 } } };

  int table,sblim;
  static struct al_table *tables[5] = 
  { alloc_0, alloc_1, alloc_2, alloc_3 , alloc_4 };
  static int sblims[5] = { 27 , 30 , 8, 12 , 30 };

  if(fr->lsf)
    table = 4;
  else
    table = translate[fr->sampling_frequency][2-fr->stereo][fr->bitrate_index];
  sblim = sblims[table];

  fr->alloc = tables[table];
  fr->II_sblimit = sblim;
}

void audio_flush(int outmode, struct audio_info_struct *ai)
{
  if (pcm_point) 
  {
    mp3_out->write(pcm_sample, pcm_point);
    pcm_point = 0;
  }
}

void read_frame_init (void)
{
  oldhead = 0;
  firsthead = 0;
}

#define HDRCMPMASK 0xfffffd00
#if 0
#define HDRCMPMASK 0xfffffdft
#endif

/* 
 * HACK,HACK,HACK 
 * step back  frames 
 */
int back_frame(struct frame *fr,int num)
{
  long bytes;
  unsigned char buf[4];
  unsigned long newhead;

  if(!firsthead)
    return 0;

  bytes = (fsize+8)*(num+2);

  if(mp3_in->seek(mp3_in->tell()-bytes)<0)
    return -1;

  if(mp3_in->read(buf,4)!=4)
    return -1;

  newhead = (buf[0]<<24) + (buf[1]<<16) + (buf[2]<<8) + buf[3];
	
  while( (newhead & HDRCMPMASK) != (firsthead & HDRCMPMASK) ) {
    if(mp3_in->read(buf,1)!=1)
      return -1;
    newhead <<= 8;
    newhead |= buf[0];
    newhead &= 0xffffffff;
  }

  if (mp3_in->seek(mp3_in->tell()-4)<0)
    return -1;
	
  read_frame(fr);
  read_frame(fr);

  if(fr->lay == 3) {
    set_pointer(512);
  }

  return 0;
}

int head_read(unsigned char *hbuf,unsigned long *newhead)
{  
  if (mp3_in->read(hbuf, 4)!=4)
    return FALSE;

  *newhead = ((unsigned long) hbuf[0] << 24) |
    ((unsigned long) hbuf[1] << 16) |
    ((unsigned long) hbuf[2] << 8)  |
    (unsigned long) hbuf[3];

  return TRUE;
}

int head_check(unsigned long newhead) 
{
  if( (newhead & 0xffe00000) != 0xffe00000)
    return FALSE;
  if(!((newhead>>17)&3))
    return FALSE;
  if( ((newhead>>12)&0xf) == 0xf)
    return FALSE;
  if( ((newhead>>10)&0x3) == 0x3 )
    return FALSE;
  return TRUE;
}


int read_frame(struct frame *fr)
{
  static unsigned long newhead;

  static unsigned char ssave[34];
  unsigned char hbuf[8];
  static int framesize;
  int l;
  int try_c = 0;

  if (halfspeed)
    if (halfphase--) {
      bitindex = 0;
      wordpointer = (unsigned char *) bsbuf;
      if (fr->lay == 3)
        memcpy (bsbuf, ssave, ssize);
      return 1;
    }
    else
      halfphase = halfspeed - 1;

#ifdef VARMODESUPPORT
  if (varmode) {
    if (mp3_in->read(hbuf,8)!=8)
      return 0;
  }
  else
#endif

    read_again:
  if(!head_read(hbuf,&newhead))
    return FALSE;

  if(oldhead != newhead || !oldhead)
  {
    fr->header_change = 1;

  init_resync:

#ifdef SKIP_JUNK
    if(!firsthead && !head_check(newhead) ) {
      int i;

      /* I even saw RIFF headers at the beginning of MPEG streams ;( */

      if(newhead == ('R'<<24)+('I'<<16)+('F'<<8)+'F') {
        char buf[40];
        mp3_in->read(buf,68);
        goto read_again;
      }
      /* search in 32 bit steps through the first 2K */
      for(i=0;i<512;i++) {
        if(!head_read(hbuf,&newhead))
          return 0;
        if(head_check(newhead))
          break;
      }
      if(i==512) {
        /* step in byte steps through next 2K */
        for(i=0;i<2048;i++) {
          memmove (&hbuf[0], &hbuf[1], 3);
          if(mp3_in->read(hbuf+3,1) != 1)
            return 0;
          newhead <<= 8;
          newhead |= hbuf[3];
          newhead &= 0xffffffff;
          if(head_check(newhead))
            break;
        }
        if(i == 2048) {
          return 0;
        }
      }
      /* 
       * should we check, whether a new frame starts at the next
       * expected position? (some kind of read ahead)
       * We could implement this easily, at least for files.
       */
    }
#endif

    if( (newhead & 0xffe00000) != 0xffe00000) {
      if (tryresync) {
        /* Read more bytes until we find something that looks
           reasonably like a valid header.  This is not a
           perfect strategy, but it should get us back on the
           track within a short time (and hopefully without
           too much distortion in the audio output).  */
        do {
          try_c++;
          memmove (&hbuf[0], &hbuf[1], 7);
#ifdef VARMODESUPPORT
          if (mp3_in->read(&hbuf[varmode?7:3],1) != 1)
#else
            if (mp3_in->read(&hbuf[3],1) != 1)
#endif
              return 0;

          /* This is faster than combining newhead from scratch */
          newhead = ((newhead << 8) | hbuf[3]) & 0xffffffff;

          if (!oldhead)
            goto init_resync;       /* "considered harmful", eh? */

        } while ((newhead & HDRCMPMASK) != (oldhead & HDRCMPMASK)
                 && (newhead & HDRCMPMASK) != (firsthead & HDRCMPMASK));
      }
      else
        return (0);
    }
    if (!firsthead)
      firsthead = newhead;

    if( newhead & (1<<20) ) {
      fr->lsf = (newhead & (1<<19)) ? 0x0 : 0x1;
      fr->mpeg25 = 0;
    }
    else {
      fr->lsf = 1;
      fr->mpeg25 = 1;
    }
    
    if (!tryresync || !oldhead) {
      /* If "tryresync" is true, assume that certain
         parameters do not change within the stream! */
      fr->lay = 4-((newhead>>17)&3);
      fr->bitrate_index = ((newhead>>12)&0xf);
      if( ((newhead>>10)&0x3) == 0x3) {
        exit(1);
      }
      if(fr->mpeg25)
        fr->sampling_frequency = 6;
      else
        fr->sampling_frequency = ((newhead>>10)&0x3) + (fr->lsf*3);
      fr->error_protection = ((newhead>>16)&0x1)^0x1;
    }

    fr->padding   = ((newhead>>9)&0x1);
    fr->extension = ((newhead>>8)&0x1);
    fr->mode      = ((newhead>>6)&0x3);
    fr->mode_ext  = ((newhead>>4)&0x3);
    fr->copyright = ((newhead>>3)&0x1);
    fr->original  = ((newhead>>2)&0x1);
    fr->emphasis  = newhead & 0x3;

    fr->stereo    = (fr->mode == MPG_MD_MONO) ? 1 : 2;

    oldhead = newhead;

    if(!fr->bitrate_index)
    {
      return (0);
    }

    switch(fr->lay)
    {
      case 1:
        fr->do_layer = do_layer1;
#ifdef VARMODESUPPORT
        if (varmode) {
          return (0);
        }
#endif
        fr->jsbound = (fr->mode == MPG_MD_JOINT_STEREO) ? 
          (fr->mode_ext<<2)+4 : 32;
        framesize  = (long) tabsel_123[fr->lsf][0][fr->bitrate_index] * 12000;
        framesize /= freqs[fr->sampling_frequency];
        framesize  = ((framesize+fr->padding)<<2)-4;
        break;
      case 2:
        fr->do_layer = do_layer2;
#ifdef VARMODESUPPORT
        if (varmode) {
          return (0);
        }
#endif
        get_II_stuff(fr);
        fr->jsbound = (fr->mode == MPG_MD_JOINT_STEREO) ?
          (fr->mode_ext<<2)+4 : fr->II_sblimit;
        framesize = (long) tabsel_123[fr->lsf][1][fr->bitrate_index] * 144000;
        framesize /= freqs[fr->sampling_frequency];
        framesize += fr->padding - 4;
        break;
      case 3:
        fr->do_layer = do_layer3;
        if(fr->lsf)
          ssize = (fr->stereo == 1) ? 9 : 17;
        else
          ssize = (fr->stereo == 1) ? 17 : 32;
        if(fr->error_protection)
          ssize += 2;
#ifdef VARMODESUPPORT
        if (varmode)
          playlimit = ((unsigned int) hbuf[6] << 8) | (unsigned int) hbuf[7];
        framesize = ssize + 
          (((unsigned int) hbuf[4] << 8) | (unsigned int) hbuf[5]);
        else {
#endif
          framesize  = (long) tabsel_123[fr->lsf][2][fr->bitrate_index] * 144000;
          framesize /= freqs[fr->sampling_frequency]<<(fr->lsf);
          framesize = framesize + fr->padding - 4;
#ifdef VARMODESUPPORT
        }
#endif
        break; 
      default:
        return (0);
    }
  }
  else
    fr->header_change = 0;

  fsizeold=fsize;	/* for Layer3 */
  bsbufold = bsbuf;	
  bsbuf = bsspace[bsnum]+512;
  bsnum = (bsnum + 1) & 1;

  fsize = framesize;
 
  if( (l=mp3_in->read(bsbuf,fsize)) != fsize)
  {
    if(l <= 0)
      return 0;
    memset(bsbuf+l,0,fsize-l);
  }

  if (halfspeed && fr->lay == 3)
    memcpy (ssave, bsbuf, ssize);

  bitindex = 0;
  wordpointer = (unsigned char *) bsbuf;

  return 1;
}

/*
 *   Allocate space for a new string containing the first
 *   "num" characters of "src".  The resulting string is
 *   always zero-terminated.  Returns NULL if malloc fails.
 */

#if 1
char *strndup (const char *src, int num)
{
  char *dst;

  if (!(dst = (char *) malloc(num+1)))
    return (NULL);
  dst[num] = '\0';
  return (strncpy(dst, src, num));
}
#endif

/*
 *   Split "path" into directory and filename components.
 *
 *   Return value is 0 if no directory was specified (i.e.
 *   "path" does not contain a '/'), OR if the directory
 *   is the same as on the previous call to this function.
 *
 *   Return value is 1 if a directory was specified AND it
 *   is different from the previous one (if any).
 */

int split_dir_file (const char *path, char **dname, char **fname)
{
  char *slashpos;

  if ((slashpos = strrchr(path, '/'))) {
    *fname = slashpos + 1;
    *dname = strndup(path, 1 + slashpos - path);
    if (lastdir && !strcmp(lastdir, *dname)) {
      /***   same as previous directory   ***/
      free (*dname);
      *dname = lastdir;
      return 0;
    }
    else {
      /***   different directory   ***/
      if (lastdir)
        free (lastdir);
      lastdir = *dname;
      return 1;
    }
  }
  else {
    /***   no directory specified   ***/
    if (lastdir) {
      free (lastdir);
      lastdir = NULL;
    };
    *dname = NULL;
    *fname = (char *)path;
    return 0;
  }
}

unsigned int getbits(int number_of_bits)
{
  unsigned long rval;

  if(!number_of_bits)
    return 0;

  {
    rval = wordpointer[0];
    rval <<= 8;
    rval |= wordpointer[1];
    rval <<= 8;
    rval |= wordpointer[2];
    rval <<= bitindex;
    rval &= 0xffffff;

    bitindex += number_of_bits;

    rval >>= (24-number_of_bits);

    wordpointer += (bitindex>>3);
    bitindex &= 7;
  }

  return rval;
}

unsigned int getbits_fast(int number_of_bits)
{
  unsigned long rval;

  {
    rval = wordpointer[0];
    rval <<= 8;	
    rval |= wordpointer[1];
    rval <<= bitindex;
    rval &= 0xffff;
    bitindex += number_of_bits;

    rval >>= (16-number_of_bits);

    wordpointer += (bitindex>>3);
    bitindex &= 7;
  }

  return rval;
}

unsigned int get1bit(void)
{
  unsigned char rval;

  rval = *wordpointer << bitindex;

  bitindex++;
  wordpointer += (bitindex>>3);
  bitindex &= 7;

  return rval>>7;
}

void set_pointer(long backstep)
{
  wordpointer = bsbuf + ssize - backstep;
  if (backstep)
    memcpy(wordpointer,bsbufold+fsizeold-backstep,backstep);
  bitindex = 0; 
}