// Engine.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_ge_Engine (Engine.js) ///////////////////////////////////

////////////////////////////////////////////////////////////////
	/////////////////////////////
	// [consts] /////////////////
	
// <<constant>>GE_NO_VALUE //
var jde_ge_noValue = -1;


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// [enums] //////////////////
	
// <<enumeration>>GE_GameWho //
var jde_ge_gameWho = {
	me : "ME",
	you : "YO"
};

// <<enumeration>>GE_GameAction //
var jde_ge_gameAction = {
	newGame : "NEWG",
	restart : "REST",
	move : "MOVE",
	undo : "UNDO",
	suggest : "SUGG",
	startAutoplay : "STAA",
	stopAutoplay : "STOA"
};
jde_ge_gameAction.parse = function (gameAction) {
	if(!(gameAction === this.newGame ||
			gameAction === this.restart ||
			gameAction === this.move ||
			gameAction === this.undo ||
			gameAction === this.suggest ||
			gameAction === this.startAutoplay ||
			gameAction === this.stopAutoplay)) {
		return jde_ge_noValue;
	}
	return gameAction;
};


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// jde_ge_Model /////////////
	
// <<class>>GE_Model //
function jde_ge_Model(board) {
	
	/////////////////////
	// Private ////////////
	
	var _board = board;
	
	var _sendEventRef,
		_updateFuncName;
	
	
	/////////////////////
	// Public ////////////
	
	this.init = function (sendEventRef, updateFuncName) {
		_sendEventRef = sendEventRef;
		_updateFuncName = updateFuncName;
	};
	
	this.reset = function () {
		_board.reset();
		
		_sendEventRef(_updateFuncName + "()");
	};
	
	this.move = function (boardCoord) {
		var boardState = _board.move(boardCoord);
		if(boardState === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		
		_sendEventRef(_updateFuncName + "()");
		return boardState;
	};
	
	this.undo = function () {
		var boardState = _board.undo();
		if(boardState === jde_ge_noValue) {
			return jde_ge_noValue;
		}

		_sendEventRef(_updateFuncName + "()");
		return boardState;
	};
	
	
	this.getBoardCells = function () {
		return _board.getCells();
	};
	
	this.getBoardCell = function (boardCoord) {
		return _board.getCell(boardCoord);
	};
	
	this.getBoardState = function () {
		return _board.getState();
	};
	
	this.getLastMove = function () {
		return _board.getLastMove();
	};
	
	this.getBoardWinCoords = function () {
		return _board.getWinCoords();
	};
	
}


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// jde_ge_APlayer ///////////
	
// <<class>>GE_APlayer //
function jde_ge_APlayer(aPlayer) {
	
	/////////////////////
	// Private ////////////
	
	var _aPlayer = aPlayer;
	
	var _sendEventRef,
		_updateFuncName;
	
	
	/////////////////////
	// Public ////////////
	
	this.init = function (sendEventRef, updateFuncName) {
		_sendEventRef = sendEventRef;
		_updateFuncName = updateFuncName;
	};
	
	this.reset = function () {
		_aPlayer.reset();
		
		//_sendEventRef(_updateFuncName + "()");
	};
	
	this.getNextMove = function (strategy) {
		return _aPlayer.getNextMove(strategy);
	};
	
	
	this.getStrategy = function () {
		return _aPlayer.getStrategy();
	};
	
	this.setStrategy = function (strategy) {
		strategy = _aPlayer.setStrategy(strategy);
		if(strategy === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		
		//_sendEventRef(_updateFuncName + "()");
		return strategy;
	};
	
	this.getFuzzyThreshold = function () {
		return _aPlayer.getFuzzyThreshold();
	};
	
	this.setFuzzyThreshold = function (threshold) {
		threshold = _aPlayer.setFuzzyThreshold(threshold);
		if(threshold === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		
		//_sendEventRef(_updateFuncName + "()");
		return threshold;
	};
	
}


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// jde_ge_Controller ////////
	
// <<class>>GE_Controller //
function jde_ge_Controller(model, aPlayer,
		gx_BoardCoord, gx_boardState, gx_aStrategy) {
	
	/////////////////////
	// Private ////////////
	
	var _model = model,
		_aPlayer = aPlayer;
	var _gx_BoardCoord = gx_BoardCoord,
		_gx_boardState = gx_boardState,
		_gx_aStrategy = gx_aStrategy;
	
	var _selfName,
		_notifyFuncName,
		_sendEventRef,
		_clearEventRef,
		_aiDelay,
		_autoDelay,
		_fastDelay,
		_updateFuncName,
		_updateFuncNameExt;
	
	var _fastAuto,
		_autoStrategy;
	
	var _startWho = jde_ge_noValue,
		_gameWho = jde_ge_noValue;
	
	var _score_all,
		_score_me,
		_score_you,
		_score_draw;
	
	var _mtx_busy,
		_mtx_auto;
	
	var that = this;
	
	
	function resetScores() {
		_score_all = 0;
		_score_me = 0;
		_score_you = 0;
		_score_draw = 0;
	}
	function incScore(dir) {
		var inc = dir < 0 ? -1 : 1;
		var boardState = _model.getBoardState();
		if(_gx_boardState.isWin(boardState)) {
			_score_all += inc;
			if(_gameWho === jde_ge_gameWho.me) {
				_score_you += inc;
			}
			else {
				_score_me += inc;
			}
		}
		else if(_gx_boardState.isDraw(boardState)) {
			_score_all += inc;
			_score_draw += inc;
		}
	}
	
	function clearIntMtxs() {
		_mtx_busy = null;
		_mtx_auto = null;
	}
	function sendIntEvent(funcName, delay, isAuto) {
		if(_mtx_busy !== null) {
			return;
		}
		_mtx_busy = _sendEventRef(
			_selfName + "." + funcName + "()", delay);
		if(isAuto === true) {
			_mtx_auto = true;
		}
	}
	function clearIntEvent() {
		if(_mtx_busy !== null) {
			_clearEventRef(_mtx_busy);
			clearIntMtxs();
		}
	}
	
	
	/////////////////////
	// Public ////////////
	
// NEEDS REVISION! ******************************************************
	this.def_startWho = jde_ge_gameWho.you;
	this.def_fastAuto = true;
	
	
	this.init = function (
			selfName, notifyFuncName,
			sendEventRef, clearEventRef,
			aiDelay, autoDelay, fastDelay,
			updateFuncName_M, updateFuncName_A,
			updateFuncName_C, updateFuncName_E) {
		_model.init(sendEventRef, updateFuncName_M);
		_aPlayer.init(sendEventRef, updateFuncName_A);
		
		_selfName = selfName;
		_notifyFuncName = notifyFuncName;
		
		_sendEventRef = sendEventRef;
		_clearEventRef = clearEventRef;
		_aiDelay = aiDelay;
		_autoDelay = autoDelay;
		_fastDelay = fastDelay;
		_updateFuncName = updateFuncName_C;
		_updateFuncNameExt = updateFuncName_E;
		
		clearIntMtxs();
		
		that.reset();
	};
	
	this.reset = function () {
		clearIntEvent();
		
		_model.reset();
		_aPlayer.reset();
		_fastAuto = that.def_fastAuto;
		_autoStrategy = _gx_aStrategy.getDefault();
		
		// Trigger -> def_startWho in next game
		_startWho = that.def_startWho === jde_ge_gameWho.me ?
			jde_ge_gameWho.you : jde_ge_gameWho.me;
		_gameWho = jde_ge_noValue;
		
		resetScores();
		
		_sendEventRef(
			_updateFuncName + "()" + "; " +
			_updateFuncNameExt + "()");
	};
	
	
	this.newGame = function () {
		if(that.getIsActionDisabled(jde_ge_gameAction.newGame)) {
			return jde_ge_noValue;
		}
		
		clearIntEvent();
		
		_model.reset();
		
		_startWho = _startWho === jde_ge_gameWho.me ?
			jde_ge_gameWho.you : jde_ge_gameWho.me;
		_gameWho = _startWho;
		
		if(_gameWho === jde_ge_gameWho.me) {
			sendIntEvent("_moveAPlayer", _aiDelay);
		}
		
		_sendEventRef(_updateFuncName + "()");
	};
	
	this.restart = function () {
		if(that.getIsActionDisabled(jde_ge_gameAction.restart)) {
			return jde_ge_noValue;
		}
		
		clearIntEvent();
		
		if(that.getIsEOG()) {
			incScore(-1);
		}
		
		_model.reset();
		
		_gameWho = _startWho;
		
		if(_gameWho === jde_ge_gameWho.me) {
			sendIntEvent("_moveAPlayer", _aiDelay);
		}
		
		_sendEventRef(_updateFuncName + "()");
	};
	
	this.move = function (boardCoord) {
		if(that.getIsActionDisabled(jde_ge_gameAction.move)) {
			return jde_ge_noValue;
		}
		
		clearIntEvent();
		
		var boardState = _model.move(boardCoord);
		if(boardState === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		
		_gameWho = _gameWho === jde_ge_gameWho.me ?
			jde_ge_gameWho.you : jde_ge_gameWho.me;
		
		if(that.getIsEOG()) {
			incScore();
		}
		else {
			sendIntEvent("_moveAPlayer", _aiDelay);
		}
		
		_sendEventRef(_updateFuncName + "()");
	};
	
	this._moveAPlayer = function () {
		clearIntMtxs();
		
		var boardCoord = _aPlayer.getNextMove();
		if(boardCoord === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		
		var boardState = _model.move(boardCoord);
		if(boardState === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		
		_gameWho = _gameWho === jde_ge_gameWho.me ?
			jde_ge_gameWho.you : jde_ge_gameWho.me;
		
		if(that.getIsEOG()) {
			incScore();
		}
		
		_sendEventRef(_updateFuncName + "()");
	};
	
	this.undo = function () {
		if(that.getIsActionDisabled(jde_ge_gameAction.undo)) {
			return jde_ge_noValue;
		}
		
		clearIntEvent();
		
		if(that.getIsEOG()) {
			incScore(-1);
		}
		
		var boardState = _model.undo();
		if(boardState === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		
		_gameWho = _gameWho === jde_ge_gameWho.me ?
			jde_ge_gameWho.you : jde_ge_gameWho.me;
		
		if(that.getIsBOG()) {
			sendIntEvent("_moveAPlayer", _aiDelay);
		}
		else if(_gameWho === jde_ge_gameWho.me) {
			//sendIntEvent("_undoPlayer");
// NEEDS REVISION! ******************************************************
			that._undoPlayer();
		}
		
		_sendEventRef(_updateFuncName + "()");
	};
	
	this._undoPlayer = function () {
		clearIntMtxs();
		
		var boardState = _model.undo();
		if(boardState === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		
		_gameWho = _gameWho === jde_ge_gameWho.me ?
			jde_ge_gameWho.you : jde_ge_gameWho.me;
		
		//_sendEventRef(_updateFuncName + "()");
	};
	
	this.suggest = function () {
		if(that.getIsActionDisabled(jde_ge_gameAction.suggest)) {
			return jde_ge_noValue;
		}
		
		clearIntEvent();
		
		sendIntEvent("_suggest", _aiDelay);
		
		_sendEventRef(_updateFuncName + "()");
	};
	
	this._suggest = function () {
		clearIntMtxs();
		
		var boardCoord = _aPlayer.getNextMove();
		if(boardCoord === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		
		_sendEventRef(_notifyFuncName +
			"(" + _gx_BoardCoord.getBuilderName(boardCoord) + ")");
		
		_sendEventRef(_updateFuncName + "()");
	};
	
	this.startAutoplay = function () {
		if(that.getIsActionDisabled(jde_ge_gameAction.startAutoplay)) {
			return jde_ge_noValue;
		}
		
		clearIntEvent();
		
		if(that.getIsEOG()) {
			_model.reset();
			
			_startWho = _startWho === jde_ge_gameWho.me ?
				jde_ge_gameWho.you : jde_ge_gameWho.me;
			_gameWho = _startWho;
		}
		
		if(!_fastAuto) {
			sendIntEvent("_autoplay", /*_fastAuto ?
				_fastDelay : */_autoDelay, true);
			
			_sendEventRef(_updateFuncName + "()");
		}
		else {
			that._autoplay();
		}
	};
	
	this._autoplay = function () {
		clearIntMtxs();
		
		if(that.getIsEOG()) {
			_model.reset();
			
			_startWho = _startWho === jde_ge_gameWho.me ?
				jde_ge_gameWho.you : jde_ge_gameWho.me;
			_gameWho = _startWho;
		}
		else {
			var boardCoord = _aPlayer.getNextMove(
				_gameWho === jde_ge_gameWho.me ?
					undefined : _autoStrategy);
			if(boardCoord === jde_ge_noValue) {
				return jde_ge_noValue;
			}
			
			var boardState = _model.move(boardCoord);
			if(boardState === jde_ge_noValue) {
				return jde_ge_noValue;
			}
			
			_gameWho = _gameWho === jde_ge_gameWho.me ?
				jde_ge_gameWho.you : jde_ge_gameWho.me;
			
			if(that.getIsEOG()) {
				incScore();
			}
		}
		
		if(!_fastAuto || that.getIsEOG()) {
			sendIntEvent("_autoplay", _fastAuto ?
				_fastDelay : _autoDelay, true);
			
			_sendEventRef(_updateFuncName + "()");
		}
		else {
			that._autoplay();
		}
	};
	
	this.stopAutoplay = function () {
		if(that.getIsActionDisabled(jde_ge_gameAction.stopAutoplay)) {
			return jde_ge_noValue;
		}
		
		clearIntEvent();
		
		if(!that.getIsEOG()) {
			if(_gameWho === jde_ge_gameWho.me) {
				sendIntEvent("_moveAPlayer", _aiDelay);
			}
		}
		
// NEEDS REVISION! ******************************************************
		_sendEventRef(_updateFuncName + "()");
	};
	
	
	//
	// isActionDisabled [ isBOG   isEOG   isBusy  isAuto ]
	// ---------------------------------------------------
	// newGame                                       1
	// ---------------------------------------------------
	// restart              1                        1
	// ---------------------------------------------------
	// move                         1        1       1
	// ---------------------------------------------------
	// undo                 1                        1
	// ---------------------------------------------------
	// suggest                      1        1       1
	// ---------------------------------------------------
	// startAutoplay                                 1
	// ---------------------------------------------------
	// stopAutoplay                                  0
	// ---------------------------------------------------
	//
	
	this.getIsActionDisabled = function (gameAction) {
		if(gameAction === jde_ge_gameAction.newGame) {
			return that.getIsAuto();
		}
		if(gameAction === jde_ge_gameAction.restart) {
			return that.getIsBOG() ||
				that.getIsAuto();
		}
		if(gameAction === jde_ge_gameAction.move) {
			return that.getIsEOG() ||
				that.getIsBusy() ||
				that.getIsAuto();
		}
		if(gameAction === jde_ge_gameAction.undo) {
			return that.getIsBOG() ||
				that.getIsAuto();
		}
		if(gameAction === jde_ge_gameAction.suggest) {
			return that.getIsEOG() ||
				that.getIsBusy() ||
				that.getIsAuto();
		}
		if(gameAction === jde_ge_gameAction.startAutoplay) {
			return that.getIsAuto();
		}
		if(gameAction === jde_ge_gameAction.stopAutoplay) {
			return !that.getIsAuto();
		}
		return true;
	};
	
	
	this.getIsHidle = function () {
		if(_startWho === jde_ge_noValue) {
			return false;
		}
		return _gameWho === jde_ge_noValue;
	};
	this.getIsBOG = function () {
		if(_gameWho === jde_ge_noValue) {
			return true;
		}
		var lastMove = _model.getLastMove();
		return lastMove === jde_ge_noValue;
	};
	this.getIsEOG = function () {
		if(_gameWho === jde_ge_noValue) {
			return true;
		}
		var boardState = _model.getBoardState();
		return !_gx_boardState.isMove(boardState);
	};
	this.getIsBusy = function () {
		if(_gameWho === jde_ge_noValue) {
			return false;
		}
		return _mtx_busy !== null;
	};
	this.getIsAuto = function () {
		if(_gameWho === jde_ge_noValue) {
			return false;
		}
		return _mtx_auto !== null;
	};
	
	
	this.getStartWho = function () {
		return _startWho;
	};
	this.getGameWho = function () {
		if(_startWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _gameWho;
	};
	
	
	this.getScoreAll = function () {
		if(_gameWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _score_all;
	};
	this.getScoreMe = function () {
		if(_gameWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _score_me;
	};
	this.getScoreYou = function () {
		if(_gameWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _score_you;
	};
	this.getScoreDraw = function () {
		if(_gameWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _score_draw;
	};
	
	
	this.getFastAuto = function () {
		if(_startWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _fastAuto;
	};
	this.setFastAuto = function (fastAuto) {
		if(_startWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return (_fastAuto = !!fastAuto);
	};
	
	this.getAiStrategy = function () {
		if(_startWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _aPlayer.getStrategy();
	};
	this.setAiStrategy = function (strategy) {
		if(_startWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _aPlayer.setStrategy(strategy);
	};
	
	this.getAutoStrategy = function () {
		if(_startWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _autoStrategy;
	};
	this.setAutoStrategy = function (strategy) {
		if(_startWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		strategy = _gx_aStrategy.parse(strategy);
		if(strategy === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return (_autoStrategy = strategy);
	};
	
	this.getFuzzyThreshold = function () {
		if(_startWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _aPlayer.getFuzzyThreshold();
	};
	this.setFuzzyThreshold = function (threshold) {
		if(_startWho === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return _aPlayer.setFuzzyThreshold(threshold);
	};
	
}

