/**********************************************************************
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 "file/file.hh"
#include "loaders/wav_load.hh"
#include "error/error.hh"
#include "status/status.hh"

i4_file_class *mp3_in=0;
i4_file_class *mp3_out=0;



/* 
 * Mpeg Layer audio decoder (see version.h for version number)
 * ------------------------
 * copyright (c) 1995,1996,1997 by Michael Hipp, All rights reserved.
 * See also 'README' !
 *
 * Moved all staticly initialized variables to top and added init_variables()
 * so I can restart this monster.
 *
 */

#include 
#include 

/* #define SET_PRIO */

#include "loaders/mp3/mpg123.hh"
#include "loaders/mp3/getlopt.hh"
#include "loaders/mp3/version.hh"

static long rates[3][3] = { 
  { 32000,44100,48000 } ,
  { 16000,22050,24000 } ,
  {  8000,11025,12000 } 
};

struct flags flags = { 0 , 0 };

int supported_rates = 0;

int outmode = DECODE_AUDIO;

char *listname = NULL;
long outscale  = 32768;
int checkrange = FALSE;
int tryresync  = TRUE;
int quiet      = FALSE;
int verbose    = 0;
int doublespeed= 0;
int halfspeed  = 0;
int change_always = 1;
int force_8bit = 0;
int force_frequency = -1;
int force_mono = 0;
long numframes = -1;
long startFrame= 0;
int usebuffer  = 0;
int buffer_fd[2];
int buffer_pid;

static struct frame fr;
static struct audio_info_struct ai;
#define FRAMEBUFUNIT (18 * 64 * 4)

static int init_output_done = FALSE;

static FILE *listfile = NULL;

static void *synth_funcs[2][2][3][2] = { 
  { { { synth_1to1 , synth_1to1_mono2stereo } ,
      { synth_2to1 , synth_2to1_mono2stereo } ,
      { synth_4to1 , synth_4to1_mono2stereo } } ,
    { { synth_1to1_8bit , synth_1to1_8bit_mono2stereo } ,
      { synth_2to1_8bit , synth_2to1_8bit_mono2stereo } ,
      { synth_4to1_8bit , synth_4to1_8bit_mono2stereo } } } ,
  { { { synth_1to1_mono , synth_1to1_mono } ,
      { synth_2to1_mono , synth_2to1_mono } ,
      { synth_4to1_mono , synth_4to1_mono } } ,
    { { synth_1to1_8bit_mono , synth_1to1_8bit_mono } ,
      { synth_2to1_8bit_mono , synth_2to1_8bit_mono } ,
      { synth_4to1_8bit_mono , synth_4to1_8bit_mono } } } 
};

void init_mpg123(void)
{

  rates[0][0] = 32000;
  rates[0][1] = 44100;
  rates[0][2] = 48000;
  rates[1][0] = 16000;
  rates[1][1] = 22050;
  rates[1][2] = 24000;
  rates[2][0] = 8000;
  rates[2][1] = 11025;
  rates[2][2] = 12000;

  flags.equalizer = 0;
  flags.aggressive = 0;

  supported_rates = 0;

  outmode = DECODE_AUDIO;

  listname = NULL;
  outscale  = 32768;
  checkrange = FALSE;
  tryresync  = TRUE;
  quiet      = FALSE;
  verbose    = 0;
  doublespeed= 0;
  halfspeed  = 0;
  change_always = 1;
  force_8bit = 0;
  /*	force_frequency = -1; */
  force_mono = 1;
  numframes = -1;
  startFrame= 0;
  usebuffer  = 0;

  init_output_done = FALSE;

  listfile = NULL;

}

void audio_info_struct_init(struct audio_info_struct *ai)
{
  ai->rate = -1;
  ai->gain = -1;
  ai->output = -1;
  ai->device = NULL;
  ai->channels = -1;
  ai->format = -1;
}

void print_rheader(struct frame *fr);

void init_output(void)
{

  if (init_output_done)
    return;
  init_output_done = TRUE;
  if (!(pcm_sample = (unsigned char *) malloc(audiobufsize * 2))) {
    perror ("malloc()");
    exit (1);
  }

}

char *get_next_file (int argc, char *argv[])
{
  static char line[1024];

  if (listname || listfile) {
    if (!listfile) {
      if (!*listname || !strcmp(listname, "-")) {
        listfile = stdin;
        listname = NULL;
      }
      else if (!(listfile = fopen(listname, "rb"))) {
        perror (listname);
        exit (1);
      }
    }
    do {
      if (fgets(line, 1023, listfile)) {
        line[strcspn(line, "\t\n\r")] = '\0';
        if (line[0]=='\0' || line[0]=='#')
          continue;
        return (line);
      }
      else {
        if (*listname)
          fclose (listfile);
        listname = NULL;
        listfile = NULL;
      }
    } while (listfile);
  }

  if (loptind < argc)
    return (argv[loptind++]);
  return (NULL);
}

void set_synth (char *arg)
{
  if (*arg == '2') {
    fr.down_sample = 1;
  }
  else {
    fr.down_sample = 2;
  }
}

#ifdef VARMODESUPPORT
void set_varmode (char *arg)
{
  audiobufsize = ((audiobufsize >> 1) + 63) & 0xffffc0;
}
#endif

void set_verbose (char *arg)
{
  verbose++;
}

topt opts[] = {
  {'k', "skip",        GLO_ARG | GLO_NUM,  0, &startFrame, 0},
  {'a', "audiodevice", GLO_ARG | GLO_CHAR, 0, &ai.device,  0},
  {'2', "2to1",        0,          set_synth, 0,           0},
  {'4', "4to1",        0,          set_synth, 0,           0},
  {'t', "test",        0,                  0, &outmode, DECODE_TEST},
  {'s', "stdout",      0,                  0, &outmode, DECODE_STDOUT},
  {'c', "check",       0,                  0, &checkrange, TRUE},
  {'v', "verbose",     0,        set_verbose, 0,           0},
  {'q', "quiet",       0,                  0, &quiet,      TRUE},
  {'y', "resync",      0,                  0, &tryresync,  FALSE},
  {'0', "single0",     0,                  0, &fr.single,  0},
  {0,   "left",        0,                  0, &fr.single,  0},
  {'1', "single1",     0,                  0, &fr.single,  1},
  {0,   "right",       0,                  0, &fr.single,  1},
  {'m', "singlemix",   0,                  0, &fr.single,  3},
  {0,   "mix",         0,                  0, &fr.single,  3},
  {'g', "gain",        GLO_ARG | GLO_NUM,  0, &ai.gain,    0},
  {'r', "rate",        GLO_ARG | GLO_NUM,  0, &force_frequency,  0},
  {0,   "8bit",        0,                  0, &force_8bit, 1},
  {'f', "scale",       GLO_ARG | GLO_NUM,  0, &outscale,   0},
  {'n', "frames",      GLO_ARG | GLO_NUM,  0, &numframes,  0},
#ifdef VARMODESUPPORT
  {'v', "var",         0,        set_varmode, &varmode,    TRUE},
#endif
  {'b', "buffer",      GLO_ARG | GLO_NUM,  0, &usebuffer,  0},
  {'d', "doublespeed", GLO_ARG | GLO_NUM,  0, &doublespeed,0},
  {'h', "halfspeed",   GLO_ARG | GLO_NUM,  0, &halfspeed,  0},
  {'@', "list",        GLO_ARG | GLO_CHAR, 0, &listname,   0},
  {0,   "equalizer",	0,				0, &flags.equalizer,1},
  {0,   "aggressive",	0,				0, &flags.aggressive,2},
  {0, 0, 0, 0, 0, 0}
};

/*
 *   Change the playback sample rate.
 */
void reset_audio_samplerate(void)
{
}

/*
 * play a frame read read_frame();
 * (re)initialize audio if necessary.
 */
void play_frame(int init,struct frame *fr)
{
  int clip;

  if((fr->header_change && change_always) || init) {
    int reset_audio = 0;

    if(force_frequency < 0) {
      if(ai.rate != freqs[fr->sampling_frequency]>>(fr->down_sample)) {
        ai.rate = freqs[fr->sampling_frequency]>>(fr->down_sample);
        reset_audio = 1;
      }
    }
    else if(ai.rate != force_frequency) {
      ai.rate = force_frequency;
      reset_audio = 1;
    }
    init_output();
    if(reset_audio) {
      reset_audio_samplerate();
    }
  }

  if (fr->error_protection) {
    getbits(16); /* crc */
  }

  clip = (fr->do_layer)(fr,outmode,&ai);

  /*
    if(clip > 0 && checkrange)
    fprintf(stderr,"%d samples clipped\n", clip);
    */
}

void set_synth_functions(struct frame *fr)
{

  *((void **)(&fr->synth)) = synth_funcs[force_mono][force_8bit][fr->down_sample][0];
  *((void **)(&fr->synth_mono)) = synth_funcs[force_mono][force_8bit][fr->down_sample][1];
  fr->block_size = 128 >> (force_mono+force_8bit+fr->down_sample);

  if(force_8bit) {
    ai.format = AUDIO_FORMAT_ULAW_8;
    make_conv16to8_table(ai.format);
  }
}


i4_bool i4_load_mp3(i4_file_class *in,  // open file at beginging of mp3 data (after header)
                    i4_file_class *out,    // open file ready to write raw data
                    i4_sound_info &actual_output,           // returned
                    i4_status_class *status)   // tells user what's going on
{                      
  int result;
  long frameNum = 0;
  char *fname;
  int init;

  extern void init_mpg123(void);
  extern void init_common(void);
  extern void init_decode(void);
  extern void init_decode_2to1(void);
  extern void init_decode_4to1(void);
  extern void init_getlopt(void);

  init_mpg123();
  init_common();
  init_decode();
  init_decode_2to1();
  init_decode_4to1();
  /*		init_decode_i386(); */
  init_getlopt();

  fr.synth = synth_1to1;
  fr.down_sample = 0;
  fr.single=0;
  force_mono=1;
  force_frequency=-1;

  

  ai.format = AUDIO_FORMAT_SIGNED_16;
  ai.gain = ai.rate = ai.output = -1;
  ai.device = NULL;
  ai.channels = 1;

  prgName = "mp3_load";
  
  mp3_in=in;
  mp3_out=out;


  int start=mp3_out->tell();

  actual_output.channels=1;
  actual_output.sample_size=2;


  /*
  if (suggested_output.sample_rate<=11*1024)
  {
    fr.down_sample = 2;
  } else if (suggested_output.sample_rate<=22*1024)
    fr.down_sample = 1;
  else
    fr.down_sample = 0;
    */





  {
    int fmts;
    int i,j;

    struct audio_info_struct ai;

    audio_info_struct_init(&ai);
    fmts = AUDIO_FORMAT_SIGNED_16;

    supported_rates = 0;
    for(i=0;i<3;i++) {
      for(j=0;j<3;j++) {
        ai.rate = rates[i][j];
        /* allow about 2% difference */
        if( ((rates[i][j]*98) < (ai.rate*100)) &&
           ((rates[i][j]*102) > (ai.rate*100)) )
          supported_rates |= 1<<(i*3+j);
      } 
    }

    if(!force_8bit && !(fmts & AUDIO_FORMAT_SIGNED_16))
      force_8bit = 1;

    if(force_8bit && !(fmts & AUDIO_FORMAT_ULAW_8)) {
      exit(1);
    }
  }


  set_synth_functions(&fr);

  make_decode_tables(outscale);
  init_layer2(); /* inits also shared tables with layer1 */
  init_layer3(fr.down_sample);

      
  read_frame_init();


  init = 1;
  int mp3_end=in->size();
  int mp3_start=in->tell();


  for(frameNum=0;
      in->tell()!=mp3_end &&
        read_frame(&fr) && 
        numframes;
      frameNum++) 
  {
    if (status)
      status->update((in->tell()+2)/(float)(mp3_end+1));

    if(frameNum < startFrame || (doublespeed && (frameNum % doublespeed))) 
    {
      if(fr.lay == 3)
        set_pointer(512);
      continue;
    }

    numframes--;
    play_frame(init,&fr);
    actual_output.sample_rate=ai.rate;

    init = 0;

  }


  audio_flush(outmode, &ai);

  actual_output.size=mp3_out->tell()-start;

  free (pcm_sample);
  return i4_T;

}