/*
 * GoatTracker pattern splitter
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "goattrk.h"

#define MAX_SPLITS 8

INSTR instr[MAX_INSTR];
unsigned char filtertable[256];
unsigned char wavetable[MAX_INSTR][MAX_WAVELEN*2];
unsigned char songorder[MAX_SONGS][MAX_CHN][MAX_SONGLEN+2];
unsigned char destsongorder[MAX_SONGS][MAX_CHN][MAX_SONGLEN+2];
unsigned char ordermaptbl[MAX_SONGLEN+2];
unsigned char pattern[MAX_PATT][MAX_PATTROWS*3+3];
unsigned char destpattern[MAX_PATT+1][MAX_PATTROWS*3+3];
INSTR instrcopybuffer;
unsigned char wavecopybuffer[MAX_WAVELEN*2];
unsigned char songname[MAX_STR];
unsigned char authorname[MAX_STR];
unsigned char copyrightname[MAX_STR];
int pattlen[MAX_PATT];
int songlen[MAX_SONGS][MAX_CHN];
int destpattlen[MAX_PATT+1];
int destsonglen[MAX_SONGS][MAX_CHN];
int destpattsplits[MAX_PATT+1];
int patternmaptable[MAX_PATT][MAX_SPLITS*2];

int dp;
int highestusedpattern = 0;
int splits = 4;
int threshold = 16;

int main(int argc, char **argv);
int loadsong(char *name);
int savesong(char *name);
int processsong(void);
void countpatternlengths(void);
void countdestpatternlengths(void);
void clearsong(int cs, int cp, int ci, int cn);

int main(int argc, char **argv)
{
  if (argc < 3)
  {
    printf("Usage: SNGSPLIT <sourcesong> <dest.song> [splits] [threshold]\n"
           "Splits all patterns of the song into as many parts as [splits] indicates,\n"
           "searching for possible duplicates and probably making the song take less\n"
           "memory. For safety reasons source & destination cannot be same, because\n"
           "a splitted song is harder to edit. Always keep the original!\n\n"
           "Default number of splits is 4.\n\n"
           "Patterns whose length is threshold or less won't be splitted at all. By\n"
           "default the threshold is 16.\n\n");
    return 1;
  }
  if (!strcmp(argv[1], argv[2]))
  {
    printf("ERROR: Source and destination are not allowed to be the same.");
    return 1;
  }

  if (argc >= 4)
  {
    sscanf(argv[3], "%u", &splits);
    if (!splits) splits = 1;
    if (splits > MAX_SPLITS) splits = MAX_SPLITS;
  }
  if (argc >= 5)
  {
    sscanf(argv[4], "%u", &threshold);
    if (!threshold) threshold = 1;
    if (threshold > 80) threshold = 80;
  }
  if (!loadsong(argv[1]))
  {
    printf("ERROR: Couldn't load source song.");
    return 1;
  }
  if (!processsong())
  {
    return 1;
  }
  if (!savesong(argv[2]))
  {
    printf("ERROR: Couldn't save destination song.");
    return 1;
  }
  return 0;
}

int processsong(void)
{
  int c,d,e;
  int splitsize;
  int songs;
  int dsl;

  dp = 0; /* Destination patterns */

  for (c = 0; c <= highestusedpattern; c++)
  {
    destpattsplits[c] = 0;
    splitsize = pattlen[c] / splits;
    if (splitsize < 4) splitsize = 4;
    if (pattlen[c] <= threshold) splitsize = pattlen[c];

    d = 0;
    while (d < pattlen[c])
    {
      int remain = pattlen[c] - d;
      int splitfound = 0;

      /* Check existing patterns for matches */
      for (e = 0; e < dp; e++)
      {
        if ((destpattlen[e] <= remain) && (destpattlen[e] >= splitsize) && (!memcmp(&pattern[c][d*3], destpattern[e], destpattlen[e]*3)))
        {
          patternmaptable[c][destpattsplits[c]] = e;
          destpattsplits[c]++;
          d += destpattlen[e];
          splitfound = 1;
          break;
        }
      }
      if (!splitfound)
      {
        /* If less than 2 splits left, do in one part */
        if (remain < splitsize * 2)
        {
          memcpy(destpattern[dp], &pattern[c][d*3], remain*3);
          destpattern[dp][remain*3] = ENDPATT;
          destpattern[dp][remain*3+1] = 0;
          destpattern[dp][remain*3+2] = 0;
          destpattlen[dp] = remain;
          patternmaptable[c][destpattsplits[c]] = dp;
          destpattsplits[c]++;
          d += remain;
          dp++;
        }
        else
        {
          memcpy(destpattern[dp], &pattern[c][d*3], splitsize*3);
          destpattern[dp][splitsize*3] = ENDPATT;
          destpattern[dp][splitsize*3+1] = 0;
          destpattern[dp][splitsize*3+2] = 0;
          destpattlen[dp] = splitsize;
          patternmaptable[c][destpattsplits[c]] = dp;
          destpattsplits[c]++;
          d += splitsize;
          dp++;
        }
      }
      /* This should never happen */
      if (destpattsplits[c] >= MAX_SPLITS * 2)
      {
        printf("ERROR: Internal error, too many splits!");
        return 0;
      }
      /* This might happen :-) */
      if (dp > MAX_PATT)
      {
        printf("ERROR: 255 patterns exceeded!");
        return 0;
      }
    }
  }

  /* Now convert all songs */
  /* Determine amount of songs to be processed */
  c = 0;
  for (;;)
  {
    if (c == MAX_SONGS) break;
    if ((!songlen[c][0])||
       (!songlen[c][1])||
       (!songlen[c][2])) break;
    c++;
  }
  songs = c;

  for (c = 0; c < songs; c++)
  {
    for (d = 0; d < MAX_CHN; d++)
    {
      dsl = 0;
      for (e = 0; e <= songlen[c][d]+1; e++)
      {
        int pattnum = songorder[c][d][e];

        ordermaptbl[e] = dsl;
        if (e < songlen[c][d])
        {
          if (pattnum < MAX_PATT)
          {
            int f;
            for (f = 0; f < destpattsplits[pattnum]; f++)
            {
              destsongorder[c][d][dsl] = patternmaptable[pattnum][f];
              dsl++;
              if (dsl > MAX_SONGLEN)
              {
                printf("ERROR: Orderlist-length of 254 exceeded!");
                return 0;
              }
            }
          }
          else
          {
            destsongorder[c][d][dsl] = pattnum;
            dsl++;
            if (dsl > MAX_SONGLEN)
            {
              printf("ERROR: Orderlist-length of 254 exceeded!");
              return 0;
            }
          }
        }
        else
        {
          if (pattnum == LOOPSONG)
          {
            destsongorder[c][d][dsl] = pattnum;
          }
          else
          {
            /* Map old orderlist position to new */
            destsongorder[c][d][dsl] = ordermaptbl[pattnum];
          }
          dsl++;
        }
      }
    }
  }
  /* Everything ok! */
  countdestpatternlengths();
  {
    int destpatttbl = 0, destpatt = 0, destsong = 0;
    int srcpatttbl = 0, srcpatt = 0, srcsong = 0;

    for (c = 0; c < MAX_SONGS; c++)
    {
      if ((songlen[c][0]) && (songlen[c][1]) && (songlen[c][2]))
      {
        for (d = 0; d < MAX_CHN; d++)
        {
          srcsong += songlen[c][d]+1;
          destsong += destsonglen[c][d]+1;
        }
      }
    }
    for (c = 0; c < highestusedpattern; c++)
    {
      srcpatt += pattlen[c]*3 + 3;
      srcpatttbl += 2;
    }
    for (c = 0; c < dp; c++)
    {
      destpatt += destpattlen[c]*3 + 3;
      destpatttbl += 2;
    }
    printf("Processing complete. Results:\n\n"
           "       Songdata Patterns Patt.Tbl Total\n"
           "Before %-8d %-8d %-8d %-8d\n"
           "After  %-8d %-8d %-8d %-8d\n",
           srcsong,srcpatt,srcpatttbl,srcsong+srcpatt+srcpatttbl,
           destsong,destpatt,destpatttbl,destsong+destpatt+destpatttbl);
  }
  return 1;
}

int loadsong(char *name)
{
  char ident[4];
  int c;

  FILE *srchandle = fopen(name, "rb");
  if (srchandle)
  {
    fread(ident, 4, 1, srchandle);
    if (!memcmp(ident, "GTS!", 4))
    {
      int d;
      unsigned char length;
      unsigned char amount;
      int loadbytes;
      clearsong(1,1,1,1);

      /* Read infotexts */
      fread(songname, sizeof songname, 1, srchandle);
      fread(authorname, sizeof authorname, 1, srchandle);
      fread(copyrightname, sizeof copyrightname, 1, srchandle);

      /* Read songorderlists */
      fread(&amount, sizeof amount, 1, srchandle);
      for (d = 0; d < amount; d++)
      {
        for (c = 0; c < MAX_CHN; c++)
        {
          fread(&length, sizeof length, 1, srchandle);
          loadbytes = length;
          loadbytes++;
          fread(songorder[d][c], loadbytes, 1, srchandle);
          /* Convert the old endmark */
          if (songorder[d][c][length] == 0xfe)
            songorder[d][c][length] = LOOPSONG;
        }
      }
      /* Read instruments */
      for (c = 1; c < MAX_INSTR; c++)
      {
        fread(&instr[c], sizeof(INSTR), 1, srchandle);
        loadbytes = instr[c].wavetableindex;
        instr[c].wavetableindex = 1;
        fread(wavetable[c], loadbytes, 1, srchandle);
      }
      /* Read patterns */
      fread(&amount, sizeof amount, 1, srchandle);
      for (c = 0; c < amount; c++)
      {
        fread(&length, sizeof length, 1, srchandle);
        fread(pattern[c], length, 1, srchandle);
      }
      /* Read filtertable */
      fread(filtertable, 256, 1, srchandle);

      countpatternlengths();
      fclose(srchandle);
      return 1;
    }
    fclose(srchandle);
  }
  return 0;
}

int savesong(char *name)
{
  char ident[] = {'G', 'T', 'S', '!'};
  FILE *handle;
  int c;

  handle = fopen(name, "wb");
  if (handle)
  {
    int d;
    unsigned char length;
    unsigned char amount;
    int writebytes;
    fwrite(ident, 4, 1, handle);

    countdestpatternlengths();

    /* Write infotexts */
    fwrite(songname, sizeof songname, 1, handle);
    fwrite(authorname, sizeof authorname, 1, handle);
    fwrite(copyrightname, sizeof copyrightname, 1, handle);

    /* Determine amount of songs to be saved */
    c = 0;
    for (;;)
    {
      if (c == MAX_SONGS) break;
      if ((!destsonglen[c][0])||
         (!destsonglen[c][1])||
         (!destsonglen[c][2])) break;
      c++;
    }
    amount = c;

    fwrite(&amount, sizeof amount, 1, handle);
    /* Write songorderlists */
    for (d = 0; d < amount; d++)
    {
      for (c = 0; c < MAX_CHN; c++)
      {
        length = destsonglen[d][c]+1;
        fwrite(&length, sizeof length, 1, handle);
        writebytes = length;
        writebytes++;
        fwrite(destsongorder[d][c], writebytes, 1, handle);
      }
    }
    /* Write instruments */
    for (c = 1; c < MAX_INSTR; c++)
    {
      instr[c].wavetableindex = 2;
      for (d = 0; d < MAX_WAVELEN*2; d+= 2)
      {
        if (wavetable[c][d] == 0xff) break;
        instr[c].wavetableindex += 2;
      }
      fwrite(&instr[c], sizeof(INSTR), 1, handle);
      fwrite(wavetable[c], instr[c].wavetableindex, 1, handle);
      instr[c].wavetableindex = 1;
    }
    /* Write patterns */
    amount = dp;
    fwrite(&amount, sizeof amount, 1, handle);
    for (c = 0; c < amount; c++)
    {
      length = destpattlen[c]*3+3;
      fwrite(&length, sizeof length, 1, handle);
      fwrite(destpattern[c], length, 1, handle);
    }
    /* Write filtertable */
    fwrite(filtertable, 256, 1, handle);

    fclose(handle);
    return 1;
  }
  return 0;
}

void countpatternlengths(void)
{
  int c, d, e;

  highestusedpattern = 0;
  for (c = 0; c < MAX_PATT; c++)
  {
    for (d = 0; d <= MAX_PATTROWS; d++)
    {
      if (pattern[c][d*3] == ENDPATT) break;
    }
    pattlen[c] = d;
  }
  for (e = 0; e < MAX_SONGS; e++)
  {
    for (c = 0; c < MAX_CHN; c++)
    {
      for (d = 0; d < MAX_SONGLEN; d++)
      {
        if (songorder[e][c][d] >= LOOPSONG) break;
        if (songorder[e][c][d] < MAX_PATT)
        {
          if (songorder[e][c][d] > highestusedpattern)
            highestusedpattern = songorder[e][c][d];
        }
      }
      songlen[e][c] = d;
    }
  }
}

void countdestpatternlengths(void)
{
  int c, d, e;

  for (c = 0; c < MAX_PATT; c++)
  {
    for (d = 0; d <= MAX_PATTROWS; d++)
    {
      if (destpattern[c][d*3] == ENDPATT) break;
    }
    destpattlen[c] = d;
  }
  for (e = 0; e < MAX_SONGS; e++)
  {
    for (c = 0; c < MAX_CHN; c++)
    {
      for (d = 0; d < MAX_SONGLEN; d++)
      {
        if (destsongorder[e][c][d] >= LOOPSONG) break;
      }
      destsonglen[e][c] = d;
    }
  }
}

void clearsong(int cs, int cp, int ci, int cn)
{
  int c;

  for (c = 0; c < MAX_CHN; c++)
  {
    int d;
    if (cs)
    {
      for (d = 0; d < MAX_SONGS; d++)
      {
        destsonglen[d][c] = 0;
        destsongorder[d][c][0] = LOOPSONG;

        memset(&songorder[d][c][0], 0, MAX_SONGLEN);
        if (!d)
        {
          songorder[d][c][0] = c;
          songorder[d][c][1] = LOOPSONG;
          songorder[d][c][2] = 0;
        }
        else
        {
          songorder[d][c][0] = LOOPSONG;
          songorder[d][c][1] = 0;
        }
      }
    }
  }
  if (cn)
  {
    memset(songname, 0, sizeof songname);
    memset(authorname, 0, sizeof authorname);
    memset(copyrightname, 0, sizeof copyrightname);
  }

  if (cp)
  {
    for (c = 0; c < MAX_PATT; c++)
    {
      int d;
      memset(&pattern[c][0], 0, MAX_PATTROWS*3);
      for (d = 0; d < MAX_PATTROWS; d++) pattern[c][d*3] = REST;
      for (d = MAX_PATTROWS; d <= MAX_PATTROWS; d++) pattern[c][d*3] = ENDPATT;
    }
  }
  if (ci)
  {
    for (c = 0; c < MAX_INSTR; c++)
    {
      memset(&instr[c], 0, sizeof(INSTR));
      memset(&wavetable[c][0], 0, MAX_WAVELEN*2);
      wavetable[c][2] = 0xff;
      wavetable[c][MAX_WAVELEN*2-2] = 0xff;
    }
    memset(&instrcopybuffer, 0, sizeof(INSTR));
    memset(wavecopybuffer, 0, MAX_WAVELEN*2);
  }
  countpatternlengths();
}


