/* Constants */
ROWS = 21; COLS = 10;

/* Enumerations */
STATES = {
  START : 0,
  DROPPING : 1,
  DROPPED : 2,
  NEW_PIECE : 3,
  GAME_OVER : 10,
}

DIR = {
  VERT : 0,
  NORTH : 0,
  HORIZ : 1,
  EAST : 1,
  SOUTH : 2,
  WEST : 3
}

LEVEL = {
  EASY : 250,
  MED : 160,
  HARD : 120
}

/* Globals */
var state = STATES.START;
var board = null;
var rowsCleared = 0;

var fallingPiece = null;
var dropTime = LEVEL.MED;
var tickTimer = null;

var pieces = null;

/* Setup functions */
function createBoard () {
  board = new Array();

  var gameBoard = document.getElementById("game_board");
  var i, j;
  for (i = 0; i < ROWS; i += 1) {
    row = document.createElement("tr");
    board[i] = new Array();
    for (j = 0; j < COLS; j += 1) {
      cell = document.createElement("td");
      board[i][j] = new Object();
      board[i][j].cell = cell;
      board[i][j].piece = null;
      row.appendChild(cell);
    }
    gameBoard.appendChild(row);
  }
  
  setUpPieces();
}

function setUpPieces () {
  var center = Math.floor((COLS-1) / 2);

  pieces = new Array();
  
  pieces.push(new Piece("pieceI", [[0,center-1],[0,center],[0,center+1],[0,center+2]], rotateI, DIR.HORIZ));
  pieces.push(new Piece("pieceL", [[1,center-1],[1,center],[1,center+1],[0,center+1]], rotateL, DIR.NORTH));
  pieces.push(new Piece("pieceT", [[1,center],[1,center-1],[0,center],[1,center+1]], rotateT, DIR.NORTH));
  pieces.push(new Piece("pieceJ", [[1,center-1],[1,center],[1,center+1],[0,center-1]], rotateJ, DIR.NORTH));
  pieces.push(new Piece("pieceS", [[0,center],[0,center+1],[1,center-1],[1,center]], rotateS, DIR.HORIZ));
  pieces.push(new Piece("pieceZ", [[0,center],[1,center],[0,center-1],[1,center+1]], rotateZ, DIR.HORIZ));
  pieces.push(new Piece("pieceO", [[0,center],[1,center],[0,center+1],[1,center+1]], function () { }, DIR.HORIZ));
}

/* Gameplay functions */
function start (button) {
  tickTimer = window.setInterval(tick, dropTime);
  button.blur();
  
  button.onclick = function () {
    button.blur();
    clearBoard();
    rowsCleared = 0;
    state = STATES.START;
    updateClearedRows();
    if (tickTimer == null) tickTimer = window.setInterval(tick, dropTime);
  }
}

function clearBoard () {
  var i, j;
  for (i = 0; i < ROWS; i += 1) {
    for (j = 0; j < COLS; j += 1) {
      setCellPiece(board[i][j], null);
    }
  }
}

function setDropTime (newVal) {
  dropTime = newVal;
  if (tickTimer != null) {
    window.clearInterval(tickTimer);
    tickTimer = window.setInterval(tick, newVal);
  }
}

function updateClearedRows () {
  document.getElementById('rows').innerText = rowsCleared;
}

function occupied (cellPiece, piece) {
  return cellPiece != null && cellPiece != piece;
}

/* User input */

function move (e) {
  if (e.keyCode == 27) {
    e.target.blur();
  }

  if (tickTimer == null && state != STATES.START && state != STATES.GAME_OVER) {
    if (e.keyCode == 80) { // P
      tickTimer = window.setInterval(tick, dropTime);
    }
    
  } else if (state == STATES.DROPPING || state == STATES.DROPPED) {
    if (e.keyCode == 37) { // left-arrow
      moveLeft();
    } else if (e.keyCode == 39) { // right-arrow
      moveRight();
    } else if (e.keyCode == 38) { // up-arrow
      rotate();
    } else if (e.keyCode == 40) { // down-arrow
      drop();
    } else if (e.keyCode == 32) { // space
      hardDrop();
    } else if (e.keyCode == 80) { // P
      window.clearInterval(tickTimer);
      tickTimer = null;
    }
  }
}

function moveLeft () {
  if (fallingPiece.cells[0][1] == 0 ||
      fallingPiece.cells[1][1] == 0 ||
      fallingPiece.cells[2][1] == 0 ||
      fallingPiece.cells[3][1] == 0 ||
      occupied(board[fallingPiece.cells[0][0]][fallingPiece.cells[0][1]-1].piece, fallingPiece) ||
      occupied(board[fallingPiece.cells[1][0]][fallingPiece.cells[1][1]-1].piece, fallingPiece) ||
      occupied(board[fallingPiece.cells[2][0]][fallingPiece.cells[2][1]-1].piece, fallingPiece) ||
      occupied(board[fallingPiece.cells[3][0]][fallingPiece.cells[3][1]-1].piece, fallingPiece) ) {
              
    // do nothing
    
  } else {
    fallingPiece.unhighlight();
    fallingPiece.cells[0][1] -= 1;
    fallingPiece.cells[1][1] -= 1;
    fallingPiece.cells[2][1] -= 1;
    fallingPiece.cells[3][1] -= 1;
    fallingPiece.highlight();
  }
}

function moveRight () {
  if (fallingPiece.cells[0][1] == COLS-1 ||
      fallingPiece.cells[1][1] == COLS-1 ||
      fallingPiece.cells[2][1] == COLS-1 ||
      fallingPiece.cells[3][1] == COLS-1 ||
      occupied(board[fallingPiece.cells[0][0]][fallingPiece.cells[0][1]+1].piece, fallingPiece) ||
      occupied(board[fallingPiece.cells[1][0]][fallingPiece.cells[1][1]+1].piece, fallingPiece) ||
      occupied(board[fallingPiece.cells[2][0]][fallingPiece.cells[2][1]+1].piece, fallingPiece) ||
      occupied(board[fallingPiece.cells[3][0]][fallingPiece.cells[3][1]+1].piece, fallingPiece) ) {
              
    // do nothing
    
  } else {
    fallingPiece.unhighlight();
    fallingPiece.cells[0][1] += 1;
    fallingPiece.cells[1][1] += 1;
    fallingPiece.cells[2][1] += 1;
    fallingPiece.cells[3][1] += 1;
    fallingPiece.highlight();
  }
}

function hardDrop () {
  while (drop()) ;
}

function rotate () {
  fallingPiece.rotate();
}

/* Tick logic */

function tick () {
  if (state == STATES.START) state = STATES.NEW_PIECE;
  else if (state == STATES.DROPPING) drop();
  else if (state == STATES.DROPPED) { if (!drop()) { tryToClearRows(); updateClearedRows() } }
  else if (state == STATES.NEW_PIECE) createPiece();
}

function createPiece () {
  i = Math.floor(Math.random()*pieces.length);
  fallingPiece = new Piece(pieces[i]);
  
  fallingPiece.highlight();
  
  state = STATES.DROPPING;
}

function gameOver () {
  alert("Game over, man! Rows cleared: " + rowsCleared);
  state = STATES.GAME_OVER;
  window.clearInterval(tickTimer);
  tickTimer = null;
}

function drop () {
  if (fallingPiece.cells[0][0] == ROWS-1 ||
      fallingPiece.cells[1][0] == ROWS-1 ||
      fallingPiece.cells[2][0] == ROWS-1 ||
      fallingPiece.cells[3][0] == ROWS-1 ||
      occupied(board[fallingPiece.cells[0][0] + 1][fallingPiece.cells[0][1]].piece, fallingPiece) ||
      occupied(board[fallingPiece.cells[1][0] + 1][fallingPiece.cells[1][1]].piece, fallingPiece) ||
      occupied(board[fallingPiece.cells[2][0] + 1][fallingPiece.cells[2][1]].piece, fallingPiece) ||
      occupied(board[fallingPiece.cells[3][0] + 1][fallingPiece.cells[3][1]].piece, fallingPiece) ) {
              
    state = STATES.DROPPED;
    return false;
    
  } else {
    fallingPiece.unhighlight();
    fallingPiece.cells[0][0] += 1;
    fallingPiece.cells[1][0] += 1;
    fallingPiece.cells[2][0] += 1;
    fallingPiece.cells[3][0] += 1;
    fallingPiece.highlight();
    
    return true;
  }
}

function tryToClearRows () {
  var clearedRows = new Array();
  var i, j;
  for (i = 0; i < 4; i += 1) {
    for (j = 0; j < COLS; j += 1) {
      if (board[fallingPiece.cells[i][0]][j].piece == null) break;
    }
    if (j == COLS) {
      clearedRows.unshift(fallingPiece.cells[i][0]);
    }
  }
        
  if (clearedRows.length > 0) {
    clearedRows.sort();
    while (clearedRows.length > 0) {
      while (clearedRows[0] == clearedRows[1]) clearedRows.shift();
      dropIntoRow(clearedRows[0]);
      if (clearedRows[0] - 1 == 0) {
        clearedRows.shift();
        rowsCleared += 1;
      } else {
        clearedRows[0] -= 1;
      }
    }
  }
  
  state = STATES.NEW_PIECE;
  for (j = 0; j < COLS; j += 1) {
    if (board[0][j].piece != null) {
      gameOver();
      break;
    }
  }
}

function dropIntoRow (i) {
  var j;
  for (j = 0; j < COLS; j += 1) {
    setCellPiece(board[i][j], board[i-1][j].piece);
    setCellPiece(board[i-1][j], null);
  }
}

/* Piece logic */
function Piece (otherPieceOrName, cells, rotate, initialDir) {
  var name = otherPieceOrName;
  if (!cells) {
    cells = otherPieceOrName.cells;
    name = otherPieceOrName.name;
    rotate = otherPieceOrName.rotate;
    initialDir = otherPieceOrName.dir;
  }
  
  this.name = name;
  this.cells = [[cells[0][0], cells[0][1]],
                [cells[1][0], cells[1][1]],
                [cells[2][0], cells[2][1]],
                [cells[3][0], cells[3][1]]];
  this.rotate = rotate;
  this.dir = initialDir;
}

function setCellPiece (cell, piece) {
  cell.piece = piece;
  cell.cell.className = (piece ? piece.name : "");
}

Piece.prototype.unhighlight = function () {
  setCellPiece(board[this.cells[0][0]][this.cells[0][1]], null);
  setCellPiece(board[this.cells[1][0]][this.cells[1][1]], null);
  setCellPiece(board[this.cells[2][0]][this.cells[2][1]], null);
  setCellPiece(board[this.cells[3][0]][this.cells[3][1]], null);
}

Piece.prototype.highlight = function () {
  setCellPiece(board[this.cells[0][0]][this.cells[0][1]], this);
  setCellPiece(board[this.cells[1][0]][this.cells[1][1]], this);
  setCellPiece(board[this.cells[2][0]][this.cells[2][1]], this);
  setCellPiece(board[this.cells[3][0]][this.cells[3][1]], this);
}

/* Rotations */
function rotateI () {
  if (this.dir == DIR.HORIZ) {
    if (this.cells[2][0] >= 2 && this.cells[2][0] <= ROWS - 2) {
      if (occupied(board[this.cells[2][0]+1][this.cells[2][1]].piece, this) ||
          occupied(board[this.cells[2][0]-1][this.cells[2][1]].piece, this) ||
          occupied(board[this.cells[2][0]-2][this.cells[2][1]].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[0][0] = this.cells[2][0]-2; this.cells[0][1] = this.cells[2][1];
        this.cells[1][0] = this.cells[2][0]-1; this.cells[1][1] = this.cells[2][1];
        this.cells[3][0] = this.cells[2][0]+1; this.cells[3][1] = this.cells[2][1];
        this.highlight();
        
        this.dir = DIR.VERT;
      }
    }
  } else {
    if (this.cells[2][1] >= 2 && this.cells[0][1] <= COLS - 2) {
      if (occupied(board[this.cells[2][0]][this.cells[2][1]+1].piece, this) ||
          occupied(board[this.cells[2][0]][this.cells[2][1]-1].piece, this) ||
          occupied(board[this.cells[2][0]][this.cells[2][1]-2].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[0][0] = this.cells[2][0]; this.cells[0][1] = this.cells[2][1]+1;
        this.cells[1][0] = this.cells[2][0]; this.cells[1][1] = this.cells[2][1]-1;
        this.cells[3][0] = this.cells[2][0]; this.cells[3][1] = this.cells[2][1]-2;
        this.highlight();
        
        this.dir = DIR.HORIZ;
      }
    }
  }
}

Piece.prototype.rotateLorJ = function (isL) {
  if (this.dir == DIR.NORTH) {
    if (this.cells[1][0] <= ROWS - 2) {
      if (occupied(board[this.cells[1][0]-1][this.cells[1][1]].piece, this) ||
          occupied(board[this.cells[1][0]+(isL ? 1 : -1)][this.cells[1][1]+1].piece, this) ||
          occupied(board[this.cells[1][0]+1][this.cells[1][1]].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[0][0] = this.cells[1][0]+1; this.cells[0][1] = this.cells[1][1];
        this.cells[2][0] = this.cells[1][0]-1; this.cells[2][1] = this.cells[1][1];
        this.cells[3][0] = this.cells[1][0]+(isL ? 1 : -1); this.cells[3][1] = this.cells[1][1]+1;
        this.highlight();
        this.dir = DIR.EAST;
      }
    }
  } else if (this.dir == DIR.EAST) {
    if (this.cells[1][1] >= 1) {
      if (occupied(board[this.cells[1][0]+1][this.cells[1][1]-(isL ? 1 : -1)].piece, this) ||
          occupied(board[this.cells[1][0]][this.cells[1][1]-1].piece, this) ||
          occupied(board[this.cells[1][0]][this.cells[1][1]+1].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[0][0] = this.cells[1][0]+1; this.cells[0][1] = this.cells[1][1]-(isL ? 1 : -1);
        this.cells[2][0] = this.cells[1][0]; this.cells[2][1] = this.cells[1][1]-1;
        this.cells[3][0] = this.cells[1][0]; this.cells[3][1] = this.cells[1][1]+1;
        this.highlight();
        this.dir = DIR.SOUTH;
      }
    }
  } else if (this.dir == DIR.SOUTH) {
    if (this.cells[1][0] >= 1) {
      if (occupied(board[this.cells[1][0]-(isL ? 1 : -1)][this.cells[1][1]-1].piece, this) ||
          occupied(board[this.cells[1][0]-1][this.cells[1][1]].piece, this) ||
          occupied(board[this.cells[1][0]+1][this.cells[1][1]].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[0][0] = this.cells[1][0]-(isL ? 1 : -1); this.cells[0][1] = this.cells[1][1]-1;
        this.cells[2][0] = this.cells[1][0]-1; this.cells[2][1] = this.cells[1][1];
        this.cells[3][0] = this.cells[1][0]+1; this.cells[3][1] = this.cells[1][1];
        this.highlight();
        this.dir = DIR.WEST;
      }
    }
  } else if (this.dir == DIR.WEST) {
    if (this.cells[1][1] <= COLS - 2) {
      if (occupied(board[this.cells[1][0]-1][this.cells[1][1]+(isL ? 1 : -1)].piece, this) ||
          occupied(board[this.cells[1][0]][this.cells[1][1]-1].piece, this) ||
          occupied(board[this.cells[1][0]][this.cells[1][1]+1].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[0][0] = this.cells[1][0]-1; this.cells[0][1] = this.cells[1][1]+(isL ? 1 : -1);
        this.cells[2][0] = this.cells[1][0]; this.cells[2][1] = this.cells[1][1]-1;
        this.cells[3][0] = this.cells[1][0]; this.cells[3][1] = this.cells[1][1]+1;
        this.highlight();
        this.dir = DIR.NORTH;
      }
    }
  }
}

function rotateT () {
  if (this.dir == DIR.NORTH) {
    if (this.cells[0][0] <= ROWS - 2) {
      if (occupied(board[this.cells[0][0]+1][this.cells[0][1]].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[1][0] += 1; this.cells[1][1] += 1;
        this.highlight();
        this.dir = DIR.EAST;
      }
    }
  } else if (this.dir == DIR.EAST) {
    if (this.cells[0][1] >= 1) {
      if (occupied(board[this.cells[0][0]][this.cells[0][1]-1].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[2][0] += 1; this.cells[2][1] -= 1;
        this.highlight();
        this.dir = DIR.SOUTH;
      }
    }
  } else if (this.dir == DIR.SOUTH) {
    if (this.cells[0][0] >= 1) {
      if (occupied(board[this.cells[0][0]-1][this.cells[0][1]].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[3][0] -= 1; this.cells[3][1] -= 1;
        this.highlight();
        this.dir = DIR.WEST;
      }
    }
  } else if (this.dir == DIR.WEST) {
    if (this.cells[0][1] <= COLS - 2) {
      if (occupied(board[this.cells[0][0]][this.cells[0][1]+1].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[1][0] -= 1; this.cells[1][1] -= 1;
        this.cells[2][0] -= 1; this.cells[2][1] += 1;
        this.cells[3][0] += 1; this.cells[3][1] += 1;
        this.highlight();
        this.dir = DIR.NORTH;
      }
    }
  }
}

function rotateL () { this.rotateLorJ(true); }
function rotateJ () { this.rotateLorJ(false); }

Piece.prototype.rotateSorZ = function (isS) {
  if (this.dir == DIR.HORIZ) {
    if (this.cells[0][0] >= 1) {
      if (occupied(board[this.cells[0][0]-(isS ? 1 : -1)][this.cells[0][1]].piece, this) ||
          occupied(board[this.cells[0][0]+(isS ? 1 : -1)][this.cells[0][1]+1].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[2][(isS ? 1 : 0)] += 2;
        this.cells[3][(isS ? 0 : 1)] -= 2;
        this.highlight();
        this.dir = DIR.VERT;
      }
    }
  } else {
    if (this.cells[0][1] >= 1) {
      if (occupied(board[this.cells[0][0]+(isS ? 1 : -1)][this.cells[0][1]].piece, this) ||
          occupied(board[this.cells[0][0]+(isS ? 1 : -1)][this.cells[0][1]-1].piece, this)) {
      } else {
        this.unhighlight();
        this.cells[2][(isS ? 1 : 0)] -= 2;
        this.cells[3][(isS ? 0 : 1)] += 2;
        this.highlight();
        this.dir = DIR.HORIZ;
      }
    }
  }
}

function rotateS () { this.rotateSorZ(true); }
function rotateZ () { this.rotateSorZ(false); }

