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

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

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

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

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

#ifdef USE_X
#include "TopShell.h"
#endif

#include "Sequencer.h"

int position;
int jumpToPos;
struct optionsInfo pOptions;
struct songInfo *pSongChar;
char played[MAX_POSITION];
struct effectInfo effects;
int patStart;
double savedTd;
int savedTpd;
int savedPosition;

void
playModule (int startPosition, struct songInfo *songCharX,
	     struct optionsInfo optionsX, int startDelay)
{
  SEQ_DECLAREBUF();
  int i;
  extern Sequencer *seq;
  extern Sample *samples[];

  pOptions = optionsX;
  pSongChar = songCharX;
  memset(&effects, 0, sizeof(effects));
  initVoices();

  for (i = 0; i < MAX_SAMPLES && !samples[i]; i++)
    ;

  seq->numVoices(pSongChar->nrChannels, pSongChar->volType, samples[i]);
  seq->panFactor(pOptions.panFactor);

#ifdef USE_X
  seq->writeEnabled(TRUE);
#endif

  if (startPosition == 0)
    for (i = 0; i < pSongChar->songlength; i++)
      played[i] = MY_FALSE;

  SEQ_START_TIMER ();

  for (i = 0; i < pSongChar->nrChannels; i++)
    {
      seq->mainVolume(pOptions.mainVolume * pSongChar->globalVol / 255);
      seq->pan(i, pSongChar->panning[i]);
    }

  nextTime = 0.0;

  if (startPosition == 0)
    {
      TICKS_PER_DIVISION (pSongChar->playSpeed);
      TEMPO (pSongChar->tempo, pSongChar->clockSpeed);
    }
  else
    {
      ticksPerDivision = savedTpd;
      tickDuration = savedTd;
    }

  thisTime = 0.0;
  
  if (startPosition == 0)
    nextTime += startDelay; // was tickDuration

  syncTime ();

  position = startPosition;
  jumpToPos = 0;
  patStart = 0;
  playNextPosition();
}

int
doRepeat()
{
  if ((position >= pSongChar->songlength) && (pOptions.repeat))
    {
      playModule(0, pSongChar, pOptions, 0);
      return (0);
    }

  return (position >= pSongChar->songlength);
}

int
playNextPosition()
{
  SEQ_DECLAREBUF();
  int extraTicks;
  int i;
  int voice;
  int tick, pattern, channel, pos, goTo;
  extern Sequencer *seq;

  savedPosition = 0;

  if (position >= pSongChar->songlength)
    return (doRepeat());

  if ((played[position] > MY_FALSE) && !(jumpToPos & MOVE_LOOP))
    {
#ifdef USE_X
      syncTime ();
      SEQ_ECHO_BACK (ECHO_LOOP);
#else
      if (pOptions.loopBreaker == 1)
	{
	  position = pSongChar->songlength;
	  return (doRepeat());
	}
      else
#endif
	for (i = 0; i < pSongChar->songlength; i++)
	  played[i] = MY_FALSE;
    }

  played[position] = MY_TRUE; // queued
  pos = tune[position];

  if (pos == ORDER_STOP)
    {
      position = pSongChar->songlength;
      return (doRepeat());
    }
  else if ((pos == ORDER_SKIP) || (pos >= pSongChar->nrPatterns))
    {
      position++;
      return (doRepeat());
    }

  syncTime ();
  SEQ_ECHO_BACK ((((unsigned int) position << 16) & 0x00ff0000) |
		 (((unsigned int) pos << 8) & 0x0000ff00) |
		 ECHO_MESSAGE);

  if (patternTempo[pos])
    {
      if (patternTempo[pos] < 32)
	TICKS_PER_DIVISION(patternTempo[pos]);
      else
	TEMPO(patternTempo[pos], pSongChar->clockSpeed);
    }

  jumpToPos = 0;
  for (pattern = patStart; pattern < patternLen[pos] && jumpToPos == 0; pattern++)
    {
#ifdef USE_X
      syncTime ();
      SEQ_ECHO_BACK(((unsigned int)pattern << 8) | ECHO_PATTERN);
#endif
      for (channel = 0; channel < pSongChar->nrChannels; channel++)
	{
	  voice = voiceTable[pos][channel];
	  if ((goTo = playNote (channel, position, pattern,
				  &((patternTable[voice])[pattern]),
				  pSongChar, &effects, &pOptions)) != 0)
	    jumpToPos |= goTo;
	}

      /* nextTime += tickDuration; */
      for (extraTicks = 0; extraTicks <= effects.delayNotes; extraTicks++)
	{
	  for (tick = 0; tick < ticksPerDivision; tick++)
	    {
	      int didPreUpdate = 0;

	      for (channel = 0; channel < pSongChar->nrChannels; channel++)
		didPreUpdate |= seq->doTick(channel, tick, pSongChar);

	      if (didPreUpdate)
		nextTime += (tickDuration > 1 ? 1 : tickDuration);

	      for (channel = 0; channel < pSongChar->nrChannels; channel++)
		seq->doUpdates(channel);

	      if (didPreUpdate)
		{
		  if (tickDuration > 1)
		    nextTime += tickDuration - 1;
		}
	      else
		nextTime += tickDuration;
	    }
	}

      effects.delayNotes = 0;

#ifndef USE_X
      if (stopFlag)
	jumpToPos = MOVE_EXIT;
#endif
    }			/* pattern */

  patStart = 0;

  if (jumpToPos & MOVE_LOOP)
    {
      patStart = effects.pattern;
      effects.pattern = 0;
      position -= 1;
    }
  if (jumpToPos & MOVE_JUMP)
    {
      patStart = effects.pattern;
      position = effects.position - 1;
    }
  if (jumpToPos & MOVE_BREAK)
    {
      patStart = effects.pattern;
    }
  if (jumpToPos & MOVE_EXIT)
    {
      savedPosition = position;
      position = pSongChar->songlength - 1;
    }
  /*
    }

    if (pOptions.repeat)
    startPosition = 0;
    }
    while (pOptions.repeat
#ifndef USE_X
    && !stopFlag
#endif
    );
    */
  position++;

  //return (position >= pSongChar->songlength);
  return (doRepeat());
}

int
endModule(unsigned char lstopFlag)
{
#ifdef USE_X
  extern TopShell *topShell;
  //  extern TrackShell *trackShell;
#endif
  int i;

  if (lstopFlag != STOP_FORWBACK)
    freePatterns ();
  else
    {
      savedTpd = ticksPerDivision;
      savedTd = tickDuration;

      // prevent a loop from being detected

      for (i = 0; i < pSongChar->songlength; i++)
	if (played[i] == MY_TRUE)
	  played[i] = 0;
      
      // prevent loop detection by "rewind"
      played[actualPos] = 0;
    }
  
#ifdef USE_X
      topShell->setPosition(-1);
#endif

  return (savedPosition);
}  
