// -*-C++-*-
// This file is part of the gmod package
// Copyright (C) 1997 by Andrew J. Robinson

#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

#ifdef USE_LOCAL
#include "soundcard.h"
#else
#include <sys/soundcard.h>
#endif

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

#include "commands.h"
#include "defines.h"
#include "structs.h"
#include "globals.h"
#include "protos.h"

#include "Sequencer.h"
#include "s3m.h"

struct listNode
{
  struct listNode *prev, *next;
  unsigned int number;
  unsigned int location;
  char type;
};

static unsigned char s3mEffectTab[] =
{CMD_SET_TICKS, CMD_JUMP, CMD_BREAK, CMD_VOLSLIDE,	/* A B C D */
 CMD_SLIDEDOWN, CMD_SLIDEUP, CMD_SLIDETO, CMD_VIBRATO,	/* E F G H */
 CMD_TREMOR, CMD_ARPEG2, CMD_VIBRAANDVOL, CMD_PORTANDVOL,	/* I J K L */
 CMD_INFO, CMD_INFO, CMD_SETOFFSET, CMD_INFO,	/* M N O P */
 CMD_RETRIGVOL, CMD_TREMOLO, CMD_EXTENDED, CMD_SET_BPM,	/* Q R S T */
 CMD_INFO, CMD_GLOBAL_VOL, CMD_INFO, CMD_INFO,	/* U V W X */
 CMD_INFO, CMD_INFO};		/* Y Z */


void
insertInList (struct listNode *list, char type, int number,
		unsigned int location)
{
  struct listNode *current = list->next;
  struct listNode *node;

  node = (struct listNode *) malloc (sizeof (struct listNode));
  node->type = type;
  node->number = number;
  node->location = location;

  while ((current != list) && (current->location < node->location))
    current = current->next;

  node->next = current;
  node->prev = current->prev;
  (current->prev)->next = node;
  current->prev = node;
}

int
catchup(FILE *modFd, int wanted, int *filePos)
{
  char input[1024];
  int bytesToRead;
  int readCount;

  while (wanted > *filePos)
    {
      bytesToRead = wanted - *filePos;

      if (bytesToRead > 1024)
	bytesToRead = 1024;

      readCount = fread(input, 1, bytesToRead, modFd);
      (*filePos) += readCount;

      if (readCount != bytesToRead)
	return 0;
    }
  
  return 1;
}

void
cvtS3mEffect (unsigned char *effect, unsigned char *parm)
{
  if ((*effect > 0) && (*effect <= 26))
    *effect = s3mEffectTab[*effect - 1];
  else
    *effect = CMD_INFO;

  if ((*effect == CMD_SET_BPM) && (*parm <= 0x20))
    *effect = CMD_INFO;
}

void
loadS3mPattern (FILE * modFd, int patNo, struct songInfo *songChar,
		  unsigned char channelMap[], int *filePos)
{
  int i;
  unsigned char hasNote, hasVolume, hasCommand;
  int readBytes, voice;
  unsigned char channel, note, instrument, volume, command, info;
  unsigned char buffer[100];
  int index;
  int patternEnd;

  if (modFd)
    {
      int bytesRead;

      bytesRead = fread(buffer, 1, 2, modFd);
      (*filePos) += bytesRead;
      
      if (bytesRead != 2)
	patternEnd = 0;
      else
	patternEnd = *filePos + INTEL_SHORT(buffer);
    }

  for (i = 0; i < songChar->nrChannels; i++)
    {
      voiceTable[patNo][i] = (patNo * songChar->nrChannels) + i;
      patternTable[voiceTable[patNo][i]] =
	(struct noteInfo *) calloc (1, sizeof (struct noteInfo) * 64);
    }

  if (modFd)
    {
      for (i = 0; i < 64; i++)
	{
	  while (*filePos < patternEnd)
	    {
	      hasNote = 0;
	      hasVolume = 0;
	      hasCommand = 0;
	      readBytes = 0;
	      index = 0;

	      note = instrument = volume = command = info = 0;

	      fread (buffer, 1, 1, modFd);
	      (*filePos) += 1;

	      if (buffer[0] == 0)
		break;

	      channel = channelMap[buffer[0] & 31];

	      if (buffer[0] & 32)
		{
		  hasNote = 1;
		  readBytes += 2;
		}

	      if (buffer[0] & 64)
		{
		  hasVolume = 1;
		  readBytes += 1;
		}

	      if (buffer[0] & 128)
		{
		  hasCommand = 1;
		  readBytes += 2;
		}

	      fread (buffer, readBytes, 1, modFd);
	      (*filePos) += readBytes;

	      if (hasNote)
		{
		  if (buffer[0] == 255)	/* empty note */
		    note = /* NOTE_LAST; */ 0;
		  else if (buffer[0] == 254)	/* stop sample */
		    note = NOTE_STOP;
		  else
		    {
		      note = ((buffer[0] >> 4) & 0x0f) * 12 + (buffer[0] & 0x0f) + 12;
		      if (note < songChar->lowestNote)
			note = songChar->lowestNote;
		      else if (note > songChar->highestNote)
			note = songChar->highestNote;
		    }
		  instrument = buffer[1];
		  index += 2;
		}

	      if (hasVolume)
		{
		  volume = buffer[index];
		  if (volume > 0)
		    volume = volume * 4 - 1;
		  index += 1;
		}

	      if (hasCommand)
		{
		  command = buffer[index];
		  info = buffer[index + 1];
		  cvtS3mEffect (&command, &info);
		}

	      if (channel != 255)
		{
		  voice = voiceTable[patNo][channel];

		  (patternTable[voice])[i].note = note;
		  (patternTable[voice])[i].sample = instrument;
		  if (hasVolume)
		    {
		      (patternTable[voice])[i].command[0] = CMD_VOLUME;
		      (patternTable[voice])[i].parm1[0] = volume;
		    }
		  else
		    {
		      (patternTable[voice])[i].command[0] = 0;
		      (patternTable[voice])[i].parm1[0] = 0;
		    }

		  (patternTable[voice])[i].parm2[0] = 0;
		  (patternTable[voice])[i].command[1] = command;
		  (patternTable[voice])[i].parm1[1] = info;
		  (patternTable[voice])[i].parm2[1] = 0;
		}
	    }
	}
    }
}

unsigned int
loadS3mIns (FILE * modFd, unsigned char *sampleInf, int *filePos)
{
  unsigned int retVal;

  fread (sampleInf, 0x50, 1, modFd);
  (*filePos) += 0x50;

  retVal = INTEL_SHORT (sampleInf + 0x0e) * 16;

  if (strncmp ((char *)(sampleInf + 0x4c), "SCRS", 4) != 0)
    retVal = 0;

  return retVal;
}

int
loadS3mModule (FILE ** modFd, struct songInfo *songChar,
		 struct optionsInfo options, unsigned char *buffer,
		 char *command)
{
  extern Sample *samples[];
  extern Sequencer *seq;

  int i;

  unsigned char header[0x60];

  int nrSamples;
  int slen, npat, loadpat = 0;
  char mname[29];
  unsigned short flags;
  unsigned char *rawData, *sampleInf;
  unsigned int currentSamp;
  struct listNode loadList, *currentList, *tmpList;
  struct listNode sampleList;
  unsigned short loopFlags = 0;
  int filePosition;
  unsigned char channelMap[32];
  int maxLoaded = 0;
  unsigned short version;
  int reloadSamples = 0;
  int cutFactor = 1;

  loadList.next = loadList.prev = &loadList;
  sampleList.next = sampleList.prev = &sampleList;

  memcpy (header, buffer, HDR_SIZE);

  if (fread (header + HDR_SIZE, 1, sizeof (header) - HDR_SIZE, *modFd) !=
      sizeof (header) - HDR_SIZE)
    {
      /* Short header */
      return 0;
    }

  strncpy (mname, (char *)header, 28);
  mname[28] = '\0';
  removeNoprint (mname);

  strcpy (songChar->name, mname);

  nrSamples = INTEL_SHORT (&header[0x22]);
  flags = INTEL_SHORT (&header[0x26]);
  songChar->nrSamples = nrSamples;
  songChar->nrChannels = 0;

  for (i = 0; i < 32; i++)
    channelMap[i] = 255;

  for (i = 0x40; i < (0x40 + 32); i++)
    if (header[i] <= 15)
      {
	channelMap[i - 0x40] = songChar->nrChannels;
	if (header[i] <= 7)
	  songChar->panning[songChar->nrChannels] = 0;
	else
	  songChar->panning[songChar->nrChannels] = 255;
	songChar->nrChannels++;
      }

  if (flags & 16)
    {				/* amiga limits */
      songChar->lowestNote = 48;
      songChar->highestNote = 83;
    }
  else
    {
      songChar->lowestNote = 12;
      songChar->highestNote = 107;
    }

  version = INTEL_SHORT (&header[0x28]);
  if (((version >> 12) & 0x0f) == 0x01)
    sprintf (songChar->desc, "ScreamTracker v%x.%02x",
	     (version >> 8) & 0x0f, version & 0xff);
  else
    sprintf (songChar->desc, "S3M-Tracker v%x.%02x",
	     (version >> 8) & 0x0f, version & 0xff);

  if ((version == 0x1300) || (flags & 64))
    songChar->volOnZero = MY_TRUE;
  else
    songChar->volOnZero = MY_FALSE;

  songChar->globalVol = header[0x30];
  if (songChar->globalVol > 0)
    songChar->globalVol = (songChar->globalVol * 4) - 1;
  else
    songChar->globalVol = 255;
  songChar->playSpeed = header[0x31];
  if ((songChar->tempo = header[0x32]) <= 0x20)
    songChar->tempo = 125;
  songChar->volType = VOL_LINEAR;
  songChar->slideType = SLIDE_PERIOD_LIN;
  songChar->clockSpeed = 60;

  slen = INTEL_SHORT (&header[0x20]);
  npat = INTEL_SHORT (&header[0x24]);

  songChar->nrTracks = npat * songChar->nrChannels;
  songChar->songlength = slen;

  rawData = (unsigned char *) malloc (slen);
  fread (rawData, slen, 1, *modFd);

  for (i = 0; i < slen; i++)
    {
      tune[i] = rawData[i];

      if (tune[i] == 255)
	tune[i] = ORDER_STOP;
      else if (tune[i] == 254)
	tune[i] = ORDER_SKIP;
      else if (tune[i] > loadpat)
	loadpat = tune[i];
    }

  loadpat++;
  songChar->nrPatterns = loadpat;

  free (rawData);

  if (INTEL_SHORT (&header[0x2a]) == 2)
    loopFlags = WAVE_UNSIGNED;

  sampleInf = (unsigned char *) malloc (nrSamples * 0x50);

  rawData = (unsigned char *) malloc (nrSamples * 2);
  fread (rawData, nrSamples, 2, *modFd);

  for (i = 0; i < nrSamples; i++)
    if ((currentSamp = INTEL_SHORT (&rawData[i * 2]) * 16) > 0)
      insertInList (&loadList, 'I', i, currentSamp);

  free (rawData);

  rawData = (unsigned char *) malloc (npat * 2);
  fread (rawData, npat, 2, *modFd);

  for (i = 0; i < loadpat; i++)
    if ((currentSamp = INTEL_SHORT (&rawData[i * 2]) * 16) > 0)
      insertInList (&loadList, 'P', i, currentSamp);
    else
      insertInList(&loadList, 'E', i, 0xffff * 16);
	
  free (rawData);

  filePosition = 0x60 + slen + (nrSamples * 2) + (npat * 2);

  if (header[0x35] == 252)
    {
      rawData = (unsigned char *) malloc (32);
      fread (rawData, 32, 1, *modFd);
      filePosition += 32;

      for (i = 0; i < 32; i++)
	if ((channelMap[i] != 255) && (rawData[i] & 32))
	  songChar->panning[channelMap[i]] = (rawData[i] & 0x0f) * 17;
    }

  for (i = 0; i < nrSamples; i++)
    samples[i] = new S3M_sample;

  do
    {
      currentList = loadList.next;

      while (currentList != &loadList)
	{
	  if (currentList->location < filePosition)
	    currentList = currentList->next;
	  else
	    {
	      if ((currentList->type == 'P') || (currentList->type == 'E'))
		{
		  if (currentList->type == 'P')
		    {
		      catchup (*modFd, currentList->location, &filePosition);
		      loadS3mPattern (*modFd, currentList->number,
					songChar, channelMap, &filePosition);
		    }
		  else
		    loadS3mPattern(0, currentList->number, songChar,
				     channelMap, &filePosition);

		  if (options.compress)
		    {
		      if (currentList->number > maxLoaded)
			maxLoaded = currentList->number;
		      for (i = 0; i < songChar->nrChannels; i++)
			voiceTable[currentList->number][i] =
			  compressVoice ((maxLoaded + 1) * songChar->nrChannels,
					  voiceTable[currentList->number][i],
					  64, songChar->nrChannels - i);
		    }
		}
	      else if (currentList->type == 'I')
		{
		  catchup (*modFd, currentList->location, &filePosition);
		  currentSamp =
		    loadS3mIns (*modFd, sampleInf +
				  (currentList->number * 0x50), &filePosition);
		  if (currentSamp != 0)
		    insertInList (&loadList, 'S', currentList->number,
				    currentSamp);
		}
	      else
		{
		  insertInList(&sampleList, 'S', currentList->number,
				 currentList->location);
		  catchup (*modFd, currentList->location, &filePosition);
		  i = samples[currentList->number]->
		    load(*seq, *modFd, currentList->number, cutFactor,
			 &loopFlags, sampleInf +
			 (currentList->number * 0x50));
		  
		  if (i < 0)
		    {
		      filePosition -= i;
		      reloadSamples = 1;
		    }
		  else
		    filePosition += i;
		}

	      (currentList->prev)->next = currentList->next;
	      (currentList->next)->prev = currentList->prev;

	      tmpList = currentList->next;
	      free (currentList);
	      currentList = tmpList;
	    }
	}

      if ((reloadSamples) && (cutFactor < MAX_CUT))
	{
	  seq->resetSamples();
	  reloadSamples = 0;
	  cutFactor++;
	  currentList = sampleList.next;

	  while (currentList != &sampleList)
	    {
	      insertInList(&loadList, 'S', currentList->number,
			     currentList->location);
	      tmpList = currentList->next;
	      free(currentList);
	      currentList = tmpList;
	    }

	  sampleList.prev = sampleList.next = &sampleList;
	}

      if (loadList.next != &loadList)
	{
	  if (command == NULL)	/* not compressed */
	    fseek (*modFd, 0, SEEK_SET);
	  else
	    {
	      pclose (*modFd);
	      *modFd = popen (command, "rb");
	    }
	  filePosition = 0;
	}
    }
  while (loadList.next != &loadList);

  currentList = sampleList.next;
  
  while (currentList != &sampleList)
    {
      tmpList = currentList->next;
      free(currentList);
      currentList = tmpList;
    }

  free (sampleInf);

  return 1;
}
