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

#include <sys/types.h>

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

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/fcntl.h>

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

#include "ult.h"

#define HEADER_SIZE	48
#define SAMPLE_SIZE	64
#define TRACK_SIZE	192
#define PATTERN_SIZE	64
#define NOTES_PER_TRACK	64
#define NR_CHANNELS	32

#define REPEAT_NOTE	0xfc	/* note signifying a RLE pattern */

typedef struct
{
  char title[33];
  int version;
  int textLen;
  char *text;
  int nrSamples;
  int nrChannels;
  int nrPatterns;
  int order[256];
  int panPositions[32];
  int sampleOffset;
  int patternOffset;
  /*  int eohOffset; */
}

ultHeader;

typedef struct
{
  int a;
}

ultPattern;

void convertUltEffect (u_long *, u_long *);

void
dumpUltHeader (ultHeader * h, struct songInfo *songChar)
{
  sprintf (songChar->desc, "UltraTracker v%d", h->version);
  strcpy (songChar->name, h->title);

  if (h->version >= 2 && h->textLen)
    {
      songChar->comment = (char *) realloc (songChar->comment, h->textLen + 1);
      bzero (songChar->comment, h->textLen + 1);
      strncpy (songChar->comment, h->text, h->textLen);
      songChar->commentLineLen = 32;
    }
}

ultHeader
getUltHeader (FILE * modFd, unsigned char *buffer, u_char **sampleData)
{
  int i;
  u_char *rawData;
  u_char tmpchar;
  int totalSampleSize;
  ultHeader header;
  u_long sampleSize = SAMPLE_SIZE;

  /* read in first part of header */
  rawData = (unsigned char *) malloc (HEADER_SIZE);
  memcpy (rawData, buffer, HDR_SIZE);	/* HEADER_SIZE == HDR_SIZE ! */
  /* fread (rawData, 1, HEADER_SIZE, modFd); */

  memcpy (header.title, rawData + 15, 32);	/* copy song title */
  header.title[32] = 0;
  rawData[15] = 0;		/* terminate "V00x" in magic number.
				 * WARNING: may overwrite title, so copy
				 * title first */

  /* get format version:
   *  2 has header+47 defined as a textLen byte,
   *  3 has pan-position table after NOP byte
   */
  header.version = atoi ((char *)(rawData + 12));

  if (header.version >= 2)
    header.textLen = rawData[47] * 32;
  else
    header.textLen = 0;
  free (rawData);

  if (header.textLen)
    {
      header.text = (char *)malloc (header.textLen + 1);
      fread (header.text, 1, header.textLen, modFd);
      header.text[header.textLen] = 0;
    }
  else
    header.text = NULL;

  /* get samples */
  fread (&tmpchar, 1, 1, modFd);
  header.nrSamples = tmpchar;

  header.sampleOffset = HEADER_SIZE + header.textLen + 1;

  /* ======== get sample data =============================== */

  /* version 4 files have C2 frequency */
  if (header.version >= 4)
    sampleSize += 2;

  totalSampleSize = sampleSize * header.nrSamples;
  *sampleData = (u_char *)malloc(totalSampleSize);
  fread (*sampleData, 1, totalSampleSize, modFd);

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

  for (i = 0; i < 256; i++)
    header.order[i] = (unsigned) rawData[i];

  /* these are stored as "last channel" and "last pattern",
   * so we add one to make it ordinal
   */
  header.nrChannels = rawData[i++] + 1;
  header.nrPatterns = rawData[i] + 1;

  header.patternOffset = header.sampleOffset +
    header.nrSamples * sampleSize + 258;

  if (header.version >= 3)
    {
      fread (rawData, 1, header.nrChannels, modFd);
      for (i = 0; i < header.nrChannels; i++)
	header.panPositions[i] = rawData[i];

      header.patternOffset += header.nrChannels;
    }
  else
    {
      for (i = 0; i < header.nrChannels; i++)
	header.panPositions[i] = 7;
    }

  free (rawData);

  return (header);
}


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

  ultHeader header;
  int i;
  u_char *sampleData;
  int chani, notei, pati, absi;
  int voice;
  int sampleSize;
#ifdef DEBUG
  int tn = 0;
#endif

  songChar->lowestNote = 36;
  songChar->highestNote = 95;
  songChar->volOnZero = MY_FALSE;
  songChar->slideType = SLIDE_PERIOD_LIN;
  songChar->clockSpeed = 60;

  header = getUltHeader(modFd, buffer, &sampleData);

  songChar->nrSamples = header.nrSamples;
  songChar->nrPatterns = header.nrPatterns;

  /* ========== copy pattern order into tune[] ============== */
#ifdef SHOW_ORDER
  for (i = 0; i < 256 && header.order[i] != 255; i++)
    printf ("%03d%c", header.order[i], (i + 1) % 20 ? ' ' : '\n');
  putchar ('\n');
#endif

  /* copy pattern orders into the tune area and find song length
   * NB: not in the docs, but apparently empty patterns are set
   * to 255; I'm assuming the first 255 ends the song.
   */
  songChar->songlength = 0;
  for (i = 0; i < 256 && header.order[i] != 255; i++)
    {
      tune[i] = header.order[i];
      songChar->songlength++;
    }

  songChar->nrTracks = header.nrPatterns * header.nrChannels;
  songChar->nrChannels = header.nrChannels;
  songChar->playSpeed = 6;
  songChar->tempo = 125;

  /* set panning */

  for (i = 0; i < header.nrChannels; i++)
    songChar->panning[i] = (header.panPositions[i] & 0x0f) * 17;

  dumpUltHeader (&header, songChar);

  /* get patterns from file */
  for (chani = 0; chani < header.nrChannels; chani++)
    {
      /* allocate memory for track */

      for (pati = 0; pati < header.nrPatterns; pati++)
	patternTable[header.nrPatterns * chani + pati] = (struct noteInfo *)
	  malloc (sizeof (struct noteInfo) * NOTES_PER_TRACK);

      for (absi = 0; absi < (NOTES_PER_TRACK * header.nrPatterns);)
	{
	  unsigned char *p;
	  u_char noteBytes[7];
	  int repeat;

	  u_long param[2], note, effect[2], sample, param2;

	  fread (noteBytes, 1, 5, modFd);
	  if (noteBytes[0] == REPEAT_NOTE)
	    {
	      fread (noteBytes + 5, 1, 2, modFd);
	      repeat = noteBytes[1];

	      if (repeat == 0)
		{
		  repeat = 1;
		}
	      p = noteBytes + 2;
	    }
	  else
	    {
	      repeat = 1;
	      p = noteBytes;
	    }

	  note = p[0];
	  sample = p[1];
	  effect[0] = p[2] >> 4;/* are these switched? */
	  effect[1] = p[2] & 0xf;
	  param[0] = INTEL_SHORT (p + 3) >> 8;
	  param[1] = INTEL_SHORT (p + 3) & 0xff;

	  convertUltEffect (&effect[0], &param[0]);
	  convertUltEffect (&effect[1], &param[1]);

	  /* special case for setoffset fine */

	  if ((effect[0] == CMD_SETOFFSET_1024) && (effect[1] == CMD_SETOFFSET_1024))
	    {
	      effect[0] = CMD_SETOFFSET_FINE;
	      effect[1] = 0;
	      param2 = param[1];
	      param[1] = 0;
	    }
	  else
	    param2 = 0;


	  if (note)		/* note->period */
	    {
	      note = note + 3 * 12 - 1;	/* shift up 3 octaves */
	    }

	  for (i = 0; i < repeat && absi < (NOTES_PER_TRACK * header.nrPatterns); i++)
	    {
	      pati = absi / NOTES_PER_TRACK;
	      notei = absi % NOTES_PER_TRACK;

	      voice = header.nrPatterns * chani + pati;
	      voiceTable[pati][chani] = voice;

	      (patternTable[voice])[notei].note = note;
	      (patternTable[voice])[notei].sample = sample;
	      (patternTable[voice])[notei].command[0] = effect[0];
	      (patternTable[voice])[notei].parm1[0] = param[0];
	      (patternTable[voice])[notei].parm2[0] = param2;
	      (patternTable[voice])[notei].command[1] = effect[1];
	      (patternTable[voice])[notei].parm1[1] = param[1];
	      (patternTable[voice])[notei].parm2[1] = 0;
	      absi++;
#ifdef DEBUG
	      tn++;
#endif
	    }
#ifdef DEBUG
	  if (i != repeat)
	    fprintf (stderr, "bail: i:%d repeat:%d\n", i, repeat);
#endif
	}

      if (options.compress)
	for (pati = 0; pati < header.nrPatterns; pati++)
	  voiceTable[pati][chani] =
	    compressVoice (voiceTable[pati][chani],
			    voiceTable[pati][chani],
			    NOTES_PER_TRACK, 1);
    }

  sampleSize = SAMPLE_SIZE;

  if (header.version >= 4)
    sampleSize += 2;

  for (i = 0; i < header.nrSamples; i++)
    {
      samples[i] = new ULT_sample;
      samples[i]->load(*seq, modFd, i, 1, sampleData + i * sampleSize,
		       &header.version);
    }

  /* all done */
  free (sampleData);

  if (header.textLen)
    free (header.text);		/* song text of size header.textLen */

  if (header.version == 1)
    songChar->volType = VOL_LOG;
  else
    songChar->volType = VOL_LINEAR;

  return (1);
}


void
convertUltEffect (u_long * effect, u_long * param)
{
  switch (*effect)
    {
    case 0:			/* arpeggio */
    case 1:			/* portamento up */
    case 2:			/* portamento down */
    case 3:			/* tone portamento */
    case 4:			/* vibrato */
    case 7:			/* tremolo */
    case 0xa:			/* volslide */
    case 0xc:			/* volume */
      break;
    case 9:			/* sample offset */
      *effect = CMD_SETOFFSET_1024;
      break;
    case 0xd:			/* pattern break */
      *param = ((*param >> 4) & 0x0f) * 10 + (*param & 0x0f);
      break;
    case 0xe:
      *effect = (0xe0) + ((*param >> 4) & 0x0f);
      *param &= 0x0f;
      break;
    case 0xf:			/* set speed */
      *effect = CVT_MOD_SPEED (*param);
      break;
    case 5:			/* special sample commands */
      *effect = 0;
      *param = 0;
      break;
    case 0xb:			/* set balance */
      *effect = CMD_SET_PAN;
      *param = (*param & 0x0f) * 17;
      break;
    default:
      *effect = 0;
      *param = 0;
    }
}
