
function GridCell(x, y) {
    this.px = x;
    this.py = y;
    this.version = 0;
}

GridCell.prototype.toStatic = function() {
    return { x: this.px, y: this.py };
};

function EmptyCell(x, y) {
    GridCell.call(this, x, y);
}
EmptyCell.prototype = Object.create(GridCell);
EmptyCell.prototype.constructor = EmptyCell;

EmptyCell.prototype.toStatic = function() {
    var ret = GridCell.prototype.toStatic.call(this);
    ret["type"] = null;
    return ret;
};

function LetterCell(x, y, letter) {
    GridCell.call(this, x, y);
    this.letter = letter;
    this.found = null;
}
LetterCell.prototype = Object.create(GridCell);
LetterCell.prototype.constructor = LetterCell;

LetterCell.prototype.toStatic = function() {
    var ret = GridCell.prototype.toStatic.call(this);
    ret.letter = this.letter;
    ret.found = this.found;
    return ret;
};

LetterCell.prototype.setFound = function(playerId, t) {
    if (!this.found) {
        this.found = playerId;
        this.version = Math.max(this.version, t);
        return true;
    }
    return false;
};

function Definition(position, alignment, text) {
    this.position = position;
    this.alignment = alignment;
    this.textArray = text;
}

Definition.POSITION_BOTTOM = {};
Definition.POSITION_RIGHT = {};

Definition.ALIGNMENT_HORIZONTAL = {};
Definition.ALIGNMENT_VERTICAL = {};

Definition.prototype.toStatic = function() {
    var ret = {
        text: this.textArray
    };
    if (this.position === Definition.POSITION_RIGHT && this.alignment === Definition.ALIGNMENT_HORIZONTAL) {
        ret.pos = 1;
    } else if (this.position === Definition.POSITION_RIGHT && this.alignment === Definition.ALIGNMENT_VERTICAL) {
        ret.pos = 2;
    } else if (this.position === Definition.POSITION_BOTTOM && this.alignment === Definition.ALIGNMENT_HORIZONTAL) {
        ret.pos = 3;
    } else {
        ret.pos = 4;
    }
    return ret;
};

function DefinitionCell(x, y, code, definitionArray, currentDefinition) {
    GridCell.call(this, x, y);
    this.definitions = [];
    switch (code) {
        case 0: // a
            this.definitions.push(new Definition(Definition.POSITION_RIGHT, Definition.ALIGNMENT_HORIZONTAL, definitionArray[currentDefinition++]));
        break;

        case 1: // b
            this.definitions.push(new Definition(Definition.POSITION_BOTTOM, Definition.ALIGNMENT_VERTICAL, definitionArray[currentDefinition++]));
        break;

        case 2: // c
            this.definitions.push(new Definition(Definition.POSITION_RIGHT, Definition.ALIGNMENT_VERTICAL, definitionArray[currentDefinition++]));
        break;

        case 3: // d
            this.definitions.push(new Definition(Definition.POSITION_BOTTOM, Definition.ALIGNMENT_HORIZONTAL, definitionArray[currentDefinition++]));
        break;

        case 4: // e
        case 5: // f
        case 6: // g
        case 7: // h
        case 8: // i
            this.definitions.push(new Definition(Definition.POSITION_RIGHT, Definition.ALIGNMENT_HORIZONTAL, definitionArray[currentDefinition++]));
            this.definitions.push(new Definition(Definition.POSITION_BOTTOM, Definition.ALIGNMENT_VERTICAL, definitionArray[currentDefinition++]));
        break;

        case 9: // j
        case 10: // k
        case 11: // l
        case 12: // m
        case 13: // n
            this.definitions.push(new Definition(Definition.POSITION_RIGHT, Definition.ALIGNMENT_VERTICAL, definitionArray[currentDefinition++]));
            this.definitions.push(new Definition(Definition.POSITION_BOTTOM, Definition.ALIGNMENT_VERTICAL, definitionArray[currentDefinition++]));
        break;

        case 14: // o
        case 15: // p
        case 16: // q
        case 17: // r
        case 18: // s
            this.definitions.push(new Definition(Definition.POSITION_RIGHT, Definition.ALIGNMENT_HORIZONTAL, definitionArray[currentDefinition++]));
            this.definitions.push(new Definition(Definition.POSITION_BOTTOM, Definition.ALIGNMENT_HORIZONTAL, definitionArray[currentDefinition++]));
        break;

        case 19: // t
        case 20: // u
        case 21: // v
        case 22: // w
        case 23: // x
            this.definitions.push(new Definition(Definition.POSITION_RIGHT, Definition.ALIGNMENT_VERTICAL, definitionArray[currentDefinition++]));
            this.definitions.push(new Definition(Definition.POSITION_BOTTOM, Definition.ALIGNMENT_HORIZONTAL, definitionArray[currentDefinition++]));
        break;
    }
}
DefinitionCell.prototype = Object.create(GridCell);
DefinitionCell.prototype.constructor = DefinitionCell;

DefinitionCell.prototype.toStatic = function() {
    var ret = GridCell.prototype.toStatic.call(this);
    ret.definitions = [];
    this.definitions.forEach((def) => {
        ret.definitions.push(def.toStatic());
    });
    return ret;
};

function parseGrid(grid, definitions, w, h) {
    var currentDefinition = 0
        ,resultGrid = []
        ,firstCharCode = 'a'.charCodeAt(0)
        ,letterCellCount = 0;

    for (let i =0; i < h; i++) {
        for (let j =0; j < w; j++) {
            let c = grid[i][j];
            if (c == c.toUpperCase()) {
                resultGrid.push(new LetterCell(j, i, c));
                letterCellCount++;
            } else if (c == 'z') {
                resultGrid.push(new EmptyCell(j, i));
            } else {
                let cell = new DefinitionCell(j, i, c.charCodeAt(0) -firstCharCode, definitions, currentDefinition);
                currentDefinition += cell.definitions.length;
                resultGrid.push(cell);
            }
        }
    }
    return {
        grid: resultGrid
        ,nbLetters: letterCellCount
    };
}

function Player(t) {
    this.score = 0;
    this.version = t;
}

Player.prototype.putScore = function(scoreInc, t) {
    this.score += scoreInc;
    this.version = Math.max(this.version, t);
};

function Grid(publicId, data) {
    this.title = data["titre"];
    this.difficulty = data["force"];
    this.width = data["nbcaseslargeur"];
    this.height = data["nbcaseshauteur"];
    var gridInf = parseGrid(data["grille"], data["definitions"], this.width, this.height);
    this.grid = gridInf.grid;
    this.remainingCells = gridInf.nbLetters;
    this.publicId = publicId;
    this.minVersion = 0;
    /** @type {Object.<string, Player>} playerId -> score */
    this.players = {};
    /** @type {number|null} grid termination time */
    this.closureTime = null;
};

Grid.colors = shuffleArray([ "007b00", "00407b", "42007b", "7b0059", "7b0000", "00747b", "007b39", "7b6300", "ff5f5f", "5c7cff" ]);

Grid.prototype.getCell = function(x, y) {
    for (var i =0, nbCells = this.grid.length; i < nbCells; i++) {
        var cell = this.grid[i];
        if (cell.px === x && cell.py === y)
            return cell;
    }
    return null;
};

function shuffleArray(arr) {
    var i = arr.length;

    while (i) {
        var rand = Math.floor(Math.random() * i)
            ,tmp = arr[--i];

        arr[i] = arr[rand];
        arr[rand] = tmp;
    }
    return arr;
}

Grid.prototype.generateNewPlayerId = function() {
    var players = {};

    for (var playerId in this.players)
        players[playerId.substr(0, playerId.indexOf('|'))] = true;

    var playerName = (function() {
        var dessertType = shuffleArray([ "muffin", "cookie", "cake", "marshmallow", "pizza", "pie", "mousse" ])
            ,ingredient = shuffleArray([ "blueberry", "chocolate", "cherry", "apple", "pear", "caramell" ]);

        for (var i =0, iMax = dessertType.length; i < iMax; i++)
            for (var j =0, jMax = ingredient.length; j < jMax; j++) {
                var id = ingredient[i] +' ' +dessertType[j];
                if (!players[id]) {
                    return id;
                }
            }
        var i =1
            ,baseId = ingredient[0] +' ' +dessertType[0] +' ';
        while (players[baseId +i])
            i++;
        return baseId +i;
    })();
    return playerName +'|' +Grid.colors[Object.keys(this.players).length % Grid.colors.length];
};

Grid.prototype.setFound = function(cell, playerId, t) {
    if (cell.setFound(playerId, t)) {
        this.remainingCells--;
        if (!this.remainingCells)
            this.closureTime = t;
    }
};

Grid.prototype.registerNewPlayer = function(t) {
    var playerId = this.generateNewPlayerId();
    this.players[playerId] = new Player(t);
    return playerId;
};

Grid.prototype.toStatic = function(v) {
    var ret = {}
        ,maxV = this.minVersion;

    if (!v) {
        ret.title = this.title;
        ret.difficulty = this.difficulty;
        ret.w = this.width;
        ret.h = this.height;
        ret.startTime = this.minVersion;
    }
    var grid = [];
    this.grid.forEach((cell) => {
        if (cell instanceof DefinitionCell || cell instanceof EmptyCell) {
            if (!v) {
                grid.push(cell.toStatic());
            }
        } else if (cell.found && cell.version > v) {
            grid.push(cell.toStatic());
            maxV = Math.max(cell.version, maxV);
        }
    });
    if (this.closureTime && this.closureTime > v) {
        ret.gridTime = this.closureTime -this.minVersion;
    }
    if (grid.length)
        ret.grid = grid;
    for (var i in this.players) {
        if (this.players[i].version > v) {
            if (!ret.players) {
                ret.players = [];
            }
            ret.players.push({
                name: i
                ,score: this.players[i].score
            });
            maxV = Math.max(this.players[i].version, maxV);
        }
    }
    ret.v = maxV;
    return Object.keys(ret).length > 0 ? ret : null;
};

module.exports.Grid = Grid;
module.exports.LetterCell = LetterCell;

