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

#include <sys/ioctl.h>
#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>

#ifdef USE_X
#include "TopShell.h"
#include "CommentShell.h"
#else
#include "CursesScr.h"
#endif

#include "SampleShell.h"

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

#include "Sequencer.h"
#include "mod.h"

/* prototype */

char *fixName(char *theString);
int catchup(FILE *, int, int *);

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

  int i;
  int gusOnboardMem;
  int samplePtr, patternLoc;
  int position;
  int voice;
  unsigned char *tunePtr;	/* array 0-127 */
  unsigned char header[1084];
  int nrSamples;		/* 16 or 32 samples */
  int slen, npat;
  int patSize, patTmpSize, offset;
  char mname[21];
  int bend;
  unsigned char isFlt8 = MY_FALSE;
  int cutFactor = 1;

  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, 20);
  mname[20] = '\0';
  removeNoprint(mname);
  strcpy(songChar->name, mname);

  if (!memcmp(&header[1080], "M.K.", 4) ||
      !memcmp(&header[1080], "M!K!", 4) ||
      !memcmp(&header[1080], "M&K!", 4))
    {
      nrSamples = 31;
      songChar->nrChannels = 4;
    }
  else if (!memcmp(&header[1080], "FLT", 3))
    {
      nrSamples = 31;
      songChar->nrChannels = 4;
      if (header[1083] == '8')
	isFlt8 = MY_TRUE;
    }
  else if (!memcmp(&header[1081], "CHN", 3))
    {
      nrSamples = 31;
      songChar->nrChannels = header[1080] - 48;
    }
  else if (!memcmp(&header[1080], "OCTA", 4))
    {
      nrSamples = 31;
      songChar->nrChannels = 8;
    }
  else if (!memcmp(&header[1082], "CH", 2))
    {
      nrSamples = 31;
      songChar->nrChannels = strtol(&header[1080], NULL, 10);
    }
  else if (!options.checkMagic)
    {
      nrSamples = 15;
      songChar->nrChannels = 4;
    }
  else
    return 0;

  if (options.extendOct == OCTAVE_EXTEND)
    {
      songChar->lowestNote = NOTE_BASE;
      songChar->highestNote = NOTE_BASE + NUM_PERIODS - 1;
    }
  else
    {
      songChar->lowestNote = 48;
      songChar->highestNote = 83;
    }

  sprintf (songChar->desc, "MOD");
  songChar->nrSamples = nrSamples;
  songChar->playSpeed = 6;
  songChar->tempo = 125;
  songChar->volType = VOL_LINEAR;
  songChar->volOnZero = MY_FALSE;
  songChar->slideType = SLIDE_PERIOD_LIN;

  if (options.use_50hz)
    songChar->clockSpeed = 50;
  else
    songChar->clockSpeed = 60;

  for (i = 0; i < songChar->nrChannels; i++)
    songChar->panning[i] = panning (i);

  patSize = 64 * songChar->nrChannels * 4;

  if (nrSamples == 31)
    {
      samplePtr = patternLoc = 1084;
      slen = header[950];
      tunePtr = &header[952];
    }
  else
    {
      samplePtr = patternLoc = 600;
      slen = header[470];
      tunePtr = &header[472];
    }

  npat = 0;

  for (i = 0; i < 128; i++)
    {
      tune[i] = tunePtr[i];

      if (tunePtr[i] > npat)
	npat = tunePtr[i];
    }

  npat++;

  if (isFlt8 == MY_TRUE)
    npat++;

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

  gusOnboardMem = seq->memory();

  for (position = 0; position < npat; position++)
    {
      unsigned char patterns[64][songChar->nrChannels][4];
      int pat, channel;

      int pp = patternLoc + (position * patSize);

      if (pp < sizeof(header))
	{
	  patTmpSize = sizeof (header) - pp;
	  if (patTmpSize > patSize)
	    {
	      memcpy(patterns, &header[pp], patSize);
	      patTmpSize = 0;
	    }
	  else
	    {
	      memcpy(patterns, &header[pp], patTmpSize);
	      offset = patTmpSize;
	      patTmpSize = patSize - patTmpSize;
	    }
	}
      else
	{
	  patTmpSize = patSize;
	  offset = 0;
	}

      if (patTmpSize > 0)
	if (fread ((char *) patterns + offset, 1, patTmpSize, *modFd) != patTmpSize)
	  {
	    /* Short file */
	    return 0;
	  }

      for (voice = 0; voice < songChar->nrChannels; voice++)
	if ((patternTable[position * songChar->nrChannels + voice] = (struct noteInfo *) malloc (sizeof (struct noteInfo) * 64)) == NULL)
	  {
	    /* Can't allocate memory for a pattern */
	    return 0;
	  }

      for (pat = 0; pat < 64; pat++)
	{
	  for (channel = 0; channel < songChar->nrChannels; channel++)
	    {
	      unsigned short tmp;
	      unsigned char *p;

	      unsigned period, sample, effect, params, note;

	      p = &patterns[pat][channel][0];

	      tmp = (p[0] << 8) | p[1];
	      sample = (tmp >> 8) & 0x10;
	      period =
		MIN (tmp & 0xFFF, 1023);
	      tmp = (p[2] << 8) | p[3];
	      sample |= tmp >> 12;
	      effect = (tmp >> 8) & 0xF;
	      params = tmp & 0xFF;

	      if (effect == CMD_EXTENDED)
		{
		  effect = ((CMD_EXTENDED << 4) & 0xf0) +
		    ((params >> 4) & 0x0f);
		  params &= 0x0f;
		}

	      note = 0;

	      if (period)
		{
		  /* Convert period to a Midi note number */

		  periodToNote (period, (int *)&note, &bend);

		  if (((note < songChar->lowestNote) || 
		       (note > songChar->highestNote)) &&
		      (options.extendOct == OCTAVE_AUTO))
		    {
		      songChar->lowestNote = NOTE_BASE;
		      songChar->highestNote = NOTE_BASE + NUM_PERIODS - 1;
		    }
		  
		  if (note < songChar->lowestNote)
		    note = songChar->lowestNote;
		  else if (note > songChar->highestNote)
		    note = songChar->highestNote;
		}

	      switch (effect)
		{
		case CMD_SPEED:
		  if (options.tolerant && (params == 0))
		    effect = 0;
		  else if (options.bpmTempos)
		    effect = CVT_MOD_SPEED (params);
		  else
		    effect = CMD_SET_TICKS;
		  break;
		case CMD_VOLUME:
		  if (params > 0)
		    params = (params * 4) - 1;
		  break;
		case CMD_BREAK:
		  params = ((params >> 4) & 0x0f) * 10 + (params & 0x0f);
		  break;
		case CMD_SET_PAN:
		  params *= 17;
		  break;
		}

	      voice = position * songChar->nrChannels + channel;
	      voiceTable[position][channel] = voice;

	      (patternTable[voice])[pat].note = note;
	      (patternTable[voice])[pat].sample = sample;
	      (patternTable[voice])[pat].command[0] = effect;
	      (patternTable[voice])[pat].parm1[0] = params;
	      (patternTable[voice])[pat].parm2[0] = 0;
	      (patternTable[voice])[pat].command[1] = 0;
	      (patternTable[voice])[pat].parm1[1] = 0;
	      (patternTable[voice])[pat].parm2[1] = 0;
	    }
	}

      if (options.compress)
	for (channel = 0; channel < songChar->nrChannels; channel++)
	  voiceTable[position][channel] =
	    compressVoice (voiceTable[position][channel],
			    voiceTable[position][channel],
			    64, 1);
    }

  if (isFlt8 == MY_TRUE)
    {
      for (position = 0; position < npat; position += 2)
	for (i = 0; i < songChar->nrChannels; i++)
	  voiceTable[position][i + 4] = voiceTable[position + 1][i];

      songChar->nrChannels = 8;
    }

  samplePtr += (npat * patSize);	/* Location where the first sample is stored */
  //printf("Calculated %d, real %d\n", samplePtr, ftell(*modFd));

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

  do {
    i = 0;

    while (i < nrSamples)
      {
	if ((samples[i]->load(*seq, *modFd, i, cutFactor,
			      &header[20 + (i * 30)], &options.ntsc) ==
	     -ENOSPC) && (cutFactor < MAX_CUT))
	  {
	    cutFactor++;
	    i = nrSamples + 1;
	    seq->resetSamples();
	    
	    if (!command)
	      fseek(*modFd, samplePtr, SEEK_SET);
	    else
	      {
		pclose(*modFd);
		*modFd = popen(command, "rb");
		slen = 0;    // reusing slen here!
		catchup(*modFd, samplePtr, &slen);
	      }
	  }
	else
	  i++;
      }
  } while (i != nrSamples);

  if (gusOnboardMem == seq->memory())
      return 0;

  return 1;
}

int
loadModule(char *name, struct songInfo *songChar, struct optionsInfo options)
{
  extern Sample *samples[];
#ifndef USE_X
  extern CursesScr *cursScreen;
#endif
  int i;

  unsigned char header[HDR_SIZE];
  char *command = NULL;

  int retVal;
  int gusOnboardMem;

  FILE *modFd;

  char compressed = MY_FALSE;

#ifdef USE_X
  extern TopShell *topShell;
  extern CommentShell *commentShell;
  char *slashpos, tmpChar = '\0';
#endif

  extern SampleShell *sampleShell;

  /* added by Peter Federighi */
  char *fixedname = NULL;

  extern Sequencer *seq;

  songChar->type = MODULE_NOT_S3M;
  songChar->globalVol = 255;
  songChar->name[0] = '\0';
  songChar->desc[0] = '\0';
  songChar->comment[0] = '\0';

  seq->sync();
  seq->resetSamples();

  gusOnboardMem = seq->memory();

  for (i = 0; i < MAX_POSITION; i++)
    patternLen[i] = 64;

  for (i = 0; i < MAX_POSITION; i++)
    patternTempo[i] = 0;

  for (i = 0; i < MAX_PATTERN * MAX_TRACK; i++)
    patternTable[i] = NULL;

#ifndef USE_X
  cursScreen->moduleFile(name);
#else
  if ((slashpos = strrchr(name, '/')) == NULL)
    slashpos = name;
  else
    slashpos++;

  if (strlen(slashpos) > 33)
    {
      tmpChar = slashpos[33];
      slashpos[33] = '\0';
    }

  topShell->moduleFile(slashpos);

  if (tmpChar != '\0')
    slashpos[33] = tmpChar;
#endif

  if (!(modFd = fopen(name, "rb")))
    return 0;

  if (fread(header, 1, sizeof (header), modFd) != sizeof (header))
    {
      fclose(modFd);
      return 0;
    }

  if ((header[0] == 31) && ((header[1] == 139) || (header[1] == 157)))
    compressed = GCOMPRESSED;
  else if ((header[2] == '-') && (header[3] == 'l') &&
	   ((header[4] == 'h') || (header[4] == 'z')) &&
	   (header[6] == '-'))
    compressed = LHACOMPRESSED;
  else if ((header[0] == 'P') && (header[1] == 'K') && (header[2] == 3) &&
	   (header[3] == 4))
    compressed = ZIPCOMPRESSED;

  if (compressed)
    {
      /* modified by Peter Federighi */
      fixedname = fixName (name);

      switch (compressed)
	{
	case GCOMPRESSED:
	  command = (char *) malloc (strlen (fixedname) +
				     strlen (GDECOMP_PGM) + 1);
	  sprintf (command, "%s%s", GDECOMP_PGM, fixedname);
	  break;
	case LHACOMPRESSED:
	  command = (char *) malloc (strlen (fixedname) +
				     strlen (LHADECOMP_PGM) + 1);
	  sprintf (command, "%s%s", LHADECOMP_PGM, fixedname);
	  break;
	case ZIPCOMPRESSED:
	  command = (char *) malloc (strlen (fixedname) +
				     strlen (ZIPDECOMP_PGM) + 1);
	  sprintf (command, "%s%s", ZIPDECOMP_PGM, fixedname);
	  break;
	default:
	  break;
	}

      fclose(modFd);
      modFd = popen (command, "rb");

      if (!modFd)
	{
	  free(command);
	  free(fixedname);
	  return 0;
	}

      if (fread(header, 1, sizeof(header), modFd) != sizeof(header))
	{
	  free(command);
	  free(fixedname);
	  return 0;
	}
    }

  if ((*(unsigned short *) &header[0] == 0x6669) ||
      !memcmp(header, "JN", 2))
    retVal = load_669_module(modFd, songChar, options, header);

  else if (!memcmp(header, "MTM", 3))
    retVal = loadMtmModule(modFd, songChar, header);

  else if (!memcmp(header, "MAS_UTrack_V", 12))
    retVal = loadUltModule(modFd, songChar, options, header);
  else if (!memcmp(&header[0x2c], "SCRM", 4))
    {
      songChar->type = MODULE_S3M;
      retVal = loadS3mModule(&modFd, songChar, options, header, command);
    }
  else if (!memcmp(header, "Extended Module: ", 17))
    retVal = loadXmModule(&modFd, songChar, options, header, command);
  else
    retVal = loadModModule(&modFd, songChar, options, header, command);

  if (compressed == MY_FALSE)
    fclose (modFd);
  else
    {
      free (command);
      free (fixedname);		/* added by Peter Federighi */
      pclose (modFd);
    }

  if (!retVal)
    freePatterns ();
  else
    {
#ifdef USE_X
      topShell->moduleTitle(songChar->name);
      topShell->setMaxPosition(songChar->songlength);
      topShell->setChannels(songChar->nrChannels);
      commentShell->setComment(songChar->comment, songChar->commentLineLen);
      sampleShell->setSamples(samples, songChar->nrSamples);
#else
      cursScreen->moduleTitle(songChar->name);
      cursScreen->setChannels(songChar->nrChannels);
      cursScreen->setType(songChar->desc);
      cursScreen->setMaxPosition(songChar->songlength);
      sampleShell->setSamples(samples, songChar->nrSamples);
      cursScreen->setMem(seq->memory(), gusOnboardMem);
#endif
    }

  return (retVal);
}

/*
 * Added by Peter Federighi (allanon@u.washington.edu) to fix names so they can
 * be parsed by 'sh' properly for the 'popen' calls.
 */
char *
fixName (char *theString)
{
  short counter, numinvalid = 0, pos = 0, length = strlen (theString);
  char *newName;
  char *validChar = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
      "1234567890-_=+.,/\0";

  if (length == 0)
    return NULL;

  /* First, find out how many invalid characters there are */
  for (counter = 0; counter < length; counter++)
    {
      if (strchr (validChar, theString[counter]) == NULL)
	numinvalid += 1;
    }

  /* malloc memory for new string, leave it to the user to free() it*/
  newName = (char *)malloc (length + numinvalid + 1);

  /* create new string, valid for command line parsing */
  for (counter = 0; counter < length; counter++)
    {
      if (strchr (validChar, theString[counter]) == NULL)
	{
	  newName[pos] = '\\';
	  newName[pos + 1] = theString[counter];
	  pos += 2;
	}
      else
	{
	  newName[pos] = theString[counter];
	  pos++;
	}
    }
  /* either do this or make the for loop go to 'counter <= length' */
  newName[length + numinvalid] = '\0';
  return newName;
}
