/*
 *  Copyright (c) by Jaroslav Kysela (Perex soft)
 */

#include <stdio.h>
#include <sys/ioctl.h>
#include "pmod.h"

short delayPattern = 0;
int orderDelta;
int quit = 0, next = 0, prev = 0, loop = 0;
dword songOptions = 0;
char *songPatterns = NULL;

void playShowPosition( word order, word row )
{
  pprintf( "\rPattern %03i/%03i; Row %03i", order, songMaxOrder, row );
  fflush( stdout );
}

void hiddenPlay( int orderStart, int orderEnd )
{
  byte *songPos, *newSongPos;
  word rowPos, rowSize;
  int end, i;

  if ( orderStart == orderEnd ) return;
  if ( orderStart > orderEnd )
    {
#if 0
      printf( "rewind: start=%d, end=%d\n", orderStart, orderEnd );
#endif
      if ( orderEnd == 0 ) return;
      orderStart = 0;
    }
  songPos = songPatternPos[ songOrder[ orderStart ] ];
  end = 0;
  newSongPos = NULL;
#if 0
  printf( "Hidden play: start=%d, end=%d\n", orderStart, orderEnd );
#endif
  while ( orderStart < orderEnd && !end )
    {
      if ( newSongPos )
        {
          songPos = newSongPos;
          newSongPos = NULL;
        }
      rowSize = *(songPos++);
      rowSize |= *(songPos++) << 8;
      if ( rowSize == SROW_TERMINATE ) 
        {
          songPos = songPatternPos[ songOrder[ ++orderStart ] ];
          continue;
        }
      rowPos = 0;
      while ( rowPos < rowSize )
        {
      	  byte channel;
	  byte win;
	  byte effect[ 2 ] = { EFF_NONE, EFF_NONE };
	  byte arg[ 2 ] = { 0, 0 };
	  
          channel = *(songPos++); rowPos++;
          if ( channel & ~0x1f ) 
            fatal( "hiddenPlay: bad consistency of song (0x%x)", channel );
          win = *(songPos++); rowPos++;
            
          if ( win & 1 ) { songPos++; rowPos++; }
          if ( win & 2 ) { songPos++; rowPos++; }
          if ( win & 4 ) { songPos++; rowPos++; }
            
          if ( win & 0x10 ) { effect[ 0 ] = *(songPos++); rowPos++; }
          if ( win & 0x20 ) { arg[ 0 ] = *(songPos++); rowPos++; }
          if ( win & 0x40 ) { effect[ 1 ] = *(songPos++); rowPos++; }
          if ( win & 0x80 ) { arg[ 1 ] = *(songPos++); rowPos++; }

          for ( i = 0; i < 2; i++ )
            switch ( effect[ i ] ) {
              case EFF_PATTERN_JUMP:
                if ( arg[ i ] > orderStart )
                  {
                    orderStart = arg[ i ];
                    newSongPos = songPatternPos[ songOrder[ orderStart ] ];
                  }
                 else
                  end = 1;
	        effect[ i ] = EFF_NONE; arg[ i ] = 0;
	        break;
	      case EFF_PATTERN_BREAK:
                orderStart++;
                newSongPos = songPatternPos[ songOrder[ orderStart ] ];
	        effect[ i ] = EFF_NONE; arg[ i ] = 0;
	        break;
	      case EFF_PATTERN_LOOP:
	        effect[ i ] = EFF_NONE; arg[ i ] = 0;
	        break;
	    }
	  hiddenPlayEffect( 1, channel, effect[ 1 ], arg[ 1 ] );
	  hiddenPlayEffect( 0, channel, effect[ 0 ], arg[ 0 ] );
        }
    }
}

void play()
{
  byte *songPos, *newSongPos, *oldSongPos, *loopSongPos = NULL;
  short end = 0;
  int order, oldOrder = -1;
  short ticks, i, loopFlag = 0, loopCount = 0;
  word row, rowSize, rowPos, w;
  word oldRow = 0xffff;
  int tmp, tmp1;

  if ( songMaxOrder < 0 )
    fatal( "play: null song?" );

  initVoices();
  timerSetup();
  clearEffects();

  order = rangeFirst( songPatterns, 0 );
  songPos = songPatternPos[ songOrder[ order ] ];
  hiddenPlay( 0, order );
  newSongPos = NULL;
  row = 0;
  timeSynchroFirst = timeSynchroLast = NULL;
  if ( !background ) playShowPosition( order, row );
  orderDelta = 0;
  disableChannels = disabledChannels = 0;
#if 0
  loop = 1;
  songPos = songPatternPos[ songOrder[ order = songMaxOrder = 1 ] ];
#if 1
  disableChannels = 0xffffffff & ~0x40;
#endif
#else
  loop = 0;
#endif
  while ( !end )
    {
      oldSongPos = songPos;
      if ( newSongPos != NULL )
        {
          songPos = newSongPos;
          newSongPos = NULL;
        }
      if ( orderDelta != 0 )
        {
          tmp1 = order;
          while ( orderDelta < 0 )
            {
              if ( ( tmp = rangePrev( songPatterns, order, 0 ) ) >= 0 ) 
                order = tmp;
              orderDelta++;
            }
          while ( orderDelta > 0 )
            {
              if ( ( tmp = rangeNext( songPatterns, order, songMaxOrder ) ) >= 0 ) 
                order = tmp;
              orderDelta--;
            }
          if ( order > songMaxOrder ) order = songMaxOrder;
          if ( ioctl( seqfd, GUS_IOCTL_QABORT ) < 0 )
            fatal( "play: GUS_IOCTL_QABORT failed\n" );
          GUS_DO_STOP();
          absTick = 0;
          songPos = songPatternPos[ songOrder[ order ] ];
          row = 0;
          clearEffects();
          timeSynchroFree();
          playShowPosition( order, row );
          hiddenPlay( tmp1, order );
        }
    
      if ( !background && ( row != oldRow || order != oldOrder ) )
        timeSynchroAdd( order, row );
    
      rowSize = *(songPos++);
      rowSize |= *(songPos++) << 8;
#if 0
      dprintf( "rowSize = 0x%x\n", rowSize );
#endif
      if ( rowSize == SROW_TERMINATE ) 
        {
          row = 0;
          if ( !loop )
            {
              if ( ( tmp = rangeNext( songPatterns, order, songMaxOrder ) ) < 0 ) 
                order = songMaxOrder + 1;
               else
                {
                  if ( order + 1 != tmp )
                    hiddenPlay( order + 1, tmp );
                  order = tmp;
                }
            }
          if ( (short)order > songMaxOrder )
            {
              end = 1;
              rowSize = 0;
            }
           else 
            {
              songPos = songPatternPos[ songOrder[ order ] ];
#if 0
              dprintf( "max = 0x%x, order = 0x%x, songOrder = 0x%x, songPos = 0x%x\n", songMaxOrder, order, songOrder[ order ], (int)(songPos - song) );
#endif
              continue;
            }
        }
      rowPos = 0;
      while ( rowPos < rowSize )
        {
	  byte channel;
	  byte win;
	  byte instr = 255;
	  byte note = 255;
	  byte volume = 255;
	  byte effect[ 2 ] = { EFF_NONE, EFF_NONE };
	  byte arg[ 2 ] = { 0, 0 };

          channel = *(songPos++); rowPos++;
          if ( channel & ~0x1f ) 
            fatal( "play: bad consistency of song (0x%x)", channel );
          win = *(songPos++); rowPos++;
            
          if ( win & 1 ) { instr = *(songPos++); rowPos++; }
          if ( win & 2 ) { note = *(songPos++); rowPos++; }
          if ( win & 4 ) { volume = *(songPos++); rowPos++; }
            
          if ( win & 0x10 ) { effect[ 0 ] = *(songPos++); rowPos++; }
          if ( win & 0x20 ) { arg[ 0 ] = *(songPos++); rowPos++; }
          if ( win & 0x40 ) { effect[ 1 ] = *(songPos++); rowPos++; }
          if ( win & 0x80 ) { arg[ 1 ] = *(songPos++); rowPos++; }

          for ( i = 0; i < 2; i++ )
            switch ( effect[ i ] ) {
              case EFF_PATTERN_JUMP:
	        if ( loop )
	          {
	            newSongPos = songPatternPos[ songOrder[ order ] ];
	            row = 0;
	          }
	         else
	        if ( newSongPos == NULL )
	          {
                    loopFlag = 0;
	            if ( arg[ i ] <= songMaxOrder && ( songLoopFlags & LFLG_JUMP ) )
	              {
	                tmp = arg[ i ] - 1;
	                tmp = rangeNext( songPatterns, tmp, songMaxOrder );
	                if ( tmp < 0 )
	                  end = 1;
	                 else
	                  {
	                    if ( tmp != arg[ i ] )
	                      hiddenPlay( arg[ i ], tmp );
	                    newSongPos = songPatternPos[ songOrder[ order = tmp ] ];
	                    row = 0;
	                  }
	              }
	             else
	              end = 1;
	          }
	        effect[ i ] = EFF_NONE; arg[ i ] = 0;
	        break;
	      case EFF_PATTERN_BREAK:
                if ( loop )
 	          {
 	            newSongPos = songPatternPos[ songOrder[ order ] ];
 	            row = 0;
 	          }
 	         else 
	        if ( newSongPos == NULL )
	          {
	            loopFlag = 0;
	            if ( (short)order < songMaxOrder )
	              tmp = rangeNext( songPatterns, order, songMaxOrder );
	             else
	              tmp = songMaxOrder + 1;
	            if ( tmp >= 0 && tmp <= songMaxOrder )
	              {
	                newSongPos = songPatternPos[ songOrder[ tmp ] ];
	                row = 0;
	                while ( order + 1 == tmp && arg[ i ]-- > 0 )
	                  {
	                    row++;
	                    w = *(newSongPos++);
	                    w |= *(newSongPos++) << 8;
	                    if ( w == SROW_TERMINATE )
	                      {
	                        end = 1;
	                        break;
	                      }
	                    newSongPos += w;
	                  }
	                if ( order + 1 != tmp )
	                  hiddenPlay( order + 1, tmp );
	                order = tmp;
	              }
	             else
	              end = 1;
	          }
	        effect[ i ] = EFF_NONE; arg[ i ] = 0;
	        break;
	      case EFF_PATTERN_LOOP:
	        switch ( loopFlag ) {
	          case 0: 
	            if ( arg[ i ] == 0 )
	              {
	                loopSongPos = oldSongPos;
	                loopFlag++;
	              }
	            break;
	          case 1:
	            if ( loopCount == 0 )
	              loopCount = arg[ i ];
	             else
	              loopCount--;
	            if ( loopCount == 0 )
	              loopFlag = 0;
	             else
	              newSongPos = loopSongPos;
	            break;
	        }
	        effect[ i ] = EFF_NONE; arg[ i ] = 0;
	        break;
	    }
#if 0
	  dprintf( "note = %d, instr = %d\n", note, instr );
#endif

	  if ( note == 254 )	/* key off, to do - maybe effect handling */
	    {
	      keyoff( channel );
	      continue;
	    }
	    
          if ( note != 255 )
            {
              if ( doPreEffect( 0,
                                channel,
                                instr,
                                note, 
                                volume, 
                                effect[ 0 ], 
                                arg[ 0 ] ) ) continue;
              if ( instr != 255 ) SET_INSTR( channel, instr );
              if ( note != 255 ) SET_NOTE( channel, note );
              SET_NOTE_PERIOD( channel );
              START_NOTE( channel );
              stopNewNoteEffects( channel );
            }
           else
          if ( instr != 255 )
            {
              if ( voices[ channel ].instr >= 0 &&
                   songInstrs[ voices[ channel ].instr ] )
                voices[ channel ].volume = 
                  songInstrs[ voices[ channel ].instr ] ->
                  samples[ voices[ channel ].sample ] -> volume;
               else
                voices[ channel ].volume = 0;
              SET_VOLUME( channel );
              instr = 255;
            }

          playEffect( 1, channel, instr, volume, effect[ 1 ], arg[ 1 ] );
          if ( effect[ 0 ] != EFF_NONE )
            playEffectOnly( 0, channel, instr, effect[ 0 ], arg[ 0 ] );
        }
      row++;
      if ( rowPos != rowSize )
        fatal( "play: bad consistency of song (0x%x != 0x%x)\n", rowPos, rowSize );

      if ( !quit && !next && !prev )
        {
          doEffects( 0 );
          do {
            ticks = songPlaySpeed;
            while ( ticks-- )
              {
                timerNext();
                if ( delayPattern > 0 || ticks > 0 ) doEffects( 1 );
              }
          } while ( delayPattern-- > 0 );
          if ( delayPattern < 0 ) delayPattern = 0;
          clearEffects();
          queueFlush();
        }

      if ( ( quit || next || prev ) && ioctl( seqfd, GUS_IOCTL_ABORT ) < 0 )
        fatal( "play: ioctl - GUS_IOCTL_ABORT" );

      if ( quit || next || prev || end )
        {
          newSongPos = NULL;
          if ( !quit && !prev && !next && ( songLoopFlags & LFLG_WHOLE ) )
            {
              int i;
              
              for ( i = 0; i < songChannels; i++ )
                GUS_DO_VS_CURRENT_VOL( i, 0 );
              timerNext();
              for ( i = 0; i < songChannels; i++ )
                GUS_DO_VS_VOICE_STOP( i );
              timerNext();
              queueFlush();
              loopFlag = 0; row = 0;
              tmp = rangeNext( songPatterns, songRestart, songMaxOrder );
              if ( tmp < 0 )
                end = 1;
               else
                {
                  songPos = songPatternPos[ songOrder[ order = tmp ] ];
                  end = 0;
                }
            }
           else
            end = 1;
        }
    }
  timeSynchroFree();
  gusStopAllChannels();
  pprintf( "\r                                              \r" );
  fflush( stdout );
}
