/*
 * $Id: sidtune.cxx,v 1.5 1996/04/02 19:36:40 ms Exp $
 *
 *
 * $Log: sidtune.cxx,v $
 * Revision 1.5  1996/04/02 19:36:40  ms
 * Look into 'sidtune.h'
 *
 * Revision 1.4  1996/03/27 01:03:35  ms
 * TEST: Added ability to fake a plain memory for PlaySID compatibility
 *
 * Revision 1.3  1996/03/25 07:47:48  ms
 * Added INFO file support
 * Added SID file support
 * Applied a few conditionals for 16-bit compatibility
 *
 * Revision 1.2  1996/03/06 15:14:24  ms
 * Fixed forcentsc speed setting
 *
 * Revision 1.1  1996/02/28 01:05:23  ms
 * Initial revision
 *
 */

#include <iostream.h>
#include <iomanip.h>
#include <fstream.h>
#include <string.h>
#if defined(__BORLANDC__) && !defined(__WIN32__) 
  #include <limits.h>
#endif

#include "mytypes.h"
#include "sidtune.h"
#include "6510.h"
#include "6581.h"
#include "pp.h"


const char text_songnumberexceed[] = "WARNING: Selected song number was too high";
const char text_unrecognizedformat[] = "ERROR: Could not determine file format";
const char text_nodescriptionfile[] = "ERROR: Did not found the corresponding description file";
const char text_nodatafile[] = "ERROR: Did not found the corresponding data file";
const char text_notenoughmemory[] = "ERROR: Not enough memory";
const char text_cantloadfile[] = "ERROR: Cannot load input file";
const char text_cantopenfile[] = "ERROR: Cannot open file for binary input";
const char text_datatoolong[] = "ERROR: Input data too long";
const char text_filetoolong[] = "ERROR: Input file size too big";
const char text_cantrestartcin[] = "ERROR: Cannot restart song when retrieving data from stdin";
const char text_50Hz[] = "50 Hz (PAL)";
const char text_60HzTimer[] = "60 Hz/Timer-Speed (PAL)";
const char text_forcentsc[] = "Forcing 60 Hz/Timer-Speed (NTSC)";
const char text_noerrors[] = "No errors";
const char text_na[] = "N/A";
// default file extensions
char text_sidext[] = ".sid";
#if defined(__MSDOS__) || defined(_Windows)
 char text_dataext[] = ".dat";
 char text_infoext[] = ".inf";
#else
 char text_dataext[] = ".data";
 char text_infoext[] = ".info";
#endif


char* filenamewithoutpath( char* s)
{
  int last_slash_pos = -1;
  for ( unsigned pos = 0; pos < strlen(s); pos++ )  {
#if defined(__MSDOS__) || defined(__WIN32__) || defined(_Windows) 
	if ( s[pos] == '\\' )
#else
	if ( s[pos] == '/' )
#endif	  
	  last_slash_pos = pos;
  }
  return( &s[last_slash_pos +1] );
}


char* fileextoffilename( char* s)
{
  int last_dot_pos = -1;
  unsigned pos = 0;
  for ( pos = 0; pos < strlen(s); pos++ )  {
	if ( s[pos] == '.' )
	  last_dot_pos = pos;
  }
  if ( last_dot_pos == (-1))
	last_dot_pos = pos;
  return( &s[last_dot_pos] );
}


sidtune::sidtune( char* fname )
{
  // filename "-" is synonym for standard input
  if ( strcmp( fname, "-" ) == 0 )  {
    safeconstructor();
	stdinconstructor();
  }
  else  {
	safeconstructor();
	filesconstructor( fname );
  }
}


sidtune::sidtune( char* fname, char* datext, char* sidext, char* infext )
{
  // filename "-" is synonym for standard input
  if ( strcmp( fname, "-" ) == 0 )  {
    safeconstructor();	
	stdinconstructor();
  }
  else  {
    safeconstructor();
	if ( datext != 0 )
	  datafileext = datext;
	if ( sidext != 0 )
	  sidfileext = sidext;
	if ( infext != 0 )
	  infofileext = infext;
	filesconstructor( fname );
  }
}


sidtune::~sidtune()
{
  if ( datafilename != 0 )
	delete( datafilename );
  status = MPU_memfree();
}


sidtune::operator int()  
{  
  return( status );  
}


const char* sidtune::returnstatusstring()  
{
  return( statusstring );  
}


const char* sidtune::returnformatstring()
{
  return( formatstring );
}


const char* sidtune::returnspeedstring()
{
  return( speedstring );  
}


int sidtune::initializesong( int selectedsong = 0 )
{
  if ( status )  {
	// do not clear memory and reload data if from stdin
	if ( !stdinflag )  {
	  statusstring = text_na;

	  // clear both C64 memory regions
	  c64memclear();
	
	  // reload data to ensure the data is unmodified
	  if ( loadfile( datafilename, c64mem1, 65536 ) == 0 )
		return(status);
	}
	
	// copy data to its correct destination and clear memory around it
	udword datalen = datafilelen - fileoffset;
	if ( loadaddr < fileoffset )
	  for ( uword l = 0; l < datalen; l++ )
	    c64mem1[ loadaddr +l ] = c64mem1[ fileoffset +l ];
	else if ( loadaddr > fileoffset )
	  for ( uword l = datalen; l > 0; l-- )
  	    c64mem1[ loadaddr +l -1 ] = c64mem1[ fileoffset +l -1 ];
	for ( udword m = 0; m < loadaddr; m++ )  c64mem1[m] = 0;
	for ( udword n = loadaddr + datalen; n < 65536; n++ )  c64mem1[n] = 0;
	
	// determine and set starting song number
	if ( selectedsong <= 0 )  selectedsong = startsong;
	else if ( selectedsong > songs )  {
	  statusstring = text_songnumberexceed;
	  selectedsong = startsong;
	}
	currentsong = selectedsong;
	
	if ( forcentscflag )
	  speedstring = text_forcentsc;
	else  {
	  songspeed = ( speed >> ( selectedsong -1)) & 1;
	  if ( songspeed == 0 )  
		speedstring = text_50Hz;
	  else //if ( songspeed == 1 )  
		speedstring = text_60HzTimer;
	}
	ubyte* c64mem2cp = c64mem2;
	if ( playsidflag )
	  c64mem2 = c64mem1;
	c64memreset( forcentscflag );
	
	// replay routine init and play defaults
	if ( musplayer )
	  MUS_installplayer();
	
	ubyte ta, tx, ty;
	ty = ( tx = ( ta = selectedsong -1 ));
	if ( playsidflag )  {	
	  //retcode =
	  interpreter( initaddr, 5, ta, 0, 0, TRUE );
	  playramrom = 5;
	}
	else  {
	  //retcode =
	  interpreter( initaddr, c64ramrom(initaddr), ta, tx, ty, FALSE );
	  playramrom = c64ramrom(playaddr);
	}
	sidsetreplayingspeed( songspeed, forcentscflag );
	
	if ( playsidflag )
	  c64mem2 = c64mem2cp;
  } // if status
  return(status);
}


void sidtune::setplaysidflag( int n )
{
  if ( n )
	playsidflag = TRUE;
  else
	playsidflag = FALSE;
}


int sidtune::returnplaysidflag()
{
  return( playsidflag );
}


void sidtune::setforcentsc( int n )
{
  if ( n )
	forcentscflag = TRUE;
  else
	forcentscflag = FALSE;
}


int sidtune::returnforcentsc()
{
  return( forcentscflag );
}


int sidtune::returnnumberofsongs()
{
  if ( status )
	return( songs );
  else
	return( 0 );
}


int sidtune::returnstartsong()
{
  if ( status )
	return( startsong );
  else
	return( 0 );
}


int sidtune::returncurrentsong()
{
  if ( status )
	return( currentsong );
  else
	return( 0 );
}


uword sidtune::returnloadaddress()
{
  if ( status )
	return( loadaddr );
  else
	return( 0 );
}


uword sidtune::returninitaddress()
{
  if ( status )
	return( initaddr );
  else
	return( 0 );
}


uword sidtune::returnplayaddress()
{
  if ( status )
	return( playaddr );
  else
	return( 0 );
}


char* sidtune::returnnameinfo()
{
  if ( status )
	return( &infostring[0][0] );
  else
	return( 0 );
}


char* sidtune::returnauthorinfo()
{
  if ( status )
	return( &infostring[1][0] );
  else
	return( 0 );
}


char* sidtune::returncopyrightinfo()
{
  if ( status )
	return( &infostring[2][0] );
  else
	return( 0 );
}


char* sidtune::returninfostring( int n )
{
  // valid 'n' is out of [0,1,...,numberofinfostrings)
  if ( status && ( n >= 0 ) && ( n < numberofinfostrings ))
	return( &infostring[n][0] );
  else
	return( 0 );
}


int sidtune::returnnumberofinfostrings()
{
  if ( status )
	return( numberofinfostrings );
  else
	return( 0 );
}


streampos sidtune::returndatafilelen()
{
  if ( status )
	return( datafilelen );
  else
	return( 0 );
}


// private members

streampos sidtune::loadfile( char* fname, void* buffer, udword buflen )
{
  streampos filelen = 0;
  status = FALSE;
  // open binary input file stream at end of file
#ifdef __BORLANDC__
  ifstream myin( fname, ios::in | ios::binary | ios::ate | ios::nocreate );
#else  
  ifstream myin( fname, ios::in | ios::bin | ios::ate | ios::nocreate );
#endif
  if ( !myin ) 
	statusstring = text_cantopenfile;
  else if (( filelen = myin.tellg() ) > (streampos)buflen )
	statusstring = text_filetoolong;
  else  {  
	// check for PowerPacker compression: load and decompress, if PP20 file
	int ppret = depp( myin, (ubyte*)buffer, buflen );
	if ( ppret == TRUE )  {
	  // decompression successful, use uncompressed datafilelen
	  filelen = ppuncompressedlen();
	  statusstring = pperrorstring;
	  status = TRUE;
	}
	else if ( ppret == (-1) )
	  // an error occured while decompressing
	  statusstring = pperrorstring;
	else if ( ppret == FALSE )  {
	  // load uncompressed file
	  myin.seekg(0, ios::beg);
#if defined(__BORLANDC__) && !defined(__WIN32__)
	  // this is not able to read in files longer than 2*0x7fff
	  // should be fixed when longer files are required !!!
	  if ( filelen > SHRT_MAX )  {
		myin.read( (ubyte*)buffer, SHRT_MAX );
		myin.read( (ubyte*)buffer+SHRT_MAX, filelen-SHRT_MAX );
	  }
	  else
		myin.read( (ubyte*)buffer, filelen );
#else
	  myin.read( (ubyte*)buffer, filelen );
#endif
	  if ( myin.bad() )
		statusstring = text_cantloadfile;
	  else  {
		statusstring = text_noerrors;
		status = TRUE;
	  }
	}
	myin.close();
  }
  return( filelen );
}


void sidtune::safeconstructor()
{
  // initialize the object with some safe defaults
  status = FALSE;
  statusstring = text_na;
  datafilename = 0;
  datafilelen = 0;
  formatstring = text_na;
  speedstring = text_na;
  stdinflag = FALSE;
  playsidflag = FALSE;
  forcentscflag = FALSE;
  
  loadaddr = ( initaddr = ( playaddr = 0 ));
  songs = ( startsong = ( currentsong = 0 ));
  speed = 0;
  fileoffset = 0;
  musplayer = FALSE;
  songspeed = 0;
  playramrom = 7;
 
  for ( int i = 0; i < 32; i++ )  {
	infostring[0][i] = 0;
	infostring[1][i] = 0;
	infostring[2][i] = 0;
	infostring[3][i] = 0;
	infostring[4][i] = 0;
  }
  numberofinfostrings = 0;
  
  datafileext = text_dataext;
  sidfileext = text_sidext;
  infofileext = text_infoext;
}

  
void sidtune::stdinconstructor()
{
  stdinflag = TRUE;
  // allocate memory for the interpreter
  if ( MPU_memalloc() )  {
	uword i = 0;
	ubyte datb;
	while ( cin.get(datb) )  c64mem1[i++] = datb;
	if (( datafilelen = cin.tellg() ) > 65536 )
	  statusstring = text_datatoolong;
	else  {
	  statusstring = text_noerrors;
	  status = TRUE;
	  // here test for the two possible single file formats
	  // PSID and MUS
	  if ( PSID_filesupport( c64mem1, datafilelen ))  {
		statusstring = text_noerrors;
		status = TRUE;
	  }
	  else if ( MUS_filesupport( c64mem1, datafilelen ))  {
		statusstring = text_noerrors;
		status = TRUE;
	  }
	  else  {
		// no further single-file-formats
		formatstring = text_na;
		statusstring = text_unrecognizedformat;
		status = FALSE;
	  }
	} // else datafilelen
  } // if MPU_memalloc
  else
	statusstring = text_notenoughmemory;
} 

  
void sidtune::filesconstructor( char* fname )
{
  // allocate memory for the interpreter
  if ( MPU_memalloc() )  {
	statusstring = text_noerrors;
	// load raw or powerpacked data
	if (( datafilelen = loadfile( fname, c64mem1, 65536 )) != 0 )  {
	  if ( PSID_filesupport( c64mem1, datafilelen ))  {
		datafilename = strdup( fname );
		status = TRUE;
		return;
	  }
	  else if ( MUS_filesupport( c64mem1, datafilelen ))  {
		datafilename = strdup( fname );
		status = TRUE;
		return;
	  }
	  
	  
	  else  {
		// Support for multiple-files formats:
		// We cannot simply try to load additional files, if a 
		// description file was specified. It would work, but is
		// error-prone. Imagine a filename mismatch or more than
		// one description file in another format.
		streampos secondfilelen = 0;
		char* secondfname = 0;
		char* extptr = 0;
		// First we see if 'fname' could be a raw data file. In this case 
		// we have to find the corresponding description file.

		
		if ( !SID_filesupport( c64mem2, 65536, c64mem1, datafilelen ) &&
		     !INFO_filesupport( c64mem2, 65536, c64mem1, datafilelen ) )  {
		  // Assuming 'fname' to hold the name of the raw data file, we 
		  // now create the name of a description file (secondfname) by
		  // appending various possible filename extensions
		  // get memory for a longer string, so we can append a file extension
		  secondfname = new char[strlen( fname ) +strlen( sidfileext )];
		  strcpy( secondfname, fname );
		  extptr = fileextoffilename( filenamewithoutpath( secondfname ));
		  strcpy( extptr, sidfileext );
		  if ( strcasecmp( fname, secondfname ) != 0 )  {
			// 1st data file was loaded into c64mem1
			// so we load the 2nd one into c64mem2 
			if (( secondfilelen = loadfile( secondfname, c64mem2, 65536 )) != 0 )  {
			  if ( SID_filesupport( c64mem1, datafilelen,
								    c64mem2, secondfilelen ))  {
				datafilename = strdup( fname );
				status = TRUE;
				delete( secondfname );
				return;
			  }
			}
		  }
		  // here we have to delete the previous object
		  delete( secondfname );
		  
		  // get memory for a longer string, so we can append a file extension
		  secondfname = new char[strlen( fname ) +strlen( infofileext )];
		  strcpy( secondfname, fname );
		  extptr = fileextoffilename( filenamewithoutpath( secondfname ));
		  strcpy( extptr, infofileext );
		  if ( strcasecmp( fname, secondfname ) != 0 )  {
			// 1st data file was loaded into c64mem1
			// so we load the 2nd one into c64mem2 
			if (( secondfilelen = loadfile( secondfname, c64mem2, 65536 )) != 0 )  {
			  if ( INFO_filesupport( c64mem1, datafilelen,
									 c64mem2, secondfilelen ))  {
				datafilename = strdup( fname );
				status = TRUE;
				delete( secondfname );
				return;
			  }
			}
		  }
		  delete( secondfname );
		  formatstring = text_na;
		  statusstring = text_nodescriptionfile;
		  status = FALSE;
		  return;
		} // end if not description file
	  
	  
		// Still unsuccessful ? Probably one put a description file name
		// into 'fname'. Assuming 'fname' to hold the name of a description
		// file, we now create the name of the data file and swap both used 
		// memory buffers (c64mem1 and 2) when calling the format support.
		// If it works, the second file is the data file.
		else if ( SID_filesupport( c64mem2, 65536, c64mem1, datafilelen ) || 
		          INFO_filesupport( c64mem2, 65536, c64mem1, datafilelen ))  {
		  // get memory for a longer string, so we can append a file extension
		  secondfname = new char[strlen( fname ) +strlen( datafileext )];
		  strcpy( secondfname, fname );
		  extptr = fileextoffilename( filenamewithoutpath( secondfname ));
		  strcpy( extptr, datafileext );
		  if ( strcasecmp( fname, secondfname ) != 0 )  {
			// 1st file was loaded into c64mem1
			// so we load the 2nd one into c64mem2 
			if (( secondfilelen = loadfile( secondfname, c64mem2, 65536 )) != 0 )  {
			  if ( SID_filesupport( c64mem2, secondfilelen,
								    c64mem1, datafilelen ))  {
				datafilelen = secondfilelen;
				datafilename = strdup( secondfname );
				status = TRUE;
				delete( secondfname );
				return;
			  }
			  else if ( INFO_filesupport( c64mem2, secondfilelen,
										 c64mem1, datafilelen ))  {
				datafilelen = secondfilelen;
				datafilename = strdup( secondfname );
				status = TRUE;
				delete( secondfname );
				return;
			  }
			} // end if load secondfile
   		  }
		  // here we have to delete the previous object
		  delete( secondfname );
		  
		  // get memory for a longer string, so we can append a file extension
		  secondfname = new char[strlen( fname ) +strlen( datafileext )];
		  strcpy( secondfname, fname );
		  extptr = fileextoffilename( filenamewithoutpath( secondfname ));
		  // last possible filename could be the filename without extension
		  strcpy( extptr, "" );
		  if ( strcasecmp( fname, secondfname ) != 0 )  {
			// 1st file was loaded into c64mem1
			// so we load the 2nd one into c64mem2 
			if (( secondfilelen = loadfile( secondfname, c64mem2, 65536 )) != 0 )  {
			  if ( SID_filesupport( c64mem2, secondfilelen,
								    c64mem1, datafilelen ))  {
				datafilelen = secondfilelen;
				datafilename = strdup( secondfname );
				status = TRUE;
				delete( secondfname );
				return;
			  }
			  else if ( INFO_filesupport( c64mem2, secondfilelen,
										 c64mem1, datafilelen ))  {
				datafilelen = secondfilelen;
				datafilename = strdup( secondfname );
				status = TRUE;
				delete( secondfname );
				return;
			  }
			} // end if load secondfile
   		  }
		  delete( secondfname );
		  formatstring = text_na;
		  statusstring = text_nodatafile;
		  status = FALSE;
		  return;
		} // end else if description file

		
		else  {
		  // here add support for further multiple-file formats
		  formatstring = text_na;
		  statusstring = text_unrecognizedformat;
		  status = FALSE;
		}
	  } // end else nosinglefile
	  
	  
	} // if loaddatafile
  } // if MPU_memalloc
  else
	statusstring = text_notenoughmemory;
} 
