#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
//
#include "proto.h"
#include "instrument.h"
#include "midi.h"
#include "loadpatchs.h"

// this source file handles the parsing of patchfiles and the loading of
// samplefiles


// don't use local vars, as supervisor-stack is rather small
static char ln[256], temp1[256], temp2[256], temp3[256], temp4[256];
static int lineno;

static char *skip_leading_spaces(char *p);
static char *get_line(FILE *fh, int skipcomments);
static char *load_rawsamplefile(FILE *fh, char *tail, int bits);
static char *load_map(FILE *fh, char *sname, unsigned int banki, int drum);
static char *create_sample_part(FILE *fh, char *sname);
static char *load_memorysample(FILE *fh, char *tail, int bits);
static char *read_fm_info(FILE *fh, char *tail);


#define FMLENGTH    256
#define FMWAVES     4


char *load_patches(char *filename) {

  FILE *fh;
  unsigned int banki;
  char *err;

  lineno = 0;
  banki = 0;

  fh = fopen(filename, "r");
  if (!fh)   return "Failed to open the patch-file";

  if (!get_line(fh, 0)) {
    fclose(fh);
    return "Bad file";
  }
  if (strcmp(ln, "HBP10GM")) {
    fclose(fh);
    return "Illegal file format";
  }

  err = NULL;
  do {
    if (!get_line(fh, 1)) {
      fclose(fh);
      return NULL;
    }
    if (strncmp(ln, "RAWSAMPLE8 ", 11) == 0) {
      err = load_rawsamplefile(fh, skip_leading_spaces(ln+11), 8);

    } else if (strncmp(ln, "RAWSAMPLE16 ", 12) == 0) {
      err = load_rawsamplefile(fh, skip_leading_spaces(ln+12), 16);

    } else if (strncmp(ln, "MEMORYSAMPLE8 ", 13) == 0) {
      err = load_memorysample(fh, skip_leading_spaces(ln+13), 8);

    } else if (strncmp(ln, "MEMORYSAMPLE16 ", 14) == 0) {
      err = load_memorysample(fh, skip_leading_spaces(ln+14), 16);

    } else if (strncmp(ln, "BANK ", 5) == 0) {
      banki = atoi(ln+5)-1;
      if (banki >= 16)  err = "Illegal bank";
      if (banki == 15)  err = "Patch-bank #15 is reserved for other uses";

    } else if (strncmp(ln, "SAMPLEPART ", 11) == 0) {
      err = create_sample_part(fh, skip_leading_spaces(ln+11));

    } else if (strncmp(ln, "MAPPING ", 8) == 0) {
      err = load_map(fh, skip_leading_spaces(ln+8), banki, 0);

    } else if (strncmp(ln, "DRUMMAPPING ", 12) == 0) {
      err = load_map(fh, skip_leading_spaces(ln+12), banki, 1);

    } else if (strncmp(ln, "NAME ", 5) == 0) {
      unsigned int patchi;
      if (sscanf(ln+5, "%d %s", &patchi, temp1) == 2) {
        patchi--;
        if (patchi >= 128)
          err = "Bad NAME - program no. must be from 1 to 128";
        else
          instruments_set_patch_name(banki, patchi, temp1);
      }

    } else if (strncmp(ln, "DRUMNAME ", 9) == 0) {
      unsigned int patchi;
      if (sscanf(ln+9, "%d %s", &patchi, temp1) == 2) {
        patchi--;
        if (patchi >= 128)
          err = "Bad DRUMNAME - program no. must be from 1 to 128";
        else
          instruments_set_drum_name(banki, patchi, temp1);
      }

    } else if (strncmp(ln, "FM ", 3) == 0) {
      err = read_fm_info(fh, skip_leading_spaces(ln+3));

    } else if ((*ln) && (*ln != '#')) {
      fclose(fh);
      sprintf(temp1, "Junk found in patch-file at line %d : %200s", lineno, ln);
      return temp1;
    }
  } while (!err);

  fclose(fh);
  return err;
}


char *create_sample_part(FILE *fh, char *sname) {

  SAMPLEINDEX si, si2;
  unsigned int start, end, looppoint;

  si = instruments_find_named_sample(sname);
  if (si == ILLEGAL_SAMPLE) {
    sprintf(temp1, "Sample '%s' not found", sname);
    return temp1;
  }

  // get '<samplename> <start> <end>'
  if (!get_line(fh,1))         return "Premature end of file in SAMPLEPART";
  if (sscanf(ln, "%s %d %d", temp1, &start, &end) != 3)
    return "Bad SAMPLEPART";

  // get 'LOOP <looppoint>'
  if (!get_line(fh,1))         return "Premature end of file after SAMPLEPART";
  if (*ln) {
    if (sscanf(ln, "%s %s", temp2, temp3) != 2)      return "Bad loop-info";
    if (strcmp(temp2, "LOOP"))    return "Bad loop-info";
    if (strcmp(temp3, "NO") == 0)
      looppoint = NO_LOOPING;
    else
      looppoint = atoi(temp3);
    // get empty line
    if (!get_line(fh,1))       return "SAMPLEPART must be terminated by an empty line";
    if (*ln)                   return "SAMPLEPART must be terminated by an empty line";
  } else
    looppoint = NO_LOOPING;

  si2 = instruments_create_sample_part(temp1, si, start, end, looppoint);
  if (si2 == ILLEGAL_SAMPLE)   return "Failed to create sample-part";

  return NULL;
}


char *load_map(FILE *fh, char *sname, unsigned int banki, int drum) {

  SAMPLEINDEX si;
  unsigned int maxduration, start, end;
  int n;
  AHDSR env;

  env.attacktime = env.decaytime = 50;  // 0.05 sec
  env.holdtime = 10;                    // 0.01 sec
  env.decayamount = 224;                // -6dB
  env.sustaintime = 3000;               // -6dB in 3 seconds
  maxduration = 0;                      // infinite

  n = sscanf(sname, "%s %d %d", temp1, &start, &end);
  if (n == 1)
    start = end = 0;
  else if (n != 3) {
    sprintf(temp1, "Bad mapping at line %d : %200s", lineno, sname);
    return temp1;
  }

  si = instruments_find_named_sample(sname);
  if (si == ILLEGAL_SAMPLE)  return "Bad samplename";

  do {
    if (!get_line(fh,1))     return "Premature end of file in MAP";
    if (strncmp(ln, "ENVELOPE ", 9) == 0) {
      unsigned int n;

      n = sscanf(ln+9, "%d %d %d %d %d %d",
                 &env.attacktime,  &env.holdtime,    &env.decaytime,
                 &env.decayamount, &env.sustaintime, &maxduration);
      if ((n < 5) || (n > 6))   return "Bad ENVELOPE - bad no. of arguments";
      if (n == 5)  maxduration = 0;

    } else if (strncmp(ln, "MAP ", 4) == 0) {
      unsigned int freq, n;

      if (drum) {
        unsigned int drumi, drumi0, drumi1;
        n = sscanf(ln+4, "%s %d", temp2, &freq);
        if (n != 2)  return "Bad MAP - illegal no. of parameters";
        if ((freq < 5000) || (freq > 100000)) {
          sprintf(temp1, "Bad MAP - %d Hz is outside legal range (5..100kHz)", freq);
          return temp1;
        }
        if (sscanf(temp2, "[%d..%d]", &drumi0, &drumi1) == 2) {
          drumi0--;
          drumi1--;
          if ((drumi0 >= 128) || (drumi1 >= 128))
            return "Bad MAP - note no. must be from 1 to 128";
          for (drumi = drumi0; drumi <= drumi1; drumi++) {
            n = instruments_map_drum(si, banki, drumi, freq);
            if (n)   printf("DRUMMAPPING error %d\n", n);
            instruments_set_drum_envelope(banki, drumi, &env, maxduration);
          }
        } else {
          drumi = atoi(temp2)-1;
          if (drumi >= 128)   return "Bad MAP - note no. must be from 1 to 128";
          n = instruments_map_drum(si, banki, drumi, freq);
          if (n)   printf("DRUMMAPPING error %d\n", n);
          instruments_set_drum_envelope(banki, drumi, &env, maxduration);
        }

      } else {
        unsigned int patchi, octave, patchi0, patchi1;
        n = sscanf(ln+4, "%s %d %d", temp2, &freq, &octave);
        if ((n < 2) || (n > 3))  return "Bad MAP - illegal no. of parameters";
        if ((freq < 5000) || (freq > 100000)) {
          sprintf(temp1, "Bad MAP - %d Hz is outside legal range (5..100kHz)", freq);
          return temp1;
        }
        octave--;
        if (n == 2)
          octave = ALL_OCTAVES;
        else if (octave > 11)
          return "Bad MAP - there are only 11 octaves";
        if (sscanf(temp2, "[%d..%d]", &patchi0, &patchi1) == 2) {
          patchi0--;
          patchi1--;
          if ((patchi0 >= 128) || (patchi1 >= 128))
            return "Bad MAP - patch no. must be from 1 to 128";
          for (patchi = patchi0; patchi <= patchi1; patchi++) {
            n = instruments_map(si, banki, patchi, octave, freq);
            if (n)  printf("MAPPING error %d\n", n);
            instruments_set_envelope(banki, patchi, &env, maxduration);
          }
        } else {
          patchi = atoi(temp2)-1;
          if (patchi >= 128)       return "Bad MAP - patch no. must be from 1 to 128";
          n = instruments_map(si, banki, patchi, octave, freq);
          if (n)  printf("MAPPING error %d\n", n);
          instruments_set_envelope(banki, patchi, &env, maxduration);
        }
      }
    }
  } while (*ln);
  return NULL;
}


char *read_fm_info(FILE *fh, char *tail) {

  int i;
  SAMPLEINDEX si;
  unsigned int length;
  signed short *data;

  if (sscanf(tail, "%s", temp3) != 1) {
    sprintf(temp1, "FM requires an argument (error at line %d)", lineno);
    return temp1;
  }

  si = instruments_find_named_sample(temp3);
  if (si != ILLEGAL_SAMPLE) {
    sprintf(temp1, "Dublicate sample name '%s'", temp4);
    return temp1;
  }

  // create sample
  si = instruments_create_sample(temp3, FMLENGTH*FMWAVES, FMLENGTH*FMWAVES, SOURCE_16BIT);
  if (si == ILLEGAL_SAMPLE)      return "Failed to create sample";
  instruments_get_sample_info(si, &data, &length, NULL, NULL);
  for (i = 0; i < length; i++)   data[i] = 0;

  while (1) {
    char wave[16];
    int v[16], n;

    if (!get_line(fh, 1))        return "Premature end of file in FM";
    if (!*ln) {
      return NULL;
    }

    // first line of harmonics are for the sine waveform
    n = sscanf(ln, "%15s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
               wave, v+0, v+1,  v+2, v+3,
                     v+4, v+5,  v+6, v+7,
                     v+8, v+9, v+10, v+11,
                     v+12, v+13, v+14, v+15);
    if ((n < 2) || (n > 17)) {
      sprintf(temp1, "An FM entry must have 2 - 17 arguments (error at line %d)", lineno);
      return temp1;
    }
    for (i = n; i < 16; i++)     v[i] = 0;
    if (strcmp(wave, "SINE") == 0)
      build_from_harmonics(data, length, FMLENGTH, v, SAMPLE_SINE);
    else
      return "Illegal waveform type in FM";
  }

  return NULL;
}


char *load_rawsamplefile(FILE *fh, char *tail, int bits) {

  unsigned int length, looppoint;
  unsigned char format;
  FILE *fs;
  SAMPLEINDEX si;
  signed short *data;

  if (bits == 8)
    format = SOURCE_8BIT;
  else if (bits == 16)
    format = SOURCE_16BIT;
  else
    return "Unknown sample-format";

  if (sscanf(tail, "%s %s", temp3, temp4) != 2) {
    sprintf(temp1, "RAWSAMPLE8 and RAWSAMPLE16 requires 2 arguments (error at line %d)", lineno);
    return temp1;
  }

  si = instruments_find_named_sample(temp4);
  if (si != ILLEGAL_SAMPLE) {
    sprintf(temp1, "Dublicate sample name '%s'", temp4);
    return temp1;
  }

  // get 'LOOP <looppoint>'
  if (!get_line(fh, 1))           return "Premature end of file in RAWSAMLEx";
  if (*ln) {
    if (sscanf(ln, "%s %s", temp1, temp2) != 2)    return "Bad loop-info";
    if (strcmp(temp1, "LOOP"))    return "Bad loop-info";
    if (strcmp(temp2, "NO") == 0)
      looppoint = NO_LOOPING;
    else
      looppoint = atoi(temp2);
    // get empty line
    if (!get_line(fh,1))          return "RAWSAMPLEx must be terminated by an empty line";
    if (*ln)                      return "RAWSAMPLEx must be terminated by an empty line";
  } else
    looppoint = NO_LOOPING;

  sprintf(ln, "HBP10GMPatches:%s", temp3);
  fs = fopen(ln, "rb");
  if (!fs)                        return "Failed to open samplefile";
  fseek(fs, 0, SEEK_END);
  length = (unsigned int)ftell(fs);
  fseek(fs, 0, SEEK_SET);
  length = length*8/bits;       // 16 bit samples

  si = instruments_create_sample(temp4, length, looppoint, format);
  if (si == ILLEGAL_SAMPLE) {
    fclose(fs);
    return "Failed to create sample";
  }
  instruments_get_sample_info(si, &data, &length, NULL, NULL);

  fread(data, bits/8, length, fs);
  fclose(fs);

  return NULL;
}


char *load_memorysample(FILE *fh, char *tail, int bits) {

  unsigned int length, ptr, looppoint;
  unsigned char format;
  SAMPLEINDEX si;

  if (bits == 8)
    format = SOURCE_8BIT;
  else if (bits == 16)
    format = SOURCE_16BIT;
  else
    return "Unknown sample-format";

  if (sscanf(tail, "%d %d %s", &ptr, &length, temp3) != 3) {
    sprintf(temp1, "Bad arguments to MEMORYSAMPLE%d (error at line %d)", bits, lineno);
    return temp1;
  }


  si = instruments_find_named_sample(temp3);
  if (si != ILLEGAL_SAMPLE) {
    sprintf(temp1, "Dublicate sample name '%s'", temp3);
    return temp1;
  }

  // get 'LOOP <looppoint>'
  if (!get_line(fh,1))            return "Premature end of file in MEMORYSAMPLEx";
  if (*ln) {
    if (sscanf(ln, "%s %s", temp1, temp2) != 2)      return "Bad loop-info";
    if (strcmp(temp1, "LOOP"))      return "Bad loop-info";
    if (strcmp(temp2, "NO") == 0)
      looppoint = NO_LOOPING;
    else
      looppoint = atoi(temp2);
    // get empty line
    if (!get_line(fh,1))          return "MEMORYSAMPLEx must be terminated by an empty line";
    if (*ln)                      return "MEMORYSAMPLEx must be terminated by an empty line";
  } else
    looppoint = NO_LOOPING;

  si = instruments_install_sample(temp3, (signed short *)ptr, length, looppoint, format);
  if (si == ILLEGAL_SAMPLE)    return "Failed to create sample";

  return NULL;
}

// ------------------------------------------------------------------------


char *skip_leading_spaces(char *p) {
  while (*p == ' ')  p++;
  return p;
}


char *get_line(FILE *fh, int skipcomments) {

  int p, done;

  done = 0;
  do {
    lineno++;
    if (!fgets(ln, 255, fh)) {
      fclose(fh);
      return NULL;
    }

    ln[255] = '\0';
    // find first ctrl-char
    for (p = 0; ln[p] >= ' '; p++)   ;
    // skip trailing spaces
    if (p)    while ((ln[p-1] == ' ') && (p))  p--;
    ln[p] = '\0';

    if (skipcomments) {
      if ((feof(fh)) || (ln[0] != '#'))   done = 1;
    } else
      done = 1;

  } while (!done);
  return ln;
}
