#define PRINT_LINE_SIZE 80

class fileexcept : public except
{
public:
	fileexcept(const char *szdesc=0):except("File exception",szdesc){};
	~fileexcept(){};
};

class loggerexcept : public except
{
public:
	loggerexcept(const char *szdesc=0):except("File exception",szdesc){};
	~loggerexcept(){};
};


static void handle_seek_error(int checkval)
{
	if ( checkval!=-1 )
		return;

	switch( errno )
	{
	case(EBADF):
		throw new fileexcept("The file handle is invalid");
		break;
	case(EINVAL):
		throw new fileexcept("The value for origin is invalid or the position specified by offset is before the beginning of the file");
		break;
	default:
		throw new fileexcept;
	}
};

static void handle_write_error(int checkval, int requested)
{
	if ( checkval==-1 )
	{
		switch( errno )
		{
		case(EBADF):
			throw new fileexcept("The file handle is invalid");
			break;
		case(ENOSPC):
			throw new fileexcept("There is not enough space left on the device for the operation");
			break;
		default:
			throw new fileexcept;
		}
	}
	else if ( checkval!=requested )
		throw new fileexcept("The data was not written full to disk");

};

static void handle_read_error(int checkval , int requested)
{
	if ( checkval==0 )
		throw new fileexcept("An attemp was made to read past the end of the file");
	else if ( checkval==-1)
	{
		if ( errno==EBADF )
			throw new fileexcept("The file handle is invalid");
		else
			throw new fileexcept;
	}
	else if (checkval!=requested)
		throw new fileexcept("The data was not read full from disk");

};

static void handle_tell_error(int checkval)
{
	if ( checkval!=-1 )
		return;

	if ( errno==EBADF )
		throw new fileexcept("The file handle is invalid");
	else
		throw new fileexcept;
};

static void handle_open_error(int checkval)
{
	if ( checkval!=-1 )
		return;

	switch( errno )
	{
	case(EACCES):
		throw new fileexcept("Tried to open read-only file for writing, or file’s sharing mode does not allow specified operations, or given path is directory");
		break;
	case(EMFILE):
		throw new fileexcept("No more file handles available (too many open files)");
		break;
	case(ENOENT):
		throw new fileexcept("File or path not found");
		break;
	default:
		throw new fileexcept;
	}
}


static void handle_close_error(int checkval)
{
	if ( checkval!=-1 )
		return;

	if ( errno==EBADF )
		throw new fileexcept("The file handle is invalid");
	else
		throw new fileexcept;
}

class logger;

class filewrapper
{
	char		_szpath[300];
	int			_file;

	logger		*_plogger;
protected:
	long		_size;

public:
	enum { invalidfile=-1 };
	enum { NO_BRANCH = 0 };

	int Discard();

	filewrapper();
	virtual ~filewrapper();

	virtual void	open(const char *);
	void			create(const char *);
	void			close();
	void			seek(long,int);
	int				read(void *,long,unsigned int);
	void			write(const void *,long,unsigned int);

	void			append(void *,int);
	const char *	path() const {	return _szpath; };
	int				size() const { return _size; };
	void			assign_logger(logger *);
	bool			is_file_open() { return _file!= invalidfile; };
//	long			tell();
};

class logger : public filewrapper
{
public:
	enum ACTION { ACT_WRITE=0,ACT_READ,ACT_WRITE_BLOCK,ACT_READ_BLOCK};

	struct LOG_ENTRY
	{
		int				_file;
		int				_action;
		unsigned int	_parameter;
		int				_cur_clock;
	};

private:
	static char * szActions[];

	dictionary<int,string>	_logfiles;
	slist<LOG_ENTRY>		_entries;
	bool					_bruntimeprint;
	bool					_bprinttoscreen;
	bool					_busethetime;
	bool					_busefile;
	int						_starttime;	
	bool					_bheaderprinted;

	dictionary<int,unsigned int>	_read_ios;
	dictionary<int,unsigned int>	_write_ios;
	dictionary<int,unsigned int>	_block_read_ios;
	dictionary<int,unsigned int>	_block_write_ios;
	
private:
	void print_entry(LOG_ENTRY &);
	void print_to_file(char *);
	void print_header();
	void print(char *);


public:
	logger(bool,bool,bool,bool);
	~logger();

	void add_file(int,string);
	void add_action(int file,int action,int parameter=0);
	void reset_timer() { _starttime = clock(); };
	void reset_ios();
	void print_statistics();
};

char * logger::szActions[] = { "WRITE","READ","WRITE_BLOCK","READ_BLOCK" };

filewrapper::filewrapper():_file(invalidfile),_size(0),_plogger(0)
{
}

filewrapper::~filewrapper()
{
	close();
}


void filewrapper::open(const char *szname)
{
	assert( szname );

	strcpy(_szpath,szname);
	_file = _open(szname,_O_RDWR|_O_BINARY);

	handle_open_error( _file );
	handle_seek_error( lseek(_file,0,SEEK_END) );
	_size = _tell( _file );
	handle_tell_error( _size );
}

void filewrapper::close()
{
	if ( _file==invalidfile )
		return;

	handle_close_error( _close(_file) );
	_file = invalidfile;
}

void filewrapper::create(const char *szname)
{
	_file = _open(szname,_O_WRONLY|_O_TRUNC|_O_CREAT|_O_BINARY,_S_IREAD|_S_IWRITE );
	handle_open_error( _file );
	handle_seek_error( lseek(_file,0,SEEK_END) );
	_size = _tell( _file );
	handle_tell_error( _size );
	assert( _size == 0 );
}

void filewrapper::seek(long offset,int origin)
{
	assert( _file!=invalidfile );
	handle_seek_error( lseek(_file,offset,origin) );
}

void filewrapper::write(const void *buffer,long filepos,unsigned int size)
{
	assert( _file!=invalidfile );
	assert( filepos >= 0 && filepos<=_size-size);

	seek(filepos,SEEK_SET);
	handle_write_error( _write(_file,buffer,size) , size );

	if ( _plogger )
		_plogger->add_action(_file,logger::ACTION::ACT_WRITE,size);
}

int filewrapper::read(void *buffer,long filepos,unsigned int size)
{
	assert( _file!=invalidfile );
	assert( filepos >= 0 && filepos<=_size-size);

	seek(filepos,SEEK_SET);
	handle_read_error( _read(_file,buffer,size) , size);


	if ( _plogger )
		_plogger->add_action(_file,logger::ACTION::ACT_READ,size);
	
	return size;
}

void filewrapper::append(void *pdata,int size)
{
	seek(0,SEEK_END);
	assert( _size == _tell(_file) );
	
	handle_write_error( _write(_file,pdata,size) , size );

	_size = _tell( _file );
	handle_tell_error( _size );

	if ( _plogger )
		_plogger->add_action(_file,logger::ACTION::ACT_WRITE,size);
}
/*
long filewrapper::tell()
{
	assert( _file!=invalidfile );

	long pos = _tell( _file );
	handle_tell_error( pos );
	return pos;
}
*/
int filewrapper::Discard()
{
	int ret = _file;

	_file=invalidfile;
	_size = 0;
	_szpath[0]=0;

	return ret;
}

void filewrapper::assign_logger(logger *plogger)
{ 
	assert( _file!=invalidfile );

	_plogger = plogger;

	if (_plogger )
		_plogger->add_file(_file,_szpath);
}

////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
//log file implementation 
logger::logger(bool bruntimeprint,bool bprinttoscreen,bool busethetime,bool busefile):
	_bruntimeprint(bruntimeprint),_bprinttoscreen(bprinttoscreen),
	_busethetime(busethetime),_busefile(busefile),
	_starttime( clock() )
{
	_bheaderprinted = false;
}

logger::~logger()
{
}

void logger::add_file(int filenum,string spath)
{
	assert( _logfiles.lookup(filenum) == nil );

	_logfiles.insert(filenum,spath);
	_read_ios.insert(filenum,0);
	_write_ios.insert(filenum,0);
	_block_read_ios.insert(filenum,0);
	_block_write_ios.insert(filenum,0);
}

void logger::add_action(int file,int action,int parameter)
{
	assert( action==ACTION::ACT_WRITE || action==ACTION::ACT_READ );

	LOG_ENTRY newentry;
	newentry._file = file;
	if ( action == ACTION::ACT_WRITE )
	{
		if ( parameter==BLOCKSIZE )
			newentry._action = ACTION::ACT_WRITE_BLOCK;
		else
			newentry._action = ACTION::ACT_WRITE;
	}
	else if ( action == ACTION::ACT_READ )
	{
		if ( parameter==BLOCKSIZE )
			newentry._action = ACTION::ACT_READ_BLOCK;
		else
			newentry._action = ACTION::ACT_READ;
	}
	else
		assert(false);


	newentry._parameter = parameter;
	newentry._cur_clock = clock();

	switch(newentry._action)
	{
	case(ACTION::ACT_WRITE):
		{
			_write_ios[ _write_ios.lookup(newentry._file) ] +=1;
		}
		break;
	case(ACTION::ACT_WRITE_BLOCK):
		{
			_block_write_ios[ _block_write_ios.lookup(newentry._file) ] +=1;
		}
		break;
	case(ACTION::ACT_READ):
		{
			_read_ios[ _read_ios.lookup(newentry._file) ] +=1;
		}
		break;
	case(ACTION::ACT_READ_BLOCK):
		{
			_block_read_ios[ _block_read_ios.lookup(newentry._file) ] +=1;
		}
		break;
	default:
		assert(false);
	}
	
//	_entries.push(newentry);

	if ( _bruntimeprint )
		print_entry(newentry);
}

void logger::print_entry(LOG_ENTRY &entry)
{
	if ( !_bheaderprinted  )
		print_header();


	char szline[PRINT_LINE_SIZE];
	memset(szline,' ',PRINT_LINE_SIZE);
	szline[PRINT_LINE_SIZE-3] = '\r';
	szline[PRINT_LINE_SIZE-2] = '\n';
	szline[PRINT_LINE_SIZE-1] = '\0';

	memset(szline,'||',2);

	int linepos = 3;

	memcpy(szline+linepos,szActions[entry._action],strlen(szActions[entry._action]));

	if ( entry._action == ACTION::ACT_READ || entry._action ==ACTION::ACT_WRITE )
	{
		char szParam[100];
		_itoa(entry._parameter,szParam,10);

		memcpy(szline+17,szParam,strlen(szParam));
	}
	memset(szline+15,'|',1);
	memset(szline+22,'|',1);
	linepos+=21;

	if ( _busefile )
	{
		dic_item it;
		it = _logfiles.lookup(entry._file);
		assert( it!= nil );
		
		const char *szfilename = (const char*) _logfiles.inf(it);
		
		memcpy(szline+linepos,szfilename,strlen(szfilename));

		memset(szline+linepos+18,'|',1);
		linepos+=20;
	}

	if ( _busethetime )
	{
		int milisecs = double(entry._cur_clock - _starttime)*1000/double(CLOCKS_PER_SEC);

		char szTime[100];
		_itoa(milisecs,szTime,10);

		memcpy(szline+linepos,szTime,strlen(szTime));
	}

	print(szline);
}

void logger::print(char *szline)
{
	if ( _bprinttoscreen )
		printf(szline);


	print_to_file(szline);
}

void logger::print_to_file(char *stoprint)
{
	if ( !is_file_open() )
		return;
		
	append(stoprint,strlen(stoprint));
}

void logger::print_header()
{
	_bheaderprinted = true;
}

void logger::reset_ios()
{
	dic_item it;

	forall_items(it,_read_ios)
		_read_ios[it] = 0;

	forall_items(it,_write_ios)
		_write_ios[it] = 0;

	forall_items(it,_block_read_ios)
		_block_read_ios[it] = 0;

	forall_items(it,_block_write_ios)
		_block_write_ios[it] = 0;
}

void logger::print_statistics()
{
	dic_item it;
	char szline[300];
	char szNumber[10];

	int itotalreads;
	int itotalblockreads;
	{
		forall_items(it,_logfiles)
		{
			strcpy(szline,"\r\n");
			strcat(szline,"\r\n\r\n# File : \t\t");
			strcat(szline,_logfiles.inf(it) );
			
			int reads = _read_ios.inf( _read_ios.lookup(_logfiles.key((it))) );
			int blockreads = _block_read_ios.inf( _block_read_ios.lookup(_logfiles.key((it))) );
			int writes = _write_ios.inf( _write_ios.lookup(_logfiles.key((it))) );
			int blockwrites = _block_write_ios.inf( _block_write_ios.lookup(_logfiles.key((it))) );

			strcat(szline,"\r\nAll Block IOs : \t");
			_itoa(blockreads+blockwrites,szNumber,10);
			strcat(szline,szNumber);

			strcat(szline,"\r\nBlock Read IOs : \t");
			_itoa(blockreads,szNumber,10);
			strcat(szline,szNumber);

			strcat(szline,"\r\nBlock Write IOs : \t");
			_itoa(blockwrites,szNumber,10);
			strcat(szline,szNumber);

			strcat(szline,"\r\n\r\nAll Non Block IOs : \t");
			_itoa(reads+writes,szNumber,10);
			strcat(szline,szNumber);

			strcat(szline,"\r\nNon Block Read IOs : \t");
			_itoa(reads,szNumber,10);
			strcat(szline,szNumber);

			strcat(szline,"\r\nNon Block Write IOs : \t");
			_itoa(writes,szNumber,10);
			strcat(szline,szNumber);

			print(szline);
		}
	}

	print("\r\n\r\n");
/*
	int itotalwrites;
	int itotalblockwrites;
	{
		print("########### Writes ##############\r\n");
		forall_items(it,_logfiles)
		{
			strcpy(szline,"\r\n");
			strcat(szline,"\r\n\r\n# File : ");
			strcat(szline,_logfiles.inf(it) );
			
			int writes = _write_ios.inf( _write_ios.lookup(_logfiles.key((it))) );
			int blockwrites = _block_write_ios.inf( _block_write_ios.lookup(_logfiles.key((it))) );
			strcat(szline,"\r\nWrite IOs : ");
			_itoa(writes,szNumber,10);
			strcat(szline,szNumber);
			strcat(szline,"\r\nBlock Write IOs : ");
			_itoa(blockwrites,szNumber,10);
			strcat(szline,szNumber);
			strcat(szline,"\r\nAll Write IOs : ");
			_itoa(writes+blockwrites,szNumber,10);
			strcat(szline,szNumber);

			itotalwrites += writes;
			itotalblockwrites += blockwrites;

			print(szline);
		}
		print("################################\r\n\r\n");
	}
	{
		print("########### Totals ##############");

		unsigned int itotalios = itotalreads+itotalwrites;
		unsigned int itotalblockios = itotalblockreads+itotalblockwrites;

		_itoa(itotalreads+itotalblockreads,szNumber,10);
		strcpy(szline,"Read IOs : ");
		strcat(szline,szNumber);
		strcat(szline,"\r\n\r\n");
		print(szline);
	}
*/
}
