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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pmod.h"

#define PAN_LEFT	3
#define PAN_RIGHT	( 15 - PAN_LEFT )

#define MAX_PATTERN_LEN ( 5 * 16 * 64 /* 32 * 1024 */ )

void loadS3M( FILE *file, char *filename )
{
  word lastSlideVol[ 32 ];
  word lastSlideUp[ 32 ];
  word lastSlideDown[ 32 ];
  byte rbuf[ 16 ];
  byte *rbuf1;
  byte *header;
  byte *orders;
  byte *pan_pos;
  word *instruments;
  word *patterns;
  word insNum, patNum, pattern;
  word version;
  byte globalVol;
  short i, v310, ident;
  word tmp;

  pprintf( "Loading Scream Tracker III file %s... ", filename );
  fflush( stdout );

  if ( ( header = malloc( 0x60 ) ) == NULL )
    fatal( "loadS3M: header alloc" );
  if ( fread( header, 0x60, 1, file ) != 1 )
    fatal( "loadS3M: header read" );
    
  version = ( header[ 0x28 ] | ( header[ 0x29 ] << 8 ) ) & 0xfff;
  if ( header[ 0x1d ] != 16 )	/* ST3 module ? */
    fatal( "loadS3M: wrong type of module\n" );
  strncpy( songName, header, 28 );
  songName[ 28 ] = 0;
  sprintf( songType, "S3M (%s %x.%02x)", ( version & 0xf000 ) == 0x0000 ? "ST" : "??", 
  					 ( version >> 8 ) & 0x0f, 
  					 version & 0xff );
  songMaxOrder = ( header[ 0x20 ] | ( header[ 0x21 ] << 8 ) ) - 1;
  songInstr = insNum = header[ 0x22 ] | ( header[ 0x23 ] << 8 );
  patNum = header[ 0x24 ] | ( header[ 0x25 ] << 8 );
  songGlobalVolume = globalVol = header[ 0x30 ];
  songGlobalVolume <<= 1;
  if ( songGlobalVolume < 1 ) songGlobalVolume = 1;
  if ( songGlobalVolume > 128 ) songGlobalVolume = 128;
  songPlaySpeed = header[ 0x31 ];
  songTempo = header[ 0x32 ];
  if ( ( header[ 0x2a ] | ( header[ 0x2b ] << 8 ) ) != 2 )
    fatal( "loadS3M: signed samples?\n" );
  songChannels = 0;
  for ( i = 0; i < 16; i++ )
    {
      tmp = header[ 0x40 + i ];
      if ( tmp == 0xff ) break;
      if ( ( tmp & 0x7f ) > 15 ) fatal( "loadS3M: adlib channel" );
      voices[ i ].pan = tmp < 8 ? PAN_LEFT : PAN_RIGHT;
      songChannels++;
    }
  v310 = header[ 0x35 ] == 252;
  free( header );

  if ( ( orders = malloc( songMaxOrder + 1 ) ) == NULL )
    fatal( "loadS3M: orders alloc" );
  if ( fread( orders, songMaxOrder + 1, 1, file ) != 1 )
    fatal( "loadS3M: orders read" );
  for ( i = 0, pattern = -1; i <= songMaxOrder; i++ )
    {
      if ( orders[ i ] == 255 ) break;
      if ( orders[ i ] == 254 ) continue;
      songOrder[ ++pattern ] = orders[ i ];
    }
  songMaxOrder = pattern;
  free( orders );

  if ( ( instruments = malloc( insNum * 2 ) ) == NULL )
    fatal( "loadS3M: instruments alloc" );
  if ( fread( instruments, insNum * 2, 1, file ) != 1 )
    fatal( "loadS3M: instruments read" );
  if ( ( patterns = malloc( patNum * 2 ) ) == NULL )
    fatal( "loadS3M: patterns alloc" );
  if ( fread( patterns, patNum * 2, 1, file ) != 1 )
    fatal( "loadS3M: patterns read" );
  if ( v310 )		/* Scream Tracker 3.10 have pan positions, too */
    {
      if ( ( pan_pos = malloc( songChannels ) ) == NULL )
        fatal( "loadS3M: pan positions alloc" );
      if ( fread( pan_pos, songChannels, 1, file ) != 1 )
        fatal( "loadS3M: pan positions read" );
      for ( i = 0; i < songChannels; i++ )
        {
          tmp = pan_pos[ i ];
          if ( tmp & 0x20 ) voices[ i ].pan = tmp & 0x0f;
        }
      free( pan_pos );
    }

  if ( ( rbuf1 = malloc( MAX_PATTERN_LEN ) ) == NULL )
    fatal( "loadS3M: rbuf1 alloc" );

  gusSetupChannels();

  for ( i = 0, ident = 0; i < (short)insNum; i++ )
    {
      struct INSTR *is = newInstrument( i );
      struct SAMPLE *smp = newSample( i, 0 );

      fseek( file, (dword)instruments[ i ] << 4, SEEK_SET );
      if ( fread( rbuf1, 0x50, 1, file ) != 1 )
        fatal( "loadS3M: sample info read\n" );
      if ( ( rbuf1[ 0 ] != 0 && rbuf1[ 0 ] != 1 ) || 
           rbuf1[ 0x1e ] != 0 || ( rbuf1[ 0x1f ] & 0xfe ) != 0 )
        fatal( "loadS3M: wrong type of sample (TYPE=0x%x, PACK=0x%x, FLAGS=0x%x)\n", 
               rbuf1[ 0 ], rbuf1[ 0x1e ], rbuf1[ 0x1f ] );
      strncpy( is -> name, &rbuf1[ 0x30 ], 28 ); is -> name[ 28 ] = 0;
      strncpy( smp -> name, &rbuf1[ 0x30 ], 28 ); smp -> name[ 28 ] = 0;
      smp -> ident = -1;
      if ( rbuf1[ 0 ] != 0 )
        {
          smp -> finetune = -128;		/* finetune 0 for S3M */
          smp -> length = rbuf1[ 0x10 ] | ( rbuf1[ 0x11 ] << 8 ) |
                          ( rbuf1[ 0x12 ] << 16 ) | ( rbuf1[ 0x13 ] << 24 );
          smp -> lstart = rbuf1[ 0x14 ] | ( rbuf1[ 0x15 ] << 8 ) |
                          ( rbuf1[ 0x16 ] << 16 ) | ( rbuf1[ 0x17 ] << 24 );
          smp -> lend   = rbuf1[ 0x18 ] | ( rbuf1[ 0x19 ] << 8 ) |
                          ( rbuf1[ 0x1a ] << 16 ) | ( rbuf1[ 0x1b ] << 24 );
          smp -> freqC4 = rbuf1[ 0x20 ] | ( rbuf1[ 0x21 ] << 8 ) |
                          ( rbuf1[ 0x22 ] << 16 ) | ( rbuf1[ 0x23 ] << 24 );
          smp -> volume = rbuf1[ 0x1c ];
          smp -> type = SMP_TYPE_UNSIGNED;
          if ( ( rbuf1[ 0x1f ] & 1 ) == 0 ) smp -> lstart = smp -> lend = 0;
          tmp = rbuf1[ 0x0e ] | ( (word)rbuf1[ 0x0f ] << 8 );
          fseek( file, (dword)tmp << 4, SEEK_SET );
          gusDownload( file, i, 0 );
          if ( smp -> length )
            {
              is -> used = smp -> used = 1;
              smp -> ident = ident++;
            }
        }
    }

  for ( i = 0; i <= songChannels; i++ )
    {
      lastSlideVol[ i ] = EFF_VOL_SLIDE;
      lastSlideUp[ i ] = EFF_SLIDE_UP;
      lastSlideDown[ i ] = EFF_SLIDE_DOWN;
    }
  for ( pattern = 0; pattern < patNum; pattern++ )
    {
      word len, pos;
      byte instr, note, vol, eff, arg, tmp1;
      int offset;

      convInitPattern();
      offset = patterns[ pattern ] << 4;
#ifdef 0
      fseek( file, (dword)patterns[ 11 ] << 4, SEEK_SET );
#else
      fseek( file, offset, SEEK_SET );
#endif
      if ( offset <= 0 )
        {
          convDonePattern();
          continue;
        }
#ifdef 0
      dprintf( "offset: 0x%x\n", (dword)patterns[ pattern ] << 4 );
#endif
      if ( fread( rbuf, 2, 1, file ) != 1 )
        fatal( "loadS3M: pattern read\n" );
      len = ( rbuf[ 0 ] | ( rbuf[ 1 ] << 8 ) ) - 2;
      if ( len <= 0 )
        {
          convDonePattern();
          continue;
        }
      if ( len > MAX_PATTERN_LEN )
        fatal( "loadS3M: pattern too big (%d bytes)\n", len );
      if ( fread( rbuf1, len, 1, file ) != 1 )
        fatal( "loadS3M: pattern read\n" );
      pos = 0;
      while ( pos < len )
        {
          convInitRow();
          while ( ( tmp = rbuf1[ pos++ ] ) != 0 )
            {
              note = 255; instr = 255; vol = 255; eff = 0; arg = 0;
#ifdef 0
              dprintf( "tmp: 0x%x\n", tmp );
#endif
              if ( tmp & 32 ) 
                { 
                  note = rbuf1[ pos++ ];
                  instr = rbuf1[ pos++ ];
                  if ( instr ) instr--; else instr = 255;
                }
              if ( tmp & 64 ) 
                {
                  vol = rbuf1[ pos++ ];
                  if ( vol == 255 ) vol--;
                }
              if ( tmp & 128 ) { eff = rbuf1[ pos++ ]; arg = rbuf1[ pos++ ]; }
#ifdef 0
              if ( note == 254 ) { note = 254; }	  /* key off */
#endif
              if ( eff != 0 )
                switch ( eff + 'A' - 1 ) {
                  case 'A': eff = EFF_SPEED; break;
                  case 'B': eff = EFF_PATTERN_JUMP; break;
                  case 'C': eff = EFF_PATTERN_BREAK; break;
                  case 'D': 
                    if ( !arg ) eff = lastSlideVol[ tmp & 0x1f ]; else
                    if ( ( arg & 0x0f ) == 0x0f && ( arg & 0xf0 ) ) { eff = EFF_FINE_VOL_UP; arg >>= 4; } else
                    if ( ( arg & 0xf0 ) == 0xf0 && ( arg & 0x0f ) ) { eff = EFF_FINE_VOL_DOWN; arg &= 0x0f; } else
                    eff = EFF_VOL_SLIDE;
                    lastSlideVol[ tmp & 0x1f ] = eff;
                    break;
                  case 'E':
                    if ( !arg ) eff = lastSlideDown[ tmp & 0x1f ]; else
                    if ( ( arg & 0xf0 ) == 0xf0 && ( arg & 0x0f ) ) { eff = EFF_FINE_SLIDE_DOWN; arg &= 0x0f; } else
                    if ( ( arg & 0xf0 ) == 0xe0 && ( arg & 0x0f ) ) { eff = EFF_EXTRA_SLIDE_DOWN; arg &= 0x0f; } else
                    eff = EFF_SLIDE_DOWN;
                    lastSlideDown[ tmp & 0x1f ] = eff;
                    break;
                  case 'F':
                    if ( !arg ) eff = lastSlideUp[ tmp & 0x1f ]; else
                    if ( ( arg & 0xf0 ) == 0xf0 && ( arg & 0x0f ) ) { eff = EFF_FINE_SLIDE_UP; arg &= 0x0f; } else
                    if ( ( arg & 0xf0 ) == 0xe0 && ( arg & 0x0f ) ) { eff = EFF_EXTRA_SLIDE_UP; arg &= 0x0f; } else
                    eff = EFF_SLIDE_UP;
                    lastSlideUp[ tmp & 0x1f ] = eff;
                    break;
                  case 'G': eff = EFF_SLIDE_TO; break;
                  case 'H': eff = EFF_VIBRATO; break;
                  case 'I': eff = EFF_TREMOR; break;
                  case 'J': eff = EFF_ARPEG; break;
                  case 'K': eff = EFF_VIBRA_AND_VOL; break;
                  case 'L': eff = EFF_PORT_AND_VOL; break;
                  case 'O': eff = EFF_SET_OFFSET; break;
                  case 'Q': eff = EFF_RETRIGGER; break;
                  case 'R': eff = EFF_TREMOLO; break;
                  case 'S':
                    tmp1 = ( arg & 0xf0 ) >> 4;
                    arg &= 0x0f;
                    switch ( tmp1 ) {
                      case 0: eff = EFF_FILTER; break;
                      case 1: eff = EFF_GLISSANDO; break;
                      case 2: eff = EFF_FINETUNE; break;
                      case 3: eff = EFF_VIBRATO_WAVE; break;
                      case 4: eff = EFF_TREMOLO_WAVE; break;
                      case 8: eff = EFF_SET_PAN; break;
                      case 0x0a: eff = 0; break;
                      case 0x0b: eff = EFF_PATTERN_LOOP; break;
                      case 0x0c: eff = EFF_CUT_NOTE; break;
                      case 0x0d: eff = EFF_DELAY_NOTE; break;
                      case 0x0e: eff = EFF_PATTERN_DELAY; break;
                      default: fatal( "loadS3M: effect 'S' - bad command 0x%x", tmp1 );
                    }
                    break;
                  case 'T': eff = EFF_TEMPO; break;
                  case 'U': eff = EFF_FINE_VIBRATO; break;
                  case 'V': eff = EFF_GLOBAL_VOL; break;
                  case 'X': eff = EFF_SET_PAN_DMP; break;
                  case 'Z': eff = EFF_SET_PAN; break;
                  default:
                    wprintf( "loadS3M: warning - bad effect '%c'[0x%x]/0x%x pos=0x%x\n", eff + 'A' - 1, eff, arg, pos );
                }
#if 0
	      if ( ( tmp & 0x1f ) != 0 ) continue;
#endif
#if 0
              dprintf( "pos: 0x%x, chn: %d, note: %d/%d, ins: %d, vol: %d, eff: 0x%x, arg: 0x%x [pos=0x%x,len=0x%x]\n",
              			pos, tmp & 0x1f, note >> 4, note & 0x0f, instr, vol, eff, arg, pos, len );
#endif
              if ( instr != 255 &&
                   ( !songInstrs[ instr ] ||
                     !songInstrs[ instr ] -> used || 
                     instr >= songInstr ) )
                continue;
              if ( ( tmp & 0x1f ) >= songChannels ) continue;
              if ( note != 255 && note != 254 && ( ( note >> 4 ) > 7 || ( note & 0x0f ) >= 12 ) )
                fatal( "loadS3M: wrong note %d/%d, pattern %d, pos 0x%x\n", note >> 4, note & 0x0f, pattern, pos );
#ifdef 0
	      if ( ( tmp & 0x1f ) != 2 ) continue;
#endif
              convAddNote( tmp & 0x1f,
              		   instr,
              		   note != 255 && note != 254 ? ( ( note >> 4 ) * 12 ) + ( note & 0x0f ) : 255,
              		   vol,
              		   eff,
              		   arg,
              		   0, 0 );
            }
          convDoneRow();
        }
      if ( pos != len )
        fatal( "loadS3M: pos != len\n" );
      convDonePattern();
    }

  free( rbuf1 );
  free( patterns );
  free( instruments );
  pprintf( "Done.\n" ); 

  if ( freqType == FTYPE_NOTHING ) freqType = FTYPE_S3M;
  currentPalFreq = 0;
}
