/// @file compressor.cpp
/// Compression/Decompression classes
///
/// @remarks
/// This is the base class from which all compressors are derived.
///
/// @author mouser
/// @date   2003.07.29
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// application includes
#include "compressor.hpp"
#include "../utilityclasses/timer/timer.hpp"
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
void OctaneCompressor::SetInfo(std::string compressorname, unsigned short int compressorguid)
{
	// Set details (compressor name and guid) from file
	instantiatedname=compressorname;
	guid=compressorguid;
}
//---------------------------------------------------------------------------


//---------------------------------------------------------------------------
// COMPRESSOR PUBLIC API - COMPRESSING/DECOMPRESSING WRAPPERS (NOT VIRTUAL!)
//  These methods just wrap the calls below inside some timing/stats gathering functions

bool OctaneCompressor::Compress(bitreader &from, bitwriter &to, bool needtowriteheader, bool savedecompressstateinfo)
{
	// just wrap the calls to derived classes
	bool bretv;
	OctaneTimer octanetimer;
	size_t fromstart,tostart;

	// do compression and measure it
	octanetimer.start();
	fromstart=from.tell_byte();
	tostart=to.tell_byte();

	if (needtowriteheader)
		{
		// save header - this is either a supershort GUID only (usually only 1 byte), or the complete stateinfo for the compressor
		SaveCompressionHeader(to,savedecompressstateinfo);
		}

	// do actual compression
	bretv=DoProtectedCompress(from,to);

	// align output bitwriter (this must be performed when we finish)
	to.align_byte();

	// finish measurements
	LastOperation_Compression_ExecutionTime=octanetimer.stop();
	Cumulative_Compression_ExecutionTime+=LastOperation_Compression_ExecutionTime;
	LastOperation_Compression_BytesRead=from.tell_byte()-fromstart;
	Cumulative_Compression_BytesRead+=LastOperation_Compression_BytesRead;
	LastOperation_Compression_BytesWritten=to.tell_byte()-tostart;
	Cumulative_Compression_BytesWritten+=LastOperation_Compression_BytesWritten;
	
	// update error counts
	if (!bretv)
		{
		++Cumulative_ErrorCount;
		++LastOperation_ErrorCount;
		}

	// return success
	return bretv;
}


bool OctaneCompressor::Decompress(bitreader &from, bitwriter &to, bool needtoreadheader)
{
	// just wrap the calls to derived classes
	bool bretv;
	OctaneTimer octanetimer;
	size_t fromstart,tostart;

	// do decompression and measure it
	octanetimer.start();
	fromstart=from.tell_byte();
	tostart=to.tell_byte();

	if (needtoreadheader)
		{
		// this decompress could be called before the header is read, or after it.
		// if the header is already read by caller, we wont try to re-read it here.
		// if it hasn't been read yet, we can read it here, and it MUST match our current compressor.
		// load header - this is either a supershort GUID only (usually only 1 byte), or the complete stateinfo for the compressor
		bretv=LoadCompressionHeader(from);
		}

	// do decompression
	bretv=DoProtectedDecompress(from,to);
	
	// align output bitwriter (this must be performed when we finish)
	to.align_byte();

	// finish measurements
	LastOperation_Decompression_ExecutionTime=octanetimer.stop();
	Cumulative_Decompression_ExecutionTime+=LastOperation_Decompression_ExecutionTime;
	LastOperation_Decompression_BytesRead=from.tell_byte()-fromstart;
	Cumulative_Decompression_BytesRead+=LastOperation_Decompression_BytesRead;
	LastOperation_Decompression_BytesWritten=to.tell_byte()-tostart;
	Cumulative_Decompression_BytesWritten+=LastOperation_Decompression_BytesWritten;

	// update error counts
	if (!bretv)
		{
		++Cumulative_ErrorCount;
		++LastOperation_ErrorCount;
		}
		
	// return success
	return bretv;
}
//---------------------------------------------------------------------------


//---------------------------------------------------------------------------
bool OctaneCompressor::CreateSymbolsAndModelsUsingStream(bitreader &from)
{
	// wrap call to save state and measure it
	bool bretv;
	OctaneTimer octanetimer;

	// do state save and measure it
	octanetimer.start();
	//
	bretv=DoProtectedCreateSymbolsAndModelsUsingStream(from);
	//
	LastOperation_State_ExecutionTime=octanetimer.stop();

	// update error counts
	if (!bretv)
		{
		++Cumulative_ErrorCount;
		++LastOperation_ErrorCount;
		}

	// return success
	return bretv;
}
	

bool OctaneCompressor::Save(bitwriter &to,bool fortempdecompressiononly)
{
	// wrap call to save state and measure it
	bool bretv;
	size_t tostart;
	OctaneTimer octanetimer;

	// do state save and measure it
	octanetimer.start();
	tostart=to.tell_byte();

	// save compressor header (see OctaneCompressor::Save() for where this is written)
	string tempstring=GetClassName();
	to.put(tempstring);
	tempstring=GetInstantiatedName();
	to.put(tempstring);
	to.put(guid);
	
	// now call derived protected method to save state
	bretv=DoProtectedSaveState(to,fortempdecompressiononly);

	// measure result
	LastOperation_State_BytesWritten=to.tell_byte()-tostart;
	LastOperation_State_ExecutionTime=octanetimer.stop();
	
	// update error counts
	if (!bretv)
		{
		++Cumulative_ErrorCount;
		++LastOperation_ErrorCount;
		}

	// return success
	return bretv;
}


bool OctaneCompressor::Load(bitreader &from)
	{
	//  Normally the compressor manager loads the header of a saved file and dynamically instantiates a compressor,
	//   but we provide this function for manually calling load on an already instantiated compressor.
	//  It reads the header, verifies it is for proper class, and then calls LoadState().
	bool bretv;

	// read standard compressor header (see OctaneCompressor::Save() for where this is written)
	string compressorclassname;
	string compressorname;
	short int compressorguid;
	from.get(compressorclassname);
	from.get(compressorname);
	from.get(compressorguid);
	
	if (compressorclassname!=GetClassName())
		{
		cout << "Error: class name from file does not match compressor class name being read; load aborted."<<endl;
		return false;
		}

	// set name but not guid
	if (instantiatedname=="")
		instantiatedname=compressorname;

	// now load state
	bretv=LoadState(from);
	return bretv;
	}
	

bool OctaneCompressor::LoadState(bitreader &from)
{
	bool bretv;
	size_t fromstart;
	OctaneTimer octanetimer;

	// do state load and measure it
	octanetimer.start();
	fromstart=from.tell_byte();
	//
	bretv=DoProtectedLoadState(from);
	//
	LastOperation_State_ExecutionTime=octanetimer.stop();
	LastOperation_State_BytesRead=from.tell_byte()-fromstart;

	// update error counts
	if (!bretv)
		{
		++Cumulative_ErrorCount;
		++LastOperation_ErrorCount;
		}

	// return success
	return bretv;
}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
void OctaneCompressor::ResetStatistics()
{
	// reset all statistics
	Cumulative_Compression_BytesRead=0;
	Cumulative_Decompression_BytesRead=0;
	Cumulative_Compression_BytesWritten=0;
	Cumulative_Decompression_BytesWritten=0;
	Cumulative_Compression_Bitrate=0;
	Cumulative_Decompression_Bitrate=0;
	Cumulative_Compression_Ratio=0;
	Cumulative_Decompression_Ratio=0;
	Cumulative_Compression_ExecutionTime=0;
	Cumulative_Decompression_ExecutionTime=0;
	Cumulative_ErrorCount=0;
	LastOperation_Compression_BytesRead=0;
	LastOperation_Decompression_BytesRead=0;
	LastOperation_Compression_BytesWritten=0;
	LastOperation_Decompression_BytesWritten=0;
	LastOperation_State_BytesRead=0;
	LastOperation_State_BytesWritten=0;
	LastOperation_Compression_Bitrate=0;
	LastOperation_Decompression_Bitrate=0;
	LastOperation_Compression_Ratio=0;
	LastOperation_Decompression_Ratio=0;
	LastOperation_Compression_ExecutionTime=0;
	LastOperation_Decompression_ExecutionTime=0;
	LastOperation_State_ExecutionTime=0;
	LastOperation_ErrorCount=0;
}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
bool OctaneCompressor::SaveCompressionHeader(bitwriter &to, bool savedecompressstateinfo)
	{
	if (savedecompressstateinfo)
		{
		// save the state information  for the compressor prior to the data
		// first write a special byte signifying that full state info follows
		to.put_byte((unsigned char)254);
		Save(to,true);
		}
	else
		{
		// save only the GUID of the compressor
		// if the GUID is valid, it should let receiver know which decompressor to use, or otherwise issue a warning
		SaveGUID(to);
		}

	// return success
	return true;
	}


bool OctaneCompressor::LoadCompressionHeader(bitreader &from)
	{
	bool bretv;
	int retvguid;

	// load the guid (or -1 if the header is not a guid)
	retvguid=LoadGUID(from);

	if (retvguid!=-1)
		{
		// the header was just a guid, so we are done reading
		// does it match our current compressor, if not give a warning and return false
		if (guid!=retvguid)
			{
			cout << "WARNING: stored compressor guid ("<<retvguid<<") does not match current compressor ("<<guid<<")."<<endl;
			return false;
			}
		return true;
		}
	else
		{
		// load the full header information (just gets checked and thrown away)
		bretv=Load(from);
		return bretv;
		}

	// return success
	return true;
	}


/// @todo - we will add code here so that we use only 1 byte for most guid, and 2 bytes for others, but for now we always use 1 byte for GUID
bool OctaneCompressor::SaveGUID(bitwriter &to)
	{
	// save a short guid
	to.put_byte((unsigned char)guid);
	// return success
	return true;
	}


/// @todo - we will add code so that we use only 1 byte for most guid, and 2 bytes for others, but for now we always use 1 byte for GUID
int OctaneCompressor::LoadGUID(bitreader &from)
	{
	// load a short guid
	unsigned char c;

	// read first character
	c=from.get_byte();
	
	// special NONGUID character
	if (c==254)
		return -1;

	return c;
	}
//---------------------------------------------------------------------------
