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

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

static byte basicEffTable[ 16 ] = {
  EFF_ARPEG,
  EFF_SLIDE_UP,
  EFF_SLIDE_DOWN,
  EFF_SLIDE_TO,
  EFF_VIBRATO,
  EFF_PORT_AND_VOL,
  EFF_VIBRA_AND_VOL,
  EFF_TREMOLO,
  EFF_SET_PAN_255,
  EFF_SET_OFFSET,
  EFF_VOL_SLIDE,
  EFF_PATTERN_JUMP,
  EFF_VOLUME,
  EFF_PATTERN_BREAK,
  0x7f,			/* wrong - EXTENDED */
  EFF_MOD_SPEED,
};

static byte extendedEffTable[ 16 ] = {
  EFF_FILTER,
  EFF_FINE_SLIDE_UP,
  EFF_FINE_SLIDE_DOWN,
  EFF_GLISSANDO,
  EFF_VIBRATO_WAVE,
  EFF_FINETUNE,
  EFF_PATTERN_LOOP,
  EFF_TREMOLO_WAVE,
  0x7f,			/* wrong - maybe PAN - but which? */
  EFF_RETRIGGER,
  EFF_FINE_VOL_UP,
  EFF_FINE_VOL_DOWN,
  EFF_CUT_NOTE,
  EFF_DELAY_NOTE,
  EFF_PATTERN_DELAY,
  0x7f,			/* wrong - unused */
};

void loadXM( FILE *file, char *filename )
{
  byte *header, *pheader;
  word version, flags;
  long size;
  word patterns, instruments;
  byte rbuf[ 4 ];
  int i, j, ident;

  pprintf( "Loading Fast Tracker II file %s... ", filename );
  fflush( stdout );

  if ( ( header = malloc( 60 + 4 ) ) == NULL )
    fatal( "loadXM: header alloc" );
  if ( fread( header, 60 + 4, 1, file ) != 1 )
    fatal( "loadXM: header read" );

  if ( strncmp( header, "Extended Module: ", 17 ) )
    fatal( "loadXM: corrupted file" );
  strncpy( songName, &header[ 17 ], 20 ); songName[ 20 ] = 0;
  version = header[ 58 ] | ( header[ 59 ] << 8 );
  if ( version < 0x104 )
    {
      songError = 1;
      dprintf( "\nObsolete version %x.%02x of eXtended Module isn't supported...\n", version >> 8, version & 0xff );
      dprintf( "Convert this song in new Fast Tracker II (load/save) and try again...\n" );
      free( header );
      return;
    }
  sprintf( songType, "XM (%x.%02x)", version >> 8, version & 0xff );
  size = header[ 60 ] | ( header[ 61 ] << 8 ) |
         ( header[ 62 ] << 16 ) | ( header[ 63 ] << 8 );
  free( header );

  if ( ( header = malloc( size - 4 ) ) == NULL )
    fatal( "loadXM: header alloc" );
  if ( fread( header, size - 4, 1, file ) != 1 )
    fatal( "loadXM: header read" );
  songMaxOrder = ( header[ 0 ] | ( header[ 1 ] << 8 ) ) - 1;
  songRestart = header[ 2 ] | ( header[ 3 ] << 8 );
  songChannels = header[ 4 ] | ( header[ 5 ] << 8 );
  patterns = header[ 6 ] | ( header[ 7 ] << 8 );
  songInstr = instruments = header[ 8 ] | ( header[ 9 ] << 8 );
  flags = header[ 10 ] | ( header[ 11 ] << 8 );
  if ( ( header[ 10 ] & ~1 ) != 0 || header[ 11 ] )
    wprintf( "\nloadXM: warning - unknown flags: 0x%x\n", flags );
  if ( freqType == FTYPE_NOTHING )
    freqType = flags & 1 ? FTYPE_LINEAR : FTYPE_AMIGA;
  songPlaySpeed = header[ 12 ] | ( header [ 13 ] << 8 );
  songTempo = header[ 14 ] | ( header[ 15 ] << 8 );

  for ( i = 0; i <= songMaxOrder; i++ )
    songOrder[ i ] = header[ 16 + i ];

#ifdef 0
  songLoopFlags = LFLG_WHOLE;
  songMaxOrder = 0;
  songOrder[ 0 ] = songOrder[ 1 ];
/*
  songOrder[ 0 ] = 1;
*/
#endif
  
  for ( i = 0; i < patterns; i++ )
    {
      unsigned short row, rows, channel;
      int pos;
    
      convInitPattern();
      if ( fread( rbuf, 4, 1, file ) != 1 )
        fatal( "loadXM: size of pattern header read (scan)" );
      size = rbuf[ 0 ] | ( rbuf[ 1 ] << 8 ) |
             ( rbuf[ 2 ] << 16 ) | ( rbuf[ 3 ] << 24 );
      if ( ( pheader = malloc( size - 4 ) ) == NULL )
        fatal( "loadXM: pattern header alloc" );
      if ( fread( pheader, size - 4, 1, file ) != 1 )
        fatal( "loadXM: pattern header read" );
      if ( pheader[ 0 ] != 0 )
        fatal( "loadXM: unknown pattern packing type" );
      rows = pheader[ 1 ] | ( pheader[ 2 ] << 8 );
      size = pheader[ 3 ] | ( pheader[ 4 ] << 8 );
      free( pheader );
      if ( size <= 0 ) 
        {
          convDonePattern();
          continue;
        }
      if ( ( pheader = malloc( size ) ) == NULL )
        fatal( "loadXM: pattern alloc" );
      if ( fread( pheader, size, 1, file ) != 1 )
        fatal( "loadXM: pattern read" );
      for ( row = 0, pos = 0; row < rows; row++ )
        {
          convInitRow();
          for ( channel = 0; channel < songChannels; channel++ )
            {
              byte note = 255, instr = 255, vol = 255, eff = 0, arg = 0;
              byte eff1 = 0, arg1 = 0;
            
              byte tmp = pheader[ pos++ ];
              if ( tmp & 0x80 )
                {
                  if ( ( tmp & 0x7f ) == 0 ) continue;
                  if ( tmp & 1 ) note = pheader[ pos++ ];
                  if ( tmp & 2 ) instr = pheader[ pos++ ];
                  if ( tmp & 4 ) vol = pheader[ pos++ ];
                  if ( tmp & 8 ) eff = pheader[ pos++ ];
                  if ( tmp & 0x10 ) arg = pheader[ pos++ ];
                  if ( tmp & 0x60 )
                    fatal( "loadXM: uknown packing bits (0x%x)\n", tmp );
                }
               else
                {
                  note = tmp;
                  instr = pheader[ pos++ ];
                  vol = pheader[ pos++ ];
                  eff = pheader[ pos++ ];
		  arg = pheader[ pos++ ];                  
                }
              if ( instr != 255 )
                if ( instr ) instr--; else instr = 0;
              if ( note == 97 ) note = 254; else	/* keyoff */
                if ( note > 0 && note != 255 ) note--;
              if ( !vol || vol == 255 ) 
                vol = 255;
               else
                {
                  if ( vol > 0 && vol < 0x10 )
                    fatal( "loadXM: unknown volume byte" );
                  if ( vol >= 0x10 && vol < 0x60 ) vol -= 0x10; else
                    {
                      arg1 = vol; arg1 &= 0x0f; vol &= 0xf0;
                      if ( vol == 0x60 ) eff1 = EFF_VOL_SLIDE; else
                      if ( vol == 0x70 ) { eff1 = EFF_VOL_SLIDE; arg1 <<= 4; } else
                      if ( vol == 0x80 ) eff1 = EFF_FINE_VOL_DOWN; else
                      if ( vol == 0x90 ) eff1 = EFF_FINE_VOL_UP; else
                      if ( vol == 0xa0 ) eff1 = EFF_VIBRATO_SPEED; else
                      if ( vol == 0xb0 ) eff1 = EFF_VIBRATO; else
                      if ( vol == 0xc0 ) eff1 = EFF_SET_PAN; else
                      if ( vol == 0xd0 ) eff1 = EFF_PAN_SLIDE; else
                      if ( vol == 0xe0 ) { eff1 = EFF_PAN_SLIDE; arg1 <<= 4; } else
                                         eff1 = EFF_SLIDE_TO;
                      vol = 255;
                    }
                }
              if ( eff )
                {
                  byte oldEff = eff;
                  
                  if ( eff == 0x0e ) { eff = extendedEffTable[ arg >> 4 ]; arg &= 0x0f; } else
                  if ( eff < 0x10 ) eff = basicEffTable[ eff ]; else
                  switch ( eff ) {
                    case ( 'G' - 'A' ) + 10: eff = EFF_GLOBAL_VOL; arg <<= 1; break;
                    case ( 'H' - 'A' ) + 10: eff = EFF_GLOBAL_VOL_SLIDE; break;
                    case ( 'K' - 'A' ) + 10: eff = EFF_KEY_OFF; break;
                    case ( 'L' - 'A' ) + 10: eff = EFF_ENVELOPE_POS; break;
                    case ( 'P' - 'A' ) + 10: eff = EFF_PAN_SLIDE; break;
                    case ( 'R' - 'A' ) + 10: eff = EFF_MRETRIGGER; break;
                    case ( 'T' - 'A' ) + 10: eff = EFF_TREMOR; break;
                    case ( 'X' - 'A' ) + 10:
                      if ( ( arg & 0xf0 ) == 0x10 ) { eff = EFF_EXTRA_SLIDE_UP; arg &= 0x0f; break; } else
                      if ( ( arg & 0xf0 ) == 0x20 ) { eff = EFF_EXTRA_SLIDE_DOWN; arg &= 0x0f; break; }
                    default:
                      wprintf( "loadXM: unknown effect 0x%x/0x%x (old 0x%x)\n", eff, arg, oldEff );
                      eff = arg = 0;
                  }
                  if ( eff == 0x7f )
                    fatal( "loadXM: unknown effect 0x%x/0x%x (old 0x%x)\n", eff, arg, oldEff );
                }
               else
                arg = 0;
#ifdef 0
	      if ( channel != 5 ) continue;
#endif
#ifdef 0
	      dprintf( "channel %d, instr %d, note %d\n", channel, instr, note );
#endif
              convAddNote( channel, instr, note, vol, eff, arg, eff1, arg1 );
            }
          convDoneRow();
        }
      free( pheader );
      convDonePattern();
    }
  free( header );

  gusSetupChannels();
    
  /* it's time to load instruments */
  ident = 0;
  for ( i = 0; i < instruments; i++ )
    {
      struct INSTR *is = newInstrument( i );
      word smpNum, smps;
      
      if ( fread( rbuf, 4, 1, file ) != 1 )
        fatal( "loadXM: instrument header size read" );
      size = rbuf[ 0 ] | ( rbuf[ 1 ] << 8 ) |
             ( rbuf[ 2 ] << 16 ) | ( rbuf[ 3 ] << 24 );
      if ( ( pheader = malloc( size - 4 ) ) == NULL )
        fatal( "loadXM: instrument header alloc" );
      if ( fread( pheader, size - 4, 1, file ) != 1 )
        fatal( "loadXM: instrument header read" );
/*
      if ( pheader[ 22 ] != 0 && pheader[ 22 ] != 's' )
        fatal( "loadXM: uknown instrument type 0x%x [%c]", pheader[ 22 ], pheader[ 22 ] );
*/
      smps = pheader[ 23 ] | ( pheader[ 24 ] << 8 );
#ifdef 0
      dprintf( "name: %s, itype: 0x%x, smps: %d\n", pheader, pheader[ 22 ], smps );
#endif
      if ( smps > 0 )
        {
          is -> used = 1;
          strncpy( is -> name, pheader, 22 ); is -> name[ 22 ] = 0;
          if ( smps > MAX_SAMPLES )
            fatal( "loadXM: too much samples per instrument (%d)", smps );
          size = pheader[ 25 ] | ( pheader[ 26 ] << 8 ) |
                 ( pheader[ 27 ] << 16 ) | ( pheader[ 28 ] << 24 );
          memmove( is -> sampleNumber, &pheader[ 29 ], 96 );
          for ( j = 0; j < 12; j++ )
            {
              is -> volEnvelope[ j ].pos = pheader[ 125 + ( j * 4 ) ] |
                                             pheader[ 125 + ( j * 4 ) + 1 ];
              is -> volEnvelope[ j ].val = pheader[ 125 + ( j * 4 ) + 2 ] |
                                             pheader[ 125 + ( j * 4 ) + 3 ];
            }
          for ( j = 0; j < 12; j++ )
            {
              is -> panEnvelope[ j ].pos = pheader[ 173 + ( j * 4 ) ] |
                                             pheader[ 173 + ( j * 4 ) + 1 ];
              is -> panEnvelope[ j ].val = pheader[ 173 + ( j * 4 ) + 2 ] |
                                             pheader[ 173 + ( j * 4 ) + 3 ];
            }
          is -> volPoints = pheader[ 221 ];
          is -> panPoints = pheader[ 222 ];
          is -> volSustain = pheader[ 223 ];
          is -> volLoopStart = pheader[ 224 ];
          is -> volLoopEnd = pheader[ 225 ];
          is -> panSustain = pheader[ 226 ];
          is -> panLoopStart = pheader[ 227 ];
          is -> panLoopEnd = pheader[ 228 ];
          is -> volType = pheader[ 229 ];
          is -> panType = pheader[ 230 ];
          is -> vibratoType = pheader[ 231 ];
          is -> vibratoSweep = pheader[ 232 ];
          is -> vibratoDepth = pheader[ 233 ];
          is -> vibratoRate = pheader[ 234 ];
          is -> fadeout = pheader[ 235 ] | ( pheader[ 236 ] << 8 );
	  if ( ( header = malloc( size ) ) == NULL )
	    fatal( "loadXM: sample header alloc" );
          for ( smpNum = 0; smpNum < smps; smpNum++ )
            {
              struct SAMPLE *smp = newSample( i, smpNum );
              
              if ( fread( header, size, 1, file ) != 1 )
	        fatal( "loadXM: sample header read" );
              strncpy( smp -> name, &header[ 18 ], 22 ); smp -> name[ 22 ] = 0;
              smp -> length = header[ 0 ] | ( header[ 1 ] << 8 ) |
                              ( header[ 2 ] << 16 ) | ( header[ 3 ] << 24 );
              smp -> lstart = header[ 4 ] | ( header[ 5 ] << 8 ) |
                              ( header[ 6 ] << 16 ) | ( header[ 7 ] << 24 );
              smp -> lend   = header[ 8 ] | ( header[ 9 ] << 8 ) |
                              ( header[ 10 ] << 16 ) | ( header[ 11 ] << 24 );
              smp -> lend += smp -> lstart;
              smp -> volume = header[ 12 ];
              smp -> finetune = (signed char)header[ 13 ];
              smp -> type = SMP_TYPE_DELTA | SMP_TYPE_PAN_USED;
              if ( ( header[ 14 ] & 3 ) == 0 ) smp -> lstart = smp -> lend = 0; else
              if ( ( header[ 14 ] & 3 ) == 2 ) smp -> type |= SMP_TYPE_PINGPONG_LOOP;
              if ( header[ 14 ] & 0x10 ) smp -> type |= SMP_TYPE_16BIT;
              smp -> pan = header[ 15 ];
              smp -> relativeNote = header[ 16 ];
              smp -> used = smp -> length > 0;
              smp -> freqC4 = NTSC_FREQ;
#ifdef 0
              if ( i == 19 )
                {
                  dprintf( "smp name: %s, l: 0x%x, s: 0x%x, e: 0x%x\n", smp -> name, smp -> length, smp -> lstart, smp -> lend );
                  dprintf( " -- volume: 0x%x, finetune: 0x%x, type: 0x%x, pan: %d, rnote: %d\n",
               			smp -> volume, smp -> finetune, smp -> type, smp -> pan, smp -> relativeNote );
                }
#endif
            }
          for ( smpNum = 0; smpNum < smps; smpNum++ )
            {
              struct SAMPLE *smp = is -> samples[ smpNum ];
              
              if ( !smp -> used ) continue;
              gusDownload( file, i, smpNum );
              if ( !smp -> length )
                {
                  smp -> used = 0;
                }
               else
                smp -> ident = ident++;
            }
          free( header );
        }
      free( pheader );
    }
  pprintf( "Done.\n" );
  currentPalFreq = 0;
  songGlobalVolume = 128;
}
