
var ChessLibrary = {
    'VERSION': '0.0.1',
    'WHITE': 'white',
    'BLACK': 'black',
}

// rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 1


/**
 * Constructor
 *
 * new PieceHelper(aElement);
 * new PieceHelper(tableCellElement);
 * new PieceHelper('R');
 * new PieceHelper('♖');
 *
 * @param String|HTMLTableCellElement|HTMLElement|PieceHelper piece
 * @return PieceHelper
 */
function PieceHelper(piece)
{
    if(piece instanceof PieceHelper) {
        this.element = piece.element;
    } else if(PieceHelper.isPieceElement(piece)) {
        this.element = piece;
    } else if((typeof piece === 'object') && (piece instanceof HTMLTableCellElement)) {
        var elem = piece.getElementsByTagName('a')[0];
        if(!elem)
            throw new TypeError('PieceHelper got HTMLTableCellElement argument, but no piece-element was found inside!');
        this.element = elem;
    } else {
        function build(pieceSymbol) {
            var elem = document.createElement('a');
            elem.setAttribute('class', 'chesspiece');
            elem.setAttribute('href', '#');
            elem.innerHTML = pieceSymbol;
            elem.onclick = function(ev){ev.preventDefault()};
            return elem;
        }

        var letterIndex = PieceHelper.LETTERS.indexOf(piece);
        if(letterIndex >= 0) {
            this.element = build(PieceHelper.SYMBOLS[letterIndex]);
        } else if(PieceHelper.SYMBOLS.indexOf(piece) >= 0) {
            this.element = build(piece);
        } else {
            throw new TypeError('Unable to create PieceHelper instance. Invalid argument: ' + piece);
        }
    }
    $(this.element).addClass(this.getColor());
}

PieceHelper.LETTERS = [
    'p', 'r', 'n', 'b', 'q', 'k',
    'P', 'R', 'N', 'B', 'Q', 'K',
];

PieceHelper.LOCALES = {
    'en': 'RNBQK',
    'no': 'TSLDK',
};

PieceHelper.SYMBOLS = [
    '♟', '♜', '♞', '♝', '♛', '♚',
    '♙', '♖', '♘', '♗', '♕', '♔',
];

/**
 * @param mixed node
 * @return Boolean
 */
PieceHelper.isPieceElement = function(node){
    return (typeof node === 'object') && (node instanceof HTMLAnchorElement);
};

/**
 * Normalize a piece notation to it's alphabetical letter
 *
 * @param String piece
 * @return String
 */
PieceHelper.normalizeToLetter = function(piece){
    if(PieceHelper.LETTERS.indexOf(piece) >= 0)
        return piece;
    var symbolIndex = PieceHelper.SYMBOLS.indexOf(piece);
    if(symbolIndex >= 0)
        return PieceHelper.LETTERS[symbolIndex];
    throw new RangeError('Unable to convert input to a piece-letter');
};

/**
 * @return String('white')|String('black')
 */
PieceHelper.prototype.getColor = function(){
    var symbol = this.element.textContent,
        i = PieceHelper.SYMBOLS.indexOf(symbol);
    if(i > 5) {
        return 'white';
    } else if(i > -1) {
        return 'black';
    }
    return false;
};

/**
 * @return BoardCoord
 */
PieceHelper.prototype.getCoord = function(){
    return new BoardCoord(this.element.parentNode);
};

/**
 * @return BoardHelper
 */
PieceHelper.prototype.getBoard = function(){
    return new BoardHelper($(this.element).closest('.chessboard')[0]);
};

/**
 * @return BoardHelper
 */
PieceHelper.prototype.getLetter = function(){
    return PieceHelper.normalizeToLetter(this.element.textContent);
};

/**
 * @return String
 */
PieceHelper.prototype.toString = function(){
    return "<PieceHelper: " + this.element.textContent + ">";
};


/**
 * new BoardCoord(tableCell);
 * new BoardCoord(2, 4);
 * new BoardCoord('b', 4);
 * new BoardCoord('b4');
 */
function BoardCoord(colIndex, rowIndex, isZeroBased)
{
    if((typeof colIndex === 'object') && (colIndex instanceof HTMLTableCellElement)) {
    //if($(colIndex).is('table.boardfields td')) {
        this.col = 8 - $(colIndex).nextAll().length;
        this.row = 8 - $(colIndex).closest('tr').prevAll().length;
    } else if(colIndex instanceof BoardCoord) {
        this.col = colIndex.col;
        this.row = colIndex.row;
    } else if((typeof colIndex == 'string') && (colIndex.length == 2)) {
        this.col = colIndex.toLowerCase().charCodeAt(0) - 96;
        this.row = parseInt(colIndex.charAt(1));
    } else {
        if(/^[a-z]+$/.test(colIndex))
            colIndex = colIndex.toLowerCase.charCodeAt(0) - 97;
        this.col = parseInt(colIndex);
        this.row = parseInt(rowIndex);
        if(isZeroBased) {
            this.col++;
            this.row++;
        }
    }

    if(!(this.row >= 1 && this.row <= 8))
        throw new RangeError('Failed to create BoardCoord. Invalid row (' + this.row + ')');
}

/**
 * @param BoardHelper board
 * @return HTMLTableCellElement
 */
BoardCoord.prototype.getField = function(board){
    var row = $(board.container).find('table.boardfields > tbody > tr').eq(8 - this.row),
        col = row.find('td').eq(this.col - 1);
    return col[0];
};

/**
 * @param BoardHelper board
 * @return HTMLTableCellElement
 */
BoardCoord.prototype.isEmpty = function(board){
    var field = this.getField(board);
    return ($(field).find('.chesspiece').length === 0);
};

/**
 * Get new BoardCoord instance based on the supplied arguments.
 * Returns undefined if the resulting coordinate would be outside of a normal chessboard.
 *
 * @param integer col
 * @param integer row
 * @return BoardCoord|undefined
 */
BoardCoord.prototype.getRelative = function(col, row){
    try {
        return new BoardCoord(this.col + col, this.row + row);
    } catch(e) {
        return;
    }
};

/**
 * @return String
 */
BoardCoord.prototype.getName = function(){
    return String.fromCharCode(96 + this.col).concat(this.row);
};

/**
 * @return String
 */
BoardCoord.prototype.toString = function(){
    return "<BoardCoord: " + this.getName() + ">";
};


/**
 * Constructor
 *
 * @param BoardCoord from
 * @param BoardCoord to
 * @param String|PieceHelper promoteTo OPTIONAL
 */
function Move(from, to, promoteTo)
{
    this.from = new BoardCoord(from);
    this.to = new BoardCoord(to);
    this.promoteTo = promoteTo;
}

/**
 * @param BoardHelper board
 * @return PieceHelper
 */
Move.prototype.getPiece = function(board){
    try {
        return new PieceHelper(this.from.getField(board));
    } catch(e) {
        try {
            return new PieceHelper(this.to.getField(board));
        } catch(e) {
            throw new Error('Unable to locate the moving piece.');
        }
    }
};

/**
 * @param BoardHelper board
 * @return Boolean
 */
Move.prototype.isPromote = function(board){
    var pieceLetter = PieceHelper.normalizeToLetter(this.getPiece(board).element.textContent);
    if(this.to.row == 8 && (pieceLetter == 'P')) {
        return true;
    } else if(this.to.row == 1 && (pieceLetter == 'p')) {
        return true;
    }
    return false;
};

/**
 * @param BoardHelper board
 * @return String
 */
Move.prototype.getSAN = function(board, locale){
    var piece = this.getPiece(board);
    var san = this.to.getName();
    var pieceLetter = PieceHelper.normalizeToLetter(piece.element.textContent);
    if(pieceLetter.toLowerCase() !== 'p') {
        if(locale) {
            var targets = PieceHelper.LOCALES[locale],
                base = PieceHelper.LOCALES['en'],
                i = base.indexOf(pieceLetter.toUpperCase());
            san = targets[i] + san;
        } else
            san = piece.element.textContent + san;
    }
    return san;
};

/*
 * @return String
 */
Move.prototype.toString = function(){
    return "<Move: " + this.from.getName() + "-" + this.to.getName() + ">";
};

function MoveCollection()
{
    Array.apply(this, arguments);
}
MoveCollection.prototype = new Array();

MoveCollection.prototype.getDestinationFields = function(){
};


/**
 * Helper class for building and handling chess diagrams in the DOM
 *
 * @param HTMLDivElement containerElement
 */
function BoardHelper(containerElement)
{
    if(!(containerElement instanceof HTMLDivElement)) {
        throw new TypeError('Must be of type HTMLDivElement');
    }
    this.container = containerElement;
    this.buildDOM();
    this.table = $(this.container).find('table.boardfields')[0];
}


BoardHelper.EVENT_NEW_POSITION = 'NewPosition.chess';
BoardHelper.EVENT_MOVE = 'Move.chess';
BoardHelper.EVENT_INVALID_MOVE = 'InvalidMove.chess';
BoardHelper.EVENT_PIECE_INSERTED = 'PieceInserted.chess';

/**
 * @return BoardHelper
 */
BoardHelper.prototype.buildDOM = function(){
    if($(this.container).find('table.boardfields').length)
        return this;
    var tbody = document.createElement('tbody');
    var row, col, rowId, colId, fieldname;
    var boardCount = $('div.chessboard').length;
    var baseID = this.container.hasAttribute('id')
            ? this.container.getAttribute('id')
            : 'chessboard' + (boardCount + 1);
    var fieldstable = $('<table/>')
        .addClass('boardfields')
        .append(tbody)
        .appendTo(this.container);

    for(rowId=8; rowId >= 1; rowId--){
        row = document.createElement('tr');
        for(colId=1; colId <= 8; colId++){
            fieldname = String.fromCharCode(96 + colId) + rowId;
            col = document.createElement('td');
            col.setAttribute('id', baseID + '-' + fieldname);
            col.setAttribute('title', fieldname);
            col.setAttribute('data-fieldname', fieldname);
            row.appendChild(col);
        }
        tbody.appendChild(row);
    }

    var rowCoordsTable = $('<table/>').addClass('coords rows');
    [8, 7, 6, 5, 4, 3, 2, 1].forEach(function(el){
        rowCoordsTable.append('<tr><td>' + el + '</td></tr>');
    });
    rowCoordsTable.insertBefore(fieldstable);

    var colCoordsRow = $('<tr/>');
    ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'].forEach(function(el){
        colCoordsRow.append('<td>' + el + '</td>');
    });
    $('<table/>').addClass('coords cols').append(colCoordsRow).insertAfter(fieldstable);

    if($.browser.msie && ($.browser.version <= 8)) {
        $(this.container)
            .find('table.boardfields tr:nth-child(odd) td:nth-child(even)')
            .add('table.boardfields tr:nth-child(even) td:nth-child(odd)', this.container)
            .css('background', '#ccc');
    }

    return this;
};

BoardHelper.prototype.getCurrentFEN = function(){
    var cells, rI, cI, cell,
        fen = "",
        blanks = 0,
        rows = $(this.table).find('tbody tr').get();
    for(rI=0; rI < rows.length; rI++) {
        cells = rows[rI].getElementsByTagName('td');
        for(cI=0; cI < cells.length; cI++) {
            cell = cells[cI];
            if(PieceHelper.isPieceElement(cell.firstChild)) {
                if(blanks > 0) {
                    fen = fen + blanks;
                    blanks = 0;
                }
                fen = fen + PieceHelper.normalizeToLetter(cell.firstChild.textContent);
            } else {
                blanks++;
                continue;
            }
        }
        if(blanks > 0) {
            fen = fen + blanks;
            blanks = 0;
        }
        if(rI < 7)
            fen = fen + '/';
    }

    var enPassantTarget = '-';
    var prevMove = this.getData('previous-move');
    if(prevMove && (prevMove.getPiece(this).getLetter().toLowerCase() == 'p')) {
        if(prevMove.from.row == 2 && prevMove.to.row == 4) {
            enPassantTarget = prevMove.to.getRelative(0, -1).getName();
        } else if(prevMove.from.row == 7 && prevMove.to.row == 5) {
            enPassantTarget = prevMove.to.getRelative(0, 1).getName();
        }
    }

    var currentTurn = this.getData('current-turn');
    if(currentTurn == 'white')
        fen = fen + ' w';
    else if(currentTurn == 'black')
        fen = fen + ' b';

    var halfmoves = this.getData('halfmoves-count');
    var fullmoveCount = (halfmoves || (halfmoves == 0))
        ? Math.max(Math.ceil(this.getData('halfmoves-count') / 1.999999), 1)
        : '-';
    fen = [fen, this.getData('castling') || '-', enPassantTarget, '0', fullmoveCount].join(' ');
    return fen;
};

/**
 * @param String key
 * @return mixed
 */
BoardHelper.prototype.getData = function(key){
    return $(this.container).data(key);
};

/**
 * @param String key
 * @param mixed value
 * @return BoardHelper
 */
BoardHelper.prototype.setData = function(key, value){
    if((typeof value == 'string') || (typeof value == 'number')) {
        $(this.container).attr("data-" + key, value);
    }
    $(this.container).data(key, value);
    return this;
};

/**
 * Reset the board to a blank state. No pieces and no records
 *
 * TODO: Reliably clear data stored in the board.
 *
 * @return BoardHelper
 */
BoardHelper.prototype.resetBoard = function(){
    $(this.table).find('td').empty();
    this.setData('current-turn', null);
    this.setData('previous-move', null);
    this.setData('halfmoves-count', 0);
    this.setData('castling', '');
    return this;
};

/**
 * @return String
 */
BoardHelper.prototype.selectPromote = function(callback, pieces){
    var sec = $('<div class="select-promote"/>')
        .append('<span class="label">Velg brikke:</span')
        .prependTo(this.container);
    var options = pieces
        ? pieces
        : PieceHelper.SYMBOLS.slice(1, 6).concat(PieceHelper.SYMBOLS.slice(7, 13));
/*
PieceHelper.SYMBOLS = [
    '♟', '♜', '♞', '♝', '♛', '♚',
    '♙', '♖', '♘', '♗', '♕', '♔',
];
*/
    options.forEach(function(elem){
        $('<span class="piece"/>').appendTo(sec).text(elem).click(function(){
            sec.remove();
            callback(this.textContent);
        });
    });
    return this;
};

/**
 * @param String fen
 * @return BoardHelper
 */
BoardHelper.prototype.loadFEN = function(fen){
    var parts = fen.split(' '),
        rowpart = parts[0],
        rows = rowpart.split('/'),
        isNumeric = /^[0-9]+$/,
        isAlpha = /^[a-zA-Z]+$/,
        row, colIndex, chrIndex, chr;
    this.resetBoard();

    for(var rowIndex=0; rowIndex < rows.length; rowIndex++) {
        row = rows[rowIndex];
        colIndex = 0;
        for(chrIndex=0; chrIndex < row.length; chrIndex++) {
            chr = row.charAt(chrIndex);
            if(isNumeric.test(chr)) { /* Blank field(s) */
                colIndex += parseInt(chr)
                continue;
            } else if(isAlpha.test(chr)) {
                var coord = new BoardCoord(colIndex, 7 - rowIndex, true);
                this.insertPiece(coord, chr);
            } else {
                throw Error("Error parsing FEN. Char '" + chr + "'");
            }
            colIndex += 1;
        }
        row -= 1;
    }

    // Set defaults
    this.setData('castling', 'kqKQ')

    // load active color from the FEN
    if(parts.length >= 2) {
        this.setData('current-turn', (parts[1] == 'w') ? ChessLibrary.WHITE : ChessLibrary.BLACK);

        // load castling availability
        if(parts.length >= 3) {
            var castling = parts[2].split('').filter(function(el){ return 'kqKQ'.indexOf(el) >= 0 }).join('');
            this.setData('castling', castling);
            /*this.setData('castling', {
                k: parts[2].indexOf('k') != -1,
                q: parts[2].indexOf('q') != -1,
                K: parts[2].indexOf('K') != -1,
                Q: parts[2].indexOf('Q') != -1,
            });*/

            // load en passant target square
            if(parts.length >= 4) {

                // load number of halfmoves since the last pawn advance or capture
                if(parts.length >= 5) {

                    // load the number of the full move
                    if(parts.length >= 6) {
                        var halfmove = parseInt(parts[5]) * 2;
                        var isWhite = this.getData('current-turn') == 'white';
                        if(isWhite && (halfmove == 2)) {
                            this.setData('halfmoves-count', 0);
                        } else {
                            if(isWhite){
                                halfmove -= 1;
                            }
                            this.setData('halfmoves-count', halfmove);
                        }
                    }
                }
            }
        }
    }
    return this;
};


/**
 * @param BoardHelper board
 * @return Boolean
 */
BoardHelper.prototype.isSameBoard = function(board){
    return this.table.isSameNode(board);
};

/**
 * @param BoardCoord coord
 * @param PieceHelper piece
 * @return BoardHelper
 */
BoardHelper.prototype.insertPiece = function(coord, piece){
    coord = new BoardCoord(coord);
    piece = new PieceHelper(piece);
    $(coord.getField(this)).empty().append(piece.element);
    this.trigger('ChessPieceInserted', [piece]);
    return this;
};

/**
 * Shortcut-function for binding an event-handler to the container-element
 * @return BoardHelper
 */
BoardHelper.prototype.bind = function(event, callback){
    $(this.container).bind(event, callback);
    return this;
};

/**
 * @param String|jQuery.Event eventType
 * @param Array args
 * @return BoardHelper
 */
BoardHelper.prototype.trigger = function(eventType, args){
    $(this.container).trigger(eventType, args);
    return this;
};

/**
 * @param Move move
 * @return BoardHelper
 */
BoardHelper.prototype.makeMove = function(move){
    if(!(move instanceof Move))
        throw new TypeError('Argument to BoardHelper.makeMove() must be a instance of Move');
    var fieldFromElement = move.from.getField(this);
    var fieldToElement = move.to.getField(this);
    var piece = new PieceHelper(fieldFromElement);
    var pieceLetter = PieceHelper.normalizeToLetter(piece.element.textContent);

    if(this.getData('require-valid-move') === true) {
        var logicEngine = this.getLogicEngine();
        if(!logicEngine.isValidMove(move)) {
            this.trigger('InvalidChessMove', [piece, move]);
            return;
        }
    }

    if(pieceLetter.toLowerCase() == 'p') {
        // Implement special logic for pawns (enpassant and promotion)

        if(move.to.row == 8 && piece.getColor() == 'white') {
            // Handle pawn-promotion for white
            if(!move.promoteTo) {
                throw new Exception('Missing promoteTo');
            }
            piece.element.textContent = new PieceHelper(move.promoteTo).element.textContent;
        } else if(move.to.row == 1 && piece.getColor() == 'black') {
            // Handle pawn-promotion for black
            if(!move.promoteTo) {
                throw new Exception('Missing promoteTo');
            }
            piece.element.textContent = new PieceHelper(move.promoteTo).element.textContent;
        } else if(move.to.col != move.from.col) {
            // Handle enpassant
            var tar = move.from.getRelative(move.to.col - move.from.col, 0);
            $(tar.getField(this)).empty();
        }
    } else if(pieceLetter.toLowerCase() == 'k') {
        /* Handle king castling */
        var castling = this.getData('castling');
        var row = move.from.row;
        var castlingDone;
        if(move.from.col == 5 && move.to.col == 7) {
            $(new BoardCoord('h' + row).getField(this)).find('.chesspiece').appendTo(new BoardCoord('f' + row).getField(this));
            castlingDone = true;
        } else if(move.from.col == 5 && move.to.col == 3) {
            $(new BoardCoord('a' + row).getField(this)).find('.chesspiece').appendTo(new BoardCoord('d' + row).getField(this));
            castlingDone = true;
        }

        if(castlingDone) {
            if(piece.getColor() == 'white') {
                castling.K = false;
                castling.Q = false;
            } else {
                castling.k = false;
                castling.q = false;
            }
            this.setData('castling', castling);
        }
        this.setData('castling', 'q');
    }


    $(fieldToElement).empty().append(piece.element);
    this.setData('current-turn', (piece.getColor() == 'white') ? 'black' : 'white');
    this.setData('previous-move', move);
    this.setData('halfmoves-count', this.getData('halfmoves-count') + 1);
    this.trigger('ChessMove', [piece, move]);
    this.trigger(BoardHelper.EVENT_MOVE, [piece, move]);
    this.trigger(BoardHelper.EVENT_NEW_POSITION);
    return this;
};


/**
 * @return BoardLogic
 */
BoardHelper.prototype.getLogicEngine = function(){
    var stored = this.getData('logic-engine');
    if(stored)
        return stored;
    return new BoardLogic(this);
};


/**
 * @param BoardHelper board
 */
function BoardLogic(board)
{
    this.board = board;
    this.lookupUrl = '/chess-api/possible-moves/';
}

/**
 * @return bool
 */
BoardLogic.prototype.isValidMove = function(move, callback){
    var possibleMoves = this.getPossibleMoves(move.from.getName());
    return possibleMoves.some(function(el){
        if(el.toString() == move.toString())
            return true;
    });
};

/**
 * @param OPTIONAL String field
 * @param OPTIONAL Function callback
 * @return XMLHttpRequest|Array
 */
BoardLogic.prototype.getPossibleMoves = function(field, callback){
    function filterResult(rawData, dataType){
        var json = jQuery.parseJSON(rawData),
            move, fromCoord, destCoord, moves = [];
        for(var i=0; i < json.length; i++) {
            move = json[i];
            moves.push(new Move(new BoardCoord(move[0]), new BoardCoord(move[1])));
        }
        return moves;
    }
    var ajaxSettings = {
        'url': this.lookupUrl,
        'async': Boolean(callback),
        'data': {'fen': this.board.getCurrentFEN()},
        'dataType': 'json',
        'dataFilter': filterResult,
        'success': callback,
    };
    if(field)
        ajaxSettings['data']['piece'] = field;
    var xhr = jQuery.ajax(ajaxSettings);
    return callback
        ? xhr
        : filterResult(xhr.responseText);
};





