
/*
* jquery.pgn.js 0.1.0 - JavaScript/jQuery Chess Library
*
* Copyright (c) 2012 Knut Neven
* http://neven.ca
*
* Date: 2012-01-01 15:37:14 -0700 (Sunday, 1 January 2012)
*
* 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.
*/


// If Firebug is not installed, prevent console.log() from creating error messages
if (typeof console == "undefined") { var console = { log: function() {} } }

(function( $ )
{

	/* Add pgn() to jQuery namespace */
	$.fn.pgn = function( options )
	{
		var pgn = new $.pgn( options, this[0] );
		pgn.init();
		return this;
	}

	/* Constructor */
	$.pgn = function( options )
	{
		this.settings = $.extend( {}, $.pgn.defaults, options );
	}

	$.extend( $.pgn,
	{
		defaults :
		{
			select_element_selector : '#pgn-select',
			xtable_element_selector : '#pgn-xtable',
			standings_element_selector : '#pgn-standings',
			fen : "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
			pgn_source : '/Content/pgn/pgn.pgn',
			event_type : 'rr'
		},

		prototype :
		{
			init : function()
			{
				this.setupAjaxPGN();
				this.getAjaxPGN();
				this.buildIndex();
				this.drawSelect();
				this.pushGames();
				this.calculateStats();
				this.sortTournament();
				this.buildXtableRR();
				this.drawXtableRR();
				this.drawStandings();
			},

			selectElement : function()
			{
				return $(this.pgn).find(this.settings.select_element_selector);
			},

			xtableElement : function()
			{
				return $(this.pgn).find(this.settings.xtable_element_selector);
			},

			standingsElement : function()
			{
				return $(this.pgn).find(this.settings.standings_element_selector);
			},

			setupAjaxPGN : function()
			{
				$.ajaxSetup({
					async : false
				});
			},

			getAjaxPGN : function()
			{
				$.get(this.settings.pgn_source, function( data )
				{
					return filePGN = data;
				});
			},

			buildIndex : function()
			{
				string = new String();
				var eloTotal = 0;
				tournament = new Tournament();
				pgngames = new PGN();
				pgngames.parseFilePGN(filePGN);
				for ( i = 0 ; i < pgngames.games.length ; i++ )
				{
					var header = pgngames.parseHeaderPGN(pgngames.games[i]);
					if ( parseInt(header['Round']) > tournament.rounds )
					{
						tournament.rounds = header['Round'];
					}
					var white = new Player();
					white.player = header['White'];
					white.elo = parseInt(header['WhiteElo']);
					eloTotal += white.elo;
					var black = new Player();
					black.player = header['Black'];
					black.elo = parseInt(header['BlackElo']);
					eloTotal += black.elo;
					var uniqueW = true;
					var uniqueB = true;
					for ( j = 0 ; j < tournament.players.length ; j++ )
					{
						if ( tournament.players[j].player == white.player )
						{
							uniqueW = false;
						}
						if ( tournament.players[j].player == black.player )
						{
							uniqueB = false;
						}
					}
					if ( uniqueW == true )
					{
						tournament.players.push(white);
					}
					if ( uniqueB == true )
					{
						tournament.players.push(black);
					}
					uniqueW = true;
					uniqueB = true;
				}
				tournament.event = header['Event'];
				tournament.site = header['Site'];
				tournament.eventdate = header['EventDate'].substr(0, 4);
				tournament.startlist = tournament.players.length;
				tournament.games = pgngames.games.length;
				tournament.category = tournament.calculateCategory( eloTotal );
				console.log(tournament.event + ' ' + tournament.site + ' ' + tournament.eventdate + ' ' + tournament.startlist + ' ' + tournament.games + ' ' + tournament.rounds + ' ' + tournament.category);
			},

			drawSelect : function()
			{
				var listString = '<form><select class="selectPGN">';
				listString += '<option value="" selected="selected">Select Game...</option>';
				for ( i = 0 ; i < pgngames.games.length ; i++ )
				{
					var header = pgngames.parseHeaderPGN(pgngames.games[i]);
					var gameNumber = (i + 1);
					listString += '<option value="' + gameNumber + '">' + gameNumber + '. ' + header['White'] + ' ' + header['Result'] + ' ' + header['Black'] + '</option>';
				}
				listString += '</select></form>';
//				this.selectElement().html(listString);
				$("#pgn-select").html(listString);
				return listString;
			},

			drawStandings : function()
			{
				var playerWidth = new Player();
				var sWidth = playerWidth.calculatePlayerWidth( tournament.players );
				var standingsString = '<pre class="standingsPGN">';
				var arrStandings = new Array(9);
				for ( i = 0 ; i < pgngames.games.length ; i++ )
				{
					var header = pgngames.parseHeaderPGN(pgngames.games[i]);
					if ( arrStandings[parseInt(header['Round'] - 1)] == undefined )
						arrStandings[parseInt(header['Round'] - 1)] = '';
					arrStandings[parseInt(header['Round'] - 1)] += '<span class="standingsGame" id="' + (i + 1) +'g">(' + header['Round'] + ') ' + string.setFieldSize(header['White'], sWidth + 2, 'R', ' ') + ' ' + string.setFieldSize(header['Result'], 9, 'C', ' ') + ' ' + header['Black'] + '</span><br/>';
				}
				for ( j = 0 ; j < arrStandings.length ; j++ )
				{
					standingsString += '<br/>Round ' + (j + 1) + '<br/>';
					standingsString += arrStandings[j];
				}
				standingsString += '</pre>';
				$("#pgn-standings").html(standingsString);
				return standingsString;
			},

			pushGames : function()
			{
				for ( i = 0 ; i < pgngames.games.length ; i++ )
				{
					var header = pgngames.parseHeaderPGN( pgngames.games[i] );
					for ( j = 0 ; j < tournament.players.length ; j++ )
					{
						if ( header['White'] == tournament.players[j].player )
						{
							var game = new Game();
							game.number = i;
							game.color = 'W';
							game.result = header['Result'];
							tournament.players[j].games.push(game);
						}
						if ( header['Black'] == tournament.players[j].player )
						{
							var game = new Game();
							game.number = i;
							game.color = 'B';
							game.result = header['Result'];
							tournament.players[j].games.push(game);
						}
					}
				}
			},

			calculateStats : function()
			{
				for ( i = 0 ; i < pgngames.games.length ; i++ )
				{
					var header = pgngames.parseHeaderPGN(pgngames.games[i]);
					for ( j = 0 ; j < tournament.players.length ; j++ )
					{
						var resultPGN = header['Result'];
						var result = 0;
						var total = 0;
						if ( tournament.players[j].player == header['White'] )
						{
							tournament.players[j].average += parseInt(header['BlackElo']);
							tournament.players[j].rounds += 1;
							if ( resultPGN == '0-1' ) { result = 0; }
							if ( resultPGN == '1-0' ) { result = 1; tournament.players[j].total += result; }
							if ( resultPGN == '1/2-1/2' ) { result = 0.5; tournament.players[j].total += result; }
							if ( resultPGN == '*' ) { result = 0; }
						}
						if ( tournament.players[j].player == header['Black'] )
						{
							tournament.players[j].average += parseInt(header['WhiteElo']);
							tournament.players[j].rounds += 1;
							if ( resultPGN == '1-0' ) { result = 0; }
							if ( resultPGN == '0-1' ) { result = 1; tournament.players[j].total += result; }
							if ( resultPGN == '1/2-1/2' ) { result = 0.5; tournament.players[j].total += result; }
							if ( resultPGN == '*' ) { result = 0; }
						}
					}
				}
				for ( i = 0 ; i < tournament.players.length ; i++ )
				{
					tournament.players[i].calculateTPR();
					for ( j = 0 ; j < tournament.players[i].games.length ; j++ )
					{
						if ( tournament.players[i].games[j].result == '1-0' )
							{ tournament.players[i].games[j].score = ( tournament.players[i].games[j].color == 'W' ) ? 1 : 0; }
						if ( tournament.players[i].games[j].result == '0-1' )
							{ tournament.players[i].games[j].score = ( tournament.players[i].games[j].color == 'W' ) ? 0 : 1; }
						if ( tournament.players[i].games[j].result == '1/2-1/2' )
							{ tournament.players[i].games[j].score = ( tournament.players[i].games[j].color == 'W' ) ? 0.5 : 0.5; }
						if ( tournament.players[i].games[j].result == '*' )
							{ tournament.players[i].games[j].score = ( tournament.players[i].games[j].color == 'W' ) ? '*' : '*'; }
					}
				}
			},

			sortTournament : function()
			{
				tournament.players.sort(tournament.sortPlayersByTPR);
				tournament.players.sort(tournament.sortPlayersByScore);
			},
			
			buildXtableRR : function()
			{
				xtable = new Xtable();
				var tempRows = new Array();
				for ( i = 0 ; i < tournament.players.length ; i++ )
				{
					var opponent = '';
					var tempGame = new Game();
					var tempRow = new Array();
					for ( j = 0 ; j < tournament.players[i].games.length ; j++ )
					{
						var gameNumber = tournament.players[i].games[j].number;
						opponent = tempGame.getOpponent(tournament.players[i].player, pgngames.games[gameNumber]);
						for ( k = 0 ; k < tournament.players.length ; k++ )
						{
							if ( tournament.players[k].player == opponent )
							{
								tempRow[k] = tournament.players[i].games[j].score;
//								console.log(gameNumber + ' ' + tournament.players[i].player + '-'+ opponent + ' ' + tempRow[k]);
							}
						}
					}
					tempRows.push(tempRow);
				}
				for ( m = 0 ; m < tempRows.length ; m++ )
				{
					for ( n = 0 ; n < tempRows[m].length ; n++ )
					{
						if ( tempRows[m][n] == undefined )
							tempRows[m][n] = '.';
						if ( m == n )
							tempRows[m][n] = 'x';
					}
					if ( tempRows[m].length < tournament.players.length )
					{
						tempRows[m][tournament.players.length - 1] = 'x';
					}
				}
				xtable.buildRowsRR(tournament.players, tempRows);
			},

			drawXtableRR : function()
			{
				var playerWidth = new Player();
				var xWidth = playerWidth.calculatePlayerWidth( tournament.players );
				var row = '<pre class="xtable">';
				var xTournamentHeader = tournament.eventdate + ' ' + tournament.site + ' ' + tournament.event + '   (' + tournament.rounds + 'r/' + tournament.startlist + 'p/' + tournament.games + 'g) Category ' + tournament.category;
				row += xTournamentHeader + '<br/>';
				var xRounds = '';
				for ( i = 0 ; i < tournament.players.length ; i++ )
				{
					xRounds += (i + 1) % 10 + (i == tournament.players.length - 1 ? '' : ' ');
				}
				var xInfoHeader = '##' + string.setFieldSize( 'Players', xWidth + 2, 'R', ' ') + string.setFieldSize('Elo', 6, 'R', ' ') + string.setFieldSize(xRounds, xRounds.length + 4, 'C', ' ') + string.setFieldSize('##', 2, 'L', ' ') + string.setFieldSize('TPR', 6, 'R', ' ');
				row += xInfoHeader + '<br/>';
				var xUnderline = '';
				for ( i = 0 ; i < xInfoHeader.length ; i++ )
				{
					xUnderline += '-';
				}
				row += xUnderline + '<br/>';

				for ( i = 0 ; i < tournament.players.length ; i++ )
				{
					var player = tournament.players[i].player;
					var elo = tournament.players[i].elo;
					var tpr = tournament.players[i].tpr;
					var total = tournament.players[i].total;
					var xTableLine = string.setFieldSize( i+1, 2, 'R', ' ') + string.setFieldSize( player, xWidth + 2, 'R', ' ') + string.setFieldSize( elo, 6, 'R', ' ');
					xTableLine += string.setFieldSize( xtable.rows[i] , tournament.rounds * 2 + 4 , 'C', ' ');			//	xtable.rows[i].length + 3
					xTableLine += string.setFieldSize( total.toString().replace(".5",'='), 2, 'L', ' ') + string.setFieldSize( tpr, 6, 'R', ' ');
					row += xTableLine + '<br/>';
				}
				row += '</pre>';
				$("#pgn-xtable").html(row);
				return row;
			}

		}
	})




////////// OBJECT CONSTRUCTORS //////////

//////////
	function Result()
	{
		// PRIVATE
		var resultPGN = '';
		var resultNumber = 0;
		var resultXtable = '';

		// GETTER SETTER
		this.__defineGetter__("resultPGN", function() { return resultPGN });
		this.__defineGetter__("resultNumber", function() { return resultNumber });
		this.__defineGetter__("resultXtable", function() { return resultXtable });
		this.__defineSetter__("resultPGN", function( val ) { resultPGN = val });
		this.__defineSetter__("resultNumber", function( val ) { resultNumber = val });
		this.__defineSetter__("resultXtable", function( val ) { resultXtable = val });

		// FUNCTIONS
		this.convertResultPGNtoNumber = function( inResult )
		{
			var reWhiteResult = /1-0/gi;
			var reBlackResult = /0-1/gi;
			var reDrawResult = /1\/2-1\/2/gi;
			var reEmpty = /\*/gi;
			return (inResult.replace(reWhiteResult, 1).replace(reBlackResult, 0).replace(reDrawResult, 0.5).replace(reEmpty, 0));
		}
		this.convertResultPGNtoXtable = function( inResult )
		{
			var reWhiteResult = /1-0/gi;
			var reBlackResult = /0-1/gi;
			var reDrawResult = /1\/2-1\/2/gi;
			var reEmpty = /\*/gi;
			return (inResult.replace(reWhiteResult, '1').replace(reBlackResult, '0').replace(reDrawResult, '=').replace(reEmpty, '.'));
		}
		this.convertResultNumbertoXtable = function ( inResult )
		{
			var reWhiteResult = /1/gi;
			var reBlackResult = /0/gi;
			var reDrawResult = /0\.5/gi;
			var reEmpty = /\*/gi;
			return (inResult.replace(reWhiteResult, '1').replace(reBlackResult, '0').replace(reDrawResult, '=').replace(reEmpty, '.'));
		}
	}

//////////
	function Game()
	{
		// PRIVATE
		var number = 0;
		var color = '';
		var result = '';
		var score = 0;
		
		// GETTER SETTER
		this.__defineGetter__("number", function() { return number });
		this.__defineGetter__("color", function() { return color });
		this.__defineGetter__("result", function() { return result });
		this.__defineGetter__("score", function() { return score });
		this.__defineSetter__("number", function( val ) { number = val });
		this.__defineSetter__("color", function( val ) { color = val });
		this.__defineSetter__("result", function( val ) { result = val });
		this.__defineSetter__("score", function( val ) { score = val });
		
		// FUNCTIONS
		this.getOpponent = function( player , gamePGN )
		{
			var opponent = '';
			var temp = new PGN();
			var header = temp.parseHeaderPGN( gamePGN );
			if ( player == header['White'] )
				opponent = header['Black'];
			if ( player == header['Black'] )
				opponent = header['White'];
			return opponent;
		}
	}

//////////
	function Player()
	{
		// PRIVATE
		var player = '';
		var playerWidth = 0;
		var elo = 0;
		var total = 0;
		var rounds = 0;
		var average = 0;
		var tpr = 0;
		var games = new Array();

		// GETTER SETTER
		this.__defineGetter__("player", function() { return player });
		this.__defineGetter__("playerWidth", function() { return playerWidth });
		this.__defineGetter__("elo", function() { return elo });
		this.__defineGetter__("total", function() { return total });
		this.__defineGetter__("rounds", function() { return rounds });
		this.__defineGetter__("average", function() { return average });
		this.__defineGetter__("tpr", function() { return tpr });
		this.__defineGetter__("games", function() { return games });
		this.__defineSetter__("player", function( val ) { player = val });
		this.__defineSetter__("playerWidth", function( val ) { playerWidth = val });
		this.__defineSetter__("elo", function( val ) { elo = val });
		this.__defineSetter__("total", function( val ) { total = val });
		this.__defineSetter__("rounds", function( val ) { rounds = val });
		this.__defineSetter__("average", function( val ) { average = val });
		this.__defineSetter__("tpr", function( val ) { tpr = val });
		this.__defineSetter__("games", function( val ) { games = val });

		// FUNCTIONS
		this.calculateTPR = function()
		{
			return tpr = Math.round((average / rounds) + 400 * ((total * 2 - rounds) / rounds));
		}

		this.calculatePlayerWidth = function( playerArray )
		{
			for ( i = 0 ; i < playerArray.length ; i++ )
			{
				if ( playerArray[i].player.length > playerWidth )
					playerWidth = playerArray[i].player.length;
			}
			return playerWidth;
		}
	}

//////////
	function Tournament()
	{
		// PRIVATE
		var event = '';
		var site = '';
		var eventdate = '';
		var type = 'rr';
		var rounds = 0;
		var startlist = 0;
		var games = 0;
		var category = 0;
		var players = new Array();

		// GETTER SETTER
		this.__defineGetter__("event", function() { return event });
		this.__defineGetter__("site", function() { return site });
		this.__defineGetter__("eventdate", function() { return eventdate });
		this.__defineGetter__("type", function() { return type });
		this.__defineGetter__("rounds", function() { return rounds });
		this.__defineGetter__("startlist", function() { return startlist });
		this.__defineGetter__("games", function() { return games });
		this.__defineGetter__("category", function() { return category });
		this.__defineGetter__("players", function() { return players });
		this.__defineSetter__("event", function( val ) { event = val });
		this.__defineSetter__("site", function( val ) { site = val });
		this.__defineSetter__("eventdate", function( val ) { eventdate = val });
		this.__defineSetter__("type", function( val ) { type = val });
		this.__defineSetter__("rounds", function( val ) { rounds = val });
		this.__defineSetter__("startlist", function( val ) { startlist = val });
		this.__defineSetter__("games", function( val ) { games = val });
		this.__defineSetter__("category", function( val ) { category = val });
		this.__defineSetter__("players", function( val ) { players = val });

		// FUNCTIONS
		this.calculateCategory = function( inEloTotal )
		{
			var averageRating = Math.round( inEloTotal / (games * 2) );
			for ( i = 1 ; i <= 25 ; i++ )
			{
				var low = 2226 + i * 25;
				var high = 2250 + i * 25;
				if ( averageRating >= low && averageRating <= high )
					category = i;
			}
			return category;
		}
		this.sortPlayersByScore = function( objA, objB )
		{
			if ( objA.total > objB.total )
				return -1;
			if ( objA.total < objB.total )
				return 1;
			return 0;
		}
		this.sortPlayersByTPR = function( objA, objB )
		{
			if ( objA.tpr > objB.tpr )
				return -1;
			if ( objA.tpr < objB.tpr )
				return 1;
			return 0;
		}
	}

//////////
	function Xtable()
	{
		// PRIVATE
		var type = '';
		var rows = new Array();

		// GETTER SETTER
		this.__defineGetter__("type", function() { return type });
		this.__defineGetter__("rows", function() { return rows });
		this.__defineSetter__("type", function( val ) { player = val });		
		this.__defineSetter__("rows", function( val ) { rows = val });

		// FUNCTIONS
		this.buildRowsRR = function( players, resultRows )
		{
			var reWhiteResult = /1/gi;
			var reBlackResult = /0/gi;
			var reDrawResult = /0\.5/gi;
			var reEmpty = /\*/gi;
			var resultRow = new String();
			for ( i = 0 ; i < players.length ; i++ )
			{
				resultRow = resultRows[i].join(' ').replace(reWhiteResult, '1').replace(reBlackResult, '0').replace(reDrawResult, '=').replace(reEmpty, '.');
				console.log(resultRow);
				rows.push(resultRow);
			}
			return rows;
		}
		this.drawXtableRR = function()
		{

		}
	}

//////////
	function PGN()
	{
		// PRIVATE
		var games = new Array();
		var header = new Object();

		// GETTER SETTER
		this.__defineGetter__("games", function() { return games });
		this.__defineGetter__("header", function() { return header });
		this.__defineSetter__("games", function( val ) { player = val });
		this.__defineSetter__("header", function( val ) { player = val });

		// FUNCTIONS
		this.parseFilePGN = function( filePGN )
		{
			var reFilePGN = /^(?=\[Event )/gmi;
			games = filePGN.split(reFilePGN);
		}
		this.parseHeaderPGN = function( gamePGN )
		{
			var reHeader = /\[(.*) "(.*)"\]/gmi;
			while ( execHeader = reHeader.exec(gamePGN) )
			{
				var key = execHeader[1];
				var value = execHeader[2];
				header[key] = value;
			}
			return header;	// Event Site Date Round White Black Result ECO WhiteElo BlackElo PlyCount EventDate
		}
	}


})(jQuery);




////////// HELPER FUNCTIONS //////////

String.prototype.setFieldSize = function( string, width, align, fill )
{
	var strInput = string.toString();
	var strSize = strInput.length;
	var adjust = width - strSize;
	if ( align == 'C' )
		{ adjust = (width%2 == 0) ? Math.floor(((width - strSize) / 2) + 1) : ((width - strSize) / 2); }
	var character = '';
	for ( y = 0 ; y < adjust ; y++ )
	{
		character += fill;
	}
	if ( align == 'R' )
		{ return character + strInput; }
	if ( align == 'L' )
		{ return strInput + character; }
	if ( align == 'C' )
		{ return character + strInput + character; }
}


/*
	for ( var key in obj )
	{
		var value = obj[key];
		console.log(key + ' ' + value);
	}
*/

/*
if ( !Object.defineProperty )
	Object.defineProperty = function(Object, propertyName, getterSetter)
	{
		if(getterSetter.getter)
			Object.__defineGetter__(propertyName, getterSetter.getter);
		if(getterSetter.setter)
			Object.__defineSetter__(propertyName, getterSetter.setter);
	};
*/
