// Driver.js
// 2006-12-31
/*
(C)2008 Julio Di Egidio
http://julio.diegidio.name
mailto:julio@diegidio.name

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 shall be used for Good, not Evil.

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.
*/
////////////////////////////////////////////////////////////////
// jde_3t_driver (Driver.js) //////////////////////////////////////////

// Requires jde_ge_Engine (Engine.js)
// Requires jde_3t_Board (Board.js)
// Requires jde_3t_APlayer (APlayer.js)
// Requires jde_3t_view (View.js)
// Requires page context
//
/*extern
	jde_ge_noValue,
	jde_ge_gameWho,
	jde_ge_gameAction,
	jde_ge_Model,
	jde_ge_APlayer,
	jde_ge_Controller
*/
/*extern
	jde_3t_boardCellState,
	jde_3t_boardState,
	jde_3t_BoardCoord,
	jde_3t_BoardWinCoords,
	jde_3t_BoardMove,
	jde_3t_Board
*/
/*extern
	jde_3t_aStrategy,
	jde_3t_aThreshold,
	jde_3t_APlayer
*/
/*extern
	jde_3t_view
*/
/*extern
	window
*/


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// jde_3t_driver ///////////
	
// <<class>>GX_Driver {singleton} //
var jde_3t_driver = new (function () {
	
	/////////////////////
	// Private ////////////
	
////////////////////////////////////////////////////////////////
	/////////////////////////////
	// engine ////////////////////
	
	// <<class>>GX_Driver.Engine {singleton} //
	var engine = new (function () {
		
		/////////////////////
		// Private ////////////
		
		var controllerName = "jde_3t_driver._getController()",
			notifyFuncName = "jde_3t_driver._notifySuggestRef";
		
		var sendEvent = function (sFunc, delay) {
			delay = isNaN(delay) ? 1 : delay;
			return window.setTimeout(sFunc, delay);
		};
		var clearEvent = function (eventId) {
			window.clearTimeout(eventId);
		};
		
		var updateFuncName_M = "jde_3t_driver._updateFuncRef_M",
			updateFuncName_A = "jde_3t_driver._updateFuncRef_A",
			updateFuncName_C = "jde_3t_driver._updateFuncRef_C",
			updateFuncName_E = "jde_3t_driver._updateFuncRef_E";
		
		var that = this;
		
		
		/////////////////////
		// Public ////////////
		
		// jde_3t_
		this._gx_boardCellState = null;
		this._gx_boardState = null;
		this._gx_BoardCoord = null;
		this._gx_BoardWinCoords = null;
		this._gx_BoardMove = null;
		//
		this._gx_aStrategy = null;
		this._gx_aThreshold = null;
		//
		this._ge_gameWho = null;
		this._ge_gameAction = null;
		//
		this._model = null;
		this._aPlayer = null;
		this._controller = null;
		
		
		this.init = function (engineParams) {
			// jde_3t_
			that._gx_boardCellState = jde_3t_boardCellState;
			that._gx_boardState = jde_3t_boardState;
			that._gx_BoardCoord = jde_3t_BoardCoord;
			that._gx_BoardWinCoords = jde_3t_BoardWinCoords;
			that._gx_BoardMove = jde_3t_BoardMove;
			//
			that._gx_aStrategy = jde_3t_aStrategy;
			that._gx_aThreshold = jde_3t_aThreshold;
			//
			that._ge_gameWho = jde_ge_gameWho;
			that._ge_gameAction = jde_ge_gameAction;
			//
			var gx_board = new jde_3t_Board(),
				gx_aPlayer = new jde_3t_APlayer(gx_board);
			that._model = new jde_ge_Model(gx_board);
			that._aPlayer = new jde_ge_APlayer(gx_aPlayer);
			that._controller = new jde_ge_Controller(that._model, that._aPlayer,
				that._gx_BoardCoord, that._gx_boardState, that._gx_aStrategy);
			
			that._controller.init(
				controllerName,
				notifyFuncName,
				sendEvent,
				clearEvent,
				engineParams.proc.aiDelay,
				engineParams.proc.autoDelay,
				engineParams.proc.fastDelay,
				updateFuncName_M,
				updateFuncName_A,
				updateFuncName_C,
				updateFuncName_E);
		};
		
	})();
	
	
////////////////////////////////////////////////////////////////
	/////////////////////////////
	// view ////////////////////
	
	// <<class>>GX_Driver.View {singleton} //
	var view = new (function () {
		
		/////////////////////
		// Private ////////////
				
		// jde_3t_
		var viewBoard,
			viewScore,
			viewInfo,
			viewCtrl,
			viewCtrlExt;
		
		var that = this;
		
		
		/* Board */
		
		function hidleBoard() {
			for(var r = 0; r < 3; r++) {
				for(var c = 0; c < 3; c++) {
					viewBoard.hidle(r, c);
				}
			}
		}
		
		function startBoard() {
			for(var r = 0; r < 3; r++) {
				for(var c = 0; c < 3; c++) {
					viewBoard.start(r, c);
				}
			}
		}
		
		function resetBoard() {
			for(var r = 0; r < 3; r++) {
				for(var c = 0; c < 3; c++) {
					var boardCell = engine._model.getBoardCell(
						new engine._gx_BoardCoord(r, c));
					
					if(boardCell === engine._gx_boardCellState.e) {
						viewBoard.empty(r, c);
					}
					else if(boardCell === engine._gx_boardCellState.x) {
						viewBoard.setX(r, c);
					}
					else if(boardCell === engine._gx_boardCellState.o) {
						viewBoard.setO(r, c);
					}
				}
			}
		}
		
		
		function notifyBoard(r, c) {
			resetBoard();
			
			var startWho = engine._controller.getStartWho();
			
			if(startWho === engine._ge_gameWho.me) {
				viewBoard.hitO(r, c);
			}
			else if(startWho === engine._ge_gameWho.you) {
				viewBoard.hitX(r, c);
			}
		}
		
		
		function updateBoard() {
			if(!engine._controller.getIsHidle()) {
				var boardState = engine._model.getBoardState();
				
				// Move/Undo-BOG
				if(engine._gx_boardState.isMove(boardState) &&
						engine._controller.getIsBOG()) {
					if(!engine._controller.getIsAuto()) {
						startBoard();
					}
					else {
						resetBoard();
					}
				}
				// Move/Undo
				else if(engine._gx_boardState.isMove(boardState)) {
					resetBoard();
					
					var lastMove = engine._model.getLastMove();
					
					if(lastMove !== jde_ge_noValue) {
						var boardCoord = lastMove.boardCoord;
						
						var r = boardCoord.row;
						var c = boardCoord.col;
						
						if(!engine._controller.getIsAuto()) {
							if(boardState === engine._gx_boardState.moveX) {
								viewBoard.holdO(r, c);
							}
							else if(boardState === engine._gx_boardState.moveO) {
								viewBoard.holdX(r, c);
							}
						}
						else {
							if(boardState === engine._gx_boardState.moveX) {
								viewBoard.setO(r, c);
							}
							else if(boardState === engine._gx_boardState.moveO) {
								viewBoard.setX(r, c);
							}
						}
					}
				}
				// Win
				else if(engine._gx_boardState.isWin(boardState)) {
					resetBoard();
					
					var boardWinCoords = engine._model.getBoardWinCoords();
					
					for(var i = 0; i < boardWinCoords.length; i++) {
						var winCoord = boardWinCoords[i];
						
						var r = winCoord.row;
						var c = winCoord.col;
						
						if(!engine._controller.getIsAuto()) {
							if(boardState === engine._gx_boardState.winX) {
								viewBoard.winX(r, c);
							}
							else if(boardState === engine._gx_boardState.winO) {
								viewBoard.winO(r, c);
							}
						}
						else {
							if(boardState === engine._gx_boardState.winX) {
								viewBoard.hiliteX(r, c);
							}
							else if(boardState === engine._gx_boardState.winO) {
								viewBoard.hiliteO(r, c);
							}
						}
					}
				}
				// Draw
				else if(engine._gx_boardState.isDraw(boardState)) {
					resetBoard();
					
					var lastMove = engine._model.getLastMove();
					
					var boardCoord = lastMove.boardCoord;
					
					var r = boardCoord.row;
					var c = boardCoord.col;
					
					if(!engine._controller.getIsAuto()) {
						if(lastMove.boardState === engine._gx_boardState.moveX) {
							viewBoard.holdX(r, c);
						}
						else if(lastMove.boardState === engine._gx_boardState.moveO) {
							viewBoard.holdO(r, c);
						}
					}
					else {
						if(lastMove.boardState === engine._gx_boardState.moveX) {
							viewBoard.setX(r, c);
						}
						else if(lastMove.boardState === engine._gx_boardState.moveO) {
							viewBoard.setO(r, c);
						}
					}
				}
			}
				// (Hidle)
			else {
				hidleBoard();
			}
		}
		
		
		function overBoardCell(r, c) {
			if(!engine._controller.getIsHidle() &&
					!engine._controller.getIsAuto()) {
				var boardState = engine._model.getBoardState();
				var gameWho = engine._controller.getGameWho();
				
				// Move-YOU
				if(engine._gx_boardState.isMove(boardState) &&
						gameWho === engine._ge_gameWho.you) {
					var boardCell = engine._model.getBoardCell(
						new engine._gx_BoardCoord(r, c));
					
					if(boardCell === engine._gx_boardCellState.e) {
						// FX patch!
						resetBoard();
						
						if(boardState === engine._gx_boardState.moveX) {
							viewBoard.hoverX(r, c);
						}
						else if(boardState === engine._gx_boardState.moveO) {
							viewBoard.hoverO(r, c);
						}
					}
				}
			}
		}
		
		function outBoardCell(r, c) {
			if(!engine._controller.getIsHidle() &&
					!engine._controller.getIsAuto()) {
				var boardState = engine._model.getBoardState();
				var gameWho = engine._controller.getGameWho();
				
				// Move-YOU
				if(engine._gx_boardState.isMove(boardState) &&
						gameWho === engine._ge_gameWho.you) {
					var boardCell = engine._model.getBoardCell(
						new engine._gx_BoardCoord(r, c));
					
					if(boardCell === engine._gx_boardCellState.e) {
						// FX patch!
						resetBoard();
						
						viewBoard.empty(r, c);
					}
				}
			}
		}
		
		
		/* Score */
		
		function updateScore() {
			var scoreMe = engine._controller.getScoreMe();
			var scoreYou = engine._controller.getScoreYou();
			var scoreDraw = engine._controller.getScoreDraw();
			
			viewScore.setMe(scoreMe === jde_ge_noValue ? 0 : scoreMe);
			viewScore.setYou(scoreYou === jde_ge_noValue ? 0 : scoreYou);
			viewScore.setDraw(scoreDraw === jde_ge_noValue ? 0 : scoreDraw);
		}
		
		
		/* Info */
		
		function updateInfo() {
			var key;
			
			if(!engine._controller.getIsHidle()) {
				var boardState = engine._model.getBoardState();
				
				// Move
				if(engine._gx_boardState.isMove(boardState)) {
					if(engine._controller.getGameWho() ===
							engine._ge_gameWho.me) {
						key = viewInfo.key.myTurn;
					}
					else if(engine._controller.getIsBusy()) {
						key = viewInfo.key.yourAutoTurn;
					}
					else {
						key = viewInfo.key.yourTurn;
					}
				}
				// Win
				else if(engine._gx_boardState.isWin(boardState)) {
					if(engine._controller.getGameWho() ===
							engine._ge_gameWho.me) {
						key = viewInfo.key.youWin;
					}
					else {
						key = viewInfo.key.iWin;
					}
				}
				// Draw
				else if(engine._gx_boardState.isDraw(boardState)) {
					key = viewInfo.key.draw;
				}
			}
				// (Hidle)
			else {
				key = viewInfo.key.newGame;
			}
			
			viewInfo.update(key);
		}
		
		
		/* Ctrl */
		
		function updateCtrl() {
			// newGame
			viewCtrl.update(
					viewCtrl.key.newGame,
					engine._controller.getIsActionDisabled(
						engine._ge_gameAction.newGame));
			// restart
			viewCtrl.update(
					viewCtrl.key.restart,
					engine._controller.getIsActionDisabled(
						engine._ge_gameAction.restart));
			// undo
			viewCtrl.update(
					viewCtrl.key.undo,
					engine._controller.getIsActionDisabled(
						engine._ge_gameAction.undo));
			// suggest
			viewCtrl.update(
					viewCtrl.key.suggest,
					engine._controller.getIsActionDisabled(
						engine._ge_gameAction.suggest));
			// startAutoplay
			viewCtrl.update(
					viewCtrl.key.startAutoplay,
					engine._controller.getIsActionDisabled(
						engine._ge_gameAction.startAutoplay));
			// stopAutoplay
			viewCtrl.update(
					viewCtrl.key.stopAutoplay,
					engine._controller.getIsActionDisabled(
						engine._ge_gameAction.stopAutoplay));
		}
		
		
		/* CtrlExt */
		
		function updateCtrlExt() {
			/*alert(
				engine._controller.def_fastAuto + "\n" +
				engine._controller.getAiStrategy() + "\n" +
				engine._controller.getAutoStrategy() + "\n" +
				engine._controller.getFuzzyThreshold());*/
// NEEDS REVISION! ******************************************************
			var ctrlExtOptions = {};
			ctrlExtOptions[viewCtrlExt.key.fastAuto] =
				engine._controller.def_fastAuto;
			ctrlExtOptions[viewCtrlExt.key.aiStrategy] =
				engine._gx_aStrategy.getOptions();
			ctrlExtOptions[viewCtrlExt.key.autoStrategy] =
				engine._gx_aStrategy.getOptions();
			ctrlExtOptions[viewCtrlExt.key.fuzzyThreshold] =
				engine._gx_aThreshold.getOptions();
			viewCtrlExt.update(ctrlExtOptions);
		}
		
		
		/* handlers */
		
		function boardCell_over(r, c) {
			overBoardCell(r, c);
		}
		function boardCell_out(r, c) {
			outBoardCell(r, c);
		}
		function boardCell_click(r, c) {
			engine._controller.move(
				new engine._gx_BoardCoord(r, c));
		}
		
		function newGame_click() {
			engine._controller.newGame();
		}
		function restart_click() {
			engine._controller.restart();
		}
		function undo_click() {
			engine._controller.undo();
		}
		function suggest_click() {
			engine._controller.suggest();
		}
		function startAutoplay_click() {
			engine._controller.startAutoplay();
		}
		function stopAutoplay_click() {
			engine._controller.stopAutoplay();
		}
		
		function fastAuto_click(v) {
			engine._controller.setFastAuto(v);
		}
		function aiStrategy_change(v) {
			engine._controller.setAiStrategy(v);
		}
		function autoStrategy_change(v) {
			engine._controller.setAutoStrategy(v);
		}
		function fuzzyThreshold_change(v) {
			engine._controller.setFuzzyThreshold(v);
		}
		function resetAll_click() {
			engine._controller.reset();
		}
		
		
		/////////////////////
		// Public ////////////
		
		this.init = function (
				boardParams,
				scoreParams,
				infoParams,
				ctrlParams,
				ctrlExtParams) {
			// jde_3t_
			viewBoard = new jde_3t_view.Board();
			viewScore = new jde_3t_view.Score();
			viewInfo = new jde_3t_view.Info();
			viewCtrl = new jde_3t_view.Ctrl();
			viewCtrlExt = new jde_3t_view.CtrlExt();
			
			var ctrlHandlers = {};
			ctrlHandlers[viewCtrl.key.newGame] = newGame_click;
			ctrlHandlers[viewCtrl.key.restart] = restart_click;
			ctrlHandlers[viewCtrl.key.undo] = undo_click;
			ctrlHandlers[viewCtrl.key.suggest] =  suggest_click;
			ctrlHandlers[viewCtrl.key.startAutoplay] = startAutoplay_click;
			ctrlHandlers[viewCtrl.key.stopAutoplay] = stopAutoplay_click;
			
			var ctrlExtHandlers = {};
			ctrlExtHandlers[viewCtrlExt.key.fastAuto] = fastAuto_click;
			ctrlExtHandlers[viewCtrlExt.key.aiStrategy] = aiStrategy_change;
			ctrlExtHandlers[viewCtrlExt.key.autoStrategy] = autoStrategy_change;
			ctrlExtHandlers[viewCtrlExt.key.fuzzyThreshold] = fuzzyThreshold_change;
			ctrlExtHandlers[viewCtrlExt.key.resetAll] = resetAll_click;
			
// NEEDS REVISION! ******************************************************
			var ctrlExtOptions = {};
			ctrlExtOptions[viewCtrlExt.key.fastAuto] =
				engine._controller.def_fastAuto;
			ctrlExtOptions[viewCtrlExt.key.aiStrategy] =
				engine._gx_aStrategy.getOptions();
			ctrlExtOptions[viewCtrlExt.key.autoStrategy] =
				engine._gx_aStrategy.getOptions();
			ctrlExtOptions[viewCtrlExt.key.fuzzyThreshold] =
				engine._gx_aThreshold.getOptions();
			
			viewBoard.init(boardParams,
				boardCell_over, boardCell_out, boardCell_click);
			viewScore.init(scoreParams);
			viewInfo.init(infoParams);
			viewCtrl.init(ctrlParams, ctrlHandlers);
			viewCtrlExt.init(ctrlExtParams, ctrlExtHandlers, ctrlExtOptions);
		};
		
		
		this.notifySuggest = function (boardCoord) {
			//alert("notifySuggest");
			notifyBoard(boardCoord.row, boardCoord.col);
		};
		this.updateModel = function () {
			//alert("updateModel");
			updateBoard();
		};
		this.updateAPlayer = function () {
			//alert("updateAPlayer");
			//
		};
		this.updateController = function () {
			//alert("updateController");
			updateScore();
			updateInfo();
			updateCtrl();
		};
		this.updateControllerExt = function () {
			//alert("updateControllerExt");
			updateCtrlExt();
		};
		
	})();
	
	
	/////////////////////
	// Public ////////////
	
	this.init = function (
			engineParams,
			boardParams,
			scoreParams,
			infoParams,
			ctrlParams,
			ctrlExtParams) {
		// Keep order!
		engine.init(
			engineParams);
		view.init(
			boardParams,
			scoreParams,
			infoParams,
			ctrlParams,
			ctrlExtParams);
		//
	};
	
	
	this._getController = function () {
		return engine._controller;
	};
	this._notifySuggestRef = function (boardCoord) {
		view.notifySuggest(boardCoord);
	};
	this._updateFuncRef_M = function () {
		view.updateModel();
	};
	this._updateFuncRef_A = function () {
		view.updateAPlayer();
	};
	this._updateFuncRef_C = function () {
		view.updateController();
	};
	this._updateFuncRef_E = function () {
		view.updateControllerExt();
	};
	
})(), driver = jde_3t_driver; // Expose alias!

