// states
var NONE = 0;
var SHIFT = 1;
var ZOOM = 2;


var KEY_LEFT = 37; 
var KEY_UP = 38;
var KEY_RIGHT = 39;
var KEY_DOWN = 40;
var KEY_PGUP = 33;
var KEY_PGDOWN = 34;

var MIN_UNIT_SIZE = 3;
var _controller = null;

// roles
var PLAY_WHITE = 1;
var PLAY_BLACK = 2;
var WATCH = 3;
var UNKNOWN = 4;

function Validator() {
  this.enabled = true;
}

Validator.prototype.enable = function(e) {
  this.enabled = e;
}

Validator.prototype.isEnabled = function() {
  return this.enabled;
}



function RealOutput(view, canvasName) {
  this.view = view;
  this.jg = new jsGraphics(canvasName);
}

RealOutput.prototype.drawElement = function(x, y, xlen, ylen, color, swapped, winning, captured, swappable) {
  this.jg.setColor(swappable ? "#FF0000" : "#888888");
  this.jg.drawRect(x, y, xlen, ylen);
  this.jg.setColor(color == BLACK ? "#000000" : "#FFFFFF");
  if (winning) {
    this.jg.setColor(color == BLACK ? "#0F770F" : "#BBFFBB");
  } 
  if (swapped) {
    this.jg.setColor(color == BLACK ? "#550F0F" : "#FFDDDD");
  } 
  if (captured) {
    this.jg.setColor(color == BLACK ? "#DDDDFF" : "#0F0F55");
  } 
  this.jg.fillRect(x+1, y+1, xlen-1, ylen-1);
}

RealOutput.prototype.drawEmpty = function(x, y, xlen, ylen) {
  this.jg.setColor("#888888");
  this.jg.fillRect(x, y, xlen, ylen);
}

RealOutput.prototype.start = function() {
  this.jg.clear();
}

RealOutput.prototype.flush = function() {
  this.jg.paint();
  
}

function ListOfMoves(elName) {
  this.elName = "#" + elName;
  this.moveIdViewed = 0;
  this.moveId = 0;
  this.refresh();
}

ListOfMoves.prototype.submitMove = function(moveId) {
  this.moveId = moveId - 1;
  this.moveIdViewed = 0;
  this.refresh();
}

ListOfMoves.prototype.undoMove = function(moveId) {
  this.moveId = moveId - 1;
  this.moveIdViewed = 0;
  this.refresh();
}

ListOfMoves.prototype.setMoveSelected = function(id) {
  this.moveIdViewed = id;
  this.refresh();
}

ListOfMoves.prototype.getViewedMoveId = function() {
  return this.moveIdViewed;
}

ListOfMoves.prototype.refresh = function() {
  var i = 1;
  var html = "<ol>";
  
  for (var j = this.moveId; j > 0; j--) {
    var color =  (j % 2) ? BLACK: WHITE;
    var class = "color" + COLOR_TEXT[color];
    if (this.moveIdViewed == j) {
      class += " moveViewed";
    }
    html += "<li " + "class='" + class + "' onclick='_controller.onMoveSelected(" + j + ");'>" + j + "</li>";
  }
  html+="</ol>";    
  $(this.elName).html(html);

}


ListOfMoves.prototype.isViewing = function() {
  return this.moveIdViewed > 0;
}


function ListOfParticipants(elName) {
  this.elName = "#" + elName;
  this.participants = [];
}

ListOfParticipants.prototype.update = function(users, whitePlayer, blackPlayer) {
  html = "";
  var participants = [];
  for (var i = 0; i < users.length; i++) {
    participants[users[i].email] = users[i];
  }
  if (participants[blackPlayer] != null) {
    html += "<tr><td class='blackPlayerName'>"+ blackPlayer + " " + participants[blackPlayer].rating + "</td></tr>";
  }
  if (participants[whitePlayer] != null) {
    html += "<tr><td class='whitePlayerName'>"+ whitePlayer + " " + participants[whitePlayer].rating + "</td></tr>";
  }
  for (var i = 0; i < users.length; i++) {
    if (users[i].email != whitePlayer && users[i].email != blackPlayer) {
	  html += "<tr><td class='viewerName'>"+ users[i].email + " " + users[i].rating + "</td></tr>";
    }
  }  
  $(this.elName).html(html);
}


function Controller(canvasName, remotePlay) {
    this.board = null;
    this.render = null;
    this.view = null;
    this.state = NONE;
	this.canvasName = canvasName;
	this.validator = null;
	this.remotePlay = remotePlay;
	if (this.remotePlay == undefined) {
	  this.remotePlay = null;
	}
	this.playerColor = NULL_COLOR;
	this.whitePlayer = "";
	this.blackPlayer = "";
	_controller = this;
}

Controller.prototype.init = function() {
  var elName = "#" + this.canvasName;
  this.elName = elName;
  $(elName).click(this.click);
  $(elName).dblclick(this.dblclick);
  $(elName).keydown(this.keydown);
  document["onkeydown"] = this.keydown;
  $(elName).keyup(this.keyup);
  $(elName).mousedown(this.mousedown);
  $(elName).mouseup(this.mouseup);
  $(elName).mousemove(this.mousemove);
  $(elName).mouseout(this.mouseout);
  $(elName).mouseover(this.mouseover);
  $(elName).scroll(this.scroll);
  $(elName).scrollLeft(this.scrollLeft);
  $(elName).scrollTop(this.scrollTop);
  window.onresize = this.onresize;

  this.validator = new Validator();
  this.turnValidation();  // initialize validator
  if (this.remotePlay == null) {
    this.newGame(UNKNOWN);
  } else {
    this.showMessage("Select your color and click 'new game'");
    this.remotePlay.sendNewGameRequest(""); // see if the game was already started, then watch it 
  }
}

Controller.prototype.setWhitePlayer = function(email) {
  this.whitePlayer = email;
}

Controller.prototype.setBlackPlayer = function(email) {
  this.blackPlayer = email;
}

Controller.prototype.getBoard = function() {
  return this.game.getBoard();
}

Controller.prototype.getPiece = function(e) {
  var realX = 1.0 * (this._getX(e) - this.view.boardStartX) / this.view.unitSize;
  var realY = 1.0 * (this._getY(e) - this.view.boardStartY) / this.view.unitSize;
  return this.getBoard().getPiece(realX, realY);
}

Controller.prototype._getX = function(e) {
  return e.pageX - e.currentTarget.offsetLeft;
}

Controller.prototype._getY = function(e) {
  return e.pageY - e.currentTarget.offsetTop;
}


Controller.prototype.redraw = function() {
  this.render.draw(this.getBoard());
  this.showMessage(this.game.getMessage());
  this.showMaterial();
}

Controller.prototype.showMessage = function(message) {
  if(message =="") {
    message = "OK";
  }
  $("#message").html(message);
}

Controller.prototype.showMaterial = function() {
  var materialBlack = this.game.getBoard().material(BLACK);
  var materialWhite = this.game.getBoard().x * this.game.getBoard().y - materialBlack;
  $("#material").html("Material: Black: " + materialBlack + "; White: " + materialWhite); 
}

Controller.prototype.showWhoseMove = function() {
  if (this.game.gameOver) {
	$("#whoseMove").html("" + this.game.getMoveId() + ": " + "<b>" + COLOR_TEXT[this.game.getWinningColor()] + "</b>" + " won");
  } else {
    $("#whoseMove").html("" + this.game.getMoveId() + ": " + "<b>" + COLOR_TEXT[this.game.getCurrentColor()] + "</b>" + " to move");
  }
}	

Controller.prototype.checkValid = function() {
  this.getBoard()._assertValid();
}

Controller.prototype.updateUsers = function(users) {
  if (this.listOfParticipants) {
    this.listOfParticipants.update(users, this.whitePlayer, this.blackPlayer);
  }
}

Controller.prototype.cancelMove = function() {
  if (_controller.remotePlay != null && _controller.playerColor != _controller.game.getCurrentColor()) {
    _controller.showMessage("Can not cancel the other player's move");
    return;
  }

  this.game.cancelMove();
  this.redraw();
  this.showWhoseMove();
}

Controller.prototype.leaveTable = function() {
  this.remotePlay.leaveTable();//OPPOSITE_COLOR[this.playerColor]);
}

Controller.prototype.makeRemoteMove = function(move) {
  assert(this.remotePlay != null && this.playerColor != this.game.getCurrentColor());
  this.recreateMove(move);  
}

Controller.prototype.recreateMove = function(move) {
  this.game.makeMoveFromRepresentation(move);
  this.redraw();
  this.showWhoseMove();  
}


Controller.prototype.submitMove = function() {
  var success = this.game.submitMove();
  if (!success) {
    this.redraw();
    return;
  }
  // for remote play, submit move is called to complete opponents move - (player color does not match)
  // as well as for the self-move. In the latter case we need to send self move to the server
  // in the former case we just visually complete the move but don't do anything with the server
  if (this.remotePlay != null 
      && this.playerColor == this.game.getLastPlayedColor()
      && this.moveToCatchup < this.game.getMoveId()-1) {
    this.remotePlay.sendMove(this.game.getLastMoveRepresentation(), this.game.getWinningColor());
  }
  this.listOfMoves.submitMove(this.game.moveId);
  this.redraw();
  this.showWhoseMove();
}

Controller.prototype.undo = function() {
  if (this.remotePlay != null) {
    this.showMessage("Undo is not supported for remote game");
    return;
  }
  assert(this.remotePlay == null);
  this.game.undoLastMove();
  this.listOfMoves.undoMove(this.game.moveId);
  this.redraw();
  this.showWhoseMove();
}

Controller.prototype.newGame = function(role, moveToCatchup) {
  this.moveToCatchup = moveToCatchup;
  if (role == UNKNOWN && this.remotePlay != null && this.game && !this.game.gameOver && this.game.getMoveId() > 1) {
    this.showMessage("Current game is not over. Can not start a new game");
    return;
  }
  if (this.remotePlay != null && role == PLAY_WHITE) {
     this.playerColor = WHITE;
  } else if (this.remotePlay != null && role == PLAY_BLACK) {
     this.playerColor = BLACK;
  } else if (this.remotePlay != null) {
    this.playerColor = NULL_COLOR;
  }
  
  if (this.remotePlay == null || this.remotePlay != null && role != UNKNOWN) {
	  var whiteComp = false;
	  var blackComp = false;
	  if (this.remotePlay == null) {
	    whiteComp = $("#whitePlayerComp")[0].checked;
	    blackComp = $("#blackPlayerComp")[0].checked;
	  }
	  this.game = new Game(6, 6, this.validator, whiteComp, blackComp);
	  if (this.remotePlay == null) {
	    this.setDemo();
	  }
	  var unitSize = $(this.elName).height() / this.getBoard().y; 
	  this.view = new View(unitSize, 0, 0, $(this.elName).width(), $(this.elName).height())
	  var out = new RealOutput(this.view, this.canvasName);
	  this.render = new Render(out, this.view);
      this.onresize();
	  this.redraw();
	  this.showWhoseMove();
	  this.listOfMoves = new ListOfMoves("moveHistory");
	  this.listOfParticipants = new ListOfParticipants("participants");
	  if (role == PLAY_WHITE || role == WATCH || moveToCatchup > 1) {
	    this.remotePlay.startListeningForNextMove(1);
	  }
  }
  if (this.remotePlay != null && role == UNKNOWN) {
    if (!$("#desiredBlack")[0].checked) {
      desiredColor = COLOR_TEXT[WHITE];
    } else {
      desiredColor = COLOR_TEXT[BLACK];
    }
    this.showMessage("Sending new game request for " + desiredColor + "... Please wait.");
    this.remotePlay.sendNewGameRequest(desiredColor);
  }
}

Controller.prototype.snapView  = function() { 
  this.view.unitSize = $(this.elName).height() / this.getBoard().y;
  this.view.boardStartX = 0;
  this.view.boardStartY = 0;
  this.view.windowWidth = $(this.elName).width();
  this.view.windowHeight = $(this.elName).height();
  this.redraw();
}

Controller.prototype.turnValidation = function() {
  if (this.remotePlay != null) {
    this.validator.enable(true);
  } else {
	  this.validator.enable($("#validator")[0].checked);
	  this.showMessage((this.validator.isEnabled())?"Enabling validation...":"Validation is off");
  }
}

Controller.prototype.setDemo = function() {
  assert(this.remotePlay == null);
  var demo = $("#demo")[0].checked;
  if (demo) {
    $("#blackPlayerComp")[0].checked = true;
    $("#whitePlayerComp")[0].checked = true;
    
    setTimeout("_controller.game.setDemoMode(true)", 100);
  }
}

Controller.prototype.setBlackPlayerComp = function() {
  assert(this.remotePlay == null);
  this.game.setPlayerComp(BLACK, $("#blackPlayerComp")[0].checked);
  this.redraw();
}

Controller.prototype.setWhitePlayerComp = function() {
  assert(this.remotePlay == null);
  this.game.setPlayerComp(WHITE, $("#whitePlayerComp")[0].checked);
  this.redraw();
}

Controller.prototype.click = function(e) {
  if (_controller.listOfMoves.isViewing()) {
    _controller.showMessage("View only mode");
    return;
  }
  if (_controller.remotePlay != null && 
     (_controller.playerColor != _controller.game.getCurrentColor())
     || (_controller.moveToCatchup > _controller.game.getMoveId())) {
    _controller.showMessage("Please wait for the other player to make a move");
    return;
  }
  var piece = _controller.getPiece(e);
  if (piece) {
    _controller.game.swap(piece);
    _controller.redraw();
  }
}

Controller.prototype.onresize=function(e) {
  var y = window.innerHeight-40;
  var x = window.innerWidth-40;
  var size = y;
  if (x < y) {
	    size = x;
  }
  $(_controller.elName).width(size);
  $(_controller.elName).height(size);
  _controller.snapView();
}

Controller.prototype.dblclick= function(e) {
}

Controller.prototype.keydown= function(e) {
  if (_controller && _controller.listOfMoves) {
	  var id = _controller.listOfMoves.getViewedMoveId();
	  if (e.keyCode == 80) { // (p)revious
	    if (id > 0) {
	      id -= 1;
	    } else {
	      id = _controller.game.getMoveId() - 1;
	    }
	    _controller.onMoveSelected(id);
	  } else if (e.keyCode == 78) { //(n)ext
	    if (id < _controller.game.getMoveId() - 1) {
	      id += 1;
	    } else {
	      id = 0;
	    }
	    _controller.onMoveSelected(id);
	  } else if (e.keyCode == 73) { // i(interrupt)
	    error;
	  }
   }  
}

Controller.prototype.keypress= function(e) {
}

Controller.prototype.keyup= function(e) {

}

Controller.prototype.mousedown= function(e) {
  _controller.pieceStart = _controller.getPiece(e);
  if (_controller.pieceStart) {
    if (e.ctrlKey || e.shiftKey) {
	  if (e.ctrlKey && !e.shiftKey) {
	    _controller.state = ZOOM;
	  } else if (e.shiftKey && !e.ctrlKey) {
	    _controller.state = SHIFT;
	  }
    }
    
    _controller.clickX = _controller._getX(e);
    _controller.clickY = _controller._getY(e);
    _controller.oldX = _controller._getX(e);
    _controller.oldY = _controller._getY(e);
  }
}
  

Controller.prototype.mouseup = function(e) {
  if (_controller.state != NONE) {
      _controller.state = NONE;
      return;
  }
  if (_controller.remotePlay != null && _controller.playerColor != _controller.game.getCurrentColor()) {
    _controller.showMessage("Please wait for the other player to make a move");
    _controller.pieceStart = null;
    _controller.pieceEnd = null;
    return;
  }
  if (_controller.listOfMoves.isViewing()) {
    _controller.showMessage("View only mode");
    _controller.pieceStart = null;
    _controller.pieceEnd = null;
    return;
  }
  _controller.pieceEnd = _controller.getPiece(e);
  var deltaX = _controller._getX(e) - _controller.clickX;
  if (deltaX < 0) deltaX = -deltaX;
  var deltaY = _controller._getY(e) - _controller.clickY;
  if (deltaY < 0) deltaY = -deltaY;
  if (deltaX > 2 || deltaY > 2) {
	  
	  if (_controller.pieceEnd == _controller.pieceStart) {	    
		  var dir = (deltaX > deltaY)? HORIZONTAL : VERTICAL;
		  if (_controller.pieceEnd) {
		    _controller.game.splitOrSwap(_controller.pieceEnd, dir);
		    _controller.redraw();
		  }
	  } else {
	    var merge = e.ctrlKey && e.shiftKey;	        
	    var pFirst = _controller.pieceStart;
	    var pLast = _controller.getPiece(e);
	    if (!pFirst || !pLast) return;
	    if (!pFirst.start[HORIZONTAL].smaller(pLast.end[HORIZONTAL]) ||
	        !pFirst.start[VERTICAL].smaller(pLast.end[VERTICAL])) {
	        var temp = pFirst;
	        pFirst = pLast;
	        pLast = temp;
        }	    
	    if (merge) {
		    var small = new Rational(1, 2048);
		    var left = (pFirst.start[HORIZONTAL].smaller(pLast.start[HORIZONTAL]))?pFirst.start[HORIZONTAL] : pLast.start[HORIZONTAL];
		    var right = (pFirst.end[HORIZONTAL].bigger(pLast.end[HORIZONTAL]))?pFirst.end[HORIZONTAL] : pLast.end[HORIZONTAL];
		    var top = (pFirst.start[VERTICAL].smaller(pLast.start[VERTICAL]))?pFirst.start[VERTICAL] : pLast.start[VERTICAL];
		    var bottom = (pFirst.end[VERTICAL].bigger(pLast.end[VERTICAL]))?pFirst.end[VERTICAL] : pLast.end[VERTICAL];
	
		    var small = 0.00001;
		    var pLeftTop = _controller.getBoard().getPiece(left.float() + small, top.float() + small );
		    var pLeftBottom = _controller.getBoard().getPiece(left.float() + small, bottom.float()- small);
		    var pRightTop = _controller.getBoard().getPiece(right.float() - small, top.float() + small);
		    var pRightBottom = _controller.getBoard().getPiece(right.float() - small, bottom.float() - small);
            _controller.game.merge(pLeftTop, pRightTop, pLeftBottom, pRightBottom);
            _controller.redraw();
        } else {
          var dir = UNKNOWN_DIR;
          if (pFirst.canSplit(VERTICAL) && pLast.canSplit(VERTICAL) && pFirst.getMiddle(VERTICAL).equals(pLast.getMiddle(VERTICAL))) {
            dir = VERTICAL;
          } else if (pFirst.canSplit(HORIZONTAL) && pLast.canSplit(HORIZONTAL) && pFirst.getMiddle(HORIZONTAL).equals(pLast.getMiddle(HORIZONTAL))) {
            dir = HORIZONTAL;
          }
          if (dir != UNKNOWN_DIR) {
	          _controller.game.longSplit(pFirst, pLast, dir);
	          _controller.redraw();
          }
	    }
	  }
	  _controller.oldX = _controller._getX(e);
	  _controller.oldY = _controller._getY(e);
  }
  _controller.pieceStart = null;
  _controller.pieceEnd = null;
}

Controller.prototype.mousemove = function(e) {
  if (!_controller.pieceStart) {
    return;
  }
  if (_controller.state == ZOOM) {
    var oldUnitSize = _controller.view.unitSize;
    _controller.view.unitSize += 1.0 * _controller.view.unitSize * (_controller._getX(e) - _controller.clickX) / 800;  
    if (_controller.view.unitSize < MIN_UNIT_SIZE) {
      _controller.view.unitSize = MIN_UNIT_SIZE;
    }  
    var newBoardSizeX = _controller.getBoard().x * _controller.view.unitSize;
    var newBoardSizeY = _controller.getBoard().y * _controller.view.unitSize;
    _controller.view.boardStartX = _controller.clickX - (_controller.clickX - _controller.view.boardStartX) * _controller.view.unitSize / oldUnitSize;
    _controller.view.boardStartY = _controller.clickY - (_controller.clickY - _controller.view.boardStartY) * _controller.view.unitSize / oldUnitSize;    
    _controller.redraw(); 
  } else if (_controller.state == SHIFT) {
    _controller.view.boardStartX += (_controller._getX(e) - _controller.oldX);
    _controller.view.boardStartY += (_controller._getY(e) - _controller.oldY);
    _controller.redraw();
  }
  _controller.oldX = _controller._getX(e);
  _controller.oldY = _controller._getY(e);
}

Controller.prototype.mouseout= function(e) {

}

Controller.prototype.mouseover = function(e) {

}

Controller.prototype.scroll= function(e) {

}

Controller.prototype.scrollLeft= function(e) {
}

Controller.prototype.scrollTop= function(e) {
}

Controller.prototype.onMoveSelected = function(v) {
  this.listOfMoves.setMoveSelected(v);
  this.game.setMoveToView(v);
  this.redraw();
}