/*
 * MOD (4chn) -> GoatTracker SNG converter.
 * Dedicated to T.M.R!
 */

#include "bme_end.h"

#include <cstdio>
#include <cstring>
#include <cstdlib>

struct Note
{
  unsigned char note;
  unsigned char instr;
  unsigned char command;
  unsigned char data;
};

struct Goatnote
{
  unsigned char note;
  unsigned char command;
  unsigned char data;
};


unsigned short periodtable[16][12] =
{
  {6848, 6464, 6096, 5760, 5424, 5120, 4832, 4560, 4304, 4064, 3840, 3624},
  {6800, 6416, 6056, 5720, 5392, 5096, 4808, 4536, 4280, 4040, 3816, 3600},
  {6752, 6368, 6016, 5672, 5360, 5056, 4776, 4504, 4256, 4016, 3792, 3576},
  {6704, 6328, 5968, 5632, 5320, 5024, 4736, 4472, 4224, 3984, 3760, 3552},
  {6656, 6280, 5928, 5592, 5280, 4984, 4704, 4440, 4192, 3960, 3736, 3528},
  {6608, 6232, 5888, 5552, 5240, 4952, 4672, 4408, 4160, 3928, 3704, 3496},
  {6560, 6192, 5840, 5512, 5208, 4912, 4640, 4376, 4128, 3896, 3680, 3472},
  {6512, 6144, 5800, 5472, 5168, 4880, 4600, 4344, 4104, 3872, 3656, 3448},
  {7256, 6848, 6464, 6096, 5760, 5424, 5120, 4832, 4560, 4304, 4032, 3840},
  {7200, 6800, 6416, 6056, 5720, 5400, 5088, 4808, 4536, 4280, 4040, 3816},
  {7152, 6752, 6368, 6016, 5672, 5360, 5056, 4776, 4504, 4256, 4016, 3792},
  {7096, 6704, 6328, 5968, 5632, 5320, 5024, 4736, 4472, 4224, 3984, 3760},
  {7048, 6656, 6280, 5928, 5592, 5280, 4984, 4704, 4440, 4192, 3952, 3736},
  {7000, 6608, 6232, 5888, 5552, 5240, 4952, 4672, 4408, 4160, 3928, 3704},
  {6944, 6560, 6192, 5840, 5512, 5208, 4912, 4640, 4376, 4128, 3896, 3680},
  {6896, 6512, 6144, 5800, 5472, 5168, 4880, 4600, 4344, 4104, 3872, 3656}
};

unsigned char modheader[1084];
unsigned char modpatterns[64*64*4*4];
Note modnotes[64*64*4];
Goatnote goatnotes[208][65];
unsigned char orderlist[3][256];
Goatnote tempnotes[65];

char ident[] = {'G', 'T', 'S', '!'};

char zeroarray[32] = {0};

int main(int argc, char **argv)
{
  if (argc < 3)
  {
    std::printf("Usage: mod2sng <mod> <sng> [channel] [transpose]\n"
           "[channel] is the channel to leave out (1-4), default 4\n"
           "[transpose] is the halfstep transpose added to notes, default 0\n");

    return EXIT_FAILURE;
  }

  FILE *in = std::fopen(argv[1], "rb");
  if (!in)
  {
    std::printf("Source open error.\n");
    return EXIT_FAILURE;
  }
  FILE *out = std::fopen(argv[2], "wb");
  if (!out)
  {
    std::printf("Destination open error.\n");
    std::fclose(in);
    return EXIT_FAILURE;
  }

  int leaveout = 3;
  int transpose = 0;

  if (argc > 3)
  {
    std::sscanf(argv[3], "%d", &leaveout);
    leaveout--;
    if ((leaveout < 0) || (leaveout > 3))
    {
      std::printf("Illegal channel number.\n");
      std::fclose(in);
      std::fclose(out);
      return EXIT_FAILURE;
    }
  }

  if (argc > 4)
  {
    std::sscanf(argv[4], "%d", &transpose);
  }

  int maxpatt = 0;
  int orderlen;

  for (int c = 0; c < 1084; c++)
  {
    modheader[c] = fread8(in);
  }
  orderlen = modheader[950];
  for (int c = 0; c < 128; c++)
  {
    if (modheader[952+c] > maxpatt) maxpatt = modheader[952+c];
  }
  maxpatt++;
  for (int c = 0; c < maxpatt * 1024; c++)
  {
    modpatterns[c] = fread8(in);
  }
  std::fclose(in);

  // Convert patterns into easier-to-read format

  Note *destptr = modnotes;
  unsigned char *srcptr = modpatterns;
  for (int c = 0; c < maxpatt * 256; c++)
  {
    // Note: FT2 saves the 13th bit of period into 5th bit of
    // samplenumber, and when loading it ofcourse cannot read
    // the period back correctly! We don't use the 13th bit!

    unsigned short period = ((srcptr[0] & 0x0f) << 8) | srcptr[1];
    unsigned char note = 0, instrument, command;
    if (period)
    {
      int offset = 0x7fffffff;

      for (int findnote = 0; findnote < 96; findnote++)
      {
        if (std::abs(period - (periodtable[0][findnote % 12] >> (findnote / 12))) < offset)
        {
          note = findnote + 1;
          offset = abs(period - (periodtable[0][findnote % 12] >> (findnote / 12)));
        }
      }
    }
    instrument = (srcptr[0] & 0xf0) | ((srcptr[2] & 0xf0) >> 4);
    command = srcptr[2] & 0x0f;
    destptr->note = note;
    destptr->instr = instrument;
    destptr->command = command;
    destptr->data = srcptr[3];
    srcptr += 4;
    destptr++;
  }

  int goatchan = 0;
  int goatpatt = 0;

  // Convert patterns into goatpatterns, and create orderlists
  for (int c = 0; c < 4; c++)
  {
    if (c != leaveout)
    {
      for (int d = 0; d < orderlen; d++)
      {
        int patt = modheader[952+d];
        int e = 0;
        int o = patt*256+c;
        bool breakflag = false;
        unsigned char prevgoatdata = 0;

        std::memset(tempnotes, 0, sizeof tempnotes);
        for (;;)
        {
          unsigned char goatcommand = 0;
          unsigned char goatdata = 0;

          if (modnotes[o+e*4].note)
          {
            tempnotes[e].note = modnotes[o+e*4].note - 1 + transpose;
            if (tempnotes[e].note > 0x5d) tempnotes[e].note = 0x5d;
          }
          else tempnotes[e].note = 0x5f;
          switch(modnotes[o+e*4].command)
          {
            case 0:
            goatcommand = modnotes[o+e*4].command;
            goatdata = modnotes[o+e*4].data;
            break;

            case 1:
            case 2:
            case 3:
            case 4:
            goatcommand = modnotes[o+e*4].command;
            if (modnotes[o+e*4].data) goatdata = modnotes[o+e*4].data;
            else goatdata = prevgoatdata;
            prevgoatdata = goatdata;
            break;

            case 0xf:
            if (modnotes[o+e*4].data < 0x20) // Ticktempo
            {
              goatcommand = 7;
              goatdata = modnotes[o+e*4].data;
            }
            break;

            case 0xc:
            // Convert zero volume to keyoff
            if ((!modnotes[o+e*4].data) && (tempnotes[e].note == 0x5f))
              tempnotes[e].note = 0x5e;
            break;

            case 0xb:
            case 0xd:
            breakflag = true;
            break;

            default:
            break;
          }
          tempnotes[e].command = (modnotes[o+e*4].instr << 3) | goatcommand;
          tempnotes[e].data = goatdata;

          e++;
          if (e == 64) break;
          if (breakflag) break;
        }
        tempnotes[e].note = 0xff;
        for (e = 0; e < goatpatt; e++)
        {
          int f;
          for (f = 0; f < 65; f++)
          {
            if ((goatnotes[e][f].note != tempnotes[f].note) ||
                (goatnotes[e][f].command != tempnotes[f].command) ||
                (goatnotes[e][f].data != tempnotes[f].data)) break;
          }
          // Same pattern
          if (f == 65)
          {
            orderlist[goatchan][d] = e;
            break;
          }
        }
        // No same pattern pattern
        if (e == goatpatt)
        {
          if (goatpatt >= 208)
          {
            std::printf("208 patterns exceeded!\n");
            return EXIT_FAILURE;
          }
          for (int f = 0; f < 65; f++)
          {
            goatnotes[goatpatt][f].note = tempnotes[f].note;
            goatnotes[goatpatt][f].command = tempnotes[f].command;
            goatnotes[goatpatt][f].data = tempnotes[f].data;
          }
          orderlist[goatchan][d] = goatpatt;
          goatpatt++;
        }
      }
      goatchan++;
    }
  }
  std::fwrite(ident, 4, 1, out); // Ident
  std::fwrite(&modheader[0], 20, 1, out); // Infotexts
  std::fwrite(zeroarray, 12, 1, out);
  std::fwrite(zeroarray, 32, 1, out);
  std::fwrite(zeroarray, 32, 1, out);
  fwrite8(out, 1); // Number of songs
  // Orderlist
  for (int c = 0; c < 3; c++)
  {
    fwrite8(out, orderlen+1);
    for (int d = 0; d < orderlen; d++)
    {
      fwrite8(out, orderlist[c][d]);
    }
    fwrite8(out, 0xff); // Loop song
    fwrite8(out, 0x0); // From beginning
  }
  // Instruments
  for (int c = 1; c < 32; c++)
  {
    fwrite8(out, 0);
    fwrite8(out, 0);
    fwrite8(out, 0);
    fwrite8(out, 0);
    fwrite8(out, 0);
    fwrite8(out, 0);
    fwrite8(out, 0);
    fwrite8(out, 2);
    std::fwrite(&modheader[20+30*(c-1)], 15, 1, out);
    fwrite8(out, 0);
    fwrite8(out, 0);
    fwrite8(out, 0);
  }
  // Patterns
  fwrite8(out, goatpatt);
  for (int c = 0; c < goatpatt; c++)
  {
    fwrite8(out, 65*3);
    for (int d = 0; d < 65; d++)
    {
      fwrite8(out, goatnotes[c][d].note);
      fwrite8(out, goatnotes[c][d].command);
      fwrite8(out, goatnotes[c][d].data);
    }
  }
  std::fclose(out);
  std::printf("Converted successfully.\n");
  return EXIT_SUCCESS;
}
