function ChessBoard() {
    this.board;
    this.fen;
    this.turn;
    this.castling;
    this.enPassant;
    this.halfMove;
    this.fullMove;
    
    this.setBoard = function(fen) {
        this.fen = fen;
        this.board = new Array();

        var boardData = this.fen.split(" ");
        
        if (boardData.length < 6) {
            this.board = null;
        } else {
            var ranks = boardData[0].split("/");

            this.turn = boardData[1];
            this.castling = boardData[2];
            this.enPassant = this.getSquareIndex(boardData[3]);
            this.halfMove = parseInt(boardData[4]);
            this.fullMove = parseInt(boardData[5]);
            
            if (ranks.length != 8) {
                this.board = null;
            } else {    
                for (var rank = 0; rank < 8; rank++) {
                    this.board.push("");
                
                    for (var charPos = 0; charPos < ranks[rank].length; charPos++) {
                        var currentChar = ranks[rank].charAt(charPos);
                        
                        if (/[1-8]/.test(currentChar)) {
                            currentChar = parseInt(currentChar);
                            
                            for (var filePos = 1; filePos <= currentChar; filePos++) {
                                this.board.push("");
                            }
                        } else if (/[prnbqkPRNBQK]/.test(currentChar)) {
                            this.board.push(currentChar);
                        } else {
                            this.board = null;
                        }
                    }
                    
                    this.board.push("");
                }
            }
        }
    };
    
    this.getMoves = function(avoidCheck) {
         var moves = new Array();
    
        for (var i = 0; i < 80; i++) {
            if (
                    ((/[wW]/.test(this.turn)) && (/[PRNBQK]/.test(this.board[i]))) ||
                    ((/[bB]/.test(this.turn)) && (/[prnbqk]/.test(this.board[i])))
                ) {

                moves[i] = this.getPieceMoves(i, avoidCheck);
            }
        }

        return moves;
    };
    
    this.getPieceMoves = function(i, avoidCheck) {

        if (
                ((/[wW]/.test(this.turn)) && (/[PRNBQK]/.test(this.board[i]))) ||
                ((/[bB]/.test(this.turn)) && (/[prnbqk]/.test(this.board[i])))
            ) {
    
            var moves = new Array();
            var moveTypes = this.getMoveTypes(this.board[i]);

            for (var j = 0; j < moveTypes.length; j++) {           
                var newIndex = parseInt(i) + parseInt(moveTypes[j][0]);
            
                if (moveTypes[j][1] == 1) {
                    while ((newIndex >= 1) && (newIndex <= 79)) {
                        var newFile = ["x", "a", "b", "c", "d", "e", "f", "g", "h", "x"][newIndex % 10];

                        if (this.isMoveLegal(i, newIndex, avoidCheck)) {
                            moves.push(newIndex);
                            
                            if (/[prnbqkPRNBQK]/.test(this.board[newIndex])) {
                                break;
                            }
                        } else if ((/[wW]/.test(this.turn)) && (/[PRNBQKprnbqk]/.test(this.board[newIndex]))) {
                            break;
                        } else if ((/[bB]/.test(this.turn)) && (/[PRNBQKprnbqk]/.test(this.board[newIndex]))) {
                            break;
                        } else if ((newFile == "x") || (newIndex <= 1) || (newIndex >= 79)) {
                            break;
                        }

                        newIndex = parseInt(newIndex) + parseInt(moveTypes[j][0]);
                    }
                } else if (this.isMoveLegal(i, newIndex, avoidCheck)) {
                    moves.push(newIndex);
                }
            }

            if ((/P/.test(this.board[i])) && (i >= 61) && (i <= 68)) {
                if ((this.board[parseInt(i) - 10] == "") && (this.board[parseInt(i) - 20] == "")) {
                    if (this.isMoveLegal(i, parseInt(i) - 20, avoidCheck)) {
                        moves.push(parseInt(i) - 20);
                    }
                }
            } else if ((/p/.test(this.board[i])) && (i >= 11) && (i <= 18)) {

                if ((this.board[parseInt(i) + 10] == "") && (this.board[parseInt(i) + 20] == "")) {
                    if (this.isMoveLegal(i, parseInt(i) + 20, avoidCheck)) {
                        moves.push(parseInt(i) + 20);
                    }
                }
            }
            
            if (/K/.test(this.board[i])) {
                if ((/K/.test(this.castling)) && (this.board[76] == "") && (this.board[77] == "")) {
                    if (this.isMoveLegal(i, 77, avoidCheck)) {
                        moves.push(77);
                    }
                }
                
                if ((/Q/.test(this.castling)) && (this.board[72] == "") && (this.board[73] == "") && (this.board[74] == "")) {
                    if (this.isMoveLegal(i, 73, avoidCheck)) {
                        moves.push(73);
                    }
                }
            } else if (/k/.test(this.board[i])) {
                if ((/k/.test(this.castling)) && (this.board[6] == "") && (this.board[7] == "")) {
                    if (this.isMoveLegal(i, 7, avoidCheck)) {
                        moves.push(7);
                    }
                }
                
                if ((/q/.test(this.castling)) && (this.board[2] == "") && (this.board[3] == "") && (this.board[4] == "")) {
                    if (this.isMoveLegal(i, 3, avoidCheck)) {
                        moves.push(3);
                    }
                }
            }

            return moves;
        } else {
            return new Array();
        }
    };
    
    this.isMoveLegal = function(i, newIndex, avoidCheck) {
        var newFile = ["x", "a", "b", "c", "d", "e", "f", "g", "h", "x"][newIndex % 10];

        if ((newFile != "x") && (newIndex >= 1) && (newIndex <= 79)) {
            if (/[pP]/.test(this.board[i])) {
                if (
                    (newIndex == (parseInt(i) + 10)) ||
                    (newIndex == (parseInt(i) - 10)) ||
                    (newIndex == (parseInt(i) + 20)) ||
                    (newIndex == (parseInt(i) - 20))
                ) {
                    if (this.board[newIndex] == "") {
                        if (avoidCheck) {
                            return !this.isMoveInCheck(i, newIndex);
                        } else {
                            return true;
                        }
                    } else {
                        return false;
                    }
                } else {
                    if ((/p/.test(this.board[i])) && (/[PRNBQK]/.test(this.board[newIndex]))) {
                        if (avoidCheck) {                    
                            return !this.isMoveInCheck(i, newIndex);
                        } else {
                            return true;
                        }
                    } else if ((/P/.test(this.board[i])) && (/[prnbqk]/.test(this.board[newIndex]))) {
                        if (avoidCheck) {                    
                            return !this.isMoveInCheck(i, newIndex);
                        } else {
                            return true;
                        }
                    } else {
                        if (newIndex == this.enPassant) {
                            if (avoidCheck) {                    
                                return !this.isMoveInCheck(i, newIndex);
                            } else {
                                return true;
                            }
                        } else {
                            return false;
                        }
                    }
                }
            } else {
                if ((/[rnbqk]/.test(this.board[i])) && ((this.board[newIndex] == "") || (/[PRNBQK]/.test(this.board[newIndex])))) {
                    if (avoidCheck) {                    
                        return !this.isMoveInCheck(i, newIndex);
                    } else {
                        return true;
                    }
                } else if ((/[RNBQK]/.test(this.board[i])) && ((this.board[newIndex] == "") || (/[prnbqk]/.test(this.board[newIndex])))) {
                    if (avoidCheck) {                    
                        return !this.isMoveInCheck(i, newIndex);
                    } else {
                        return true;
                    }
                } else {
                    return false;
                }
            }
        } else {
            return false;
        }
    };
    
    this.isMoveInCheck = function(i, newIndex) {
        var boardCopy = this.clone();

        boardCopy.applyMove(i, newIndex, this.board[i]);

        var moves = boardCopy.getMoves(false);

        for (var source in moves) {
            for (var destination in moves[source]) {
                if (/[kK]/.test(boardCopy.board[moves[source][destination]])) {
                    return true;
                }

                if ((/K/.test(this.board[i])) && (i == 75) && (newIndex == 77)) {
                    if (
                        (moves[source][destination] == 75) ||
                        (moves[source][destination] == 76) ||
                        (moves[source][destination] == 77)
                    ) {
                        return true;
                    }
                } else if ((/K/.test(this.board[i])) && (i == 75) && (newIndex == 73)) {
                    if (
                        (moves[source][destination] == 75) ||
                        (moves[source][destination] == 74) ||
                        (moves[source][destination] == 73)
                    ) {
                        return true;
                    }
                } else if ((/k/.test(this.board[i])) && (i == 5) && (newIndex == 7)) {
                    if (
                        (moves[source][destination] == 5) ||
                        (moves[source][destination] == 6) ||
                        (moves[source][destination] == 7)
                    ) {
                        return true;
                    }
                } else if ((/k/.test(this.board[i])) && (i == 5) && (newIndex == 3)) {
                    if (
                        (moves[source][destination] == 5) ||
                        (moves[source][destination] == 4) ||
                        (moves[source][destination] == 3)
                    ) {
                        return true;
                    }
                }
            }
        }
        
        boardCopy = null;

        return false;
    };

    this.applyMove = function(oldIndex, newIndex, pawnPromotionChar) {
        if (/[pP]/.test(this.board[oldIndex])) {
            this.halfMove = 0;
        } else if (/[prnbqkPRNBQK]/.test(this.board[newIndex])) {
            this.halfMove = 0;
        } else {
            this.halfMove++;
        }
        
        this.board[newIndex] = this.board[oldIndex];
        this.board[oldIndex] = "";

        if (/[bB]/.test(this.turn)) {
            this.fullMove++;
        }

        if ((/P/.test(this.board[newIndex])) && (newIndex == this.enPassant)) {
            this.board[newIndex + 10] = "";
        } else if ((/p/.test(this.board[newIndex])) && (newIndex == this.enPassant)) {
            this.board[newIndex - 10] = "";
        }

        if ((/P/.test(this.board[newIndex])) && (newIndex >= 41) && (newIndex <= 48) && (oldIndex == parseInt(newIndex) + 20)) {
            this.enPassant = parseInt(newIndex) + 10;
        } else if ((/p/.test(this.board[newIndex])) && (newIndex >= 31) && (newIndex <= 38) && (oldIndex == newIndex - 20)) {
            this.enPassant = parseInt(newIndex) - 10;
        } else {
            this.enPassant = null;
        }

        if ((/P/.test(this.board[newIndex])) && (newIndex >= 1) && (newIndex <= 8)) {
            this.board[newIndex] = pawnPromotionChar;
        } else if ((/p/.test(this.board[newIndex])) && (newIndex >= 71) && (newIndex <= 78)) {
            this.board[newIndex] = pawnPromotionChar;
        }
        
        if ((/K/.test(this.board[newIndex])) && (oldIndex == 75) && (newIndex == 77)) {
            this.board[78] = "";
            this.board[76] = "R";
        } else if ((/K/.test(this.board[newIndex])) && (oldIndex == 75) && (newIndex == 73)) {
            this.board[71] = "";
            this.board[74] = "R";
        } else if ((/k/.test(this.board[newIndex])) && (oldIndex == 5) && (newIndex == 7)) {
            this.board[8] = "";
            this.board[6] = "r";
        } else if ((/k/.test(this.board[newIndex])) && (oldIndex == 5) && (newIndex == 3)) {
            this.board[1] = "";
            this.board[4] = "r";
        }

        if ((/R/.test(this.board[newIndex])) && (oldIndex == 78)) {
            this.castling = this.castling.replace("K", "");
        } else if ((/R/.test(this.board[newIndex])) && (oldIndex == 71)) {
            this.castling = this.castling.replace("Q", "");
        } else if ((/r/.test(this.board[newIndex])) && (oldIndex == 8)) {
            this.castling = this.castling.replace("k", "");
        } else if ((/r/.test(this.board[newIndex])) && (oldIndex == 1)) {
            this.castling = this.castling.replace("q", "");
        } else if (/K/.test(this.board[newIndex])) {
            this.castling = this.castling.replace(/K/, "");
            this.castling = this.castling.replace(/Q/, "");
        } else if (/k/.test(this.board[newIndex])) {
            this.castling = this.castling.replace(/k/, "");
            this.castling = this.castling.replace(/q/, "");
        }
        
        if (this.castling == "") {
            this.castling = "-";
        }

        this.turn = (/[wW]/.test(this.turn)) ? "b" : "w";
        this.updateFen();
    };
    
    this.getAlgebraicMove = function(oldIndex, newIndex, pawnPromotionChar) {
        var source = /[pP]/.test(this.board[oldIndex]) ? "" : this.board[oldIndex].toUpperCase();
        var moveTypeChar = /[prnbqkPRNBQK]/.test(this.board[newIndex]) ? "x" : "";
        var destination = this.getSquareId(newIndex);
    
        var sourceFiles = new Array();
        var sourceRanks = new Array();
    
        for (var i = 1; i < 80; i++) {
            if ((this.board[i] == this.board[oldIndex]) && (i != oldIndex)) {
                var potentialAmbiguousMoves = this.getPieceMoves(i, true);
                
                for (var j = 0; j < potentialAmbiguousMoves.length; j++) {               
                    if (potentialAmbiguousMoves[j] == newIndex) {                   
                        sourceFiles[i % 10] = true;
                        sourceRanks[Math.floor(i / 10)] = true;
                    }
                }
            }
        }

        if ((/[pP]/.test(this.board[oldIndex])) && (moveTypeChar == "x")) {
            source = ["x", "a", "b", "c", "d", "e", "f", "g", "h", "x"][oldIndex % 10];
        } else if ((sourceFiles.length > 0) && (sourceRanks.length > 0)) {
            if (!sourceFiles[oldIndex % 10]) {
                source += ["x", "a", "b", "c", "d", "e", "f", "g", "h", "x"][oldIndex % 10];
            } else if (!sourceRanks[Math.floor(oldIndex / 10)]) {
                source += 8 - Math.floor(oldIndex / 10);
            } else {
                source += this.getSquareId(oldIndex);
            }
        }

        if ((/P/.test(this.board[oldIndex])) && (newIndex == this.enPassant)) {
            source = ["x", "a", "b", "c", "d", "e", "f", "g", "h", "x"][oldIndex % 10];
            moveTypeChar = "x";
        } else if ((/p/.test(this.board[oldIndex])) && (newIndex == this.enPassant)) {
            source = ["x", "a", "b", "c", "d", "e", "f", "g", "h", "x"][oldIndex % 10];
            moveTypeChar = "x";
        }

        if ((/P/.test(this.board[oldIndex])) && (newIndex >= 1) && (newIndex <= 8)) {
            return source + moveTypeChar + destination + pawnPromotionChar.toUpperCase();
        } else if ((/p/.test(this.board[oldIndex])) && (newIndex >= 71) && (newIndex <= 78)) {
            return source + moveTypeChar + destination + pawnPromotionChar.toUpperCase();
        }
        
        if ((/K/.test(this.board[oldIndex])) && (oldIndex == 75) && (newIndex == 77)) {
            return "O-O";
        } else if ((/K/.test(this.board[oldIndex])) && (oldIndex == 75) && (newIndex == 73)) {
            return "O-O-O";
        } else if ((/k/.test(this.board[oldIndex])) && (oldIndex == 5) && (newIndex == 7)) {
            return "O-O";
        } else if ((/k/.test(this.board[oldIndex])) && (oldIndex == 5) && (newIndex == 3)) {
            return "O-O-O";
        }
        
        return source + moveTypeChar + destination;
    };

    this.updateFen = function() {
        var fen = "";
        var enPassant = this.getSquareId(this.enPassant);
        var castling = this.castling;
    
        for (var i = 0; i < 80; i++) {
            if (((i % 10) != 0) && ((i % 10) != 9)) {
                if (/[prnbqkPRNBQK]/.test(this.board[i])) {
                    fen += this.board[i];
                } else {
                    var count = 0;
                
                    while ((this.board[i] == "") && ((i % 10) < 9)) {
                        count++;
                        i++;
                    }

                    fen += count + this.board[i];
                }
                
                if (((i % 10) == 9) && (i != 79)) {
                    fen += "/";
                } else {
                    fen += this.board[i];
                }
            } else if (((i % 10) == 9) && (i != 79)) {
                fen += "/";
            }
        }

        this.fen = fen + " " + this.turn + " " + (castling ? castling : "-") + " " + (enPassant ? enPassant : "-") + " " + this.halfMove + " " + this.fullMove;
    };
    
    this.getSquareIndex = function(squareId) {
        if (/[a-h][1-8]/.test(squareId)) {        
            return (8 - parseInt(squareId.charAt(1))) * 10 + ("xabcdefghx".indexOf(squareId.charAt(0)));
        } else {
            return null;
        }
    };
    
    this.getSquareId = function(squareIndex) {
        if ((squareIndex >= 0) && (squareIndex <= 80)) {
            var rank = 8 - Math.floor(squareIndex / 10);
            var file = ["x", "a", "b", "c", "d", "e", "f", "g", "h", "x"][squareIndex % 10];
            
            if (file == "x") {
                return null;
            } else {
                return file + rank;
            }
        } else {
            return null;
        }
    };
    
    this.parseMove = function(move) {
        if (/^(1-0|0-1|½-½|1\/2-1\/2)/.test(move)) {
            if (/^1-0/.test(move)) {
                return [[0, 0]];
            } else if (/^0-1/.test(move)) {
                return [[0, 0]];
            } else if (/^(½-½|1\/2-1\/2)/.test(move)) {
                return [[0, 0]];
            }
        } else if (/^(O-O-O|0-0-0|o-o-o|O-O|0-O|o-o)/.test(move)) {
            if ((/^(O-O-O|0-0-0|o-o-o)/.test(move)) && (/[wW]/.test(this.turn))) {
                return [[75, 73]];
            } else if ((/^(O-O|0-0|o-o)/.test(move)) && (/[wW]/.test(this.turn))) {
                return [[75, 77]];            
            } else if ((/^(O-O-O|0-0-0|o-o-o)/.test(move)) && (/[bB]/.test(this.turn))) {
                return [[5, 3]];
            } else if ((/^(O-O|0-0|o-o)/.test(move)) && (/[bB]/.test(this.turn))) {
                return [[5, 7]];
            }
        } else if (/^[KQRBN]?[a-h]?[1-8]?[x:-]?[a-h][1-8]/.test(move)) {
            move.match(/^([KQRBN]?)([a-h]?)([1-8]?)([x:-]?)([a-h][1-8])/);

            var potentialMoves = new Array();
            var piece = RegExp.$1;
            var sourceRank = RegExp.$3;
            var sourceFile = RegExp.$2;
            var destinationSquare = this.getSquareIndex(RegExp.$5);

            if (piece) {
                piece = (/[wW]/.test(this.turn)) ? piece.toUpperCase() : piece.toLowerCase();
            } else {
                piece = (/[wW]/.test(this.turn)) ? "P" : "p";
            }

            for (var i = 0; i < 80; i++) {
                if (this.board[i] == piece) {
                    var moves = this.getPieceMoves(i, true);
                    var rank = 8 - Math.floor(i / 10);
                    var file = ["x", "a", "b", "c", "d", "e", "f", "g", "h", "x"][i % 10];

                    for (var j = 0; j < moves.length; j++) {
                        if (moves[j] == destinationSquare) {                            
                            if ((sourceFile) && (sourceRank)) {                                    
                                if ((rank == sourceRank) && (file == sourceFile)) {
                                    potentialMoves.push([i, destinationSquare]);
                                }
                            } else if (sourceFile) {
                                if (file == sourceFile) {
                                    potentialMoves.push([i, destinationSquare]);
                                }
                            } else if (sourceRank) {
                                if (rank == sourceRank) {
                                    potentialMoves.push([i, destinationSquare]);
                                }
                            } else {
                                potentialMoves.push([i, destinationSquare]);
                            }
                        }
                    }
                }
            }
            
            return potentialMoves;
        } else {
            return null;
        }
    }

    this.getMoveTypes = function(pieceChar) {   
        if (/p/.test(pieceChar)) {
            return [[11, 0], [10, 0], [9, 0]];
        } else if (/P/.test(pieceChar)) {
            return [[-11, 0], [-10, 0], [-9, 0]];
        } else if (/[rR]/.test(pieceChar)) {
            return [[-1, 1], [10, 1], [1, 1], [-10, 1]];
        } else if (/[nN]/.test(pieceChar)) {
            return [[-12, 0], [-21, 0], [-19, 0], [-8, 0], [12, 0], [21, 0], [19, 0], [8, 0]];
        } else if (/[bB]/.test(pieceChar)) {
            return [[-11, 1], [-9, 1], [11, 1], [9, 1]];
        } else if (/[qQ]/.test(pieceChar)) {
            return [[-11, 1], [-10, 1], [-9, 1], [1, 1], [11, 1], [10, 1], [9, 1], [-1, 1]];
        } else if (/[kK]/.test(pieceChar)) {
            return [[-11, 0], [-10, 0], [-9, 0], [1, 0], [11, 0], [10, 0], [9, 0], [-1, 0]];
        }
    };
    
    this.clone = function() {
        var Clone = new ChessBoard();
        
        Clone.board = this.board.slice(0);
        Clone.fen = this.fen;
        Clone.turn = this.turn;
        Clone.castling = this.castling;
        Clone.enPassant = this.enPassant;
        Clone.halfMove = this.halfMove;
        Clone.fullMove = this.fullMove;

        return Clone;
    };
}