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

/*
 *	gmod.c	- Module player for GUS and Linux.
 *		(C) Hannu Savolainen, 1993
 *
 *	NOTE!	This program doesn't try to be a complete module player.
 *		It's just a too I used while developing the driver. In
 *		addition it can be used as an example on programming
 *		the VoxWare Sound Driver with GUS.
 */

/*
 * Many modifications have been done by Andrew J. Robinson.
 * Refer to the ChangeLog for details.
 */


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

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

#include <sys/ultrasound.h>
#include <stdio.h>
#include <string.h>

#include <time.h>		/* for randomize */
#include <stdlib.h>		/* for randomize */

#include "defines.h"
#include "structs.h"
#include "tables.h"
#include "protos.h"

#ifdef USE_X
#include <limits.h>
#include <qmsgbox.h>

#include "TopShell.h"
#include "TrackShell.h"
#include "OptShell.h"
#include "QueueShell.h"
#include "CommentShell.h"
#else
#include "CursesScr.h"
#endif

#include "SampleShell.h"
#include "Sequencer.h"
#include "Sample.h"

SEQ_DEFINEBUF (2048);

int patternLen[MAX_POSITION];
int patternTempo[MAX_POSITION];
struct noteInfo *patternTable[MAX_PATTERN * MAX_TRACK];

struct voiceInfo voices[MAX_TRACK];

int tune[MAX_POSITION];
short voiceTable[MAX_POSITION][MAX_TRACK];
double tickDuration;

int mixerfd;
Sample *samples[MAX_SAMPLES];
double thisTime, nextTime;
int ticksPerDivision;

#ifndef USE_X
unsigned char stopFlag = 0;
#endif
unsigned char background = 0;
int actualPos = 0;
int positionChange = 0;

unsigned int seqInput = ECHO_NONE;

struct xStruct xInfo;
int currentMod;

#ifdef USE_X
TopShell *topShell;
TrackShell *trackShell;
OptShell *optShell;
QueueShell *queueShell;
CommentShell *commentShell;
char * emptyList[] = { "" };
#else
CursesScr *cursScreen;
#endif
SampleShell *sampleShell;

void readRc (FILE *, char *, struct optionsInfo *);
unsigned int procInput (void);
void setSignals (void);

#ifdef USE_X
  struct optionsInfo options =
    {255, 0, 1, 1, 0, 0, 0, 255, 0, 0, 100, 0, 0};
#else
  struct optionsInfo options =
    {255, 0, 0, 1, 1, 0, 0, 0, 255, 0, 0, 100, 0, 0};
#endif
struct optionsInfo savedOpt;
FILE *rcFp = NULL;
struct songInfo songChar;
int startPos = 0;

Sequencer *seq;

int
main (int argc, char *argv[])
{
  static char ident[] = IDENT;
  int i, seqOpenRet, nameStart, numFiles, randSwap;
#ifndef USE_X
  int exitCode = ERR_NOERROR;
  char mixerName[13];
  int mixDevmask;
#endif
  char *tmpArgv, *rcFilename;
  
  // do all the opens first in case the process is SUID or SGID

  Sequencer lseq;
  seq = &lseq;
  seqOpenRet = lseq.open();

#ifndef USE_X
  if (options.mixer == 255)
    sprintf (mixerName, "%s", "/dev/mixer");
  else
    sprintf (mixerName, "%s%u", "/dev/mixer", options.mixer);

  mixerfd = open (mixerName, O_RDWR, 0);
#endif

  // drop SUID and SGID priveledge if we have it
  setuid(getuid());
  setgid(getgid());

  songChar.comment = (char *)calloc (1, 1);

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

#ifdef USE_X
  QApplication a(argc, argv);
  commentShell = new CommentShell;
  optShell = new OptShell;
  queueShell = new QueueShell;
  sampleShell = new SampleShell;
  trackShell = new TrackShell;
  topShell = new TopShell(commentShell, optShell, queueShell, sampleShell, trackShell);
  a.setMainWidget(topShell);
  topShell->show();
#endif

  nameStart = parseArgs (argc, argv, &options);

#ifdef USE_X
  savedOpt.extendOct = options.extendOct;
#endif

#ifndef USE_X
  printf (HEADING);
  printf ("Original source (C) Hannu Savolainen, 1993\n");
  printf ("MTM/ULT loaders by Robert Sanders\n");
  printf ("Continuing development by Andrew J. Robinson\n\n");

  if (nameStart == argc)
    {
      printf ("\nUsage: %s [options] modfile . . .\n", argv[0]);
      printf ("Use %s -h for help.\n\n", argv[0]);
      exit (ERR_BADARGS);
    }
#endif

  switch (seqOpenRet)
    {
    case -1:
#ifdef USE_X
      QMessageBox::message("Xgmod Error", "Cannot open /dev/sequencer");
#else
      perror ("/dev/sequencer");
#endif
      exit (ERR_SEQUENCER);
    case -2:
#ifdef USE_X
      QMessageBox::message("Xgmod Error", "Cannot determine number of synths");
#else
      perror ("/dev/sequencer");
#endif
      exit (ERR_SEQUENCER);
    case -3:
#ifdef USE_X
      QMessageBox::message("Xgmod Error", "Cannot determine synth information");
#else
      perror ("/dev/sequencer");
#endif
      exit (ERR_SEQUENCER);
    case -4:
#ifdef USE_X
      QMessageBox::message("Xgmod Error", "Gravis Ultrasound not detected");
#else
      fprintf (stderr, "Gravis Ultrasound not detected\n");
#endif
      exit (ERR_NOGUS);
    }

#ifndef USE_X
  if (options.mixer == 255)
    sprintf (mixerName, "%s", "/dev/mixer");
  else
    sprintf (mixerName, "%s%u", "/dev/mixer", options.mixer);

  if (mixerfd == -1)
    printf ("Mixer (%s) not available.\n", mixerName);
  else
    {
      ioctl (mixerfd, SOUND_MIXER_READ_DEVMASK, &mixDevmask);
      if (!(mixDevmask & SOUND_MASK_SYNTH))
	{
	  printf ("This mixer (%s) does not support volume control.\n", mixerName);
	  close (mixerfd);
	  mixerfd = -1;
	}
    }
#endif

  if ((tmpArgv = getenv ("HOME")) != NULL)
    {
      rcFilename = (char *) malloc (strlen (tmpArgv) + 9);
      strcpy (rcFilename, tmpArgv);
      strcat (rcFilename, USER_RC_NAME);
      rcFp = fopen (rcFilename, "r");
      free (rcFilename);
    }

  if (rcFp == NULL)
    rcFp = fopen (RC_NAME, "r");

#ifndef USE_X
  cursScreen = new CursesScr(background);
#ifdef USE_NCURSES
  sampleShell = new SampleShell(background);
#else
  sampleShell = new SampleShell(background, options.showEmptySamples);
#endif
#endif /* USE_X */

  numFiles = argc - nameStart;
  srand (time (NULL));

  if (options.randomize)
    {
      for (i = nameStart; i < argc; i++)
	{
	  randSwap = nameStart + (rand () % numFiles);
	  tmpArgv = argv[i];
	  argv[i] = argv[randSwap];
	  argv[randSwap] = tmpArgv;
	}
    }

  xInfo.nrFileStrings = argc - nameStart;

#ifndef USE_X
#define String char *
#endif

  if (xInfo.nrFileStrings > 0)
    {
      xInfo.fileStrings = (char * *)malloc (sizeof (char *) * xInfo.nrFileStrings);

      for (i = nameStart; i < argc; i++)
	{
	  xInfo.fileStrings[i - nameStart] = strdup (argv[i]);
#ifdef USE_X
	  queueShell->addFile(xInfo.fileStrings[i - nameStart]);
#endif
	}
    }
  else
    xInfo.fileStrings = NULL;
  
  savedOpt = options;

#ifdef USE_X
  seq->readEnabled(TRUE);
#else
  setSignals ();
#endif

  //seq->stopAllChannels(MY_FALSE);

#ifdef USE_X
  currentMod = -1;

  a.exec();
#else
  currentMod = 0;

  while ((stopFlag != STOP_EXIT) && (currentMod < xInfo.nrFileStrings))
    {
      if (startPlayback (stopFlag))
	{
	  seqInput = ECHO_NONE;

	  while (seqInput != ECHO_END)
	    {
	      NoXProcessEvent ();
	    }
	  
	  ioctl(seq->seqFd(), SNDCTL_SEQ_RESET, 0);
	  startPos = endModule (stopFlag);

	  switch (stopFlag)
	    {
	    case STOP_FORWBACK:
	      startPos = actualPos + positionChange;
	      
	      if (startPos < 0)
		startPos = 0;
	      
	      break;
	    case STOP_EXIT:
	      break;
	    case STOP_PREV:
	      if (currentMod > 0)
		currentMod--;
	      startPos = 0;
	      break;
	    default:
	    case STOP_NEXT:
	      currentMod++;
	      startPos = 0;
	      break;
	    }
	}
      else
	{
	  currentMod++;
	  startPos = 0;
	}
    }

  //SEQ_DUMPBUF ();

  //lseq.close(); // destructor does close
  fclose (rcFp);
  delete cursScreen;
  exit (exitCode);
#endif /* not USE_X */
}

int
startPlayback(unsigned char lstopFlag)
{
  int loadRc = 0;
  int startDelay; 
  extern char played[MAX_POSITION];
#ifdef USE_X
  extern TopShell *topShell;
#endif

#ifdef USE_X
  if (optShell->highlightChecked() == TRUE)
    queueShell->currentClicked();
#endif
  if (lstopFlag != STOP_FORWBACK)
    {
      struct timeval tvStart, tvEnd;
      startPos = 0;

      gettimeofday(&tvStart, NULL);

      options = savedOpt;

#ifdef USE_X
      if (optShell->fiftyhzChecked() == TRUE)
	options.use_50hz = 1;
      else
	options.use_50hz = 0;
      
      if (optShell->ntscChecked() == TRUE)
	options.ntsc = 1;
      else
	options.ntsc = 0;
      
      if (optShell->bpmChecked() == TRUE)
	options.bpmTempos = 0;
      else
	options.bpmTempos = 1;

      options.extendOct = optShell->octaveSelected();
      options.checkMagic = optShell->magicChecked();
#endif

      if (rcFp != NULL)
	{
	  readRc (rcFp, xInfo.fileStrings[currentMod], &options);
	}
      
      loadRc = loadModule (xInfo.fileStrings[currentMod], &songChar, options);
      
      gettimeofday(&tvEnd, NULL);
      tvEnd.tv_sec -= tvStart.tv_sec;
      
      if (tvStart.tv_usec > tvEnd.tv_usec)
	{
	  tvEnd.tv_sec -= 1;
	  tvEnd.tv_usec = tvEnd.tv_usec + 1000000 - tvStart.tv_usec;
	}
      else
	tvEnd.tv_usec -= tvStart.tv_usec;

      if (tvEnd.tv_sec >= 1)
	startDelay = 0;
      else
	startDelay = (1000000 - tvEnd.tv_usec) / 10000;
    }
  else
    {
      loadRc = 1;
      startDelay = 0;
      // prevent "rewind" from detecting a loop
      played[startPos] = 0;
    }

#ifndef USE_X
  stopFlag = 0;
#endif

  if (loadRc)
    {
      actualPos = 0;
      positionChange = 0;
      
      playModule(startPos, &songChar, options, startDelay);
    }
#ifdef USE_X
  else
    topShell->moduleTitle("Load Failed");
#endif

  return (loadRc);
}
