/*
 * Copyright (c) 2002, 2003, 2004, 2005, 2007 by Christian Nowak <chnowak@web.de>
 * Last update: 20th October, 2007
 */

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

#include "modplay.h"
#include "s3m.h"
#include "effects.h"


#define DESCRIPTION "Future Crew Screamtracker 3"
#define AUTHOR      "Christian Nowak <chnowak@web.de>"
#define VERSION     "v0.01b"
#define COPYRIGHT   "Copyright (c) 2003, 2007"


#define SAFE_MALLOC(dest, a) \
  dest = malloc(a); \
  if (dest == NULL) { \
    MODFILE_Free(s3m); \
    return -2; \
  }


/**
 * int MODFILE_SetS3M(u8 *modfile, int modlength, MODFILE *mod);
 *
 * Processes the raw data of an S3M file and copies it to a
 * structure. The structure can then be used as a handle
 * of the S3M file. The original raw data isn't needed by the
 * handle.
 *
 * Returns a value <0 on error.
 *
 * Parameters:
 * modfile   - A pointer to the raw S3M data
 * modlength - The length of the raw data in bytes
 * mod       - A pointer to the S3M handle
 **/
int MODFILE_SetS3M(u8 *s3mfile, int s3mlength, MODFILE *s3m) {

  int ofs = 0;
  int i;
  BOOL panning_present = FALSE;
  int remapChannels[MODPLAY_MAX_CHANNELS];

  if (!MODFILE_IsS3M(s3mfile, s3mlength) || (s3m == NULL))
    return -1;

  /* 0 */
  memcpy(s3m->songname, &s3mfile[ofs], 28);
  ofs+= 28;
  /* 1c */
  ofs++;
  /* 1d */
  if (s3mfile[ofs++] != 0x10) {

    return -1;
  }

  ofs += 2;

  /* 20 */
  s3m->songlength  = s3mfile[ofs++];
  s3m->songlength |= s3mfile[ofs++] << 8;

  /* 22 */
  s3m->nInstruments  = s3mfile[ofs++];
  s3m->nInstruments |= s3mfile[ofs++] << 8;
  s3m->nSamples = s3m->nInstruments; /* S3M doesn't have multisamples */

  /* 24 */
  s3m->nPatterns  = s3mfile[ofs++];
  s3m->nPatterns |= s3mfile[ofs++] << 8;

  /* 26 */
  s3m->st2_vibrato      = (s3mfile[ofs] & 0x02) != 0;
  s3m->st2_tempo        = (s3mfile[ofs] & 0x04) != 0;
  s3m->amiga_sliding    = (s3mfile[ofs] & 0x08) != 0;
  s3m->optimize_vols    = (s3mfile[ofs] & 0x10) != 0;
  s3m->amiga_boundaries = (s3mfile[ofs] & 0x20) != 0;
  s3m->enable_sfx       = (s3mfile[ofs] & 0x40) != 0; /* Fast volume slides */
  ofs += 2;

  /* 28 */
  s3m->tracker_version = (s3mfile[ofs] << 8) | s3mfile[ofs + 1];
  ofs += 2;

  /* 2a */
  s3m->unsigned_samples = (s3mfile[ofs] == 2);
  ofs += 2;

  /* 2c */
  if (memcmp(&s3mfile[ofs], "SCRM", 4) != 0) {

    return -1;
  }
  ofs += 4;

  /* 30 */
  s3m->master_volume = s3mfile[ofs++];
  /* 31 */
  s3m->start_speed = s3mfile[ofs++];
  /* 32 */
  s3m->start_tempo = s3mfile[ofs++];
  /* 33 */
  ofs++;
  /* 34 */
  ofs++;
  /* 35 */
  panning_present = (s3mfile[ofs++] == 252);
  /* 36 */
  ofs = 0x40;

  /* 40 */
  for (i = 0; i < MODPLAY_MAX_CHANNELS; i++) {

    s3m->channels[i].voiceInfo.enabled = FALSE;
    remapChannels[i] = 255;
  }

  for (i = s3m->nChannels = 0; i < 32; i++, ofs++) {

    if (s3mfile[ofs] < 16) {

      remapChannels[i] = s3m->nChannels;
      s3m->nChannels++;
      s3m->channels[remapChannels[i]].voiceInfo.enabled = TRUE;
      s3m->channels[remapChannels[i]].default_panning = s3mfile[ofs] < 8 ? 86 : 167;
    }
  }
  s3m->nChannels++; /* Global FX channel */

  /* 60 */
  memcpy(s3m->playlist, &s3mfile[ofs], s3m->songlength);
  ofs += s3m->songlength;

  /* Parapointers to intruments */
  /*  s3m->instruments = malloc(s3m->nInstruments * sizeof(MOD_Instrument));*/
  SAFE_MALLOC(s3m->instruments, s3m->nInstruments * sizeof(MOD_Instrument));
  memset(s3m->instruments, 0, s3m->nInstruments * sizeof(MOD_Instrument));
  /*  s3m->samples = malloc(s3m->nSamples * sizeof(MOD_Sample));*/
  SAFE_MALLOC(s3m->samples, s3m->nSamples * sizeof(MOD_Sample));
  memset(s3m->samples, 0, s3m->nSamples * sizeof(MOD_Sample));

  for (i = 0; i < s3m->nInstruments; i++, ofs += 2) {

    int instrPointer = (s3mfile[ofs] | s3mfile[ofs+1] << 8) << 4;

    s3m->instruments[i].volumeFade = 32767;
    /* 0 */
    /*    s3m->instruments[i].adlib = s3mfile[instrPointer++] != 1;*/
    if (/*!s3m->instruments[i].adlib*/s3mfile[instrPointer++] == 1) {

      int sampleseg;
      int j;
      int temp;

      s3m->samples[i].relative_note = 0;
      s3m->samples[i].panning = 255;

      /* 1 */
      /*      memcpy(s3m->instruments[i].dosname, &s3mfile[instrPointer], 12);
	      s3m->instruments[i].dosname[12] = '\0';*/
      instrPointer += 12;

      /* d */
      sampleseg = s3mfile[instrPointer + 1] |
	(s3mfile[instrPointer + 2] << 8) |
	(s3mfile[instrPointer + 0] << 16);
      sampleseg <<= 4;
      instrPointer += 3;

      /* 10 */
      s3m->samples[i].sampleInfo.length = s3mfile[instrPointer] |
	(s3mfile[instrPointer + 1] << 8) |
	(s3mfile[instrPointer + 2] << 16) |
	(s3mfile[instrPointer + 2] << 24);
      instrPointer += 4;

      /* 14 */
      s3m->samples[i].sampleInfo.loop_start = s3mfile[instrPointer] |
	(s3mfile[instrPointer + 1] << 8) |
	(s3mfile[instrPointer + 2] << 16) |
	(s3mfile[instrPointer + 3] << 24);
      instrPointer += 4;

      /* 18 */
      s3m->samples[i].sampleInfo.loop_end = s3mfile[instrPointer] |
	(s3mfile[instrPointer + 1] << 8) |
	(s3mfile[instrPointer + 2] << 16) |
	(s3mfile[instrPointer + 3] << 24);
      instrPointer += 4;

      /* 1c */
      s3m->samples[i].default_volume = s3mfile[instrPointer++];

      /* 1d */
      instrPointer++;

      /* 1e */
      /*      s3m->instruments[i].packed = (s3mfile[instrPointer++] == 1);*/
      instrPointer++;

      /* 1f */
      s3m->samples[i].sampleInfo.looped = (s3mfile[instrPointer] & 1) != 0;
      s3m->samples[i].sampleInfo.stereo = (s3mfile[instrPointer] & 2) != 0;
      s3m->samples[i].sampleInfo.bit_16 = (s3mfile[instrPointer] & 4) != 0;
      s3m->samples[i].sampleInfo.pingpong = FALSE;
      instrPointer++;

      if (!s3m->samples[i].sampleInfo.looped) {

	s3m->samples[i].sampleInfo.loop_start =
	  s3m->samples[i].sampleInfo.loop_end =
	  s3m->samples[i].sampleInfo.length - 1;
      }

      /* 20 */
      s3m->samples[i].default_middle_c = s3m->samples[i].middle_c =
	s3mfile[instrPointer] |
	(s3mfile[instrPointer + 1] << 8) |
	(s3mfile[instrPointer + 2] << 16) |
	(s3mfile[instrPointer + 3] << 24);
      instrPointer += 4;

      /* 24 */
      instrPointer += 4;

      /* 28 */
      instrPointer += 2;

      /* 2a */
      instrPointer += 2;

      /* 2c */
      instrPointer += 4;

      /* 30 */
      memcpy(s3m->instruments[i].name, &s3mfile[instrPointer], 28);
      instrPointer += 28;

      /* 4c */
      if (memcmp(&s3mfile[instrPointer], "SCRS", 4) != 0) {

	return -1;
      }

      /* Define a new instrument */
      strcpy(s3m->instruments[i].name, s3m->samples[i].name);
      for (j = 0; j < 256; j++) {

	s3m->instruments[i].samples[j] = &s3m->samples[i];
	s3m->instruments[i].note[j] = j;
      }

      /* Disable instrument envelopes */
      s3m->instruments[i].envVolume.enabled = FALSE;
      s3m->instruments[i].envPanning.enabled = FALSE;

      /* Copy sample data */
      temp = s3m->samples[i].sampleInfo.length *
	(s3m->samples[i].sampleInfo.bit_16 || s3m->samples[i].sampleInfo.stereo ? 2 : 1);

      /*      s3m->samples[i].sampleInfo.sampledata = malloc(temp);*/
      SAFE_MALLOC(s3m->samples[i].sampleInfo.sampledata, temp);
      memset(s3m->samples[i].sampleInfo.sampledata, 0, temp);

      if (s3m->samples[i].sampleInfo.bit_16) {

	u8 *di = &s3mfile[sampleseg];
	int l = s3m->samples[i].sampleInfo.length;

	for (j = 0; j < l; j++) {

	  u16 d;

	  d = di[j<<1] | di[(j << 1) + 1] << 8;
	  if (s3m->unsigned_samples)
	    d ^= 0x8000;
	  ((u16*)s3m->samples[i].sampleInfo.sampledata)[j] = d;
	}
      } else {

	u8 *di = &s3mfile[sampleseg];
	int l = s3m->samples[i].sampleInfo.length;

	for (j = 0;j < l; j++) {

	  u8 d;

	  d = di[j];
	  if (s3m->unsigned_samples)
	    d ^= 0x80;
	  ((u8*)s3m->samples[i].sampleInfo.sampledata)[j] = d;
	}
      }
    }
  }

  /* Parapointers to patterns */
  /*  s3m->patterns = malloc(s3m->nPatterns * sizeof(MOD_Note*));*/
  SAFE_MALLOC(s3m->patterns, s3m->nPatterns * sizeof(MOD_Note*));
  memset(s3m->patterns, 0, s3m->nPatterns * sizeof(MOD_Note*));
  /*  s3m->patternLengths = malloc(s3m->nPatterns * sizeof(int));*/
  SAFE_MALLOC(s3m->patternLengths, s3m->nPatterns * sizeof(int));

  for (i = 0; i < s3m->nPatterns; i++, ofs += 2) {

    int pattPointer = (s3mfile[ofs] | s3mfile[ofs + 1] << 8) << 4;
    int pattPackedLen;
    int destChannel;
    int destLine;

    s3m->patternLengths[i] = 64;
    /*    s3m->patterns[i] = malloc(sizeof(MOD_Note) * s3m->nChannels * 64);*/
    SAFE_MALLOC(s3m->patterns[i], sizeof(MOD_Note) * s3m->nChannels * 64);
    memset(s3m->patterns[i], 255, sizeof(MOD_Note) * s3m->nChannels * 64);

    pattPackedLen = s3mfile[pattPointer] |
      (s3mfile[pattPointer + 1] << 8);
    pattPointer += 2;

    destChannel = destLine = 0;

    while (1) {

      int dest;
      u8 compByte = s3mfile[pattPointer++];

      if (compByte == 0) {

				destLine++;
				if (destLine >= 64) {

	  			break;
				}
      } else {

				destChannel = remapChannels[compByte & 31];
				dest = (destLine * s3m->nChannels) + destChannel ;
				if (destChannel != 255) {

	  			/* Note/Instrument */
	  			if (compByte & 32) {

	    			s3m->patterns[i][dest].note = s3mfile[pattPointer++];
	    			s3m->patterns[i][dest].instrument = s3mfile[pattPointer++];
	  			}

	  			/* Volume byte */
	  			if (compByte & 64) {

	    			s3m->patterns[i][dest].volume = s3mfile[pattPointer++];
	  			}

	  			/* Effect number/parameter */
	  			if (compByte & 128) {

		  			u16 effect = s3mfile[pattPointer++];
		  			u8 operand = s3mfile[pattPointer++];

		  			/* Convert effect/operand to internal format */
		  			switch (effect) {
			  			
			  			case 'A' - 'A' + 1:	/* Set Speed */
			  				effect = EFFECT_S3Ma0;
			  				break;

			  			case 'B' - 'A' + 1: /* Jump To Order xy */
								s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].effect[0] = EFFECT_STb0;
								s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].operand[0] = operand;
	  						effect = EFFECT_NONE;
	  						operand = 0;
			  				break;
			  				
			  			case 'C' - 'A' + 1: /* Break Pattern */
								s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].effect[0] = EFFECT_STd0;
								s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].operand[0] = operand;
	  						effect = EFFECT_NONE;
	  						operand = 0;
			  				break;
			  				
			  			case 'D' - 'A' + 1: /* Volume Slide */
			  				effect = EFFECT_S3Md0;
			  				break;

			  			case 'E' - 'A' + 1: /* Portamento Down */
			  				effect = EFFECT_S3Me0;
			  				break;

			  			case 'F' - 'A' + 1: /* Portamento Up */
			  				effect = EFFECT_S3Mf0;
			  				break;
			  			
			  			case 'G' - 'A' + 1: /* Tone Portamento */
			  				effect = EFFECT_ST30;
			  				break;
			  			
			  			case 'H' - 'A' + 1: /* Vibrato */
			  				effect = EFFECT_ST40;
			  				break;
			  			
			  			case 'I' - 'A' + 1: /* Tremor */
			  				effect = EFFECT_NONE;
			  				break;
			  			
			  			case 'J' - 'A' + 1: /* Arpeggio */
			  				effect = EFFECT_ST00;
			  				break;
			  			
			  			case 'K' - 'A' + 1: /* Vibrato + Volume Slide */
			  				effect = EFFECT_S3Mk0;
			  				break;
			  				
			  			case 'L' - 'A' + 1: /* Tone Portamento + Volume Slide */
			  				effect = EFFECT_S3Ml0;
			  				break;
			  				
			  			case 'O' - 'A' + 1: /* Sample Offset */
			  				effect = EFFECT_ST90;
			  				break;
			  			
			  			case 'Q' - 'A' + 1: /* Retrig + Volume Slide */
			  				effect = EFFECT_S3Mq0;
			  				break;
			  			
			  			case 'R' - 'A' + 1: /* Tremolo */
			  				effect = EFFECT_S3Mr0;
			  				break;
			  			
			  			case 'S' - 'A' + 1: /* Various */
			  			
			  				switch ((operand >> 4) & 0x0f) {
				  				
				  				case 0x02: /* Set Finetune */
				  					effect = EFFECT_STe5;
				  					operand = operand & 0x0f;
				  					break;
				  				
				  				case 0x03: /* Vibrato Waveform */
				  					effect = EFFECT_STe4;
				  					operand = operand & 0x0f;
				  					break;
				  				
				  				case 0x04: /* Tremolo Waveform */
				  					effect = EFFECT_STe7;
				  					operand = operand & 0x0f;
				  					break;
				  				
				  				case 0x08: /* Panning */
				  					effect = EFFECT_STe8;
				  					operand = operand & 0x0f;
				  					break;
				  				
				  				case 0x0b: /* Pattern Loop */
										operand = operand & 0x0f;
										s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].effect[0] = EFFECT_STe6;
										s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].operand[0] = operand;
	  								effect = EFFECT_NONE;
	  								operand = 0;
				  					break;
				  				
				  				case 0x0c: /* Note Cut */
										effect = EFFECT_STec;
										operand = operand & 0x0f;
				  					break;
				  				
				  				case 0x0d: /* Note Delay */
				  					effect = EFFECT_STed;
				  					operand = operand & 0x0f;
				  					break;

				  				case 0x0e: /* Pattern Delay */
										operand = operand & 0x0f;
										s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].effect[0] = EFFECT_STee;
										s3m->patterns[i][(destLine * s3m->nChannels) + s3m->nChannels - 1].operand[0] = operand;
	  								effect = EFFECT_NONE;
	  								operand = 0;
				  					break;

				  				default:
				  					effect = EFFECT_NONE;
				  					break;
			  				}
			  				break;
			  				
			  			case 'T' - 'A' + 1: /* Set Tempo */
			  				effect = EFFECT_S3Mt0;
			  				break;
			  			
			  			case 'V' - 'A' + 1: /* Set Global Volume */
			  				effect = EFFECT_XM10;
			  				break;

			  			default:
			  				effect = EFFECT_NONE;
			  				break;
		  			}
		  			
	    			s3m->patterns[i][dest].effect[0] = effect;
	    			s3m->patterns[i][dest].operand[0] = operand;
	  			}
				} else {

	  			if (compByte & 32)
	    			pattPointer += 2;
	  			if (compByte & 64)
	    			pattPointer++;
	  			if (compByte & 128)
	    			pattPointer += 2;
				}
      }
    }
  }

  s3m->filetype = MODULE_S3M;
  return 0;
}




/**
 * BOOL MODFILE_IsS3M(u8 *modfile, int modlength);
 *
 * Checks whether the raw data in memory is a valid
 * S3M file.
 *
 * Returns TRUE if the data is an S3M file, FALSE
 * if not.
 *
 * Parameters:
 *
 * modfile   - Pointer to the raw data to be checked
 * modlength - Length of the raw data in bytes
 **/
BOOL MODFILE_IsS3M(u8 *modfile, int modlength) {

  if ((modfile == NULL) || (modlength < 0x30))
    return FALSE;

  return memcmp(&modfile[0x2c], "SCRM", 4) == 0;
}




int MODFILE_S3MGetFormatID(void) {

  return MODULE_S3M;
}

char *MODFILE_S3MGetDescription(void) {

  return DESCRIPTION;
}

char *MODFILE_S3MGetAuthor(void) {

  return AUTHOR;
}

char *MODFILE_S3MGetVersion(void) {

  return VERSION;
}

char *MODFILE_S3MGetCopyright(void) {

  return COPYRIGHT;
}