
//Headers {{{
/*
 *  DvorakNG, a Dvorak typing tutor
 *  Copyright (C) 2003 Kamil Burzynski
 *  Heavily based on 'Dvorak7min 1.6.1' by Ragnar Hojland Espinosa
 *			     <ragnar@ragnar-hojland.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <new>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <algorithm>
#include <functional>
#include <sys/time.h>
#include <fstream>
#include <pwd.h>
#include <unistd.h>
#include <sys/types.h>
#include <string>
#include <sstream>
//#include <iostream>

using namespace std;

#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <curses.h>
#include <getopt.h>
#include <errno.h>
#include <sys/stat.h>

#include "lessons.h"

/* these three represent the visualization of the key on screen */
#define PRESSED_NOT       0
#define PRESSED_REQUESTED 1
#define PRESSED_BY_USER   2

/* Update cp[sm] every LATENCY tenths of second */
#define LATENCY 1

//int tryToBePretty = 1;
//int beepsArentIrritating = 0;
//int flashesArentIrritating = 0;
//int nastiness = 0;
//int colorSupport = 1;
//int hideKeys = 1;

typedef	unsigned long	UINT32;
float	CalcRatio( UINT32 hits, UINT32 misses );
WINDOW	*window = NULL;

#define NASTINESS_PARAM	"nastiness"
#define KEYBOARD_PARAM	"keyboard"
#define COLOR_PARAM			"color"
#define PRETTY_PARAM		"pretty"
#define BEEPS_PARAM			"beeps"
#define FLASHES_PARAM		"flashes"
#define RANDOMIZE_SMALL_WEIGHT		"small_ratio"
#define RANDOMIZE_CAPITAL_WEIGHT	"capital_ratio"
#define RANDOMIZE_PAGES						"random_pages"
#define REPETITION_PARAM					"repetition"
//}}}

//Definitions{{{
const UINT32	version_major = 0;
const UINT32	version_minor = 6;
const UINT32	version_micro = 0;

const char	version_string[] = "rc1";

const	char		*config_version_string = "VER_0.2.0";

int max_editable_lines = 6;
int right_margin = 70;

char typables[] = "`1234567890[]',.pyfgcrl/=\\aoeuidhtns-;qjkxbmwvz~!@#$%^&*(){}\"<>PYFGCRL?+|AOEUIDHTNS_:QJKXBMWVZ\n ";
char randomizable[] = "pyfgcrlaoeuidhtnsqjkxbmwvzPYFGCRLAOEUIDHTNSQJKXBMWVZ?_:',.!()\"-;";

typedef	map<string,string>	ConfigMap;
ConfigMap	Config;

int			helpInterval;
int			helpLength;
time_t	nextHelp;
void		Error( const string &str );
//}}}

char keymap[] = //{{{
{
	'`', '~', '1', '!', '2', '@', '3', '#', '4', '$', '5', '%', '6', '^',
	'7', '&', '8', '*', '9', '(', '0', ')', '[', '{', ']', '}', 0,
	3, '\'', '"', ',', '<', '.', '>', 'P', 'Y', 'F', 'G', 'C', 'R', 'L',
	'/', '?', '=', '+',  0,
	5, 'A', 'O', 'E', 'U', 'I', 'D', 'H', 'T', 'N', 'S', '-', '_',
	'\\', '|', 0,
	7, /*'<', '>', */';', ':', 'Q', 'J', 'K', 'X', 'B', 'M', 'W', 'V', 'Z', 0,
	0
}; //}}}

char colormap[] = //{{{
{
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, -5, -5, -4, -4, -3, -3, -2, -2, 2, 2, 3, 4, 5,
	0, 0, 0, 0,  0, 5, -5, -4, -3, -2, -2, 2, 2, 3, 4, 5, 0, 0, 0, 0, 0, 2, 0, /*0,*/ 5, /*-5,*/ -4, -3, -2, -2, 2, 2, 3, 4, 5, 0, 0
}; //}}}

class	THSEntry //{{{
{
	private:
		UINT32	FHits;
		UINT32	FMisses;
		UINT32	FTime;
		time_t	FCreation;
		UINT32	FLesson;

	public:
		typedef	bool	(*Predicate)( THSEntry &l, THSEntry &r );

		THSEntry( ifstream &file );
		THSEntry( UINT32 hits, UINT32 misses, UINT32 timev, UINT32 lesson );
		~THSEntry();

		void	Print( UINT32 row, UINT32 column, UINT32 color, char *msg ) const;
		void	PrintHistory( UINT32 row ) const;
		float	GetSpeed() const;

		static bool	GetFasterEntry( THSEntry &l, THSEntry &r );
		static bool	GetPerfectEntry( THSEntry &l, THSEntry &r );

		void	Store( ofstream &file );
		void	Restore( ifstream &file );
}; //}}}

bool	THSEntry::GetFasterEntry( THSEntry &l, THSEntry &r ) //{{{
{
	bool	res = true;

	if( r.GetSpeed() < l.GetSpeed() )
	{
		res = false;
	}

	return( res );
} //}}}
bool	THSEntry::GetPerfectEntry( THSEntry &l, THSEntry &r ) //{{{
{
	bool	res = true;

	if( CalcRatio( r.FHits, r.FMisses ) < CalcRatio( l.FHits, l.FMisses ) )
	{
		res = false;
	}

	return( res );
} //}}}

class	THSManager //{{{
{
	private:
		struct	CharStat
		{
			UINT32	FHits;
			UINT32	FMisses;
		};

		typedef	list<THSEntry>				EntryList;
		typedef	map<UINT32,CharStat>	CharScores;

		EntryList		FEntries;
		UINT32			FHistory;
		CharScores	FCharScores;
		UINT32			FExpectedChar;
		string			FFileName;

	public:
		THSManager( const string &filename );
		~THSManager();

		void			RestoreEntries();
		void			StoreEntries();

		void			AddEntry( THSEntry &entry );
		THSEntry	&GetBestEntry( bool this_session, THSEntry::Predicate pred );

		void			ShowHistory();
		void			ShowScores();
		void			SetExpectedChar( UINT32 c );
		void			CharTyped( UINT32 c );
		void			ShowCharScores();
		void			Reset();
}; //}}}
class	TLessonSlot //{{{
{
	private:
		THSManager	FHSManager;
		string			FFileName;
		UINT32			FPosition;
		string			FConfigName;
		string			FText;

	public:
		TLessonSlot( const string &name );

		void	Show( UINT32 i, UINT32 row );
		void	SetFileName( const string &name );

		void	Store();
		void	Restore();
		void	ShowHistory();
		void	ShowScores();
		void	ShowCharScores();

		UINT32	GetLength() const;
		void		SetPosition( UINT32 pos );
		void		Reset();
		void		Play();
}; //}}}
class	TLessonManager //{{{
{
	private:
		static const	UINT32	NumSlots = 10;

		typedef	TLessonSlot	Slot;

		vector<Slot>	FSlots;

	public:
		TLessonManager();
		~TLessonManager();

		void	Init();
		void	Menu();
}; //}}}
THSManager			HSManager( (string)( getpwuid( getuid() )->pw_dir ) + "/.dvorakng/history" );
TLessonManager	LessonManager;

char	*OnOffFrom01( bool par ) //{{{
{
	char	*res = "";

	if( par == true )
	{
		res = "on";
	}
	else
	{
		res = "off";
	}

	return( res );
} //}}}
UINT32	GetIntParameter( const string &par ) //{{{
{
	istringstream	ss( Config[ par ] );

	UINT32		res = 0;

	ss >> res;

	return( res );
} //}}}
void	SetIntParameter( const string &par, UINT32 val ) //{{{
{
	ostringstream	ss;

	ss << val;

	Config[ par ] = ss.str();
} //}}}
bool	GetParameter( const string &par ) //{{{
{
	bool		res = false;
	string	val = Config[ par ];

	if( (val == "1") || (val == "on") )
	{
		res = true;
	}

	return( res );
} //}}}
void	SetParameter( const string &par, bool val ) //{{{
{
	if( val == true )
	{
		Config[ par ] = "1";
	}
	else
	{
		Config[ par ] = "0";
	}
} //}}}
void	ToggleParameter( const string &par ) //{{{
{
	SetParameter( par, !GetParameter( par ) );
} //}}}
void	StoreConfig() //{{{
{
	string		location = getpwuid( getuid() )->pw_dir;
	location += "/.dvorakng/config";

	ofstream	file( location.c_str(), ios::binary | ios::out );

	if( file.good() == true )
	{
		ConfigMap::iterator	it;

		for( it=Config.begin();it!=Config.end();++it )
		{
			file << it->first << " " << it->second << endl;
		}
	}
	else
	{
		Error( "Cannot restore configuration file!" );
	}
} //}}}
void	RestoreConfig() //{{{
{
	string		location = getpwuid( getuid() )->pw_dir;
	location += "/.dvorakng/config";

	ifstream	file( location.c_str(), ios::binary | ios::in );

	if( file.good() == true )
	{
		do
		{
			string	par;
			string	val;

			file >> par >> val;

			if( file.good() == true )
			{
				ConfigMap::iterator	it = Config.find( par );

				if( it != Config.end() )
				{
					Config[ par ] = val;
				}
				else
				{
					Error( string("Unknown parameter in configuration file: ") + par );
				}
			}
		}
		while( file.good() == true );

		if( (GetIntParameter( RANDOMIZE_SMALL_WEIGHT ) < 1) || (GetIntParameter( RANDOMIZE_SMALL_WEIGHT ) > 100) )
		{
			SetIntParameter( RANDOMIZE_SMALL_WEIGHT, 20 );
		}

		if( (GetIntParameter( RANDOMIZE_CAPITAL_WEIGHT ) < 1) || (GetIntParameter( RANDOMIZE_CAPITAL_WEIGHT ) > 100) )
		{
			SetIntParameter( RANDOMIZE_CAPITAL_WEIGHT, 3 );
		}

		if( (GetIntParameter( RANDOMIZE_PAGES ) < 1) || (GetIntParameter( RANDOMIZE_PAGES ) > 20) )
		{
			SetIntParameter( RANDOMIZE_PAGES, 3 );
		}
	}
	else
	{
		Error( "Cannot restore configuration file!" );
	}
} //}}}
void	ConfigMenu() //{{{
{
	char	c;

	noecho();
	curs_set( FALSE );

	attron( A_BOLD );

	do
	{
		UINT32	row = 0;

		clear();
		attron( COLOR_PAIR( 7 ) );

		mvprintw( row, 0, "Configuration menu:" );

		attron( COLOR_PAIR( 3 ) );

		row += 2;

		mvprintw( row + 0, 1, "[N]astiness:  %s", OnOffFrom01( GetParameter( NASTINESS_PARAM ) ) );
		mvprintw( row + 1, 1, "[K]eyboard:   %s", OnOffFrom01( GetParameter( KEYBOARD_PARAM ) ) );
		mvprintw( row + 2, 1, "[B]eeps:      %s", OnOffFrom01( GetParameter( BEEPS_PARAM ) ) );
		mvprintw( row + 3, 1, "[F]lashes:    %s", OnOffFrom01( GetParameter( FLASHES_PARAM ) ) );
		mvprintw( row + 4, 1, "[P]retty:     %s", OnOffFrom01( GetParameter( PRETTY_PARAM ) ) );
		mvprintw( row + 5, 1, "[C]olor:      %s", OnOffFrom01( GetParameter( COLOR_PARAM ) ) );
		mvprintw( row + 6, 1, "[R]epetition: %s", OnOffFrom01( GetParameter( REPETITION_PARAM ) ) );

		mvprintw( row + 8, 1,  "Small letter ratio in random tests (+/-: [A]/[O]):   %s", Config[ RANDOMIZE_SMALL_WEIGHT ].c_str() );
		mvprintw( row + 9, 1,  "Capital letter ratio in random tests (+/-: [E]/[U]): %s", Config[ RANDOMIZE_CAPITAL_WEIGHT ].c_str() );
		mvprintw( row + 10, 1, "Number of pages in random tests (+/-: [I]/[D]):      %s", Config[ RANDOMIZE_PAGES ].c_str() );

		row += 12;

		mvprintw( row + 0,  1, "[L]oad configuration" );
		mvprintw( row + 1, 1, "[S]tore configuration" );

		attron( COLOR_PAIR( 5 ) );

		row += 3;

		mvprintw( row, 0, "Press Esc, Space or Enter for main menu." );

		c = getch();

		row += 2;

		switch( tolower( c ) )
		{
			case 'n':
				ToggleParameter( NASTINESS_PARAM );
				break;

			case 'r':
				ToggleParameter( REPETITION_PARAM );
				break;

			case 'k':
				ToggleParameter( KEYBOARD_PARAM );
				break;

			case 'b':
				ToggleParameter( BEEPS_PARAM );
				break;

			case 'f':
				ToggleParameter( FLASHES_PARAM );
				break;

			case 'p':
				ToggleParameter( PRETTY_PARAM );
				break;

			case 'c':
				ToggleParameter( COLOR_PARAM );
				break;

			case 'l':
				RestoreConfig();
				attroff( A_BOLD );
				mvprintw( row, 0, "Configuration loaded." );
				getch();
				attron( A_BOLD );
				break;

			case 's':
				StoreConfig();
				attroff( A_BOLD );
				mvprintw( row, 0, "Configuration stored." );
				getch();
				attron( A_BOLD );
				break;

			case 'a':
				{
					UINT32	randomize_capital_weight = GetIntParameter( RANDOMIZE_CAPITAL_WEIGHT );

					if( randomize_capital_weight < 100 )
					{
						randomize_capital_weight++;

						SetIntParameter( RANDOMIZE_CAPITAL_WEIGHT, randomize_capital_weight );
					}
				}
				break;

			case 'o':
				{
					UINT32	randomize_capital_weight = GetIntParameter( RANDOMIZE_CAPITAL_WEIGHT );

					if( randomize_capital_weight > 1 )
					{
						randomize_capital_weight--;

						SetIntParameter( RANDOMIZE_CAPITAL_WEIGHT, randomize_capital_weight );
					}
				}
				break;

			case 'e':
				{
					UINT32	randomize_small_weight = GetIntParameter( RANDOMIZE_SMALL_WEIGHT );

					if( randomize_small_weight < 100 )
					{
						randomize_small_weight++;

						SetIntParameter( RANDOMIZE_SMALL_WEIGHT, randomize_small_weight );
					}
				}
				break;

			case 'u':
				{
					UINT32	randomize_small_weight = GetIntParameter( RANDOMIZE_SMALL_WEIGHT );

					if( randomize_small_weight > 1 )
					{
						randomize_small_weight--;

						SetIntParameter( RANDOMIZE_SMALL_WEIGHT, randomize_small_weight );
					}
				}
				break;

			case 'i':
				{
					UINT32	randomize = GetIntParameter( RANDOMIZE_PAGES );

					if( randomize < 20 )
					{
						randomize++;

						SetIntParameter( RANDOMIZE_PAGES, randomize );
					}
				}
				break;

			case 'd':
				{
					UINT32	randomize = GetIntParameter( RANDOMIZE_PAGES );

					if( randomize > 1 )
					{
						randomize--;

						SetIntParameter( RANDOMIZE_PAGES, randomize );
					}
				}
				break;

			default:
				break;
		};
	}
	while( (c != 27) && (c != ' ') && (c != '\n') );

	echo();
	curs_set( TRUE );
	attroff( A_BOLD );
} //}}}
char	*RandomizeText() //{{{
{
	char		*buf = new char[ 1 + 1 + GetIntParameter( RANDOMIZE_PAGES ) * (80 * 6 + 1) ]; //\x2 + \x0 + PAGES * \x1 + text
	char		*t = buf;
	UINT32	page;
	UINT32	ch;
	UINT32	length;

	*(t++) = '\x2';

	srand( time( 0 ) );

	for( page=0;page<GetIntParameter( RANDOMIZE_PAGES );page++ )
	{
		UINT32	column;
		UINT32	line;

		length = 2 + (int)( 6.0 * rand() / (RAND_MAX + 1.0) );

		for( line=0;line<6;line++ )
		{
			length = 2 + (int)( 6.0 * rand() / (RAND_MAX + 1.0) );

			for( column=0;column<79;column++ )
			{
				length--;

				if( length == 0 )
				{
					*(t++) = ' ';
					length = 2 + (int)( 6.0 * rand() / (RAND_MAX + 1.0) );
				}
				else
				{
					ch = (int)( (sizeof( randomizable ) + ((GetIntParameter( RANDOMIZE_SMALL_WEIGHT ) - 1) + (GetIntParameter( RANDOMIZE_CAPITAL_WEIGHT ) - 1)) * 26 - 1.0) * rand() / (RAND_MAX + 1.0) );

					if( ch < (GetIntParameter( RANDOMIZE_SMALL_WEIGHT ) * 26) )
					{
						ch %= 26;
					}
					else
					{
						if( ch < ((GetIntParameter( RANDOMIZE_SMALL_WEIGHT ) + GetIntParameter( RANDOMIZE_CAPITAL_WEIGHT )) * 26) )
						{
							ch = 26 + (ch % 26);
						}
						else
						{
							ch = 52 + (ch % 26);
						}
					}

					*(t++) = randomizable[ ch ];
				}
			}

			while( *(t - 1) == ' ' )
			{
				t--;
			}

			*(t++) = '\n';
		}

		*(t++) = '\x1';
	}

	t--;
	*t = 0;

	return( buf );
} //}}}
char	*CreateText( vector<char> &repetition ) //{{{
{
	UINT32									lines;
	UINT32									column;
	vector<char>						dest;
	vector<char>::iterator	it;

	dest.push_back( 0x02 );

	column = 0;
	lines = 0;

	for( it=repetition.begin();it!=repetition.end();++it )
	{
		if( *it != ' ' )
		{
			dest.push_back( *it );

			column++;

			if( column > 79 )
			{
				column = 0;
				dest.push_back( '\n' );
				lines++;

				if( lines > 5 )
				{
					dest.push_back( 0x01 );
					lines = 0;
				}
			}
		}
	}

	dest.push_back( 1 );
	dest.push_back( 0 );
	dest.push_back( 0 );

	char *buf = new char[ dest.size() ];

	copy( dest.begin(), dest.end(), buf );

	return( buf );
} //}}}

void	Error( const string &str ) //{{{
{
	clear();
	noecho();
	curs_set( FALSE );
	attron( A_BOLD );
	attron( COLOR_PAIR( 4 ) ); //red
	mvprintw( 0, 0, "%s", str.c_str() );

	attron( COLOR_PAIR( 1 ) ); //blue
	mvprintw( 2, 0, "[Press ESCAPE to continue.]" );

	while( getch() != 27 )	;

	curs_set( TRUE );
	echo();
} //}}}
int	myGetch( char shouldBe ) //{{{
{
	int deviation;
	int ch;

	ch = getch();

	if( ch != ERR )
	{
		helpInterval = 10000 + (random() % 10000);
		deviation = random() % 300;
		nextHelp = time(0) + (helpInterval + deviation) / 1000;
		helpLength = 1;
		return ch;
	}

	if( time(0) < nextHelp )
	{
		return ch;
	}

	if( (helpLength--) == 0 )
	{
		helpInterval = random() % 1000;
		helpLength = random() % 8;
	}

	deviation = random() % 300;
	nextHelp = time(0) + (helpInterval + deviation) / 1000;

	if( (random() % 26) == 0 )
	{
		return KEY_BACKSPACE;
	}
	else
	{
		return shouldBe;
	}
} //}}}
inline	UINT32	CalcTimeDiff( timeval &start, timeval &end ) //{{{
{
	return( (UINT32)( (end.tv_sec - start.tv_sec) * 1000000.0 + (end.tv_usec - start.tv_usec) ) );
} //}}}
inline float CalcSpeed( timeval timeStart, timeval timeFinish, int hits ) //{{{
{
	return ((float) hits * 1000000.0) / CalcTimeDiff( timeStart, timeFinish );
} //}}}
float	CalcRatio( UINT32 hits, UINT32 misses ) //{{{
{
	float ratio = hits - misses;

	if( misses <= hits )
	{
		if( (hits != 0) || (misses != 0) )
		{
			if( hits != 0 )
			{
				ratio = (ratio < 0) ? 0 : (100.0 * ratio / hits);
			}
			else
			{
				ratio = 100.0;
			}
		}
		else
		{
			ratio = 100.0;
		}
	}
	else
	{
		ratio = 0.0;
	}

	return( ratio );
} //}}}
int importText( char const * const fileName, char** buf, unsigned int length ) //{{{
{
	FILE					*file;
	int						c;
	unsigned int	i = 0;
	int						lines = 0;
	int						column;
	struct stat		st;

	file = fopen( fileName, "r" );

	if( !file )
	{
		//postmortem = sys_errlist[ errno ];
		return -1;
	}

	fstat( fileno( file ), &st );

	if( static_cast<unsigned int>( st.st_size ) > length )
	{
		length = st.st_size;
	}

	*buf = new char[ length + 1 + 1 ];

	column = 0;
	*buf[ i++ ] = '\x2';

	while( 1 )
	{
		c = fgetc( file );

		//if( errno )
		//{
			//char txt[20];
			//sprintf( txt, "%d", errno );
		//Error( txt );
			//postmortem = sys_errlist[ errno ];
			//fclose( file );
			//return -1;
		//}

		if( c == EOF )
		{
			fclose( file );
			(*buf)[ i ] = '\x1';
			break;
		}

		if( index( typables, c ) == NULL )
		{
			continue;
		}

		if( (c == '\n') || (column > right_margin) )
		{
			if( column > right_margin )
			{
				(*buf)[ i++ ] = '\n';
			}

			while( i > 0 )
			{
				if( (*buf)[ i - 1 ] == ' ' )
				{
					--i;
				}
				else
				{
					break;
				}
			}

			column = 0;
			++lines;

			if( c == ' ' )
			{
				continue;
			}
		}

		if( lines == max_editable_lines )
		{
			lines = 0;
			(*buf)[ i++ ] = '\x1';
		}
		else
		{
			(*buf)[ i++ ] = (char) c;
			++column;
		}

		if( i >= (length - 1) )
		{
			fclose( file );
			(*buf)[ i ] = '\x1';
			break;
		}
	}

	/* strip unwanted whitespace from the end */
	while( i > 0 )
	{
		if( ((*buf)[ i - 1 ] == ' ') || ((*buf)[ i - 1 ] == '\n') || ((*buf)[ i - 1 ] == '\x1') )
		{
			--i;
		}
		else
		{
			break;
		}
	}

	(*buf)[ i ] = '\0';
	return i;
} //}}}
void boxed( int x, int y, char u, char d, char pressed, int key ) //{{{
{
	if( pressed == PRESSED_REQUESTED )
	{
		attron( A_REVERSE );
	}

	if( pressed == PRESSED_BY_USER )
	{
		attron( A_BOLD );
	}

	if( GetParameter( COLOR_PARAM ) == true )
	{
		attron( COLOR_PAIR( 7 - abs( colormap[ key ] ) ) );
	}

	if( GetParameter( PRETTY_PARAM ) == true )
	{
		move( y + 0, x );
		addch( ACS_ULCORNER );
		addch( ACS_HLINE );
		addch( ACS_HLINE );
		addch( ACS_HLINE );
		addch( ACS_URCORNER );

		move( y + 1, x );
		addch( ACS_VLINE );
		addch( u );
		addch( ' ' );
		addch( ' ' );
		addch( ACS_VLINE );

		move( y + 2, x );
		addch( ACS_VLINE );
		addch( ' ' );
		addch( ' ' );
		addch( d );
		addch( ACS_VLINE );

		move( y + 3, x );
		addch( ACS_LLCORNER );
		addch( ACS_HLINE );
		addch( ACS_HLINE );
		addch( ACS_HLINE );
		addch( ACS_LRCORNER );
	}
	else
	{
		mvprintw( y + 0, x, ".---.");
		mvprintw( y + 1, x, "|%c  |", u);
		mvprintw( y + 2, x, "|  %c|", d);
		mvprintw( y + 3, x, "`---'");
	}

	if( GetParameter( COLOR_PARAM ) == true )
	{
		attron( COLOR_PAIR( 7 ) );
	}

	if( pressed == PRESSED_REQUESTED )
	{
		attroff( A_REVERSE );
	}

	if( pressed == PRESSED_BY_USER )
	{
		attroff( A_BOLD );
	}
} //}}}
void show_layout( char pressedKey, char pressed ) //{{{
{
	int	key = 0;
	int	x = 0;
	int	y = 0;

	if( GetParameter( KEYBOARD_PARAM ) == false )
	{
		return;
	}

	while( keymap[ key ] != 0 )
	{
		while( keymap[ key ] != 0 )
		{
			char u = keymap[ key ];
			char d;

			if( isupper( u ) != 0 )
			{
				/* hack, to fix the keymap i wrote erroneusly.. */
				d = u;
				u = tolower( d );
			}
			else
			{
				++key;
				d = keymap[ key ];
			}

			if( (u == pressedKey) || (d == pressedKey) )
			{
				boxed( x, y, d, u, pressed, key );
			}
			else
			{
				if( pressed != PRESSED_REQUESTED )
				{
					boxed( x, y, d, u, PRESSED_NOT, key );
				}
			}

			x += 6;
			++key;
		}

		++key;
		y += 4;
		x = keymap[ key ];
		++key;
	}

	refresh();
} //}}}
void myaddnstr( char const *text, int lenght ) //{{{
{
	lenght += 1;

	char	*buf = new char[ lenght ];
	char	*obuf = buf;
	int		i = lenght;

	lenght = 0;

	while( (--i) != 0 )
	{
		if( *text != '\x2' )
		{
			*(buf++) = *(text);
			lenght++;
		}

		if( *text == '\0' )
		{
			break;
		}

		++text;
	}

	addnstr( obuf, lenght );

	delete	obuf;
} //}}}
UINT32	do_text( const char * const consttext, UINT32 lesson, THSManager &hsm ) //{{{
{
	int	hits;
	int	misses;
	int	this_page_size;
	const char	*text = consttext;

	timeval				timeStart;
	timeval				timeFinish;
	timeval				timeCurrent;
	float					speed;
	vector<char>	repetition;

	const char	*p = text;
	const char	*i;
	const char	*this_page_start;

	UINT32	res = 0xffffffff;

	curs_set( FALSE );
	show_layout( ' ', PRESSED_NOT );
	mvaddstr( LINES - 1, 0, "[ ] Press a key to start" );

	i = (char*) index( text, 0x1 );
	this_page_start = text;
	this_page_size  = i ? i - this_page_start : strlen( this_page_start );

	move( ((GetParameter( KEYBOARD_PARAM ) == false) ? 0 : 17), 0 );

	myaddnstr( text, this_page_size );

	hits = 0; misses = 0;

	refresh();
	noecho();
	move( LINES - 1, 1 );
	getch();

	halfdelay( LATENCY );

	move( LINES - 1, 0 );
	clrtoeol();
	timeStart.tv_sec = 0;
	timeStart.tv_usec = 0;
	nextHelp = time(0) + (helpInterval / 1000);

	do
	{
		repetition.clear();

		while (*p)
		{
			int ch = 0;

			if( (*p == 0x2) || ((p == consttext) && (lesson == 0)) )
			{
				if( *p == 0x2 )
				{
					++p;
				}

				if( timeStart.tv_sec == 0 )
				{
					gettimeofday( &timeStart, NULL );
				}
			}

			if( *p > 27 )
			{
				show_layout( *p, PRESSED_REQUESTED );
			}

			hsm.SetExpectedChar( *p );

			do
			{
				ch = myGetch( *p );
				if( timeStart.tv_sec != 0 )
				{
					float	ratio = CalcRatio( hits, misses );

					gettimeofday( &timeCurrent, NULL );
					speed = CalcSpeed( timeStart, timeCurrent, hits );
					attron( A_BOLD );
					mvprintw( LINES - 1, 0, "Lesson %d: CPS %.2f  CPM %.2f Hits: %d Misses: %d Seconds: %.1f Ratio: %.2f%%", lesson, speed, speed * 60, hits, misses, CalcTimeDiff( timeStart, timeCurrent ) / 1000000.0, ratio );
					attroff( A_BOLD );
					clrtoeol();
				}
			}
			while( ch == ERR );

			show_layout( ch, PRESSED_BY_USER );

			if( (ch == *p) || ((*p == '\n') && (ch == ' ') ) )
			{
				if( ch == *p )
				{
					hsm.CharTyped( ch );
				}

				++p;
				++hits;
			}
			else
			{
				/* any other possibilities? */
				if( ((ch == 8) || (ch == KEY_BACKSPACE) || (ch == KEY_DC) || (ch == 127) ) && (p > this_page_start) )
				{
					--p;
					if( *p == '\x2' )
					{
						--p;
					}
				}
				else
				{
					if( (ch == 27) || (ch == KEY_END) )
					{
						break;
					}
					else
					{
						if( GetParameter( BEEPS_PARAM ) == true )
						{
							beep();
						}

						if( GetParameter( FLASHES_PARAM ) == true )
						{
							flash();
						}

						++misses;
						hsm.CharTyped( ch );

						if( (GetParameter( REPETITION_PARAM ) == true) && (*p != '\n') )
						{
							if( (repetition.size() == 0) || (repetition.back() != *p) )
							{
								repetition.push_back( *p );
							}
						}

						if( (GetParameter( NASTINESS_PARAM ) == true) && (p > this_page_start) )
						{
							--hits;
							--p;

							if( *p == '\x2' )
							{
								--p;
							}
						}
					}
				}
			}

			move( ((GetParameter( KEYBOARD_PARAM ) == false) ? 0 : 17), 0 );

			i = this_page_start;
			attron( A_BOLD );

			while( i < p )
			{
				if( *i != 0x2 )
				{
					addch( *i );
				}

				++i;
			}

			if( *i == '\x2' )
			{
				++i;
			}

			attroff( A_BOLD );
			attron( A_REVERSE );

			if( *i != 0x2 )
			{
				addch( *i );
			}

			++i;

			attroff( A_REVERSE );

			while( (*i != 0) && (i < (this_page_start + this_page_size) ) )
			{
				if (*i != 0x2) {
					addch(*i);
				}
				++i;
			}

			if( *p == 0x1 )
			{
				char	*next;

				++p;
				next = (char*) index( p, 0x1 );
				this_page_start = p;
				this_page_size  = next ? next - this_page_start : strlen( this_page_start );
				move( ((GetParameter( KEYBOARD_PARAM ) == false) ? 0 : 17), 0 );
				clrtobot();
				myaddnstr( this_page_start, this_page_size );
			}
		}

		if( res == 0xffffffff )
		{
			res = p - consttext;
		}

		if( GetParameter( REPETITION_PARAM ) == false )
		{
			break;
		}

		text = CreateText( repetition );

		i = (char*) index( text, 0x1 );
		this_page_start = text;
		this_page_size  = i ? i - this_page_start : strlen( this_page_start );

		show_layout( ' ', PRESSED_NOT );

		move( ((GetParameter( KEYBOARD_PARAM ) == false) ? 0 : 17), 0 );

		clear();
		myaddnstr( text, this_page_size );

		refresh();

		halfdelay( LATENCY );

		move( LINES - 1, 0 );
		p = text;
	}
	while( repetition.empty() == false );

	gettimeofday( &timeFinish, NULL );

	clear();
	cbreak();

	if( timeStart.tv_sec == 0 )
	{
		mvprintw( 0, 0, "You haven't done enough to get a good benchmark." );

		attron( COLOR_PAIR( 1 ) ); //blue
		attron( A_BOLD );
		mvprintw( 2, 0, "[Press ESCAPE to continue.]" );
		move( 4, 1 );
		refresh();

		while( getch() != 27 )	;
	}
	else
	{
		THSEntry	entry( hits, misses, CalcTimeDiff( timeStart, timeFinish ), lesson );
		hsm.AddEntry( entry );
		hsm.ShowScores();
	}

	curs_set( TRUE );
	echo();
	clear();
	refresh();

	return( res );
} //}}}
void menuInteractive() //{{{
{
	int		i;
	int		num;
	char	buf[ 255 ];

	while( 1 )
	{
		clear();
		refresh();

		attron( COLOR_PAIR( 7 ) );
		attron( A_BOLD );
		mvprintw( 2, 0, "DvorakNG v%ld.%ld.%ld%s, Kamil Burzynski, 2003", version_major, version_minor, version_micro, version_string );
		mvprintw( 3, 0, "based on Dvorak7min" );

		attroff( A_BOLD );

		for( i=0;lessons[ i*2 ];++i )
		{
			mvprintw( i/2 + 5, (i % 2) * 40, "%2d. %s", i + 1, lessons[ i*2 ] );
		}

		mvprintw( i/2 + 5, (i % 2) * 40, "%2d. Random typing test", i + 1 );

		move( 21, 0 );
		clrtobot();
		mvprintw( 21, 0, "Goto [C]onfiguration menu or [L]esson menu, [Q]uit" );
		mvprintw( 22, 0, "Show [S]cores, [H]istory or [P]er-character stats" );
		mvprintw( 23, 0, "Or type a lesson number: " );

		refresh();
		echo();

		getnstr( buf, sizeof( buf ) );

		buf[0] = toupper( buf[ 0 ] );

		if( buf[ 0 ] == 'L' )
		{
			LessonManager.Menu();
			continue;
		}

		if( buf[ 0 ] == 'C' )
		{
			ConfigMenu();
			continue;
		}

		if( buf[ 0 ] == 'H' )
		{
			HSManager.ShowHistory();
			continue;
		}

		if( buf[ 0 ] == 'P' )
		{
			HSManager.ShowCharScores();
			continue;
		}

		if( buf[ 0 ] == 'S' )
		{
			HSManager.ShowScores();
			continue;
		}

		if( (buf[ 0 ] == 'Q') || (buf[ 0 ] == 27) )
		{
			clear();
			move( 0, 0 );
			refresh();
			return;
		}

		num = atoi( buf );

		if( (buf[0] == 0) || (num < 1) || (num > (i + 1) ) )
		{
			mvprintw( 23, 0, "Invalid lesson number. Press any key." );
			getch();
			continue;
		}

		clear();
		refresh();

		if( num > i )
		{
			//random test
			char	*text = RandomizeText();
			do_text( text, num, HSManager );
			delete	text;
		}
		else
		{
			do_text( lessons[ (num - 1) * 2 + 1 ], num, HSManager );
		}
	}
} //}}}
int initColors() //{{{
{
	start_color();

	if( has_colors() == 0 )
	{
		SetParameter( COLOR_PARAM, false );
		return -1;
	}

	init_pair( 1, COLOR_BLUE , COLOR_BLACK );
	init_pair( 2, COLOR_GREEN , COLOR_BLACK );
	init_pair( 3, COLOR_CYAN , COLOR_BLACK );
	init_pair( 4, COLOR_RED , COLOR_BLACK );
	init_pair( 5, COLOR_MAGENTA , COLOR_BLACK );
	init_pair( 6, COLOR_YELLOW , COLOR_BLACK );
	init_pair( 7, COLOR_WHITE , COLOR_BLACK );

	return 0;
} //}}}
void initApp() //{{{
{
	window = initscr();

	right_margin = COLS - 10;
	max_editable_lines = LINES - 18 - 1;
	helpInterval = 10000 + (random() % 10000);
	helpLength = 1;

	if( GetParameter( COLOR_PARAM ) == true )
	{
		initColors();
	}
} //}}}
void closeApp() //{{{
{
	move( 0, 0 );
	clear();
	refresh();
	endwin();
} //}}}
int	playFile( const char *filename ) //{{{
{
	char	*buffer;
	int		r;
	int		len = 0;

	r = importText( filename, &buffer, len );
	if( r <= 0 )
	{
		return -1;
	}

	do_text( buffer, 0, HSManager );

	delete[] buffer;

	return 0;
} //}}}
void showHelp() //{{{
{
	printf("Usage: dvorakng [OPTION]... [FILE]...\n\n"
			"  -b, --bell                   beep on error\n"
			"  -f, --flash                  flash the screen on error\n"
			"  -u, --ugly                   use low ascii for artwork\n"
			"  -n, --nastiness              set nastiness on by default\n"
			"  -k, --showkeys               shows keyboard layout\n"
			"  -r, --repetition             turns repetition on\n"
			"      --help                   display this help and exit\n"
			"      --version                output version information and exit\n"
			"\n");
} //}}}
void showVersionInfo() //{{{
{
	printf ("DvorakNG v%ld.%ld.%ld%s - FREE Software with NO Warranty\n"
			"(C) 2003 Kamil Burzynski <k.burzynski@adbglobal.com>\n\n"
			"Lessons are (C) 1995 Dan Wood <danwood@karelia.com>\n\n"
			"Heavily based on 'dvorak 7min tutor hack 1.6.1' (C) 1998-2001\n"
			"Ragnar Hojland Espinosa <ragnar@ragnar-hojland.com>\n"
			"\n", version_major, version_minor, version_micro, version_string );
} //}}}
int	main( int argc, char *argv[] ) //{{{
{
	struct option	long_opts[] =
	{
		{ "help",				0, 0, 'H' },
		{ "version",		0, 0, 'V' },
		{ "nastiness",	0, 0, 'n' },
		{ "bell",				0, 0, 'b' },
		{ "flash",			0, 0, 'f' },
		{ "ugly",				0, 0, 'u' },
		{ "showkeys",		0, 0, 'k' },
		{ "repetition", 0, 0, 'r' },
		{ 0, 0, 0, 0 }
	};

	int	opt_index = 0;
	int	c;
	set<char>	opts;

	SetParameter( BEEPS_PARAM, false );
	SetParameter( FLASHES_PARAM, false );
	SetParameter( NASTINESS_PARAM, false );
	SetParameter( PRETTY_PARAM, true );
	SetParameter( KEYBOARD_PARAM, false );
	SetParameter( COLOR_PARAM, true );
	SetParameter( REPETITION_PARAM, false );
	Config[ RANDOMIZE_SMALL_WEIGHT ] = "20";
	Config[ RANDOMIZE_CAPITAL_WEIGHT ] = "3";
	Config[ RANDOMIZE_PAGES ] = "3";

	while( 1 )
	{
		c = getopt_long( argc, argv, "HVnbfukr", long_opts, &opt_index );

		if( c == -1 )
		{
			break;
		}

		switch( c )
		{
			case 'H':
				showHelp();
				return 1;

			case 'V':
				showVersionInfo();
				return 1;

			case 'b':
			case 'f':
			case 'n':
			case 'u':
			case 'k':
			case 'r':
				opts.insert( c );
				break;
		}
	}

	initApp();

	string		location = getpwuid( getuid() )->pw_dir;
	location += "/.dvorakng";

	mkdir( location.c_str(), 0755 );

	RestoreConfig();
	LessonManager.Init();

	for( set<char>::iterator it=opts.begin();it!=opts.end();++it )
	{
		switch( *it )
		{
			case 'b':
				SetParameter( BEEPS_PARAM, true );
				break;

			case 'f':
				SetParameter( FLASHES_PARAM, true );
				break;

			case 'n':
				SetParameter( NASTINESS_PARAM, true );
				break;

			case 'u':
				SetParameter( PRETTY_PARAM, false );
				break;

			case 'k':
				SetParameter( KEYBOARD_PARAM, true );
				break;

			case 'r':
				SetParameter( REPETITION_PARAM, true );
				break;
		}
	}

	if( optind < argc )
	{
		while( optind < argc )
		{
			if( playFile( argv[ optind ] ) < 0 )
			{
				closeApp();
				//printf( "%s: %s: %s\n", argv[ 0 ], argv[ optind ], postmortem );
				return 1;
			}

			++optind;
		}
	}
	else
	{
		HSManager.RestoreEntries();

		menuInteractive();

		HSManager.StoreEntries();
	}

	closeApp();
	showVersionInfo();
	//StoreConfig();

	return 0;
} //}}}

//Lesson classes
TLessonManager::TLessonManager() //{{{
{
} //}}}
TLessonManager::~TLessonManager() //{{{
{
	vector<TLessonSlot>::iterator	it;

	for( it=FSlots.begin();it!=FSlots.end();++it )
	{
		it->Store();
	}
} //}}}
void	TLessonManager::Init() //{{{
{
	UINT32	i;

	string		location = getpwuid( getuid() )->pw_dir;
	location += "/.dvorakng/Slot_";

	for( i=0;i<NumSlots;i++ )
	{
		ostringstream	oss;

		oss << location << i;

		FSlots.push_back( TLessonSlot( oss.str() ) );

		FSlots.back().Restore();
	}
} //}}}
void	TLessonManager::Menu() //{{{
{
	char		c;
	UINT32	i;
	UINT32	selected = NumSlots;

	do
	{
		UINT32	row = 0;

		noecho();
		curs_set( FALSE );

		clear();
		attron( COLOR_PAIR( 7 ) );
		attron( A_BOLD );

		mvprintw( row, 0, "Lesson menu:" );

		attron( COLOR_PAIR( 3 ) );

		row += 2;

		for( i=0;i<NumSlots;i++ )
		{
			if( selected == i )
			{
				attron( A_BOLD );
			}
			else
			{
				attroff( A_BOLD );
			}

			FSlots[ i ].Show( i, row + i );
		}

		if( selected == NumSlots )
		{
			attroff( A_BOLD );
		}
		else
		{
			attron( A_BOLD );
		}

		row += 11;

		mvprintw( row + 0, 0, "Show [S]cores, [H]istory or [P]er-character stats" );

		mvprintw( row + 2, 0, "[T]ype in" );

		mvprintw( row + 4, 0, "Set file [N]ame" );
		mvprintw( row + 5, 0, "Adjust [O]ffset" );
		mvprintw( row + 6, 0, "[R]eset slot" );

		row += 8;

		attron( A_BOLD );

		attron( COLOR_PAIR( 5 ) );

		mvprintw( row, 0, "Press Esc, Space or Enter for main menu." );

		c = getch();

		row += 2;
		attron( COLOR_PAIR( 7 ) );

		switch( tolower( c ) )
		{
			case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
				selected = c - '0';
				break;

			case 't':
				if( selected != NumSlots )
				{
					FSlots[ selected ].Play();
				}
				break;

			case 'o':
				if( selected != NumSlots )
				{
					UINT32	len = FSlots[ selected ].GetLength();

					if( len != 0 )
					{
						char txt[ 9 ];
						echo();
						curs_set( TRUE );
						memset( txt, 0, sizeof( txt ) );
						mvprintw( row, 0, "Enter position offset: " );
						getnstr( txt, sizeof( txt ) - 1 );

						if( txt[ 0 ] != '\0' )
						{
							UINT32				pos;
							istringstream	iss( txt );

							iss >> pos;

							if( pos < len )
							{
								FSlots[ selected ].SetPosition( pos );
							}
						}

					}
				}
				break;

			case 'r':
				if( selected != NumSlots )
				{
					char	c;

					mvprintw( row, 0, "Are you sure (y/[n])?" );

					c = getch();

					if( tolower( c ) == 'y' )
					{
						FSlots[ selected ].Reset();
					}
				}
				break;

			case 'n':
				if( selected != NumSlots )
				{
					char txt[ 256 ];
					echo();
					curs_set( TRUE );
					memset( txt, 0, sizeof( txt ) );
					mvprintw( row, 0, "Enter filename: " );
					getnstr( txt, sizeof( txt ) - 1 );

					if( txt[ 0 ] != '\0' )
					{
						FSlots[ selected ].SetFileName( txt );
					}
				}
				break;

			case 's':
				if( selected != NumSlots )
				{
					FSlots[ selected ].ShowScores();
				}
				break;

			case 'h':
				if( selected != NumSlots )
				{
					FSlots[ selected ].ShowHistory();
				}
				break;

			case 'p':
				if( selected != NumSlots )
				{
					FSlots[ selected ].ShowCharScores();
				}
				break;

			default:
				break;
		};
	}
	while( (c != 27) && (c != ' ') && (c != '\n') );

	echo();
	curs_set( TRUE );
	attroff( A_BOLD );
} //}}}

TLessonSlot::TLessonSlot( const string &name ) : FHSManager( name + "_history" ) //{{{
{
	FConfigName = name + "_text";

	ifstream	file( FConfigName.c_str(), ios::binary | ios::in );
	FPosition = 0;

	if( file.good() == true )
	{
		UINT32	i;
		char		txt[ 256 ] = { 0 };

		file >> i;

		file.ignore( 1 ); //separating space

		file.getline( txt, 256 );

		SetFileName( txt );

		if( FFileName != "" )
		{
			FPosition = i;
		}
	}
} //}}}
void	TLessonSlot::Show( UINT32 i, UINT32 row ) //{{{
{
	mvprintw( row, 0, "[%ld]: Pos: %6ld/%6ld  File name: %s", i, FPosition, FText.size(), FFileName.c_str() );
} //}}}
void	TLessonSlot::SetFileName( const string &name ) //{{{
{
	char	*buffer = NULL;
	int		r;
	int		len = 0;

	FPosition = 0;
	FText = "";

	r = importText( name.c_str(), &buffer, len );

	if( r > 0 )
	{
		FText = buffer;
		FFileName = name;
	}
	else
	{
		if( name != "" )
		{
			Error( "Can't open file!" );
		}
	}

	delete[] buffer;
} //}}}
void	TLessonSlot::ShowHistory() //{{{
{
	FHSManager.ShowHistory();
} //}}}
void	TLessonSlot::ShowScores() //{{{
{
	FHSManager.ShowScores();
} //}}}
void	TLessonSlot::ShowCharScores() //{{{
{
	FHSManager.ShowCharScores();
} //}}}
void	TLessonSlot::Store() //{{{
{
	FHSManager.StoreEntries();

	ofstream	file( FConfigName.c_str(), ios::binary | ios::out );

	if( file.good() == true )
	{
		file << FPosition << " " << FFileName;
	}
} //}}}
void	TLessonSlot::Restore() //{{{
{
	FHSManager.RestoreEntries();
} //}}}
UINT32	TLessonSlot::GetLength() const //{{{
{
	return( FText.size() );
} //}}}
void	TLessonSlot::SetPosition( UINT32 pos ) //{{{
{
	FPosition = pos;
} //}}}
void	TLessonSlot::Reset() //{{{
{
	FFileName = "";
	FPosition = 0;
	FText = "";
} //}}}
void	TLessonSlot::Play() //{{{
{
	clear();
	FPosition += do_text( FText.c_str() + FPosition, 0, FHSManager );

	if( FPosition == 1 )
	{
		//small workaround for sick data format..
		FPosition = 0;
	}
} //}}}

//HighScore classes{{{
THSManager::THSManager( const string &filename ) : FFileName( filename ) //{{{
{
	FHistory = 0;
} //}}}
THSManager::~THSManager() //{{{
{
} //}}}
void	THSManager::AddEntry( THSEntry &entry ) //{{{
{
	FEntries.push_back( entry );
} //}}}
THSEntry	&THSManager::GetBestEntry( bool this_session, THSEntry::Predicate pred ) //{{{
{
	EntryList::iterator	it = FEntries.begin();

	if( this_session == true )
	{
		advance( it, FHistory );
	}

	return( *max_element( it, FEntries.end(), pred ) );
} //}}}
void	THSManager::ShowScores() //{{{
{
	curs_set( FALSE );
	cbreak();
	noecho();
	clear();
	refresh();

	if( FEntries.size() != 0 )
	{
		FEntries.back().Print( 0, 0, 3, "Last entry:" );

		if( FHistory != FEntries.size() )
		{
			GetBestEntry( true, THSEntry::GetFasterEntry ).Print( 7, 0, 2, "Fastest entry (session):" );
			GetBestEntry( true, THSEntry::GetPerfectEntry ).Print( 14, 0, 6, "Most accurate entry (session):" );
		}

		GetBestEntry( false, THSEntry::GetFasterEntry ).Print( 7, 40, 2, "Fastest entry (ever):" );
		GetBestEntry( false, THSEntry::GetPerfectEntry ).Print( 14, 40, 6, "Most accurate entry(ever):" );
	}

	attron( COLOR_PAIR( 1 ) ); //blue
	attron( A_BOLD );
	mvprintw( 22, 0, "[Press ESCAPE to continue.]" );
	move( 22, 28 );
	refresh();

	while( getch() != 27 )	;

	echo();
	curs_set( TRUE );
}; //}}}
void	THSManager::ShowHistory() //{{{
{
	EntryList::iterator		it;
	UINT32								i = 0;
	UINT32								j = 0;
	UINT32								x;
	UINT32								y;
	bool									printed = false;

	clear();
	noecho();
	curs_set( FALSE );
	//attron( A_BOLD );
	attron( COLOR_PAIR( 3 ) );

	getmaxyx( window, y, x );

	for( it=FEntries.begin();it!=FEntries.end();++it,++j )
	{
		if( j >= FHistory )
		{
			attron( A_BOLD );
		}

		it->PrintHistory( i );
		printed = true;

		if( i == (y - 2) )
		{
			attroff( A_BOLD );
			mvprintw( i + 1, 0, "[Press any key]" );
			getch();
			i = 0;
			clear();
			printed = false;
		}
		else
		{
			i++;
		}
	}

	echo();
	curs_set( TRUE );

	if( printed == true )
	{
		attroff( A_BOLD );
		mvprintw( i, 0, "[Press any key]" );
		getch();
	}
} //}}}
void	THSManager::RestoreEntries() //{{{
{
	//string		location = getpwuid( getuid() )->pw_dir;
	//location += "/.dvorakng/history";

	ifstream	file( FFileName.c_str(), ios::binary | ios::in );

	string	ver;

	file >> ver;

	if( (file.good() == true) && (ver == config_version_string) )
	{
		while( file.good() == true )
		{
			file >> ver;

			if( file.good() == true )
			{
				if( ver == "E" )
				{
					FEntries.push_back( THSEntry( file ) );

					if( file.good() == false )
					{
						FEntries.pop_back();
					}
				}
				else
				{
					if( ver == "C" )
					{
						UINT32	ch;
						UINT32	hits;
						UINT32	miss;

						file >> ch >> hits >> miss;

						if( file.good() == true )
						{
							FCharScores[ ch ].FHits  = hits;
							FCharScores[ ch ].FMisses = miss;
						}
					}
					else
					{
						Error( "Unknown file entry!" );
						file.ignore( 100, '\n' );
					}
				}
			}
		}
	}
	else
	{
		//Error( "Wrong database file, cannot restore history." );
	}

	FHistory = FEntries.size();
} //}}}
void	THSManager::StoreEntries() //{{{
{
	//string		location = getpwuid( getuid() )->pw_dir;
	//location += "/.dvorakng/history";

	ofstream	file( FFileName.c_str(), ios::binary | ios::out );

	if( file.good() == true )
	{
		CharScores::iterator	cit;
		EntryList::iterator		it;

		file << config_version_string << endl;

		for( cit=FCharScores.begin();cit!=FCharScores.end();++cit )
		{
			file << "C" << " " << cit->first << " " << cit->second.FHits << " " << cit->second.FMisses << endl;
		}

		for( it=FEntries.begin();it!=FEntries.end();++it )
		{
			it->Store( file );
		}
	}
} //}}}
void	THSManager::SetExpectedChar( UINT32 c ) //{{{
{
	FExpectedChar = c;
	//mvprintw( 23, 0, "%c", c );
} //}}}
void	THSManager::CharTyped( UINT32 c ) //{{{
{
	CharStat	&stats = FCharScores[ FExpectedChar ];

	//mvprintw( 23, 1, "%c", c );
	if( c == FExpectedChar )
	{
		stats.FHits++;
	}
	else
	{
		stats.FMisses++;
	}
} //}}}
void	THSManager::ShowCharScores() //{{{
{
	CharScores::iterator	it;
	UINT32								i;
	UINT32								total_hits = 0;
	UINT32								total_miss = 0;

	clear();
	noecho();
	curs_set( FALSE );
	attron( A_BOLD );
	attron( COLOR_PAIR( 3 ) );

	for( it=FCharScores.begin(),i=0;it!=FCharScores.end();++it,++i )
	{
		if( (i / 2) == 24 )
		{
			attron( COLOR_PAIR( 1 ) ); //blue
			mvprintw( 24, 0, "-- MORE --" );
			getch();
			clear();
			attron( COLOR_PAIR( 3 ) );
			i = 0;
		}

		if( it->first != '\n' )
		{
			mvprintw( i / 2, (i % 2) * 40, "'%c': Hit %5d, Miss %5d -> %6.2f%%", it->first, it->second.FHits, it->second.FMisses, CalcRatio( it->second.FHits, it->second.FMisses ) );

			total_hits += it->second.FHits;
			total_miss += it->second.FMisses;
		}
		else
		{
			i--;
		}
	}

	i = i/2 + 1 + (i % 2);
	mvprintw( i, 0, "Total hits: %d, total miss: %d, total ratio: %.2f%%", total_hits, total_miss, CalcRatio( total_hits, total_miss ) );
	i++;
	attron( COLOR_PAIR( 1 ) ); //blue
	mvprintw( i, 0, "[Press ESCAPE to continue.]" );
	while( getch() != 27 )	;

	curs_set( TRUE );
	echo();
} //}}}
void	THSManager::Reset() //{{{
{
	FEntries.clear();
	FHistory = 0;
	FCharScores.clear();
} //}}}

THSEntry::THSEntry( ifstream &file ) //{{{
{
	Restore( file );
} //}}}
THSEntry::THSEntry( UINT32 hits, UINT32 misses, UINT32 timev, UINT32 lesson ) : FHits( hits ), FMisses( misses ), FTime( timev ), FCreation( time( 0 ) ), FLesson( lesson ) //{{{
{
} //}}}
THSEntry::~THSEntry() //{{{
{
} //}}}
void	THSEntry::Print( UINT32 row, UINT32 column, UINT32 color, char *msg ) const //{{{
{
	float	ratio = CalcRatio( FHits, FMisses );
	float	speed = GetSpeed();

	attroff( A_BOLD );
	attron( COLOR_PAIR( color ) );
	mvprintw( row, column, msg );

	attron( A_BOLD );

	row += 2;
	column += 1;
	if( FLesson != 0 )
	{
		mvprintw( row + 0, column, "Time: %.2f seconds  Lesson: %d", FTime / 1000000.0, FLesson );
	}
	else
	{
		mvprintw( row + 0, column, "Time: %.2f seconds", FTime / 1000000.0 );
	}
	mvprintw( row + 1, column, "Total: %d  Misses: %d  Ratio: %.2f%%", FHits, FMisses, ratio );
	mvprintw( row + 2, column, "CPS: %.2f  CPM: %.2f", speed, speed * 60 );
	mvprintw( row + 3, column, "Typed at: %s", ctime( &FCreation ) );

	attroff( A_BOLD );
} //}}}
float THSEntry::GetSpeed() const //{{{
{
	return ((float) FHits * 1000000.0) / FTime;
} //}}}
void	THSEntry::Restore( ifstream &file ) //{{{
{
	file >> FHits >> FMisses >> FTime >> FCreation >> FLesson;
	//mvprintw(0,0,"%ld %ld %ld %ld %ld", FHits, FMisses, FTime, FCreation, FLesson );
	//getch();
} //}}}
void	THSEntry::Store( ofstream &file ) //{{{
{
	file << "E" << " " << FHits << " " << FMisses << " " << FTime << " " << FCreation << " " << FLesson << endl;
} //}}}
void	THSEntry::PrintHistory( UINT32 row ) const //{{{
{
	float ratio = CalcRatio( FHits, FMisses );
	float	speed = GetSpeed();

	mvprintw( row, 0, "Hits %5d, misses %5d, ratio %6.2f%%, CPS %5.2f, CPM %7.2f, %.1fs"/*, typed %s"*/, FHits, FMisses, ratio, speed, speed*60, FTime/1000000.0/*, ctime( &FCreation )*/ );
	//mvprintw( row+1, 0, "09876543210987654321098765432109876543210987654321098765432109876543210987654321" );
} //}}}
//}}}

