/// @file octanetester.cpp
/// Classes for testing octane functions and compressors
///
/// @remarks
/// The OctaneTester class contains functions for testing compressors and
///  providing a commandline and interactive interface to octane functions.
///
/// @author mouser
/// @author Jibz
/// @date   2003.07.29
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// application includes
#include "octanetester.hpp"
#include "../utilityclasses/stringparser/stringparser.hpp"
#include "../utilityclasses/timer/timer.hpp"
// system includes
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstdio>
using namespace std;
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
OctaneTester::OctaneTester()
{
	// constructor
	currentcompressorp=NULL;
}

OctaneTester::~OctaneTester()
{
	// destructor
}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
void OctaneTester::ShowHelp()
{
	cout << OCTANE_VERSIONSTRING << endl;
	cout << "Commands are case-sensitive (to use as commandline arguments separate with ';')"<< endl;
	cout << " Compression and Decompression"<<endl;
	cout << "  'compress $inputfile [$outputfile]' to compress a file+compressor." << endl;
	cout << "  'decompress $inputfile [$outputfile]' to decompress a file+compressor." << endl;
	cout << "  'compressr $inputfile [$outputfile]' to compress a file; save only raw data." << endl;
	cout << "  'decompressr $inputfile [$outputfile]' to decompress a file of raw data." << endl;
 	cout << "  'test $text' to test compression and decompression a line of $text." << endl;
  	cout << "  'testfile $inputfile' to test compression and decompression of a file." << endl;
	cout << " Compressor Creation, Configuration, and File Management"<<endl;
	cout << "  'compressors' to get a list of compressors and compressor classes." << endl;
	cout << "  'create $compressorclass' to instantiate a compressor class and select it."<<endl;
	cout << "  'setinfo $name [$GUID]' to set name and GUID for instantiated compressor."<<endl;
	cout << "  'select [$name|$num|G$guid]' to select an instantiated compressor." << endl;
	cout << "  'makestate $inputfile' to create state data using $inputfile." << endl;
	cout << "  'save $outputfile' to save current compressor (set GUID with setinfo)." << endl;
	cout << "  'load $inputfile' to load/instantiate a previously saved compressor." << endl;
	cout << "  'delete [$name|$num|G$guid]' to delete an instantiated compressor."<<endl;
	cout << " Miscellaneous Commands"<<endl;
	cout << "  'debug' to show debugging info for current compressor/dictionary." << endl;
	cout << "  'parameters' to see a list of available runtime parameters." << endl;
	cout << "  'set $parametername $value' to set a runtime parameter to some value." << endl;
	cout << "  'run $commandfile' to run octane commands from a file." << endl;
	cout << "  'help' to show these instructions." << endl;
	cout << "  'quit' to exit this program." << endl;

	// brief list of all compressors registered
	ListCompressors(true);

	// info about the current compressor being tested
	if (currentcompressorp==NULL)
		{
		//cout << "No instantiated compressor currently selected (do you need to run 'create' to instantiate one?)."<<endl;
		}
	else
		{
		cout << "Current: " << currentcompressorp->GetInstantiatedName() << " ("<< currentcompressorp->GetDescription() <<")"<<endl;
		// show any derived class intro information?
		// currentcompressorp->ShowCompressorIntroInformation();
		}
	}


void OctaneTester::TestLoop()
{
	// a built in test loop that just asks user for input and compresses and decompresses in debug mode
	// and lets user load and save files.
	std::string line;

	// show instructions
	ShowHelp();
	
	// loop processing user commands
	cout << '>';
	while (GrabLineFromConsole(line))
		{
		if (line == "quit")
	    		break;
		else if (line!="")
			ParseCommand(line);
		cout << '>';
		}
}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
bool OctaneTester::ParseCommand(const string &inputstr)
{
	// process a single command, called from commandline or TestLoop above
	bool bretv=false;
	
	// return if no command provided
	if (inputstr.empty())
		return false;

	// create parser for string
	stringparser parser(inputstr);
	// grab main command keyword
	string command(parser.get_token());

	// NOW PARSE COMMAND

	if (command == "version")
		{
		// version info
		cout << OCTANE_VERSIONSTRING << endl;
		return true;
		}
	else if (command == "help")
		{
		// show help information
		ShowHelp();
		return true;
		}
	else if	(command == "debug")
		{
		// tell compressor to display any debug info it can
		if (!CheckCompressorSelected(currentcompressorp))
			return false;
		currentcompressorp->ShowDebuggingInfo();
		return true;
		}
	else if (command == "compressors")
		{
		// list registered compressors
		ListCompressors(false);
		return true;
		}
	else if (command == "parameters")
		{
		// list available parameters
		if (!CheckCompressorSelected(currentcompressorp))
			return false;
		cout << currentcompressorp->GetParametersInformation();
		return true;
		}
	else if (command == "select")
		{
		// select a registered compressor
		string param(parser.get_token());
		if (param == "")
			{
			cout << "you must specify the instantiated compressor to select."<<endl;
			return false;
			}
		if (SelectInstantiatedCompressor(param)==NULL)
			{
			cout << "error: compressor not found; type 'compressors' for a list of registered compressors."<<endl;
			return false;
			}
		cout << "compressor "<< currentcompressorp->GetInstantiatedName() << " selected."<<endl;
		return true;
		}
	else if (command == "run")
		{
		// run octane commands from a file
		string param(parser.get_token());
		if (param == "")
			{
			cout << "you must specify the filename to run."<<endl;
			return false;
			}
		return ExecuteCommandsFromFile(param,false);
		}
	else if (command == "set")
		{
		// set a parameter
		if (!CheckCompressorSelected(currentcompressorp))
			return false;
		string param1(parser.get_token());
		if (param1 == "")
			{
			cout << "you must specify the parameter to set."<<endl;
			return false;
			}
		string param2(parser.get_token());
		if (param2 == "")
			{
			cout << "you must specify a value for the parameter."<<endl;
			return false;
			}
		bretv = currentcompressorp->SetParameter(param1, param2);
		if (bretv)
			cout << "ok."<<endl;
		else
			cout << "error setting parameter or parameter not known."<<endl;
		return bretv;
		}

	else if (command == "load")
		{
		// load previously saved compressor
		// note that this operation creates a new compressor, so we don't need to insist that an instantiated compressor
		string param(parser.get_token());
		if (param == "")
			{
			cout << "you must specify the filename to load the compressor from."<<endl;
			return false;
			}
		// run the command
		return RunCommand_LoadCompressor(param);
		}
	else if (command == "save")
		{
		// load previously saved state
		if (!CheckCompressorSelected(currentcompressorp))
			return false;
		string param(parser.get_token());
		if (param == "")
			{
			cout << "you must specify the filename to save the compressor to."<<endl;
			return false;
			}
		// run the command
		return RunCommand_SaveCompressor(param);
		}
	else if (command == "create")
		{
		// create a new compressor based on a compressor class
		string param(parser.get_token());
		if (param == "")
			{
			cout << "you must specify the base class of the compressor to build."<<endl;
			return false;
			}
			
		// optional name
		string param2(parser.get_token());

		// optional guid
		string param3(parser.get_token());
		short int guid=0;
		if (param3!="")
			guid=atoi(param3.c_str());

		// run the command
		bretv=RunCommand_CreateCompressor(param,param2,guid);
		if (bretv)
				cout << "compressor "<< currentcompressorp->GetInstantiatedName() << " created and selected."<<endl;
		return bretv;
		}

	else if (command == "delete")
		{
		// load previously saved state
		string param(parser.get_token());
		if (param == "")
			{
			cout << "you must specify the instantiated compressor to delete."<<endl;
			return false;
			}
		// run the command
		bool bretv=RunCommand_DeleteCompressor(param);
		if (bretv)
			{
			cout << "compressor deleted."<<endl;
			return true;
			}
		}

	else if (command == "setinfo")
		{
		// set name and guid for a compressor
		if (!CheckCompressorSelected(currentcompressorp))
			return false;
		string param(parser.get_token());
		if (param == "")
			{
			cout << "you must specify a name for the compressor."<<endl;
			return false;
			}
		string param2(parser.get_token());
		// set the values
		unsigned short int guidnum=0;
		if (param2!="")
			guidnum=atoi(param2.c_str());
		currentcompressorp->SetInfo(param,guidnum);
		cout << "compresser info changed."<<endl;
		return true;
		}

	else if	(command == "makestate")
		{
		// make state information (i.e. build dictionary etc) using a file as training input
		if (!CheckCompressorSelected(currentcompressorp))
			return false;
		string param1(parser.get_token());
		if (param1 == "")
			{
			cout << "you must specify the filename to use for input."<<endl;
			return false;
			}
		// run command
		bool bretv=RunCommand_MakeState(param1);
		cout << "  execution time:  "<< currentcompressorp->GetStatistics_LastOperation_State_ExecutionTime()<<" seconds."<<endl;
		return bretv;
		}

	else if (command == "compress")
		{
		// compress a file and write compressor info to front
		if (!CheckCompressorReady(currentcompressorp))
			return false;
		string param1(parser.get_token());
		if (param1 == "")
			{
			cout << "you must specify the filename to use."<<endl;
			return false;
			}
		string param2(parser.get_token());
		if (param2 == "")
			{
			// assume an output filename
			param2 = MakeAssumedOutputFilename(param1, "decompressed", "compressed");
			}
		// run command (when savedecompresstateinfo==true it means write compressor state before raw data)
		bool savedecompressstateinfo=true;
		return RunCommand_Compress(param1,param2, savedecompressstateinfo);
		}
	else if	(command == "decompress")
		{
		// decompress a file and expect compressor state info at front
		// we don't need an existing compressor for this decompress, since we expect full compressor state at head
		//		if (!CheckCompressorReady(currentcompressorp))
		//			return false;
		string param1(parser.get_token());
		if (param1 == "")
			{
			cout << "you must specify the filename to use."<<endl;
			return false;
			}
		string param2(parser.get_token());
		if (param2 == "")
			{
			// assume an output filename
			param2 = MakeAssumedOutputFilename(param1, "compressed", "decompressed");
			}
		// run command (when savedecompresstateinfo==false it means only write compressed data)
		bool savedecompressstateinfo=true;
		return RunCommand_Decompress(param1,param2,savedecompressstateinfo);
		}
	else if (command == "compressr")
		{
		// compress a raw file without state info
		if (!CheckCompressorReady(currentcompressorp))
			return false;
		string param1(parser.get_token());
		if (param1 == "")
			{
			cout << "you must specify the filename to use."<<endl;
			return false;
			}
		string param2(parser.get_token());
		if (param2 == "")
			{
			// assume an output filename
			param2 = MakeAssumedOutputFilename(param1, "decompressed", "compressed");
			}
		// run command (when savedecompresstateinfo==false it means only write compressed data)
		bool savedecompressstateinfo=false;
		return RunCommand_Compress(param1,param2, savedecompressstateinfo);
		}
	else if	(command == "decompressr")
		{
		// decompress a raw file, without compressor info
		if (!CheckCompressorReady(currentcompressorp))
			return false;
		string param1(parser.get_token());
		if (param1 == "")
			{
			cout << "you must specify the filename to use."<<endl;
			return false;
			}
		string param2(parser.get_token());
		if (param2 == "")
			{
			// assume an output filename
			param2 = MakeAssumedOutputFilename(param1, "compressed", "decompressed");
			}
		// run command (when savedecompresstateinfo==false it means only write compressed data)
		bool savedecompressstateinfo=false;
		return RunCommand_Decompress(param1,param2,savedecompressstateinfo);
		}

	else if (command == "testfile")
		{
		// compress and decompress a text string as a test
		if (!CheckCompressorReady(currentcompressorp))
			return false;
		string param1(parser.get_token());
		if (param1 == "")
			{
			cout << "you must specify the file to test compress."<<endl;
			return false;
			}

		// run command
		return RunCommand_TestFile(param1);
		}
	else if (command == "test")
		{
		// compress and decompress a text string as a test
		if (!CheckCompressorReady(currentcompressorp))
			return false;
		if (inputstr.length() <= 5)
			{
			cout << "error: you need to specify text to compress."<<endl;
			return false;
			}
		// get text to compress
		string inputstring(inputstr.substr(5));
		// run command
		return RunCommand_TestString(inputstring);
		}

	// if we get here it means the command was not understood
	cout << "command not understood(" << inputstr << "); type 'help' for a list of known commands."<<endl;
	return false;
}

bool OctaneTester::ExecuteCommandsFromStream(std::ifstream &from, bool quiet)
	{
	// run a stream full of commands and time them
	// return true on success
	bool bretv=true;

	// record start time
	OctaneTimer octanetimer;

	// parse stream
	string line;
	int linenum=1;
	while (getline(from, line))
		{
		// skip comments and blank lines
		string::size_type i = line.find_first_of("\015\012");
		if (i != string::npos)
			line.erase(i);
		if (line.length() < 3)
			continue;
		if (line[0] == '/' && line[1] == '/')
			continue;
		// show debugging info
		if (!quiet)
			cout << "]" << line << ""<<endl;
		// run it and stop on exit
		if (!(bretv=ParseCommand(line)))
			break;
		++linenum;
		}

	if (!quiet)
		{
		if (bretv)
			cout << "Script finished successfully after "<<octanetimer.stop()<<" seconds."<<endl;
		else
			cout << "Script aborted with error on line "<<linenum<< " after "<<octanetimer.stop()<<" seconds."<<endl;
		}

	// return success
	return bretv;
	}


bool OctaneTester::ExecuteCommandsFromFile(const std::string &filename, bool quiet)
	{
	// open a file input stream and run it
	// return true on success
	ifstream from(filename.c_str());
	if (!from)
		{
		cout << "error: unable to open file '" << filename << "'."<<endl;
		return false;
		}
	return ExecuteCommandsFromStream(from,quiet);
	}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
bool OctaneTester::GrabLineFromConsole(string &line)
{
	// read a line of input from console and return true if a line was read or false if error ctrl-break type abort
	// we had to write this because of a known bug in vc6 getline() which requires double CRs on getline input.  good work bill gates.
	// return true on successful read of a line (can be blank)

	// clear line
	line="";

	// loop reading characters until CR
	unsigned char c;
	for (;;)
		{
		c = cin.get();
		if (c=='\n')
			break;
		line+=c;
		}
	return true;
}


void OctaneTester::ListCompressors(bool briefmode)
{
	// list all registered compressors
	int index;
	OctaneCompressor *compressorp;
	string guidstr;
	char tempstr[16];
	int compressorcount;
	
	// Registered base compressor types
	cout << "Registered Compressor Types";
	if (briefmode)
		cout <<": ";
	else
		cout << "\n---------------------------"<<endl;

	compressorcount=global_compressormanagerp->GetBaseCompressorCount();
	if (compressorcount==0)
		cout << "no base compressor types registered.";	
	for (index=0;index<compressorcount;++index)
		{
		compressorp=global_compressormanagerp->GetBaseCompressorp(index);
		if (briefmode)
			cout << compressorp->GetClassName() << " ";
		else
			cout << "  "<<setiosflags(ios::left) << setw(20) << compressorp->GetClassName() << " " << (compressorp->GetDescription()).c_str()<<endl;
		}
	cout << endl;

	// Instantiated compressors
	cout << "Instantiated Compressors";
	if (briefmode)
		cout <<":    ";
	else
		cout << "\n------------------------"<<endl;
	compressorcount=global_compressormanagerp->GetInstantiatedCompressorCount();
	if (compressorcount==0)
		cout << "(none)";
	int indexnum=0;
	string prefixstr;
	for (index=0;index<compressorcount;++index)
		{
		compressorp=global_compressormanagerp->GetInstantiatedCompressorp(index);
		if (compressorp->GetCompressorStateFileGUID()!=0)
			{
			sprintf(tempstr,"GUID=%d",compressorp->GetCompressorStateFileGUID());
			guidstr=string(tempstr);
			}
		else
			guidstr="NOGUID";
		// set prefix to indicate which one selected
		if (currentcompressorp==compressorp)
			prefixstr="*";
		else
			prefixstr=" ";
		//
		if (briefmode)
			cout << "["<<indexnum<< prefixstr << compressorp->GetInstantiatedName() << " " << guidstr << "] ";
		else
			cout << " "<< prefixstr << setiosflags(ios::left) << setw(4) << indexnum << " " << setiosflags(ios::left) << setw(20) << compressorp->GetInstantiatedName() << " " << (compressorp->GetDescription()).c_str()<< " ("<< guidstr<<")"<<endl;
		++indexnum;
		}
	cout << endl;
}

OctaneCompressor* OctaneTester::SelectInstantiatedCompressor(const string &compressorname)
{
	// lookup compressor
	OctaneCompressor* compressorp=global_compressormanagerp->FindInstantiatedCompressorpFromStringId(compressorname);
	// set current compressor
	if (compressorp!=NULL)
		currentcompressorp=compressorp;
	return compressorp;
}

bool OctaneTester::CheckCompressorSelected(OctaneCompressor *compressorp)
{
	// check if the compressor is nonnull and is ready
	// show warning and return false if not.
	if (compressorp==NULL)
		{
		cout << "No instantiated compressor currently selected (run 'create' or 'load')."<<endl;
		return false;
		}
	// one is selected
	return true;
}

bool OctaneTester::CheckCompressorReady(OctaneCompressor *compressorp)
	{
	// check if the compressor is nonnull and is ready
	// show warning and return false if not.
	if (!CheckCompressorSelected(compressorp))
		return false;
	if (!compressorp->IsReadyToCompress())
		{
		cout << "compressor "<< currentcompressorp->GetInstantiatedName() << " is not ready for compression; do you need to make or load state data first?"<<endl;
		return false;
		}
	// it is ready
	return true;
	}

string OctaneTester::MakeAssumedOutputFilename(const string &origname, const string &origext, const string &newext)
{
	// helper function to smartly add an extension to a file name, based on current compressor
	//  and removing old extension if appropriate.

	if (currentcompressorp==NULL)
		{
		string origfullext(string(".") + origext);
		string::size_type pos = origname.rfind(origfullext);
		if (pos == string::npos)
			return origname + "." + newext;
		return string(origname, 0, pos) + newext;
		}

	string newfullext(string(".") + currentcompressorp->GetInstantiatedName() + "." + newext);

	if (origext == "")
		return origname + newfullext;

	string origfullext(string(".") + currentcompressorp->GetInstantiatedName() + "." + origext);

	string::size_type pos = origname.rfind(origfullext);

	if (pos == string::npos)
		return origname + "." + newext;

	return string(origname, 0, pos) + newext;
}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
bool OctaneTester::RunCommand_TestString(std::string inputstring)
	{
	bool bretv;
	// create bitreader and bitwriter, they will be deleted on exit
	string compressedstring;
	string_bitreader from(inputstring);
	string_bitwriter to(compressedstring);

	// do compression of text (third argument==false means don't write header info)
	bretv = currentcompressorp->Compress(from, to, false, false);
	if (!bretv)
		{
		cout << "Error compressing."<<endl;
		return false;
		}

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

	// show	some info about	compression
	cout <<	"Testing " << currentcompressorp->GetInstantiatedName() << ".."<<endl;
	cout << "  input length: " << setw(3)<< (int)(inputstring.length()) << "\n";
	cout << " output length: " << setw(3)<< (int)(compressedstring.length()) << endl;

	// show result in nice format
	string nicestring(compressedstring);
	NiceifyHighAsciiString(nicestring);
	cout << "    compressed: " << nicestring << endl;

	// decompress it back to original to check it
	// create bitreader and bitwriter, they will be deleted on exit
	string resultstring;
	string_bitreader from2(compressedstring);
	string_bitwriter to2(resultstring);
	// do decompression (third argument==false means don't expect header info)
	bretv = currentcompressorp->Decompress(from2, to2, false);
	if (!bretv)
		{
		cout << "Error decompressing."<<endl;
		return false;
		}

	// show result
	cout <<	"      original: "<< inputstring << "\n";
	cout <<	"  decompressed: "<< resultstring << endl;
	if (inputstring != resultstring)
		{
		cout << "ERROR: decompression does not match original text."<<endl;
		return false;
		}

	// we are done with success
	return true;
	}


bool OctaneTester::RunCommand_TestFile(std::string filename)
	{
	bool bretv,bretv2;
	std::string tempoutfilename_compressed="testfiletemp_compressed.out";
	std::string tempoutfilename_decompressed="testfiletemp_decompressed.out";
	
	// compress file with no state info
	cout << "Compressing file without state: ";
	bretv=RunCommand_Compress(filename,tempoutfilename_compressed,false);
	if (bretv)
		{
		cout << "Decompressing compressed file: ";
		bretv=RunCommand_Decompress(tempoutfilename_compressed,tempoutfilename_decompressed,false);
		if (bretv)
			{
			// now see if they are identical
			cout << "--> Comparing decompressed file with original: ";
			if (CheckFilesAreIdentical(tempoutfilename_decompressed,filename))
				cout << "FILES MATCH."<<endl;
			else
				cout << "FILES DO *NOT* MATCH."<<endl;
			}
		}
	cout << endl;
		
	// again with state
	cout << "Compressing file with state: ";
	bretv2=RunCommand_Compress(filename,tempoutfilename_compressed,true);
	if (bretv2)
		{
		cout << "Decompressing compressed file: ";
		bretv2=RunCommand_Decompress(tempoutfilename_compressed,tempoutfilename_decompressed,true);
		if (bretv2)
			{
			// now see if they are identical
			cout << "--> Comparing decompressed file with original: ";
			if (CheckFilesAreIdentical(tempoutfilename_decompressed,filename))
				cout << "FILES MATCH."<<endl;
			else
				cout << "FILES DO *NOT* MATCH."<<endl;
			}
		}
	cout << endl;

	// now delete temporary files
	DeleteAFile(tempoutfilename_compressed);
	DeleteAFile(tempoutfilename_decompressed);
	
	// return success
	return (bretv&&bretv2);
	}
//---------------------------------------------------------------------------



//---------------------------------------------------------------------------
bool OctaneTester::RunCommand_CreateCompressor(std::string compressorclassname,std::string compressorname, unsigned short guidnum)
{
	// create a new compressor based on a base class

	// select base compressor class type
	OctaneCompressor *compressorp = global_compressormanagerp->FindBaseCompressorClass(compressorclassname);
	if (compressorp==NULL)
		{
		// couldn't find base class
		cout << "ERROR: could not find compressor base class '"<<compressorclassname<<"'."<<endl;
		return false;
		}

	// ask base class to make us a derived class
	compressorp=compressorp->MakeCompressorInstantiation();
	if (compressorp==NULL)
		{
		// couldn't find base class
		cout << "ERROR: failed to instantiate a derived compressor from base class '"<<compressorclassname<<"'."<<endl;
		return false;
		}

	// set details for it
	compressorp->SetInfo(compressorname,guidnum);
	
	// now add it
	bool bretv=global_compressormanagerp->AddCompressor(compressorp);
	if (!bretv)
		{
		// failed to add it
		delete compressorp;
		return false;
		}
	else
		{
		// set this new compressor to be our current compressor?
		if (currentcompressorp==NULL || true)
			currentcompressorp=compressorp;
		}
	
	return true;
}

	
bool OctaneTester::RunCommand_LoadCompressor(std::string filename)
	{
	// create bitreader, they will be deleted on exit
	file_bitreader from(filename.c_str());
	if (from.error())
		{
		cout << "Error opening file " << filename << " for input.  Loadstate failed."<<endl;
		return false;
		}
	// load the state
	OctaneCompressor * compressorp= global_compressormanagerp->CreateCompressorFromSavedStream(from);
	if (compressorp==NULL)
		{
		cout <<"Failed to load compressor."<<endl;
		return false;
		}
		
	// add compressor
	bool bretv=global_compressormanagerp->AddCompressor(compressorp);
	if (bretv==false)
		{
		cout <<"Failed to add compressor to compressor manager."<<endl;
		delete compressorp;
		return false;
		}
	else
		{
		// set this new compressor to be our current compressor?
		if (currentcompressorp==NULL || true)
			currentcompressorp=compressorp;
		}

	// show results
	cout <<	"Loaded compressor from file '" << filename << "'.\n";
	cout << "  state file size: "<< currentcompressorp->GetStatistics_LastOperation_State_BytesRead()<<" bytes.\n";
	cout << "  execution time:  "<< currentcompressorp->GetStatistics_LastOperation_State_ExecutionTime()<<" seconds."<<endl;
	// show new compressor info
	/// @todo show compressor id info

	// return success
	return true;
	}


bool OctaneTester::RunCommand_SaveCompressor(std::string filename)
	{
	// create bitreader, they will be deleted on exit
	file_bitwriter to(filename.c_str());
	if (to.error())
		{
		cout << "Error opening file " << filename << " for input.  SaveCompressor failed."<<endl;
		return false;
		}
	// save the state (second parameter==false means save complete state, not just state required for decompression)
	bool bretv = currentcompressorp->Save(to,false);
	if (!bretv)
		{
		cout <<"Failed to save state data."<<endl;
		return false;
		}
	// show result
	cout <<	"Saved compressor file '" << filename << "'.\n";
	cout << "  state file size: "<< currentcompressorp->GetStatistics_LastOperation_State_BytesWritten()<<" bytes.\n";

	// return success
	return true;
	}


bool OctaneTester::RunCommand_DeleteCompressor(std::string compressoridstring)
	{
	// delete a compressor
	
	// look up the compressor
	OctaneCompressor *compressorp = global_compressormanagerp->FindInstantiatedCompressorpFromStringId(compressoridstring);
	if (compressorp==NULL)
		{
		cout <<"Couldn't locate compressor '"<<compressoridstring<<"'."<<endl;
		return false;
		}

	// delete it
	global_compressormanagerp->DeleteCompressor(compressorp);

	// if this was our current one, then we need to change it
	if (currentcompressorp==compressorp)
		{
		if (global_compressormanagerp->GetInstantiatedCompressorCount()>0)
			currentcompressorp=global_compressormanagerp->GetInstantiatedCompressorp(0);
		else
			currentcompressorp=NULL;
		}

	// return success
	return true;
	}


bool OctaneTester::RunCommand_MakeState(std::string infilename)
	{
	// create bitreader and bitwriter, they will be deleted on exit
	file_bitreader from(infilename.c_str());
	if (from.error())
		{
		cout << "Error opening file "<<infilename<< " for input.  Makestate failed."<<endl;
		return false;
		}

	// make state data
	cout << "Computing state data using "<<currentcompressorp->GetInstantiatedName() << ":"<<endl;
	bool bretv = currentcompressorp->CreateSymbolsAndModelsUsingStream(from);
	if (!bretv)
		{
		cout <<	"Failed to generate state data."<<endl;
		return false;
		}
	return true;
	}


bool OctaneTester::RunCommand_Compress(std::string infilename,std::string outfilename, bool savedecompressstateinfo)
	{
	// create bitreader and bitwriter, they will be deleted on exit
	file_bitreader from(infilename.c_str());
	if (from.error())
		{
		cout << "Error opening file " << infilename << " for input.  Compression failed."<<endl;
		return false;
		}
	file_bitwriter to(outfilename.c_str());
	if (to.error())
		{
		cout << "Error opening file " << outfilename << " for output.  Compression failed."<<endl;
		return false;
		}

	// do the compression
	cout << "Compressing with " << currentcompressorp->GetInstantiatedName() << ":"<<endl;
	bool bretv = currentcompressorp->Compress(from, to, savedecompressstateinfo, savedecompressstateinfo);
	if (!bretv)
		{
		cout << "Compression failed."<<endl;
		return false;
		}

	// show output
	cout << "       original size: " << currentcompressorp->GetStatistics_LastOperation_Compression_BytesRead() << " bytes (" << infilename << ")\n";
	cout << "     compressed size: " << currentcompressorp->GetStatistics_LastOperation_Compression_BytesWritten() << " bytes (" << outfilename << ")\n";
    if (savedecompressstateinfo)
		cout << " est. cost of header: " << currentcompressorp->GetDiskspaceUsed(true)<< " bytes.\n";
	cout << "      execution time: " << currentcompressorp->GetStatistics_LastOperation_Compression_ExecutionTime() << " seconds.\n";
	cout << "   compression ratio: " << currentcompressorp->GetStatistics_LastOperation_Compression_Ratio()*100.0 << "% (" << currentcompressorp->GetStatistics_LastOperation_Compression_Bitrate() << " bits per byte)."<<endl;

	// return success
	return true;
	}


bool OctaneTester::RunCommand_Decompress(std::string infilename,std::string outfilename, bool savedecompressstateinfo)
	{
	// create bitreader and bitwriter, they will be deleted on exit
	OctaneCompressor *compressorp;
	int headersize=0;

	file_bitreader from(infilename.c_str());
	if (from.error())
		{
		cout << "Error opening file " << infilename << " for input.  Decompression failed."<<endl;
		return false;
		}
	file_bitwriter to(outfilename.c_str());
	if (to.error())
		{
		cout << "Error opening file " << outfilename << " for output.  Decompression failed."<<endl;
		return false;
		}

	if (savedecompressstateinfo)
		{
		// first read guid info - and if found try to create a compressor from known compressors
		int retvguid=OctaneCompressor::LoadGUID(from);
		if (retvguid==-1)
			{
			// compressor details are in from stream
			compressorp=global_compressormanagerp->CreateCompressorFromSavedStream(from);
			if (compressorp==NULL)
				{
				cout << "Error creating compressor from the input stream."<<endl;
				return false;
				}
			headersize=from.tell_byte();
			cout << "Recreated temporary compressor from input stream."<<endl;
			}
		else
			{
			// only the guid is in the from stream, now we have to look it up
			compressorp=global_compressormanagerp->FindInstantiatedCompressorpFromGuid(retvguid);
			if (compressorp==NULL)
				{
				cout << "Error, could not find an instantiated compressor which matches input stream expectation(GUID="<<retvguid<<")."<<endl;
				return false;
				}
			cout << "Using instantiated compressor with guid "<<retvguid<<"."<<endl;
			}
		}
	else
		{
		// use current compressor
		compressorp=currentcompressorp;
		}

	// do the decompression
	cout << "Decompressing with " << compressorp->GetInstantiatedName() << ":"<<endl;
	// we pass the last argument false because we have already loaded any compressor state data
	bool bretv = compressorp->Decompress(from, to, false);
	if (!bretv)
		cout << "Decompression failed."<<endl;
	else
		{
		// kludge to fixup lastoperation size to account for header size
		compressorp->Add_LastOperation_Decompression_BytesRead(headersize);
		// show result
		cout <<"      original size: "
			<< compressorp->GetStatistics_LastOperation_Decompression_BytesRead()
			<< " (" << infilename << ")\n"
			<< "  decompressed size: "
			<< compressorp->GetStatistics_LastOperation_Decompression_BytesWritten()
			<< " (" << outfilename << ")\n"
			<< "     execution time: "
			<< compressorp->GetStatistics_LastOperation_Decompression_ExecutionTime()
			<< " seconds.\n"
			<< "  compression ratio: "
			<< compressorp->GetStatistics_LastOperation_Decompression_Ratio()*100.0
			<< "% (" << compressorp->GetStatistics_LastOperation_Decompression_Bitrate() << " bits per byte)."<<endl;
		}

	if (savedecompressstateinfo)
		{
		// kill the temporary compressor
		delete compressorp;
		}

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



//---------------------------------------------------------------------------
bool OctaneTester::DeleteAFile(std::string filename)
{
	// helper function, delete a file if it exists, or do nothing if not

	remove(filename.c_str());

	// return true on deletion of file or no file
	return true;
}

bool OctaneTester::CheckFilesAreIdentical(std::string filename1,std::string filename2)
{
	// test files for equality

	// inefficient way but works

	file_bitreader from1(filename1.c_str());
	file_bitreader from2(filename2.c_str());
	if (from1.error() || from2.error())
		{
		// error
		return false;
		}
	
	if (from1.bytes_left()!=from2.bytes_left())
		{
		// dont match
		return false;
		}

	int len=from1.bytes_left();
	unsigned char c1,c2;
	while (len>0)
		{
		c1=from1.get_byte();
		c2=from2.get_byte();
		if (c1!=c2)
			return false;
		--len;
		}

	// return true on success
	return true;
}
//---------------------------------------------------------------------------
