//--------------------------------------------------------------------------------
// <copyright file="StorageController_FileState.cpp" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
//   Implementation of StorageController::FileState, an entity inner class of
//   StorageController that holds the attributes and state of a file stored by
//   the storage controller and which implements some of its storage and retrieval
//   actions.
// </summary>
//
// <owner>Mike Cookson</owner>
//--------------------------------------------------------------------------------

#include "StorageController.h"
#include "DoThrow.h"
#include "nsextend.h"
#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <boost/filesystem.hpp>
#include <string>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "LqrSMACPrintfBuffer.h"
#include "FCCMessage_Utilities.h"

using namespace std;
using namespace boost::filesystem;
using namespace boost::posix_time;
using namespace LiquidRobotics::PayloadInterface::FCCMessage;
using namespace LiquidRobotics::SMACFramework;

namespace LiquidRobotics {
namespace PayloadInterface {

IMPLEMENT_INNER_EXCEPTION(StorageController::FileState, CannotCreateFileExists, std::runtime_error)
IMPLEMENT_INNER_EXCEPTION(StorageController::FileState, CannotOverwriteReadOnly, std::runtime_error)
IMPLEMENT_INNER_EXCEPTION(StorageController::FileState, FileNotFound, std::runtime_error)
IMPLEMENT_INNER_EXCEPTION(StorageController::FileState, FileCRCCheckError, std::runtime_error)
IMPLEMENT_INNER_EXCEPTION(StorageController::FileState, FileMD5CheckError, std::runtime_error)
IMPLEMENT_INNER_EXCEPTION(StorageController::FileState, FileHasNotBeenChecked, std::runtime_error)
IMPLEMENT_INNER_EXCEPTION(StorageController::FileState, FileBoundsError, std::runtime_error)
IMPLEMENT_INNER_EXCEPTION(StorageController::FileState, FileCheckValuesNotEstablished, std::runtime_error)

SHARED_LIB_PUBLIC StorageController::FileState::FileIOError::FileIOError(int _savedErrno, const string& _fileName, const string& whatArg)
: std::runtime_error(whatArg)
, savedErrno(_savedErrno)
, fileName(_fileName)
{
}

SHARED_LIB_PUBLIC StorageController::FileState::MissingSCDirectory::MissingSCDirectory(const string& _dirName, const string& whatArg)
: std::runtime_error(whatArg)
, dirName(_dirName)
{
}

SHARED_LIB_PUBLIC StorageController::FileState::FileSystemFull::FileSystemFull(const string& _fileName, const string& whatArg)
: std::runtime_error(whatArg)
, fileName(_fileName)
{
}

// Default constructor (used for serialization?)
StorageController::FileState::FileState()
: fileID((FileIDType) NO_FILE_ID)
, fileSize(0)
, fileWasChecked(false)
, fileReadOnly(false)
, crcSet(false)
, crcCalcSet(false)
, md5Set(false)
, md5CalcSet(false)
, firstCheckIncomplete(true)
, dirty(false)
{
}

// Constructor with a known directory and actual file name; the reported file name is determined based
// on the reported names already in the list and the characteristics and check values are determined by
// the file itself
StorageController::FileState::FileState(StorageController& sc, const string& _actualFileName, bool readOnly)
: actualFileName(_actualFileName)
, fileID((FileIDType) NO_FILE_ID)
, fileSize(0)
, fileWasChecked(false)
, fileReadOnly(readOnly)
, crcSet(false)
, crcCalcSet(false)
, md5Set(false)
, md5CalcSet(false)
, firstCheckIncomplete(true)
, dirty(false)
{
	// lock the storage controller during this
	StorageController::Lock lock(sc);

	// verify that the actual filename passed in is not already in the dictionary
	if (sc.fileStateByActualName.find(_actualFileName) != sc.fileStateByActualName.end())
	{
		stringstream stm;
		stm << "A file with actualFileName='" << _actualFileName << "' already exists in the storage controller at "
		    << sc.config.GetStorageDirectoryName();

		DoThrow<FileState::CannotCreateFileExists>(__FILE__, __LINE__, stm.str());
	}

	// get the directory name from the storage controller's configuration
	const string scDir = sc.config.GetStorageDirectoryName();

	// get the path and name of the file together and verify that it exists
	string actualFilePath = GetActualFilePath(scDir, actualFileName, readOnly, true);

	// use stat(2) to pick up the file attributes
	struct stat statBuf;
	int status;
	int errSave;

	do
	{
		status = stat(actualFilePath.c_str(), &statBuf);
		errSave = errno;
	} while((status == -1) && (errSave == EINTR));

	if (status == -1)
	{
		stringstream msg;
		msg << "stat() failed for fullPath='" << actualFilePath << "'";
		string formattedMsg = FormatMessage(__FILE__, __LINE__, msg.str());

		throw FileState::FileIOError(errSave, actualFilePath, formattedMsg);
	}

	fileSize = statBuf.st_size;
	modifiedTime = from_time_t(statBuf.st_mtime);
	creationTime = modifiedTime;

	// the filenames in the directory can be greater than 8.3, so rationalize according to what is
	// already in the file state by reported name
	reportedFileName = SuggestReportedFileName(scDir, actualFileName, sc.fileStateByReportedName);

	// compute the CRC of the file; it is assumed to be as it should be (nobody else will specify it)
	CRC16 crcCalculator;
	crcCalculator.AddFileContent(actualFilePath);
	crc = crcCalc = crcCalculator;
	crcSet = crcCalcSet = true;

	// compute the MD5 of the file; it is assumed to be as it should be (nobody else will specify it)
	MD5 md5Calculator;
	md5Calculator.AppendFileContent(actualFilePath);
	md5 = md5Calc = md5Calculator.Final();
	md5Set = md5CalcSet = true;

	firstCheckIncomplete = false;			// there is no pending check for correctness
	fileWasChecked = true;					// file is correct
}

// Constructor using reported name as the basis for an actual filename, creates the file according to specifications
StorageController::FileState::FileState(StorageController& sc,
		const string& _reportedFileName, FileIDType _fileID, FileSizeType _fileSize, datetime _creationTime,
		datetime _modifiedTime, bool _readOnly, const CRC16& _crc, const MD5::Entity& _md5,
		bool overwrite, bool ignoreReadOnly)

: reportedFileName(_reportedFileName)
, fileID(_fileID)
, fileSize(_fileSize)
, creationTime(_creationTime)
, modifiedTime(_modifiedTime)
, fileWasChecked(false)
, fileReadOnly(_readOnly)
, crc(_crc)
, crcSet(true)
, crcCalcSet(false)
, md5(_md5)
, md5Set(true)
, md5CalcSet(false)
, firstCheckIncomplete(true)
, dirty(false)
{
	Lock lock(sc);

	// read the storage directory name from the storage controller's configuration
	const string scDir = sc.config.GetStorageDirectoryName();

	// create an actual filename
	actualFileName = SuggestActualFileName(reportedFileName, scDir);

	// create the file in the directory at the correct size
	string actualFilePath = GetActualFilePath(scDir, false, true, overwrite);

	const int flags = O_CREAT | O_RDWR | (overwrite ? O_TRUNC : O_EXCL);
	const int mode = S_IWUSR | S_IRUSR;
	int fd;
	int errSave;

	do
	{
		fd = open(actualFilePath.c_str(), flags, mode);
		errSave = errno;
	} while((fd == -1) && (errSave == EINTR));

	if (fd == -1)
	{
		if (errSave == EEXIST)
		{
			stringstream stm;
			stm << "Actual file exists at '" << actualFilePath << "' (2)";
			DoThrow<CannotCreateFileExists>(__FILE__, __LINE__, stm.str());
		}
		else
			throw FileIOError(errSave, actualFilePath,
					FormatMessage(__FILE__, __LINE__, "Failed to create file using open()"));
	}

	// file now open, I'm going to initialize the file to the right size to pre-allocate the space
	char zeroBuffer[512];
	memset(zeroBuffer, '\0', sizeof(zeroBuffer));

	size_t remaining = fileSize;
	size_t offs = 0;

	while(remaining)
	{
		int toWrite = std::min(remaining, sizeof(zeroBuffer));
		int status;

		do
		{
			status = write(fd, zeroBuffer, toWrite);
			errSave = errno;

			if ((status == -1) && (errSave == EINTR))
				lseek(fd, offs, SEEK_SET);

		} while((status == -1) && (errSave == EINTR));

		if (status == -1)
		{
			close(fd);
			unlink(actualFilePath.c_str());
			throw FileIOError(errSave, actualFilePath,
					FormatMessage(__FILE__, __LINE__, "Failed to write to file during initialization"));
		}
		else if (status != toWrite)
		{
			close(fd);
			unlink(actualFilePath.c_str());
			throw FileSystemFull(actualFilePath,
					FormatMessage(__FILE__, __LINE__, "Filesystem full during file initialization"));
		}

		offs += toWrite;
		remaining -= toWrite;
	}

	close(fd);
}

#define DOCTOR(item)	item(right.item)

StorageController::FileState::FileState(const FileState& right)
: DOCTOR(actualFileName)
, DOCTOR(reportedFileName)
, DOCTOR(fileID)
, DOCTOR(fileSize)
, DOCTOR(creationTime)
, DOCTOR(modifiedTime)
, DOCTOR(fileWasChecked)
, DOCTOR(fileReadOnly)
, DOCTOR(crc)
, DOCTOR(crcSet)
, DOCTOR(crcCalc)
, DOCTOR(crcCalcSet)
, DOCTOR(md5)
, DOCTOR(md5Set)
, DOCTOR(md5Calc)
, DOCTOR(md5CalcSet)
, DOCTOR(firstCheckIncomplete)
, dirty(false)
{
}

#define DOASSIGN(item)	(item = right.item)

StorageController::FileState& StorageController::FileState::operator=(const FileState& right)
{
	if (this != &right)
	{
		DOASSIGN(actualFileName);
		DOASSIGN(reportedFileName);
		DOASSIGN(fileID);
		DOASSIGN(fileSize);
		DOASSIGN(creationTime);
		DOASSIGN(modifiedTime);
		DOASSIGN(fileWasChecked);
		DOASSIGN(fileReadOnly);
		DOASSIGN(crc);
		DOASSIGN(crcSet);
		DOASSIGN(crcCalc);
		DOASSIGN(crcCalcSet);
		DOASSIGN(md5);
		DOASSIGN(md5Set);
		DOASSIGN(md5Calc);
		DOASSIGN(md5CalcSet);
		DOASSIGN(firstCheckIncomplete);
		dirty = true;
	}

	return *this;
}

inline bool CheckEq(const string& s1, const string& s2)				{ return !s1.compare(s2); }
template<typename T> bool CheckEq(const T& t1, const T& t2)			{ return t1 == t2; }

#define CHECKEQ(item)  CheckEq(item, right.item)

bool StorageController::FileState::operator==(const FileState& right) const
{
	return CHECKEQ(actualFileName)
	    && CHECKEQ(reportedFileName)
	    && CHECKEQ(fileID)
	    && CHECKEQ(fileSize)
	    && CHECKEQ(creationTime)
	    && CHECKEQ(modifiedTime)
	    && CHECKEQ(fileWasChecked)
	    && CHECKEQ(fileReadOnly)
	    && CHECKEQ(crc)
	    && CHECKEQ(crcSet)
	    && CHECKEQ(md5)
	    && CHECKEQ(md5Set)
	    && CHECKEQ(firstCheckIncomplete);
}

/*void StorageController::FileState::AssignFileChecks()
{
	crcSet = md5Set = false;

	try
	{
		CRC16 newCRC;
		newCRC.AddFileContent(actualFileName);
		crcSet = true;

		MD5 md5Calculator;
		md5Calculator.AppendFileContent(actualFileName);
		md5 = md5Calculator.Final();
		md5Set = true;
	}
	catch(std::runtime_error& exc)
	{
		stringstream stm;

		stm << "AssignFileChecks caught exception while computing check values: "
		    << exc.what();

		string msg = stm.str();

		DoThrow<FileNotFound>(__FILE__, __LINE__, msg);
	}
}*/

void StorageController::FileState::AssignFileChecks(const CRC16& _crc, const MD5::Entity& _md5)
{
	crc = _crc;
	crcSet = true;
	md5 = _md5;
	md5Set = true;
}

void StorageController::FileState::CheckFile(const string& scDir)
{
	if (fileWasChecked)
	{
		fileWasChecked = false;
		dirty = true;
	}

	if (!crcSet)
		DoThrow<FileCheckValuesNotEstablished>(__FILE__, __LINE__, "CRC check value was not set");

	if (!md5Set)
		DoThrow<FileCheckValuesNotEstablished>(__FILE__, __LINE__, "MD5 check value was not set");

	string actualFilePath = GetActualFilePath(scDir, true);

	try
	{
		CRC16 checkCRC;
		checkCRC.AddFileContent(actualFilePath);

		if (!crcCalcSet || (checkCRC != crcCalc))
		{
			crcCalcSet = true;
			crcCalc = checkCRC;
			dirty = true;
		}
	}
	catch(std::runtime_error& exc)
	{
		stringstream stm;
		stm << "StorageController::FileState::CheckFile caught exception during CRC check: "
		    << exc.what();

		string msg = stm.str();
		DoThrow<FileCRCCheckError>(__FILE__, __LINE__, msg);
	}

	try
	{
		MD5 md5Calculator;
		md5Calculator.AppendFileContent(actualFilePath);
		MD5::Entity checkMD5 = md5Calculator.Final();

		if (!md5CalcSet || (checkMD5 != md5Calc))
		{
			md5CalcSet = true;
			md5Calc = checkMD5;
			dirty = true;
		}
	}
	catch(std::runtime_error& exc)
	{
		stringstream stm;
		stm << "StorageController::FileState::CheckFile caught exception during MD5 check: "
		    << exc.what();

		string msg = stm.str();
		DoThrow<FileMD5CheckError>(__FILE__, __LINE__, msg);
	}

	if (crc != crcCalc)
	{
		stringstream stm;
		stm << "CRC check failed: ref=" << crc << " file=" << crcCalc;

		string msg = stm.str();
		DoThrow<FileCRCCheckError>(__FILE__, __LINE__, msg);
	}

	if (md5 != md5Calc)
	{
		stringstream stm;
		stm << "MD5 check failed: ref=" << md5 << " file=" << md5Calc;

		string msg = stm.str();
		DoThrow<FileMD5CheckError>(__FILE__, __LINE__, msg);
	}

	firstCheckIncomplete = false;
	fileWasChecked = true;
	dirty = true;
}

void StorageController::FileState::ReadFile(ByteArray& result, const string& scDir, FileSizeType offset, FileSizeType maxSize, bool verifyChecked)
{
	result.clear();

	string actualFilePath = GetActualFilePath(scDir, true);

	// normally, throw an exception if outside of bounds (ignore the case of offset=0 and fileSize=0)
	if ((offset || fileSize) && (offset >= fileSize))
	{
		stringstream stm;
		stm << "Cannot read beyond the file size (" << fileSize << ") for " << actualFilePath;

		DoThrow<FileBoundsError>(__FILE__, __LINE__, stm.str());
	}

	// throw exception if file integrity not checked
	if (verifyChecked && !fileWasChecked)
	{
		stringstream stm;
		stm << "Read from '" << actualFilePath << "' was requested, but the file has not been checked";

		DoThrow<FileHasNotBeenChecked>(__FILE__, __LINE__, stm.str());
	}

	// special case: reading at offset 0 in zero-length file
	if (!offset && !fileSize)
		return;

	int fd;
	int errSave;
	FileSizeType readSize = min(fileSize-offset, maxSize);

	result.resize(readSize);

	do
	{
		fd = open(actualFilePath.c_str(), O_RDONLY);
		errSave = errno;
	} while((fd == -1) && (errSave == EINTR));

	if (fd == -1)
	{
		switch(errSave)
		{

		case ENOENT:
		{
			stringstream msg;
			msg << "open() reported file not found for fullPath='" << actualFilePath << "'";
			DoThrow<FileState::FileNotFound>(__FILE__, __LINE__, msg.str());
		}

		default:
		{
			stringstream msg;
			msg << "open() failed for fullPath='" << actualFilePath << "'";

			string formattedMsg = FormatMessage(__FILE__, __LINE__, msg.str());
			throw FileState::FileIOError(errSave, actualFilePath, formattedMsg);
		}

		}
	}

	int status;

	do
	{
		status = lseek(fd, offset, SEEK_SET);
		errSave = errno;
	} while((status == -1) && (errSave == EINTR));

	if (status == -1)
	{
		close(fd);

		stringstream stm;
		stm << "lseek() to offset " << offset << " in " << actualFilePath << " failed";

		string formattedMsg = FormatMessage(__FILE__, __LINE__, stm.str());

		throw FileState::FileIOError(errSave, actualFilePath, formattedMsg);
	}

	do
	{
		status = read(fd, &result.at(0), readSize);
		errSave = errno;
	} while((status == -1) && (errSave == EINTR));

	close(fd);

	if (status == -1)
	{
		stringstream stm;
		stm << "read() from '" << actualFilePath << "' offset " << offset << " failed";

		string formattedMsg = FormatMessage(__FILE__, __LINE__, stm.str());

		throw FileState::FileIOError(errSave, actualFilePath, formattedMsg);
	}
	else if (status != (int) readSize)
	{
		stringstream stm;
		stm << "Read at offset " << offset << " returned " << status << " bytes; but " << readSize << " bytes were expected";

		DoThrow<FileBoundsError>(__FILE__, __LINE__, stm.str());
	}
}

void StorageController::FileState::WriteFile(const string& scDir, uint32_t offset, const ByteArray& data)
{
	// writes to the file should not result in modified time update if the file has not been checked yet
	string actualFilePath = GetActualFilePath(scDir, true, true, true);

	if ((offset+data.size()) > fileSize)
	{
		stringstream stm;
		stm << "Cannot write " << data.size() << " bytes at offset " << offset
		    << ", which would exceed the file size (" << fileSize << ") for " << actualFilePath;

		DoThrow<FileBoundsError>(__FILE__, __LINE__, stm.str());
	}

	if (!data.size())
		return;

	int fd;
	int errSave;

	do
	{
		fd = open(actualFilePath.c_str(), O_RDWR);
		errSave = errno;
	} while((fd == -1) && (errSave == EINTR));

	if (fd == -1)
	{
		switch(errSave)
		{

		case ENOENT:
		{
			stringstream msg;
			msg << "open() reported file not found for fullPath='" << actualFilePath << "'";

			DoThrow<FileState::FileNotFound>(__FILE__, __LINE__, msg.str());
		}

		default:
		{
			stringstream msg;
			msg << "open() failed for fullPath='" << actualFilePath << "'";

			string formattedMsg = FormatMessage(__FILE__, __LINE__, msg.str());

			throw FileState::FileIOError(errSave, actualFilePath, formattedMsg);
		}

		}
	}

	int status;

	do
	{
		status = lseek(fd, offset, SEEK_SET);
		errSave = errno;
	} while((status == -1) && (errSave == EINTR));

	if (status == -1)
	{
		close(fd);

		stringstream stm;
		stm << "lseek() to offset " << offset << " in " << actualFilePath << " failed";

		string formattedMsg = FormatMessage(__FILE__, __LINE__, stm.str());

		throw FileState::FileIOError(errSave, actualFilePath, formattedMsg);
	}

	do
	{
		status = write(fd, &data.at(0), data.size());
		errSave = errno;
	} while((status == -1) && (errSave == EINTR));

	close(fd);

	if (!firstCheckIncomplete)
	{
		time_t timeNow = time(NULL);
		datetime dtNow = from_time_t(timeNow);

		if (dtNow != modifiedTime)
		{
			modifiedTime = dtNow;
			dirty = true;
		}
	}

	if (fileWasChecked)
	{
		fileWasChecked = false;
		dirty = true;
	}

	if (status == -1)
	{
		stringstream stm;
		stm << "write() to '" << actualFilePath << "' offset " << offset << " failed";

		string formattedMsg = FormatMessage(__FILE__, __LINE__, stm.str());

		throw FileState::FileIOError(errSave, actualFilePath, formattedMsg);
	}
	else if (status != (int) data.size())
	{
		stringstream stm;
		stm << "Write at offset " << offset << " wrote " << status << " bytes; but " << data.size() << " were expected";

		throw FileState::FileSystemFull(actualFilePath,	FormatMessage(__FILE__, __LINE__, stm.str()));
	}
}

void StorageController::FileState::SetActualFileName(const char* newValue)
{
	if (newValue == NULL)
		DoThrow<invalid_argument>(__FILE__, __LINE__, "SetActualFileName newValue is NULL");

	if (actualFileName.compare(newValue) || fileWasChecked || crcSet || md5Set || !firstCheckIncomplete)
		dirty = true;

	actualFileName.assign(newValue);
	fileWasChecked = false;
	crcSet = false;
	md5Set = false;
	firstCheckIncomplete = true;
}

void StorageController::FileState::SetActualFileName(const string& newValue)
{
	if (actualFileName.compare(newValue) || fileWasChecked || crcSet || md5Set || !firstCheckIncomplete)
		dirty = true;

	actualFileName.assign(newValue);
	fileWasChecked = false;
	crcSet = false;
	md5Set = false;
	firstCheckIncomplete = true;
}

void StorageController::FileState::SetReportedFileName(const char* newValue)
{
	if (newValue == NULL)
		DoThrow<invalid_argument>(__FILE__, __LINE__, "SetReportedFileName newValue is NULL");

	if (reportedFileName.compare(newValue))
	{
		reportedFileName.assign(newValue);
		dirty = true;
	}
}

void StorageController::FileState::SetReportedFileName(const string& newValue)
{
	if (reportedFileName.compare(newValue))
	{
		reportedFileName.assign(newValue);
		dirty = true;
	}
}

void StorageController::FileState::SetFileID(FileIDType newValue)
{
	if (fileID != newValue)
	{
		fileID = newValue;
		dirty = true;
	}
}

void StorageController::FileState::SetFileIDToDefault()
{
	SetFileID((FileIDType) NO_FILE_ID);
}

void StorageController::FileState::SetFileSize(FileSizeType newValue)
{
	if (fileSize != newValue)
	{
		fileSize = newValue;
		dirty = true;
	}
}

void StorageController::FileState::SetCreationTime(datetime newValue)
{
	if (creationTime != newValue)
	{
		creationTime = newValue;
		dirty = true;
	}
}

void StorageController::FileState::SetModifiedTime(datetime newValue)
{
	if (modifiedTime != newValue)
	{
		modifiedTime = newValue;
		dirty = true;
	}
}

void StorageController::FileState::SetFileChecked(bool newValue)
{
	if (fileWasChecked != newValue)
	{
		fileWasChecked = newValue;
		dirty = true;
	}
}

void StorageController::FileState::SetReadOnly(bool newValue)
{
	if (fileReadOnly != newValue)
	{
		fileReadOnly = newValue;
		dirty = true;
	}
}

CRC16 StorageController::FileState::GetCRC() const
{
	if (!crcSet)
		DoThrow<FileCheckValuesNotEstablished>(__FILE__, __LINE__, "File CRC check value not set yet");

	return crc;
}

void StorageController::FileState::SetCRC(const CRC16& newValue)
{
	if ((crc != newValue) || !crcSet)
	{
		crc = newValue;
		crcSet = true;
		dirty = true;
	}
}

CRC16 StorageController::FileState::GetCalculatedCRC() const
{
	if (!fileWasChecked)
		DoThrow<FileHasNotBeenChecked>(__FILE__, __LINE__, "File has not been checked, so calculated CRC is not available");

	return crcCalc;
}

MD5::Entity StorageController::FileState::GetMD5() const
{
	if (!md5Set)
		DoThrow<FileCheckValuesNotEstablished>(__FILE__, __LINE__, "File MD5 check value not set yet");

	return md5;
}

void StorageController::FileState::SetMD5(const MD5::Entity& newValue)
{
	if ((md5 != newValue) || !md5Set)
	{
		md5 = newValue;
		md5Set = true;
		dirty = true;
	}
}

MD5::Entity StorageController::FileState::GetCalculatedMD5() const
{
	if (!fileWasChecked)
		DoThrow<FileHasNotBeenChecked>(__FILE__, __LINE__, "File has not been checked, so calculated MD5 is not available");

	return md5Calc;
}

string StorageController::FileState::SuggestActualFileName(const string& reportedFileName, const string& scDirectory,
											bool permute, const MapByString* _reportedNameList)
{
	string result;
	path scDirPath(scDirectory);

	if (!exists(scDirPath))
	{
		stringstream stm;
		stm << "SC directory '" << scDirPath << "' does not exist";

		throw MissingSCDirectory(scDirectory, FormatMessage(__FILE__, __LINE__, stm.str()));
	}

	string realName = ReportedStrToRealName(reportedFileName);

	scDirPath /= realName;

	if (exists(scDirPath) && permute)
		result = PermuteFileName(reportedFileName, scDirectory);
	else
		result = realName;

	return result;
}

bool StorageController::FileState::PermutedFileName(const string& baseName, const string& ext, int tryNum, string& result)
{
	if (tryNum < 0)
		return false;

	const string::size_type baseLengthResult = 8;
	const string::size_type numPermChars = 4;
	const string::size_type numRetaining = baseLengthResult-numPermChars;

	if (baseName.length() > numRetaining)
		result = baseName.substr(0, numRetaining);
	else
	{
		result = baseName;

		int numToAdd = numRetaining-result.length();

		if (numToAdd > 0)
			result += string(numToAdd, '_');
	}

	char buffer[2*numPermChars+2];
	memset(buffer, '0', sizeof(buffer));
	ltoa((long) tryNum, &buffer[numPermChars], 36);

	const string::size_type bufferLen = strlen(&buffer[numPermChars]);

	if (bufferLen > numPermChars)
		return false;

	result += &buffer[bufferLen];

	if (ext.length())
	{
		result += ".";
		result += ext;
	}

	return true;
}

string StorageController::FileState::PermuteFileName(const string& reportedFileName, const string& scDirectory,
				const MapByString* _reportedNameList)
{
	using namespace boost;
	string realName = ReportedStrToRealName(reportedFileName);

	vector<string> filenameComponents;
	split(filenameComponents, realName, is_any_of("."), token_compress_off);

	const int componentsCount = filenameComponents.size();

	if ((componentsCount < 1) || (componentsCount > 2))
	{
		stringstream stm;

		stm << "PermuteFileName: real file name '"
		    << realName
		    << "' from reported name '"
		    << reportedFileName
		    << "' count not be broken into base name and extension";

		string msg = stm.str();
		DoThrow<invalid_argument>(__FILE__, __LINE__, msg);
	}

	string baseName = filenameComponents[0];
	string ext;

	if (componentsCount == 2)
		ext = filenameComponents[1];

	string testFileName;

	for (int permutation = 0; PermutedFileName(baseName, ext, permutation, testFileName); ++permutation)
	{
		bool failed = false;

		try
		{
			GetActualFilePath(scDirectory, testFileName, false, false, true);
		}
		catch(CannotCreateFileExists& exists)
		{
			failed = true;
		}

		if (!failed)
			return testFileName;
	}

	throw invalid_argument(FormatMessage(__FILE__, __LINE__, "PermuteFileName: failed to find an unused permutation"));
}

string StorageController::FileState::SuggestReportedFileName(const string& scDirectory, const string& actualFileName,
		const MapByString& _reportedNameList)
{
	using namespace boost;
	string result;

	vector<string> filenameComponents;
	split(filenameComponents, actualFileName, is_any_of("."), token_compress_off);

	const string::size_type componentsCount = filenameComponents.size();
	const string::size_type baseNameLength = 8;
	const string::size_type extLength = 3;

	if (componentsCount == 0)
		DoThrow<invalid_argument>(__FILE__, __LINE__, "Empty string passed to SuggestReportedFileName");

	string baseName = filenameComponents[0];

	if (baseName.length() > baseNameLength)
		baseName = baseName.substr(0, baseNameLength);

	trim(baseName);

	size_t n;
	while((n = baseName.find_first_of(' ')) != string::npos)
		baseName[n] = '_';

	string ext;

	if (componentsCount > 1)
		ext = filenameComponents[1];

	if (ext.length() > extLength)
		ext = ext.substr(0, extLength);

	trim(ext);

	while((n = ext.find_first_of(' ')) != string::npos)
		ext[n] = '_';

	stringstream stm;
	stm << baseName;

	if (ext.length())
		stm << "." << ext;

	int permutation = -1;
	string testFileName = stm.str();
	bool found;
	bool permuteWorked = true;
	MapByString::const_iterator it;

	while(it = _reportedNameList.find(result),
	      found = (it != _reportedNameList.end()),
	      found && permuteWorked)

		permuteWorked = PermutedFileName(baseName, ext, ++permutation, testFileName);

	if (found)
		DoThrow<invalid_argument>(__FILE__, __LINE__, "SuggestReportedFileName: failed to find an unused permutation");

	return RealNameToReportedStr(testFileName);
}

string StorageController::FileState::GetActualFilePath(const string& scDir,
													   bool mustExist,
													   bool testForOverwrite,
													   bool overwriteEnabled,
													   bool overrideReadOnly) const
{
	return GetActualFilePath(scDir, actualFileName, GetReadOnly(), mustExist, testForOverwrite, overwriteEnabled,
								overrideReadOnly || firstCheckIncomplete);
}

string StorageController::FileState::GetActualFilePath(const string& scDir,
													   const string& actualFileName,
													   bool readOnly,
													   bool mustExist,
													   bool testForOverwrite,
													   bool overwriteEnabled,
													   bool overrideReadOnly)
{
	// create the file in the directory at the correct size
	path scDirPath(scDir);

	if (!exists(scDirPath))
	{
		string scDirPathString = scDirPath.string();
		stringstream stm;
		stm << "Storage controller directory '" << scDirPathString << "' does not exist";

		throw MissingSCDirectory(scDirPathString, FormatMessage(__FILE__, __LINE__, stm.str()));
	}

	path filePath = scDirPath;
	filePath /= actualFileName;

	if (exists(filePath))
	{
		if (testForOverwrite)
		{
			if (!overwriteEnabled)
			{
				stringstream stm;
				stm << "A file already exists at " << filePath.string();

				DoThrow<CannotCreateFileExists>(__FILE__, __LINE__, stm.str());
			}
			else if (readOnly && !overrideReadOnly)
			{
				stringstream stm;
				stm << "A read-only file already exists at " << filePath.string();

				DoThrow<CannotOverwriteReadOnly>(__FILE__, __LINE__, stm.str());
			}
		}
	}
	else if (mustExist)
	{
		stringstream stm;
		stm << "No file exists at " << filePath.string();

		DoThrow<FileNotFound>(__FILE__, __LINE__, stm.str());
	}

	return filePath.string();
}


}
}
