const CELL_COLOR_EMPTY = '#111';
const CELL_COLOR_FOOD = 'green';

const START_CORNER_OFFSET = 8;
const SINGLE_PLAYER_START_LENGTH = 8;
const BOARD_MIN_DIM = 10;

const DIRECTION_UP = 0;
const DIRECTION_DOWN = 1;
const DIRECTION_LEFT = 2;
const DIRECTION_RIGHT = 3;

/**
 * Snake
 */
function SinglePlayerSnake(board, energyMax)
{
    this.board = board;
    this.color = 'red';
    this.direction = DIRECTION_RIGHT;
    this.cells = [];
    for (var i = 0; i < SINGLE_PLAYER_START_LENGTH; i++) {
        var cell = board.cells[board.rows - 1 - START_CORNER_OFFSET][i];
        this.cells.unshift(cell);
        cell.setColor(this.color);
    }
    this.head = this.cells[0];
    this.keys = {up: 38, down: 40, left: 37, right: 39};
    this.lastDirection = this.direction;
    this.energy = new EnergyBar(energyMax, this.color);
}

function MultiPlayerSnake(board, player, energyMax)
{
    this.board = board;
    this.player = player;
    this.inGame = true;
    if (player == 1) {
        this.color = 'red';
        this.direction = DIRECTION_RIGHT;
        this.head = board.cells[board.rows - 1 - START_CORNER_OFFSET][START_CORNER_OFFSET];
        this.head.setColor(this.color);
        this.keys = {up: 38, down: 40, left: 37, right: 39};
    } else {
        this.color = 'blue';
        this.direction = DIRECTION_LEFT;
        this.head = board.cells[START_CORNER_OFFSET][board.cols - 1 - START_CORNER_OFFSET];
        this.head.setColor(this.color);
        this.keys = {up: 87, down: 83, left: 65, right: 68};
    }
    this.lastDirection = this.direction;
    this.energy = new EnergyBar(energyMax, this.color);
}

SinglePlayerSnake.prototype.getScore = function SinglePlayerSnake_getScore()
{
    return this.cells.length - SINGLE_PLAYER_START_LENGTH;
}

SinglePlayerSnake.prototype.move = function SinglePlayerSnake_move()
{
    this.head = this.board.getNextCell(this.head, this.direction);
    this.lastDirection = this.direction;
    switch (this.head.color) {
    case CELL_COLOR_EMPTY:
        this.head.setColor(this.color);
        this.cells.unshift(this.head);
        var tail = this.cells.pop();
        tail.setColor(CELL_COLOR_EMPTY);
        return this.energy.decrement();
    case CELL_COLOR_FOOD:
        this.head.setColor(this.color);
        this.cells.unshift(this.head);
        this.board.makeFood(this.head);
        this.energy.fillUp();
        return true;
    default: // Er stødt ind i en slange
        return false;
    }
}

MultiPlayerSnake.prototype.move = function MultiPlayerSnake_move()
{
    this.head = this.board.getNextCell(this.head, this.direction);
    this.lastDirection = this.direction;
    switch (this.head.color) {
    case CELL_COLOR_EMPTY:
        this.head.setColor(this.color);
        this.inGame = this.energy.decrement();
        return this.inGame;
    case CELL_COLOR_FOOD:
        this.head.setColor(this.color);
        this.board.makeFood(this.head);
        this.energy.fillUp();
        return true;
    default: // Er stødt ind i en slange
        this.inGame = false;
        return false;
    }
}

SinglePlayerSnake.prototype.changeDirection = 
MultiPlayerSnake.prototype.changeDirection = 
function Snake_changeDirection(keyCode)
{
    switch (keyCode) {
    case this.keys.up:
        if (this.lastDirection != DIRECTION_DOWN)
            this.direction = DIRECTION_UP;
        return true;
    case this.keys.down:
        if (this.lastDirection != DIRECTION_UP)
            this.direction = DIRECTION_DOWN;
        return true;
    case this.keys.left:
        if (this.lastDirection != DIRECTION_RIGHT)
            this.direction = DIRECTION_LEFT;
        return true;
    case this.keys.right:
        if (this.lastDirection != DIRECTION_LEFT)
            this.direction = DIRECTION_RIGHT;
        return true;
    default:
        return false;
    }
}

/**
 * EnergyBar
 */
function EnergyBar(maxEnergy, color)
{
    this.maxEnergy = maxEnergy;
    if (this.maxEnergy > 0) {
        this.element = document.createElement('div');
        this.element.style.backgroundColor = color;
        this.element.className = 'energyBar';
        this.fillUp();
    } else {
        this.element = null;
    }
}

EnergyBar.prototype.fillUp = function EnergyBar_fillUp()
{
    if (this.maxEnergy > 0) {
        this.currentEnergy = this.maxEnergy;
        this.update();
    }
}

EnergyBar.prototype.decrement = function EnergyBar_decrement()
{
    if (this.maxEnergy > 0) {
        this.currentEnergy--;
        this.update();
        return this.currentEnergy > 0;
    } else {
        return true;
    }
}

EnergyBar.prototype.update = function EnergyBar_update()
{
    if (this.maxEnergy > 0) {
        var percent = Math.floor(this.currentEnergy / this.maxEnergy * 100) + '%';
        this.element.style.width = percent;
    }
}

/**
 * Board
 */
function Board(multiplayer, rows, cols, foodCount, energyMax, speed)
{
    this.multiplayer = multiplayer;
    this.rows = rows;
    this.cols = cols;
    this.speed = speed;
    this.cells = [];
    this.table = document.createElement('table');
    
    for (var r = 0; r < this.rows; r++) {
        var tr = document.createElement('tr');
        var row = [];
        for (var c = 0; c < this.cols; c++) {
            var cell = new Cell(this, r, c);
            tr.appendChild(cell.element);
            row.push(cell);
        }
        this.cells.push(row);
        this.table.appendChild(tr);
    }
    
    if (multiplayer) {
        this.snake1 = new MultiPlayerSnake(this, 1, energyMax);
        this.snake2 = new MultiPlayerSnake(this, 2, energyMax);
    } else {
        this.snake1 = new SinglePlayerSnake(this, energyMax);
        this.snake2 = null;
    }
    
    for (var i = 0; i < foodCount; i++) {
        this.makeFood(this.snake1.head);
    }
}

Board.prototype.getNextCell = function Board_getNextCell(cell, dir)
{
    var row = cell.row;
    var col = cell.col;
    switch (dir) {
    case DIRECTION_UP:
        row--;
        if (row < 0) row += this.rows;
        break;
    case DIRECTION_DOWN:
        row++;
        if (row >= this.rows) row -= this.rows;
        break;
    case DIRECTION_LEFT:
        col--;
        if (col < 0) col += this.cols;
        break;
    case DIRECTION_RIGHT:
        col++;
        if (col >= this.cols) col -= this.cols;
        break;
    }
    return this.cells[row][col];
}

Board.prototype.moveSnakes = function Board_moveSnakes()
{
    var moved = this.snake1.move();
    if (this.snake2) moved &= this.snake2.move();
    return moved;
}

Board.prototype.changeSnakeDirection = function Board_changeSnakeDirection(keyCode)
{
    if (this.snake1 && this.snake1.changeDirection(keyCode)) return true;
    if (this.snake2 && this.snake2.changeDirection(keyCode)) return true;
    return false;
}

Board.prototype.makeFood = function Board_makeFood(startCell)
{
    var cell = startCell;
    while (cell.color != CELL_COLOR_EMPTY) {
        var row = Math.floor(Math.random() * this.rows) % this.rows;
        var col = Math.floor(Math.random() * this.cols) % this.cols;
        cell = this.cells[row][col];
    }
    cell.setColor(CELL_COLOR_FOOD);
}

Board.prototype.getWinner = function Board_getWinner()
{
    if (!this.multiplayer) throw 'NOT MULTIPLAYER GAME';
    if (this.snake1.inGame && this.snake2.inGame) throw 'GAME NOT OVER';
    // Edge case: on a head to head colition, snake 1 would win, because it moves first
    if (this.snake1.head == this.snake2.head) return 0;
    if (this.snake1.inGame) return 1;
    if (this.snake2.inGame) return 2;
    return 0;
}

/**
 * Cell
 */
function Cell(board, row, col)
{
    this.element = document.createElement('td');
    this.board = board;
    this.row = row;
    this.col = col;
    this.setColor(CELL_COLOR_EMPTY);
}

Cell.prototype.setColor = function Cell_setColor(newColor)
{
    this.color = newColor;
    this.element.style.backgroundColor = newColor;
}

/**
 * Game
 */
var game = {
    paused: false,
    player1Score: 0,
    player2Score: 0,
    board: null,
    interval: null
};

game.make = function Game_make()
{
    var foodCount = Math.abs(parseInt(document.getElementById('foods').value));
    document.getElementById('foods').value = foodCount;
    var energyMax = Math.abs(parseInt(document.getElementById('runout').value));
    document.getElementById('runout').value = energyMax;
    var speed = Math.abs(parseInt(document.getElementById('spd').value));
    document.getElementById('spd').value = speed;
    var rows = Math.max(parseInt(document.getElementById('rows').value), BOARD_MIN_DIM);
    document.getElementById('rows').value = rows;
    var cols = Math.max(parseInt(document.getElementById('cols').value), BOARD_MIN_DIM);
    document.getElementById('cols').value = cols;
    
    var board = new Board(infoBox.multiplayer, rows, cols, foodCount, energyMax, speed);
    
    var container = document.getElementById('tabel');
    while (container.hasChildNodes()) container.removeChild(container.firstChild);
    if (board.snake1 && board.snake1.energy.element)
        container.appendChild(board.snake1.energy.element);
    if (board.snake2 && board.snake2.energy.element)
        container.appendChild(board.snake2.energy.element);
    container.appendChild(board.table);
    
    return board;
}

game.start = function Game_start()
{
    if (this.board) throw 'GAME ALREADY STARTED';
    
    infoBox.hide();
    
    this.board = this.make();
    this.interval = setInterval(function () {game.moveSnakes()}, this.board.speed);
}

game.moveSnakes = function Game_moveSnakes()
{
    var gameOver = !this.board.moveSnakes();
    if (gameOver) this.end();
}

game.pauseResume = function Game_pauseResume()
{
    if (!this.board) throw 'GAME NOT STARTED';
    if (this.paused) {
        infoBox.hide();
        this.interval = setInterval(function () {game.moveSnakes()}, this.board.speed);
        this.paused = false;
    } else {
        clearInterval(this.interval);
        infoBox.show('pause', null);
        this.paused = true;
    }
}

game.end = function Game_end()
{
    if (!this.board) throw 'GAME NOT STARTED';
    var fromPause = this.paused;
    if (this.paused) this.pauseResume();
    
    clearInterval(this.interval);
    
    if (fromPause) {
        infoBox.show('finish', ['Spil afbrudt']);
    } else if (this.board.multiplayer) {
        var winner = this.board.getWinner();
        
        var result;
        switch (winner) {
        case 1:
            this.player1Score++;
            result = 'Rød slange vandt!';
            break;
        case 2:
            this.player2Score++;
            result = 'Blå slange vandt!';
            break;
        default:
            result = 'Ingen vinder!';
            break;
        }
        
        function plural(n) { return n == 1 ? '1 sejr' : n + ' sejre'; }
        infoBox.show('finish', [
            result,
            'Rød slange: ' + plural(this.player1Score) + '.',
            'Blå slange: ' + plural(this.player2Score) + '.'
            ]);
    } else {
        var score = this.board.snake1.getScore();
        infoBox.show('finish', ['Du scorede ' + score + ' point!']);
    }
    
    this.board = null;
}

game.onKey = function Game_onKey(keyCode)
{
    if (!this.board) return false;
    if (keyCode == 32) {
        this.pauseResume();
        return true;
    }
    if (this.paused) return false;
    return this.board.changeSnakeDirection(keyCode);
}

game.onLoad = function Game_onLoad()
{
    infoBox.selectSinglePlayer();
    infoBox.show('newgame', null);
}

game.preview = function Game_preview()
{
    if (this.board) throw 'THE GAME IS ON';
    game.make();
}

/**
 * InfoBox
 */
var infoBox = {multiplayer: false}

infoBox.show = function InfoBox_show(type, messages)
{
    switch (type) {
    case 'newgame':
        document.getElementById('prefs').style.display = 'block';
        break;
    case 'pause':
        document.getElementById('reset').style.display = 'block';
        break;
    case 'finish':
        document.getElementById('finish').style.display = 'block';
        var result = document.getElementById('result');
        while (result.hasChildNodes()) result.removeChild(result.firstChild);
        var tag = 'h1';
        for (var i = 0; i < messages.length; i++) {
            var element = document.createElement(tag);
            element.textContent = messages[i];
            document.getElementById('result').appendChild(element);
            tag = 'p';
        }
        break;
    default:
        throw 'UNKNOWN INFOBOX TYPE';
        break;
    }
    document.getElementById('rules').style.display = 'block';
}

infoBox.hide = function InfoBox_hide()
{
    document.getElementById('prefs').style.display = 'none';
    document.getElementById('reset').style.display = 'none';
    document.getElementById('finish').style.display = 'none';
    document.getElementById('rules').style.display = 'none';
}

infoBox.selectSinglePlayer = function InfoBox_selectSinglePlayer()
{
    this.multiplayer = false;
    document.getElementById('single').checked = true;
    document.getElementById('foods').value = 1;
    document.getElementById('runout').value = 0;
    game.preview();
}

infoBox.selectMultiPlayer = function InfoBox_selectMultiPlayer()
{
    this.multiplayer = true;
    document.getElementById('multi').checked = true;
    document.getElementById('foods').value = 10;
    document.getElementById('runout').value = 60;
    game.preview();
}


