#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <SDL/SDL_stdinc.h>  // for Sint16 etc
#include "dump-wave.h"

bool only_info;

struct SampleData {
  Sint16 *buffer,
         *buf;  // NB! buf > buffer
  Uint32 data_size,  // nr short int's
         loop_start,
         loop_end,
         low_freq,high_freq,root_freq;
  Uint16 sample_rate,
         scale_freq,
         scale_fact;
  bool okay,
       unsigned_enc,
       pingpong_enc;
  bool read_sample(FILE *src,int nr,bool *sel);
  bool write_bb_sample(FILE *out);
} *samples;

struct PatchData {
  Uint8 nr_samp,
        nr_valid_samp; // if sample rate = 44100
  bool read_patch(FILE *src,bool *sel);
  bool write_bb_patch(FILE *out,const char *gus_patch);
} patch_data;

void alert(const char *form,...) {
  char buf[200];
  va_list ap;
  va_start(ap,form);
  vsnprintf(buf,200,form,ap);
  va_end(ap);
  puts(buf);
}

bool PatchData::read_patch(FILE *src,bool *sel) {
  char word[100];
  Uint8 dum8;
  Uint16 dum16;
  Uint32 dum32;
  if (fread(word,22,1,src)!=1) return false;
  if (strncmp(word,"GF1PATCH",8)) { word[8]=0; alert("unexpected file type '%s'",word); return false; }
  word[22]=0;
  printf("%s\n",word);
  if (fread(word,60,1,src)!=1) return false; // description
  if (fread(&dum8,1,1,src)!=1) return false;
  printf("nr instruments %u\n",dum8);
  if (fread(&dum8,1,1,src)!=1) return false;
  printf("voices %u\n",dum8);
  if (fread(&dum8,1,1,src)!=1) return false;
  printf("channels %u\n",dum8);
  if (fread(&dum16,2,1,src)!=1) return false;
  printf("waveforms %u\n",dum16);
  if (fread(&dum16,2,1,src)!=1) return false;
  printf("master volume %u\n",dum16);
  if (fread(&dum32,4,1,src)!=1) return false;
  printf("data size %u\n",dum32);
  if (fread(word,36,1,src)!=1) return false; // reserved
  if (fread(word,2+16+4,1,src)!=1) return false;  // instr id,name,size
  if (fread(&dum8,1,1,src)!=1) return false;
  printf("layers %u\n",dum8);
  if (fread(word,40,1,src)!=1) return false;  // reserved
  if (fread(&dum8,1,1,src)!=1) return false;
  printf("layers dupl %u\n",dum8);
  if (fread(&dum8,1,1,src)!=1) return false;
  printf("layer %u\n",dum8);
  if (fread(&dum32,4,1,src)!=1) return false;
  printf("layer size %u\n",dum32);
  if (fread(&nr_samp,1,1,src)!=1) return false;
  printf("nr samples %u\n",nr_samp);
  if (fread(word,40,1,src)!=1) return false; // reserved
  samples=new SampleData[nr_samp];
  nr_valid_samp=0;
  for (int sample=0;sample<nr_samp;++sample) {
    SampleData *sd=samples+sample;
    if (!sd->read_sample(src,sample,sel)) return false;
    sd->okay= sel ? sel[sample] : true;
    if (sd->okay) ++nr_valid_samp;
  }
  return true;
}

bool SampleData::read_sample(FILE *src,int nr,bool *sel) {
  Uint8 dum8,
        word[100],
        wave_name[10];
  Uint16 dum16;
  if (fread(wave_name,7,1,src)!=1) return false;  // wave name
  wave_name[7]=0;
  printf("--- sample %d ---\n",nr);
  printf("wave name '%s'",wave_name);
  if (sel && sel[nr]) printf(" <-- selected");
  putchar('\n');
  if (fread(&dum8,1,1,src)!=1) return false;
  printf("fractions %u %u\n",dum8 & 0xffff,(dum8>>4) & 0xffff);
  //printf("data size %u\n",(word[3]<<24)|(word[2]<<16)|(word[1]<<8)|word[0]);
  if (fread(&data_size,4,1,src)!=1) return false;
  printf("data size %u\n",data_size);
  if (fread(&loop_start,4,1,src)!=1) return false;
  printf("loop start %u\n",loop_start);
  if (fread(&loop_end,4,1,src)!=1) return false;
  printf("loop end %u\n",loop_end);
  if (fread(&sample_rate,2,1,src)!=1) return false;
  printf("sample rate %u\n",sample_rate);
  if (fread(&low_freq,4,1,src)!=1) return false;
  printf("low freq %u\n",low_freq);
  if (fread(&high_freq,4,1,src)!=1) return false;
  printf("high freq %u\n",high_freq);
  if (fread(&root_freq,4,1,src)!=1) return false;
  printf("root freq %u\n",root_freq);
  if (fread(&dum16,2,1,src)!=1) return false; // not used
  if (fread(&dum8,1,1,src)!=1) return false;
  printf("panning %u\n",dum8);
  if (fread(word,3,1,src)!=1) return false;
  printf("envelope on: %d,%d,%d\n",word[0],word[1],word[2]);
  if (fread(word,3,1,src)!=1) return false; // envelope off
  if (fread(word,3,1,src)!=1) return false;
  printf("envelope offset on: %d,%d,%d\n",word[0],word[1],word[2]);
  if (fread(word,3,1,src)!=1) return false; // envelope offset off
  if (fread(word,6,1,src)!=1) return false; // tremolo, vibrato
  if (fread(&dum8,1,1,src)!=1) return false;
  printf("sampling mode %u = %u %u %u %u %u %u %u %u\n",
         dum8,(dum8>>7)&1,(dum8>>6)&1,(dum8>>5)&1,(dum8>>4)&1,(dum8>>3)&1,(dum8>>2)&1,(dum8>>1)&1,dum8&1);
  if ((dum8&1)==0) { alert("error: 8 bit encoding"); exit(1); }
  unsigned_enc=(dum8>>1)&1;
  if (((dum8>>2)&1)==0) alert("warning: not looping encoding");
  pingpong_enc=(dum8>>3)&1;
  if (fread(&scale_freq,2,1,src)!=1) return false;
  printf("scale freq %u\n",scale_freq);
  if (fread(&scale_fact,2,1,src)!=1) return false;
  printf("scale factor %u\n",scale_fact);
  if (fread(word,36,1,src)!=1) return false;  // reserved
  data_size /= 2;  // NB! char -> short
  loop_start /= 2;
  loop_end /= 2;
  int new_data_size=data_size;
  if (pingpong_enc)
    new_data_size += loop_end-loop_start;
  buffer=new Sint16[new_data_size];
  if (fread(buffer,data_size*2,1,src)!=1) {
    alert("data read failed");
    return false;
  }
  if (only_info) { delete[] buffer; return true; }

  if (unsigned_enc) {
    for (Uint32 i=0;i<data_size;++i) { int val=buffer[i]; buffer[i]=val-0x8000; }
  }
  if (!sel) {  // sel = 0 if no selection done
    char outf[50];
    snprintf(outf,50,"wave-files/%d-%s.wav",nr,wave_name);
    if (!init_dump_wav(outf,1,sample_rate) ||
        !dump_wav((char*)buffer,data_size) ||
        !close_dump_wav()) return false;
  }
  if (pingpong_enc) {
    Uint32 i;
    for (i=loop_end;i<data_size;++i) buffer[i+loop_end-loop_start]=buffer[i];
    for (i=loop_start;i<loop_end;++i) buffer[2*loop_end-i]=buffer[i];
    data_size=new_data_size;
    loop_end+=loop_end-loop_start;
    printf("pingpong: new data size %u\n",data_size*2);
  }
  Uint32 skip;  // skip start low values
  for (skip=0;abs(buffer[skip]) < 2000 && skip < loop_start;++skip);
  printf("skipping first %d samples\n",skip);
  buf=buffer+skip;
  data_size -= skip;
  loop_start -= skip;
  loop_end -= skip;
  for (int i=0;i<300;++i) buf[i]=buf[i] * i / 300; // slower attack

  return true;
}

bool SampleData::write_bb_sample(FILE *out) {
  if (fwrite(&data_size,4,1,out)!=1 ||
      fwrite(&loop_start,4,1,out)!=1 ||
      fwrite(&loop_end,4,1,out)!=1 ||
      fwrite(&sample_rate,2,1,out)!=1 ||
      fwrite(&root_freq,4,1,out)!=1 ||
      fwrite(&scale_freq,2,1,out)!=1 ||
      fwrite(&scale_fact,2,1,out)!=1) {
    alert("write_bb_sample: write problem");
    return false;
  }
  if (fwrite(buf,data_size*2,1,out)!=1) {
    alert("write_bb_sample: problem writing buffer");
    return false;
  }
  delete[] buffer;
  return true;
}

bool PatchData::write_bb_patch(FILE *out,const char *gus_patch) {
  Uint8 dum8;
  char comment[100];
  snprintf(comment,100,"from %s",gus_patch);
  if (fwrite("BB-PATCH",8,1,out)!=1 ||
      (dum8=1, fwrite(&dum8,1,1,out)!=1) ||    // format
      fwrite(comment,100,1,out)!=1 ||
      (dum8=nr_valid_samp, fwrite(&dum8,1,1,out)!=1) // nr valid samples
     ) {
    alert("write_bb_patch: initialization failed");
    return false;
  }
  for (int i=0;i<nr_samp;++i) {
    if (!samples[i].okay) continue;
    if (!samples[i].write_bb_sample(out)) return false;
  }
  return true;
}

int nxt(int lst,int& an) { if (an>=lst-1) { alert("missing option parameter"); exit(1); } return ++an; }

int main(int argc,char** argv) {
  const char *gus_patch_file=0, // e.g. /usr/share/midi/freepats/Tone_000/067_Baritone_Sax.pat
             *outf=0,           // e.g. 067-BaritonSax-soft.bb-pat
             *selection=0;
  bool *sel=0;
  for (int an=1;an<argc;++an) {
    if (!strcmp(argv[an],"-h")) {
      puts("Converter from GUS patches to BigBand patches");
      puts("Usage:");
      puts("  gus-patch [options] <GUS patch file>");
      puts("Options:");
      puts("  -o <BB-patch-file> - output file");
      puts("  -sel n1,n2,..      - select patch nrs");
      puts("  -i                 - only print info");
      puts("GUS patch contents listed to stdout");
      puts("Without -sel option, wave files are created in directory wave-files/");
      exit(0);
    }
    if (!strcmp(argv[an],"-o")) outf=argv[nxt(argc,an)];
    else if (!strcmp(argv[an],"-sel")) selection=argv[nxt(argc,an)];
    else if (!strcmp(argv[an],"-i")) only_info=true;
    else if (argv[an][0]=='-') { alert("unknown option %s",argv[an]); exit(1); }
    else gus_patch_file=argv[an];
  }
  if (!gus_patch_file) { alert("input gus patch file?"); exit(1); }
  if (selection) {
    sel=new bool[20];
    for (int i=0;i<20;++i) sel[i]=false;
    for (int i=0;;) {
      int nr; char ch=0;
      i+=sscanf(selection+i,"%d%c",&nr,&ch);
      if (nr>=20) { alert("sel %d?"); exit(1); }
      sel[nr]=true;
      if (!ch) break;
    }
  }
  FILE *in,*out;
  if ((in=fopen(gus_patch_file,"r"))==0) { alert("patch file %s not readable",gus_patch_file); exit(1); }
  if (patch_data.read_patch(in,sel)) puts("------\npatch file read okay");
  else { alert("error: patch file read failed"); exit(1); }
  fclose(in);
  if (only_info) exit(0);

  if (outf) {
    if ((out=fopen(outf,"w"))==0) { alert("error: bb-pat file %s not writable",outf); exit(1); }
    if (!patch_data.write_bb_patch(out,gus_patch_file)) { alert("error: file write problems"); exit(1); }
    printf("written: %s\n",outf);
    fclose(out);
  }
  if (!selection)
    puts("written: wave files");
  return 0;
}
  
