#include "AccountFile.h"

// Author: Steve Talbot

AccountFile:: AccountFile(){

	_accountInfo = NULL;

	_userID_PageID_Pairs = new map<int, int>();

	_fileDrive = "";

	_fileDirectory = "";

	_fileName = "";

	// 4b per int ..
	_intSize = 4;

	// 2b per bool ..
	_boolSize = 2;

	// (3 ints per record / block) x (4b per int)
	// (2 bool per record / block) x (2b per int) +
	// --------------------------------------------
	//  16b per record / block ..
	_blockSize = 16;

}


AccountFile:: ~AccountFile(){

	if( _accountInfo != NULL ) delete _accountInfo;

	delete _userID_PageID_Pairs;

}


int AccountFile:: findAccountInfoPageID( int id_, fstream* file_ ){

	int pageID = -1;

	if ( !file_->fail() ) {

		int fileIntValue = -1;

		// locate "page-to-read", referenced from beginning of file ..
		//file.seekg( ios::beg );
		file_->seekg(0, ios::end);
		int fileLength = file_->tellg();
		file_->seekg(0, ios::beg);

		// "i" == pageID of record to read,
		// linear scan / sequential access,
		// only looking at "login id" of each record (1st field) ..
		int blockNumber = 0;
		for(int i=0; i<fileLength; i+=_blockSize ){

			file_->seekg( i );

			file_->read( reinterpret_cast< char * > ( &( fileIntValue ) ), _intSize );

			// if "id" is found in the file,
			// break stop looking through the file ..
			if ( id_ == fileIntValue ) {

				pageID = blockNumber;
				break;

			} // end "if()" ..

			// prepare for next "block" number ..
			blockNumber++;

		} // end "for()" ..

	} // end "if()" ..

	return pageID;

} // end "findAccountInfoPageID( int id_ )" ..


bool AccountFile:: hasAccountInfo( int id_ ){

	//string directoryPlusFileName = "C:\\Temp\\Bank_Accounts.bin";
	string directoryPlusFileName = _fileDrive;
	directoryPlusFileName += _fileDirectory;
	directoryPlusFileName += _fileName;

	char* fileName = (char*)directoryPlusFileName.c_str();

	fstream file( fileName,  ios::in | ios::binary );

	if ( !file.fail() ) {

		int pageID = findAccountInfoPageID( id_, &file );

		if( pageID < 0 )
			return false;
		else
			return true;

	} else {

		// if the file fails to open,
		// equivalent to pageID not found ..
		return false;

	}

	// cleanup ..
	file.clear();
	file.close();

} // end "hasAccountInfo" ..


bool AccountFile:: loadAccountInfoFromFile( int id_ ){

	bool readPerformed = false;

	//string directoryPlusFileName = "C:\\Temp\\Bank_Accounts.bin";
	string directoryPlusFileName = _fileDrive;
	directoryPlusFileName += _fileDirectory;
	directoryPlusFileName += _fileName;

	char* fileName = (char*)directoryPlusFileName.c_str();

	fstream file( fileName,  ios::in | ios::binary );

	if ( !file.fail() ) {

		int pageID = findAccountInfoPageID( id_, &file );

		if ( pageID >= 0 ){

			// "file.seekg( i )" from above should have already
			// positioned the "file pointer" in the correct position
			// to continue reading ..

			// dont do this .. this should be done in "Data" ..
			//if( _accountInfo!= NULL ) delete _accountInfo;

			// make sure file pointer starts at beg of file ..
			// (locate page-to-read referenced from beginning of file) ..
			file.seekg( ios::beg );

			// move "file pointer" to position in file where account
			// resides ..
			file.seekg( pageID * _blockSize );

			_accountInfo = new AccountInfo(0, 0, 0);

			int fileIntValue = -1;

			bool fileBoolValue = false;

			// "user id" ..
			// "fileValue" == "id" was read in from above ..
			file.read( reinterpret_cast< char * > ( &( fileIntValue ) ), _intSize );
			_accountInfo->setID( fileIntValue );

			// "user pin" ..
			file.read( reinterpret_cast< char * > ( &( fileIntValue ) ), _intSize );
			_accountInfo->setPN( fileIntValue );

			// "user account balance" ..
			file.read( reinterpret_cast< char * > ( &( fileIntValue ) ), _intSize );
			_accountInfo->setB( fileIntValue );

			// "user account isActive" ..
			file.read( reinterpret_cast< char * > ( &( fileBoolValue ) ), _boolSize );
			_accountInfo->setIsActive( fileBoolValue );

			// "user account isOverdrawn" ..
			file.read( reinterpret_cast< char * > ( &( fileBoolValue ) ), _boolSize );
			_accountInfo->setIsOverdrawn( fileBoolValue );

			// add "id" "pageID" pair to map collection for future use
			// when writing this "AccountInfo" back to file ..
			addUserId_PageID_Pair( id_, pageID );

			readPerformed = true;


		} // end "if()" ..

	} // end "if()" ..

	// cleanup ..
	file.clear();
	file.close();

	return readPerformed;

} // end "loadAccountInfoFromFile( int id_ )" ..


bool AccountFile:: writeAccountInfoToFile(){

	bool writePerformed = false;

	if ( _accountInfo != NULL ) {

		//string directoryPlusFileName = "C:\\Temp\\Bank_Accounts.bin";
		string directoryPlusFileName = _fileDrive;
		directoryPlusFileName += _fileDirectory;
		directoryPlusFileName += _fileName;

		char* fileName = (char*)directoryPlusFileName.c_str();

		// Workaround:
		// "ios::in | ios::out" prevents this unwanted behavior,
		// data _preceeding_ it is cleared / overwritten.
		// allowing the desired "random access" (unix = rb+) writes ..
		fstream file( fileName, ios::in | ios::out | ios::binary );

		// this means the file doesn't exist yet,
		// so attempt to create the file ready for output ..
		// (this should have been taken care of in "allocateDataPage") ..
		if( file.fail() ){

			file.open( fileName, ios::out | ios::binary | ios::app );

			// needed in order to "clear" errorflags left over from first "fail" ..
			file.clear();

		}

		if ( !file.fail() ) {

			// see if the account info for this "id_" was loaded in from file previously ..
			int pageID = findPageID( _accountInfo->getID() );

			if( pageID >= 0 ){

				// make sure file pointer starts at beg of file ..
				// (locate page-to-write referenced from beginning of file) ..
				file.seekp( ios::beg );

				// move "file pointer" to position in file where account resides ..
				file.seekp( pageID * _blockSize );

			} else {

				// because this is record is not yet in the file,
				// new records are just appended to the end of the file ..
				file.seekp(0, ios::end);
				int fileLength = file.tellp();
				file.seekp(0, ios::beg);
				if( fileLength == 0 )
					file.seekp( ios::beg );
				else
					file.seekp( fileLength );

			}

			int fieldIntValue = -1;

			bool fieldBoolValue = false;

			// write the values in the "AccountInfo" to file ..

			// "user id" ..
			fieldIntValue = _accountInfo->getID();
			file.write( reinterpret_cast< const char * >( &fieldIntValue ), _intSize );

			// "user pin" ..
			fieldIntValue = _accountInfo->getPN();
			file.write( reinterpret_cast< const char * >( &fieldIntValue ), _intSize );

			// "user account balance" ..
			fieldIntValue = _accountInfo->getB();
			file.write( reinterpret_cast< const char * >( &fieldIntValue ), _intSize );

			// "user account isActive" ..
			fieldBoolValue = _accountInfo->getIsActive();
			file.write( reinterpret_cast< const char * >( &fieldBoolValue ), _boolSize );

			// "user account isOverdrawn" ..
			fieldBoolValue = _accountInfo->getIsOverdrawn();
			file.write( reinterpret_cast< const char * >( &fieldBoolValue ), _boolSize );

			writePerformed = true;

		} // end "if()" ..

		// cleanup ..
		file.clear();
		file.close();

	} // end "if()" ..

	return writePerformed;

} // end "writeAccountInfoToFile( int id_ )" ..


/*
 * GETTERS ..
 */

AccountInfo* AccountFile:: getAccountInfo(){

	return _accountInfo;

}

string AccountFile:: getFileDrive(){

	return _fileDrive;

}

string AccountFile:: getFileDirectory(){

	return _fileDirectory;

}

string AccountFile:: getFileName(){

	return _fileName;

}

/*
 * SETTERS ..
 */

void AccountFile:: setAccountInfo( AccountInfo* accountInfo_ ){

	_accountInfo = accountInfo_;

}

void AccountFile:: setFileDrive( string fileDrive_ ){

	_fileDrive = fileDrive_;

}

void AccountFile:: setFileDirectory( string fileDirectory_ ){

	_fileDirectory = fileDirectory_;

}

void AccountFile:: setFileName( string fileName_ ){

	_fileName = fileName_;

}

/*
 * Utility Methods ..
 */

int AccountFile:: findPageID( int id_ ){

	int result = -1;

	map<int, int>::iterator it;
	for( it=_userID_PageID_Pairs->begin(); it!=_userID_PageID_Pairs->end(); it++ ){

		if( (*it).first == id_ ){

			result = (*it).second;
			break;

		}

	}

	return result;

}

void AccountFile:: addUserId_PageID_Pair( int id_, int pageID_ ){

	_userID_PageID_Pairs->insert( pair<int, int>( id_, pageID_ ) );

}

