/* ================================================================================== */
/* Copyright (c) 1998-1999 3Com Corporation or its subsidiaries. All rights reserved. */
/* ================================================================================== */

#include "EmulatorCommon.h"
#include "SessionFile.h"

#include "Byteswapping.h"		// Canonical
#include "Miscellaneous.h"		// StMemory, RunLengthEncode, GzipEncode, etc.


/***********************************************************************
 *
 * FUNCTION:	SessionFile constructor
 *
 * DESCRIPTION:	Initialize the SessionFile object.  Sets the fFile data
 *				member to refer to the given FileHandle (which must
 *				exist for the life of the SessionFile).
 *
 * PARAMETERS:	None
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

SessionFile::SessionFile (ChunkFile& f) :
	fFile (f),
	fCanReload (false),
	fCfg ()
{
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile destructor
 *
 * DESCRIPTION:	Releases SessionFile resources.  Currently does nothing.
 *
 * PARAMETERS:	None
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

SessionFile::~SessionFile (void)
{
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::ReadROMFileReference
 *
 * DESCRIPTION:	Read a reference to a paired ROM file from the session
 *				file.  For robustness, several approaches are used to
 *				find a ROM file.  Because these approaches are platform-
 *				specific, most of the work is off-loaded to the Platform
 *				sub-system.  If the file reference can be read, it's
 *				recorded in the configuration record to be used the next
 *				time a new session is created.
 *
 * PARAMETERS:	f - reference to FileReference to receive the read value.
 *
 * RETURNED:	True if a value could be found, false otherwise.
 *
 ***********************************************************************/

Bool SessionFile::ReadROMFileReference (FileReference& f)
{
	// Look for a ROM reference using a platform-specific method.

	Bool	result = Platform::ReadROMFileReference (fFile, f);

	return result;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::ReadRAMImage
 *
 * DESCRIPTION:	Read the RAM image from the session file.  Attempts to
 *				read a number of versions of the image, including
 *				compressed and uncompressed versions.  If the image can
 *				be read, its size is recorded in the configuration
 *				record to be used the next time a new session is created.
 *
 * PARAMETERS:	image - reference to the pointer to receive the address
 *					of the RAM image.
 *
 *				size - reference to the integer to receive the size of
 *					the RAM image.
 *
 * RETURNED:	True if the image was found and could be read in.
 *
 ***********************************************************************/

Bool SessionFile::ReadRAMImage (void* image)
{
	long	result = this->ReadChunk (kRAMDataTag, image, kGzipCompression);

	if (result == ChunkFile::kChunkNotFound)
		result = this->ReadChunk (kRLERAMDataTag, image, kRLECompression);

	if (result == ChunkFile::kChunkNotFound)
		result = this->ReadChunk (kUncompRAMDataTag, image, kNoCompression);

	return result;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::ReadMetaRAMImage
 *
 * DESCRIPTION:	Read the MetaRAM image from the session file.  Attempts
 *				to read a number of versions of the image, including
 *				compressed and uncompressed versions.
 *
 * PARAMETERS:	image - reference to the pointer to receive the address
 *					of the MetaRAM image.
 *
 *				size - reference to the integer to receive the size of
 *					the MetaRAM image.
 *
 * RETURNED:	True if the image was found and could be read in.
 *
 ***********************************************************************/

Bool SessionFile::ReadMetaRAMImage (void* image)
{
	long	result = this->ReadChunk (kMetaRAMDataTag, image, kGzipCompression);

	if (result == ChunkFile::kChunkNotFound)
		result = this->ReadChunk (kRLEMetaRAMDataTag, image, kRLECompression);

	return result;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::ReadMetaROMImage
 *
 * DESCRIPTION:	Read the MetaROM image from the session file.  Attempts
 *				to read a number of versions of the image, including
 *				compressed and uncompressed versions.
 *
 * PARAMETERS:	image - reference to the pointer to receive the address
 *					of the MetaROM image.
 *
 *				size - reference to the integer to receive the size of
 *					the MetaROM image.
 *
 * RETURNED:	True if the image was found and could be read in.
 *
 ***********************************************************************/

Bool SessionFile::ReadMetaROMImage (void* image)
{
	long	result = this->ReadChunk (kMetaROMDataTag, image, kGzipCompression);

	return result;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::ReadHwrDBallType
 *
 * DESCRIPTION:	Read the Dragonball hardware registers from the
 *				session file.
 *
 * PARAMETERS:	hwRegs - reference to the struct to receive the register
 *					data.
 *
 * RETURNED:	True if the data was found and could be read in.
 *
 ***********************************************************************/

Bool SessionFile::ReadHwrDBallType (HwrDBallType& hwRegs)
{
	Chunk	chunk;
	if (fFile.ReadChunk (kDBRegs, chunk))
	{
		memcpy (&hwRegs, chunk.GetPointer (), sizeof (hwRegs));
		return true;
	}

	return false;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::ReadHwrDBallEZType
 *
 * DESCRIPTION:	Read the DragonballEZ hardware registers from the
 *				session file.
 *
 * PARAMETERS:	hwRegs - reference to the struct to receive the register
 *					data.
 *
 * RETURNED:	True if the data was found and could be read in.
 *
 ***********************************************************************/

Bool SessionFile::ReadHwrDBallEZType (HwrDBallEZType& hwRegs)
{
	Chunk	chunk;
	if (fFile.ReadChunk (kDBEZRegs, chunk))
	{
		memcpy (&hwRegs, chunk.GetPointer (), sizeof (hwRegs));
		return true;
	}

	return false;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::ReadDBallRegs
 *
 * DESCRIPTION:	Read the Dragonball CPU registers structure from the
 *				session file.
 *
 * PARAMETERS:	cpuRegs - reference to the struct to receive the register
 *					data.
 *
 * RETURNED:	True if the data was found and could be read in.
 *
 ***********************************************************************/

Bool SessionFile::ReadDBallRegs (regstruct& cpuRegs)
{
	Chunk	chunk;
	if (fFile.ReadChunk (kCPURegs, chunk))
	{
		memcpy (&cpuRegs, chunk.GetPointer (), sizeof (cpuRegs));
		return true;
	}

	return false;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::ReadDeviceType
 *
 * DESCRIPTION:	Read the id of the device to emulate.  If the id can be
 *				read, it's recorded in the configuration record to be
 *				used the next time a new session is created.
 *
 * PARAMETERS:	v - reference to the DeviceType to receive the read value.
 *
 * RETURNED:	True if the value was found and could be read in.
 *
 ***********************************************************************/

Bool SessionFile::ReadDeviceType (DeviceType& v)
{
	uae_u32	type;
	Bool	result = fFile.ReadInt (kDeviceType, type);

	if (result)
	{
		v = (DeviceType) type;
	}

	return result;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::ReadSilkscreenType
 *
 * DESCRIPTION:	Read the id of the silkscreen to display.  If the id
 *				can be read, it's recorded in the configuration record
 *				to be used the next time a new session is created.
 *
 * PARAMETERS:	v - reference to the SilkscreenType to receive the read
 *					value.
 *
 * RETURNED:	True if the value was found and could be read in.
 *
 ***********************************************************************/

Bool SessionFile::ReadSilkscreenType (SilkscreenType& v)
{
	uae_u32	type;
	Bool	result = fFile.ReadInt (kSilkscreenType, type);

	if (result)
	{
		v = (SilkscreenType) type;
	}

	return result;
}




Bool SessionFile::ReadConfiguration (AppPreferences::Configuration& cfg)
{
	if (!this->ReadDeviceType (cfg.fDeviceType))
		return false;

	if (!this->ReadSilkscreenType (cfg.fSilkscreenType))
		cfg.fSilkscreenType = kSilkscreenEnglish;

	if (!this->ReadROMFileReference (cfg.fROMFile))
		return false;

	cfg.fRAMSize = this->GetRAMImageSize ();
	if (cfg.fRAMSize == ChunkFile::kChunkNotFound)
		return false;

	cfg.fRAMSize /= 1024;

	return true;
}


long SessionFile::GetRAMImageSize (void)
{
	long	numBytes;

	Chunk	chunk;
	if (fFile.ReadChunk (kRAMDataTag, chunk) || fFile.ReadChunk (kRLERAMDataTag, chunk))
	{
		ChunkStream	s (chunk);
		s >> numBytes;
	}
	else
	{
		numBytes = fFile.FindChunk (kUncompRAMDataTag);
	}

	return numBytes;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::WriteROMFileReference
 *
 * DESCRIPTION:	Write a reference to the ROM file to use for this
 *				session.  For robustness, the reference is written out
 *				in several different ways.  Because the way the file is
 *				later looked up is platform-dependent, most of the work
 *				is off-loaded to the Platform sub-system.
 *
 * PARAMETERS:	f - reference to the FileReference for the ROM file.
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

void SessionFile::WriteROMFileReference (const FileReference& f)
{
	// Save a ROM reference using a platform-specific method.

	Platform::WriteROMFileReference (fFile, f);

	// Save the name of the ROM file.

	string	romFileName = f.GetFileName ();
	fFile.WriteString (SessionFile::kROMNameTag, romFileName);

	// Remember that we were using this ROM file most recently.

	fCfg.fROMFile = f;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::WriteRAMImage
 *
 * DESCRIPTION:	Write the given data as the RAM image for the session
 *				file.  The data is written using the default compression.
 *
 * PARAMETERS:	image - pointer to the data to be written.  No munging
 *					of this data is performed; it is expected that any
 *					byteswapping has already taken place.
 *
 *				size - number of bytes in the image.
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

void SessionFile::WriteRAMImage (const void* image, uae_u32 size)
{
	this->WriteChunk (kRAMDataTag, size, image, kGzipCompression);
	fCfg.fRAMSize = size / 1024;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::WriteMetaRAMImage
 *
 * DESCRIPTION:	Write the given data as the MetaRAM image for the session
 *				file.  The data is written using the default compression.
 *
 * PARAMETERS:	image - pointer to the data to be written.  No munging
 *					of this data is performed; it is expected that any
 *					byteswapping has already taken place.
 *
 *				size - number of bytes in the image.
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

void SessionFile::WriteMetaRAMImage (const void* image, uae_u32 size)
{
	this->WriteChunk (kMetaRAMDataTag, size, image, kGzipCompression);
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::WriteMetaROMImage
 *
 * DESCRIPTION:	Write the given data as the MetaROM image for the session
 *				file.  The data is written using the default compression.
 *
 * PARAMETERS:	image - pointer to the data to be written.  No munging
 *					of this data is performed; it is expected that any
 *					byteswapping has already taken place.
 *
 *				size - number of bytes in the image.
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

void SessionFile::WriteMetaROMImage (const void* image, uae_u32 size)
{
	this->WriteChunk (kMetaROMDataTag, size, image, kGzipCompression);
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::WriteHwrDBallType
 *
 * DESCRIPTION:	Write the Dragonball hardware registers to the session
 *				file.
 *
 * PARAMETERS:	hwRegs - the DB registers to write to disk.  No munging
 *					of this data is performed; it is expected that any
 *					byteswapping has already taken place.
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

void SessionFile::WriteHwrDBallType (const HwrDBallType& hwRegs)
{
	fFile.WriteChunk (kDBRegs, sizeof (hwRegs), &hwRegs);
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::WriteHwrDBallEZType
 *
 * DESCRIPTION:	Write the Dragonball EZ hardware registers to the session
 *				file.
 *
 * PARAMETERS:	hwRegs - the DB registers to write to disk.  No munging
 *					of this data is performed; it is expected that any
 *					byteswapping has already taken place.
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

void SessionFile::WriteHwrDBallEZType (const HwrDBallEZType& hwRegs)
{
	fFile.WriteChunk (kDBEZRegs, sizeof (hwRegs), &hwRegs);
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::WriteDBallRegs
 *
 * DESCRIPTION:	Write the Dragonball CPU registers to the session file.
 *
 * PARAMETERS:	cpuRegs - the registers to write to disk. No munging
 *					of this data is performed; it is expected that any
 *					byteswapping has already taken place.
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

void SessionFile::WriteDBallRegs (const regstruct& cpuRegs)
{
	fFile.WriteChunk (kCPURegs, sizeof (cpuRegs), &cpuRegs);
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::WriteDeviceType
 *
 * DESCRIPTION:	Write the id of the device to emulate.
 *
 * PARAMETERS:	v - id of the device to emulate.
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

void SessionFile::WriteDeviceType (DeviceType v)
{
	fFile.WriteInt (kDeviceType, (uae_u32) v);

	// Remember that we were using this device most recently.

	fCfg.fDeviceType = v;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::WriteSilkscreenType
 *
 * DESCRIPTION:	Write the id of the silkscreen to display.
 *
 * PARAMETERS:	v - id of the silkscreen to display.
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

void SessionFile::WriteSilkscreenType (SilkscreenType v)
{
	fFile.WriteInt (kSilkscreenType, (uae_u32) v);

	// Remember that we were using this silkscreen most recently.

	fCfg.fSilkscreenType = v;
}




/***********************************************************************
 *
 * FUNCTION:	SessionFile::SetCanReload
 *
 * DESCRIPTION:	.
 *
 * PARAMETERS:	None.
 *
 * RETURNED:	Nothing.
 *
 ***********************************************************************/

void SessionFile::SetCanReload (Bool val)
{
	fCanReload = val;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::GetCanReload
 *
 * DESCRIPTION:	.
 *
 * PARAMETERS:	None.
 *
 * RETURNED:	Nothing.
 *
 ***********************************************************************/

Bool SessionFile::GetCanReload (void)
{
	return fCanReload;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::ReadChunk
 *
 * DESCRIPTION:	Read an arbitrary chunk of data, utilizing the given
 *				type of compression, returning a pointer to the read
 *				data and the size of the data.
 *
 * PARAMETERS:	tag - marker identifying the data to be read.
 *
 *				size - reference to the integer to receive the number
 *					of bytes in the data.  This size may not be the same
 *					as the size of the chunk if the chunk is compressed.
 *
 *				image - reference to the pointer to receive the address
 *					of the read data.
 *
 *				compType - type of compression used for this chunk.  The
 *					caller is expected to know what kind of compression
 *					is being used; the compression type is not stored
 *					along with the chunk.
 *
 * RETURNED:	True if the image was found and could be read in.
 *
 ***********************************************************************/

long SessionFile::ReadChunk (ChunkFile::Tag tag, void* image,
							 CompressionType compType)
{
	// Get the size of the chunk.

	long	chunkSize = fFile.FindChunk (tag);
	if (chunkSize == ChunkFile::kChunkNotFound)
	{
		return chunkSize;
	}

	if (chunkSize)
	{
		// If no compression is being used, just read the data.

		if (compType == kNoCompression)
		{
			fFile.ReadChunk (chunkSize, image);
		}

		// The data is compressed.

		else
		{
			// Use the chunk size to create a buffer.

			StMemory	packedImage (chunkSize);

			// Read the chunk into memory.

			fFile.ReadChunk (chunkSize, packedImage.Get ());

			// The size of the unpacked image is stored as the first 4 bytes
			// of the chunk.

			long		unpackedSize = *(long*) packedImage.Get ();
			Canonical (unpackedSize);

			// Get pointers to the source (packed) data and
			// destination (unpacked) data.

			void*	src = packedImage.Get () + sizeof (long);
			void*	dest = image;

			// Decompress the data into the dest buffer.

			if (compType == kGzipCompression)
				::GzipDecode (&src, &dest, chunkSize - sizeof (long), unpackedSize);
			else
				::RunLengthDecode (&src, &dest, chunkSize - sizeof (long), unpackedSize);
		}
	}
	
	return true;
}


/***********************************************************************
 *
 * FUNCTION:	SessionFile::WriteChunk
 *
 * DESCRIPTION:	Write an arbitrary chunk of data, utilizing the given
 *				type of compression.
 *
 * PARAMETERS:	tag - marker used to later retrieve the data.
 *
 *				size - number of bytes in the image to write.
 *
 *				image - pointer to the image to write.
 *
 *				compType - type of compression to use.
 *
 * RETURNED:	Nothing
 *
 ***********************************************************************/

void SessionFile::WriteChunk (ChunkFile::Tag tag, uae_u32 size,
				const void* image, CompressionType compType)
{
	// No compression to be used; just write the data out as-is.

	if (compType == kNoCompression)
	{
		fFile.WriteChunk (tag, size, image);
	}

	// Use some form of compression.

	else
	{
		// Get the worst-case size for the compressed data.

		long		worstPackedSize = sizeof (long) +
						((compType == kGzipCompression)
							? ::GzipWorstSize (size)
							: ::RunLengthWorstSize (size));

		// Create a new buffer to hold the compressed data.

		StMemory	packedImage (worstPackedSize);

		// Write out the uncompressed size of the data as the first
		// 4 bytes of the chunk.

		Canonical (size);
		*(long*) packedImage.Get () = size;
		Canonical (size);

		// Get pointers to the source (unpacked) data and
		// destination (packed) data.

		void*	src = (void*) image;
		void*	dest = packedImage.Get () + sizeof (long);

		// Compress the data.

		if (compType == kGzipCompression)
			::GzipEncode (&src, &dest, size, worstPackedSize);
		else
			::RunLengthEncode (&src, &dest, size, worstPackedSize);

		// Calculate the compressed size of the data.

		long	packedSize = (char*) dest - (char*) packedImage;

		// Write the compressed data to the file.

		fFile.WriteChunk (tag, packedSize, packedImage.Get ());
	}
}
