// OpenCP Module Player
// copyright (c) '94-'98 Niklas Beisert <nbeisert@physik.tu-muenchen.de>
//
// SIDPlay SidTune class
//
// revision history: (please note changes here)
//  -kb980717  Tammo Hinrichs <opencp@gmx.net>
//    -first release
//

// $Date: 1998/12/20 12:53:53 $
//
// Information on usage of this class in "include/sidtune.h".
//

#include <string.h>

#include "sidtune.h"
#include "myendian.h"
#include "binfile.h"


#define FILE_HDR_SIZE	128


static const char text_songNumberExceed[] = "WARNING: Selected song number was too high";
static const char text_unrecognizedFormat[] = "ERROR: Could not determine file format";
static const char text_cantLoadFile[] = "ERROR: Could not load input file";
static const char text_dataTooLong[] = "ERROR: Music data size exceeds C64 memory";
static const char text_PAL_VBI[] =  "50 Hz VBI (PAL)     ";
static const char text_PAL_CIA[] =  "CIA 1 Timer A (PAL) ";
static const char text_NTSC_VBI[] = "60 Hz VBI (NTSC)    ";
static const char text_NTSC_CIA[] = "CIA 1 Timer A (NTSC)";
static const char text_noErrors[] = "No errors";
static const char text_na[] = "N/A";


// ------------------------------------------------- constructors, destructor

sidTune::sidTune()
{
	safeConstructor();
}

sidTune::sidTune( binfile &f )
{
	safeConstructor();
	filesConstructor( f );
}


sidTune::~sidTune()
{
	safeDestructor();
}


// -------------------------------------------------- public member functions

char sidTune::open( binfile &f )
{
	safeDestructor();
	safeConstructor();
	filesConstructor(f);
	return status;
}


char sidTune::getInfo( sidTuneInfo &outInfo )
{
	outInfo = info;
	return true;
}


// First check, whether a song is valid. Then copy any song-specific
// variable information such a speed/clock setting to the info structure.
//
// This is a private member function. It is used only by player.cpp.
uword sidTune::selectSong(uword selectedSong)
{
	// Determine and set starting song number.
	if (selectedSong == 0)
	{
		selectedSong = info.startSong;
	}
	else if ((selectedSong > info.songs) || (selectedSong > classMaxSongs))
	{
		info.statusString = text_songNumberExceed;
		selectedSong = info.startSong;
	}
	// Retrieve song speed definition.
	info.songSpeed = songSpeed[selectedSong-1];
	// Assign song speed description string depending on clock speed.
	if (info.clockSpeed == SIDTUNE_CLOCK_PAL)
	{
		if (info.songSpeed == SIDTUNE_SPEED_VBI_PAL)
		{
			info.speedString = text_PAL_VBI;
		}
		else
		{
			info.speedString = text_PAL_CIA;
		}
	}
	else  //if (info.clockSpeed == SIDTUNE_CLOCK_NTSC)
	{
		if (info.songSpeed == SIDTUNE_SPEED_VBI_NTSC)
		{
			info.speedString = text_NTSC_VBI;
		}
		else
		{
			info.speedString = text_NTSC_CIA;
		}
	}
	return (info.currentSong=selectedSong);
}


void sidTune::setIRQaddress(uword address)
{
	info.irqAddr = address;
}


// ------------------------------------------------- private member functions

char sidTune::placeSidTuneInC64mem( ubyte huge * c64buf )
{
	if (status)
	{
		// Check the size of the data.
		if ( info.c64dataLen > 65536 )
		{
			info.statusString = text_dataTooLong;
			return (status = false);
		}
		else
		{
			udword endPos = info.loadAddr + info.c64dataLen;
			if (endPos <= 65536)
			{
				// Load data from file to the correct destination.
				file->seek(fileOffset);
				file->read((void huge *)(c64buf + info.loadAddr),
					info.c64dataLen);
			}
			else
			{
				// Security - split data which would exceed the end of the C64 memory.
				file->seek(fileOffset);
				file->read((void huge *)(c64buf + info.loadAddr),
					info.c64dataLen - (endPos - 65536));

				// Wrap the remaining data to the start address of the C64 memory.
				file->seek(fileOffset + info.c64dataLen - (endPos - 65536));
				file->read((void huge *)c64buf, (endPos - 65536));
			}
			return (status = true);
		}
	}
	else
	{
		return (status = false);
	}
}


void sidTune::safeConstructor()
{
	// Initialize the object with some safe defaults.
	status = false;

	info.statusString = text_na;
	info.dataFileLen = 0;
	info.formatString = text_na;
	info.speedString = text_na;
	info.loadAddr = ( info.initAddr = ( info.playAddr = 0 ));
	info.songs = ( info.startSong = ( info.currentSong = 0 ));
	info.musPlayer = false;
	info.songSpeed = SIDTUNE_SPEED_VBI_PAL;
	info.clockSpeed = SIDTUNE_CLOCK_PAL;

	for ( int si = 0; si < classMaxSongs; si++ )
	{
		songSpeed[si] = SIDTUNE_SPEED_VBI_PAL;  // all: 50 Hz
	}

	file = 0;
	fileOffset = 0;

	for ( int sNum = 0; sNum < infoStringNum; sNum++ )
	{
		for ( int sPos = 0; sPos < infoStringLen; sPos++ )
		{
			infoString[sNum][sPos] = 0;
		}
	}
	info.numberOfInfoStrings = 0;

	info.numberOfCommentStrings = 1;
	info.commentString = new char *[info.numberOfCommentStrings];
	info.commentString[0] = strdup("--- SAVED WITH SIDPLAY V?.?? ---");
}


void sidTune::safeDestructor()
{
	// Remove copy of comment field.
	udword strNum = 0;
	// Check and remove every available line.
	while (info.numberOfCommentStrings-- > 0)
	{
		if (info.commentString[strNum] != 0)
		{
			delete[] info.commentString[strNum];
			info.commentString[strNum] = 0;
		}
		strNum++;  // next string
	};
	delete[] info.commentString;  // free the array pointer

	status = false;
}


void sidTune::acceptSidTune( udword dataLen )
{
	info.dataFileLen = dataLen;
	info.c64dataLen = dataLen - fileOffset;

	status = true;
}


// Initializing the object based upon what we find in the specified file.

void sidTune::filesConstructor( binfile &f )
{
	file = &f;

	if ( PSID_fileSupport(*file))
	{
		acceptSidTune(info.dataFileLen);
		return;
	}
//	if ( RAW_fileSupport(*file ))
//	{
//		acceptSidTune(info.dataFileLen);
//		return;
//	}

	info.formatString = text_na;
	info.statusString = text_unrecognizedFormat;

	status = false;
}


void sidTune::convertOldStyleSpeedToTables(udword oldStyleSpeed)
{
	// Create the speed/clock setting tables.
	//
	// This does not take into account the PlaySID bug upon evaluating the
	// SPEED field. It would most likely break compatibility to lots of
	// sidtunes, which have been converted from .SID format and vice versa.
	// The .SID format did the bit-wise/song-wise evaluation of the SPEED
	// value correctly, like it was described in the PlaySID documentation.

	int toDo = ((info.songs <= classMaxSongs) ? info.songs : classMaxSongs);
	for (int s = 0; s < toDo; s++)
	{
		if (( (oldStyleSpeed>>(s&31)) & 1 ) == 0 )
		{
			songSpeed[s] = SIDTUNE_SPEED_VBI_PAL;  // 50 Hz
		}
		else
		{
			songSpeed[s] = SIDTUNE_SPEED_CIA_1A;   // CIA 1 Timer A
		}
	}
}
