// chessboard.js 
// Copyright 2006, Brian Mottershead. All rights reserved.
//

var images_dir = "/assets/templates/carlislechess/images";

function ChessPiece(sq, side, piece_code) {
    this.side = side;
    this.piece_code = piece_code;
    this.square = sq;
    this.file = sq.file;
    this.rank = sq.rank;
    this._initialMouseOffset = null;
    this._grabOffset = null;

    if (sq) {
        this.piece = this.square.childNodes[0];
        sq.piece = this;
        sq.onmousedown = function() { return false; }
    }
    ChessPiece.util.addListener(sq, 'mousedown', ChessPiece.mouseDown);
    this.toString = function() {
        return "[ChessPiece: "+this.square.tagName+" "+this.square.getAttribute('id')
                 +" "+this.side+this.piece_code+"]";
    }
}

ChessPiece.prototype = {
    // Moves the draggable HTML Element to the specified coordinate
    reposition : function(coordinate) { 
        this.piece.style["top"] = coordinate.y + "px";  
        this.piece.style["left"] = coordinate.x + "px"; 
        this.piece.style["zIndex"] = 10;
    },

    // Called from the mousedown event handler when the user starts dragging
    // this element.
    dragStart : function(event) {
        var dragEvent = new DragEvent('draginit', event, this);
        this._initialMouseOffset = dragEvent.mouseOffset;
        this._grabOffset = dragEvent.mouseOffset.minus(dragEvent.topLeftOffset);
    },

    // Called from the mousemove event handler as the user drags 
    // this element.
    dragMove : function(event) {
	if (this.side == document.turn && !document.lookedAtSolution) {
            var dragMoveEvent = new DragEvent('dragmove', event, this);
            var newTopLeftOffset = dragMoveEvent.mouseOffset.minus(this._grabOffset);
            var dragDelta = newTopLeftOffset.minus(dragMoveEvent.topLeftOffset);
            this.reposition(dragMoveEvent.topLeftPosition.plus(dragDelta));
	}
    },

    // Called from mouseup event handler when the user drops this element at
    // the end of a drag.
    dragEnd : function(event) {
        var offset=ChessPiece.util.topLeftOffset(this.piece);
        var boardOffset = getBoardOffset();
        var file = getFile(Math.round((offset.x-boardOffset.x)/50));    
        var rank = getRank(Math.round((offset.y-boardOffset.y)/50));
        if (this.file==file && this.rank==rank) {
            this.reposition(new Coordinate(0,0));
            return;
        }
        this.doMove(file, rank);
    },

    doMove : function(file, rank) {
        var newSquare = document.getElementById(getId(file,rank)); 
        if (newSquare == null) {
            this.reposition(new Coordinate(0,0));
	    return;
        }
        var legalMove = this.isLegalMove(newSquare, file, rank);
        var legalCastle = false;

        // If it isn't a normal legal move, and it is a King move
        // it might be a valid castle.
        if (legalMove=="f" && this.piece_code=="K") {
            legalCastle = this.isLegalCastle(file, rank);
            if (legalCastle) {
                legalMove="t";
            }
        }       
        
        // If it is still not legal, give up and put the piece back where it was.
        if (legalMove=="f") {
            this.reposition(new Coordinate(0,0));
            return;
        }

	// Is this a promotion
	var promotion = (this.piece_code=="P" && rank==(this.side=="W"?7:0));

        // Make the move, check whether it leaves the side's own King in check.
        // If so, undo the move.  Otherwise, change the HTML display including adding
        // notation to the displayed move list.
        this.move(newSquare, file, rank, legalCastle, promotion);
    	if ((document.epd.Variant==undefined || document.epd.Variant!="maze") 
            && kingIsInCheck(this.side)) {
            this.undoMove();
            return;
        }

	var pv=true;
        if (typeof document.epd.pv == "string") {
    	   var san = newSquare.piece.getSAN(this,legalMove=="x",legalCastle,promotion);
	   var moves = document.epd.pv.split(" ");
	   var pv_move = moves[document.ply];
           if (pv_move!=san) {
		this.undoMove();
		return;
	   }
	   document.ply++;
	} else if (typeof document.epd.bm == "string") {
	    pv=false;
	    if (!newSquare.piece.isBestMove(this,legalMove=="x",legalCastle,promotion)) {
	        this.undoMove();
	        return;
            }
	}
	piece = newSquare.piece;
	this.square.innerHTML = '';
	newSquare.innerHTML = '<img src='+images_dir+'/'+piece.side+piece.piece_code+'.gif>';
	piece.piece = newSquare.childNodes[0];
	newSquare.piece.addMoveToList(this, legalMove=="x", legalCastle, promotion);
	setTurnIndicator();
	if (pv) 
	    getPV();
    },

    undoMove : function( ) {
        setPosition(document.prevPosition);
        document.prevPosition = null;
    },

    move : function(newSquare, file, rank, legalCastle, promotion) {
        document.prevPosition = getCurrentPosition();

        // Handle en-passant capture by pawn
        if (newSquare == document.enPassantTarget && this.piece_code=="P") {
            var captureSquare = document.getElementById(
                 getId(file,this.side=="W"?rank-1:rank+1));
            captureSquare.innerHTML =null;
            captureSquare.piece = null;
        }

        // Pawn promotion: always to Q
        if (promotion) {
            this.piece_code = 'Q';
        }           

        this.updateCastlingInfo(newSquare);
        this.updateEnPassantInfo(newSquare, file, rank);
        new ChessPiece(newSquare, this.side, this.piece_code);

        this.piece_code = null;
        this.square.piece = null;
        this.piece = null;
	if (document.epd.Variant == undefined || document.epd.Variant != "maze") 
            document.turn = this.side=="W"?"B":"W";

        // If it was a castle, we have to do the rook part.
        if (legalCastle) {
            var rookFrom, rookTo, toFile, toRank;
            if (newSquare.id=='c1') {
                rookFrom = 'a1';
                rookTo = 'd1';
                toFile = 3;
                toRank = 0;
            } else if (newSquare.id=='g1') {
                rookFrom = 'h1';
                rookTo = 'f1';
                toFile = 5;
                toRank = 0;
            } else if (newSquare.id=='c8') {
                rookFrom = 'a8';
                rookTo = 'd8';
                toFile = 3;
                toRank = 7;
            } else if (newSquare.id=='g8') {
                rookFrom = 'h8';
                rookTo = 'f8';
                toFile = 5;
                toRank = 7;
            }
            rookFrom = document.getElementById(rookFrom);
            rookTo = document.getElementById(rookTo);
            rookFrom.innerHTML = null;
            rookFrom.piece = null;
            rookTo.innerHTML = '<img src='+images_dir+'/'+this.side+'R.gif>';
            new ChessPiece(rookTo, this.side, 'R');
        }
    },

    // Checks whether a move would be legal.
    isLegalMove : function(newSquare, file, rank) {
        if (this.file == file && this.rank == rank)
            return false;
        var isCapture = this.isValidCapture(newSquare);
        if (isCapture=="f")
            return "f";
        return this.pieceCanMove(file, rank, isCapture)?(isCapture?"x":"t"):"f";
    },

    // Returns true if the piece can do the move.
    pieceCanMove : function(file, rank, isCapture) {
        var piece_code = this.piece_code;
        var legal = (piece_code=="B" && this.isLegalBishopMove(file,rank))
          || (piece_code=="R" && this.isLegalRookMove(file,rank))
          || (piece_code=="Q" && this.isLegalQueenMove(file,rank))
          || (piece_code=="K" && this.isLegalKingMove(file,rank))
          || (piece_code=="N" && this.isLegalKnightMove(file,rank))
          || (piece_code=="P" && this.isLegalPawnMove(file,rank,isCapture=="t"));
        return legal;
    },

    // Handles legal move checking for bishops
    isLegalBishopMove : function(file,rank) {
        return Math.abs(file-this.file)==Math.abs(rank-this.rank) 
        && this.lineIsOpen(file,rank);
    },

    // Handles legal move checking for rooks.
    isLegalRookMove : function(file,rank) {
        var legal = (file==this.file || rank==this.rank) && this.lineIsOpen(file,rank);
        return legal;
    },

    // Handles legal move checking for queens.
    isLegalQueenMove : function(file,rank) {
        return (Math.abs(file-this.file)==Math.abs(rank-this.rank)
                    || file==this.file || rank==this.rank) && this.lineIsOpen(file,rank);
    },

    // Handles legal move checking for knights.
    isLegalKnightMove : function(file, rank) {
        var fileDistance = Math.abs(file-this.file);
        var rankDistance = Math.abs(rank-this.rank);
        return (fileDistance>0 && rankDistance>0 && fileDistance+rankDistance==3);
    },

    // Handles legal move checking for kings.
    isLegalKingMove : function(file, rank) {
        var fdelta = file-this.file;
        var rdelta = rank-this.rank;
        var legal = (Math.abs(fdelta)<=1 && Math.abs(rdelta)<=1)
        return legal;
    },

    // Checks whether a castles move is legal
    isLegalCastle : function(file, rank) {
        var rdelta = rank-this.rank;
        var fdelta = file-this.file;
        if (rdelta != 0 || Math.abs(fdelta) != 2) {
            return false;
        } else {
            return fdelta==-2?castlingAvailable(this.side,"Q")
            :castlingAvailable(this.side,"K");
        }
    },

    // Handles legal move checking for pawns.
    isLegalPawnMove : function(file, rank, capture) {
        var deltar = this.rank - rank;
        var deltaf = this.file - file;
        var legal = false;

        if (!capture) {
            legal = deltaf==0 
                    && (   (this.side=="W" && (deltar==-1 || (this.rank==1 && deltar==-2)))
                         || (this.side=="B" && (deltar==1  || (this.rank==6 && deltar==2 ))))
                    && (deltar==-1 || deltar==1 || this.lineIsOpen(file,rank))
        } else {
            legal = Math.abs(deltaf)==1 && deltar==(this.side=="W"?-1:1);
        }
        return legal;
    },

    // Checks whether the squares between this square and the target square (but not
    // including the two squares) are unoccupied.
    lineIsOpen : function(file, rank) {
        var f=this.file;
        var r=this.rank;
        var f_incr = (f<file?1:(f>file?-1:0));
        var r_incr = (r<rank?1:(r>rank?-1:0));

        if (f != file)
            f += f_incr;
        if (r != rank)
            r += r_incr;
        while (f != file || r != rank) {
            if (isOccupied(f, r))
                return 0;
            if (f != file)
                f += f_incr;
            if (r != rank)
                r += r_incr;
        }
        return 1;
    },
        
    // This returns "t" if the specified sqaure is occupied by 
    // a piece of the opponent, "f" if the square is occupied
    // by a piece of the same side, and "" and if the square is empty.
    isValidCapture : function(square) {
        var legal = "";
        if (square && square.piece
            && typeof square.piece != "undefined" 
            && square.piece.side
            && typeof square.piece.side != "undefined") {
            legal = square.piece.side==this.side?"f":"t";
        } else if (this.piece_code=="P" && square==document.enPassantTarget) {
            legal = "t";
        }
        return legal;
    },

    // This updates the information as to the castling moves
    // that are still available to each side.  
    updateCastlingInfo : function(newSquare) {
        if (!newSquare) 
            return;

        // If the King moves that makes both O-O and O-O-O unavailable
        if (this.piece_code == 'K') {
            document.castling &= ~(this.side=="W"?3:12);

        // Any piece moving to or from h1, h8, a1, or a8 ends the option
        // to castle on that side.
        } else {
            var bit = 0;
            if      (this.square.id=='h1' || newSquare.id=='h1') { bit = 1; }
            else if (this.square.id=='a1' || newSquare.id=='a1') { bit = 2; }
            else if (this.square.id=='h8' || newSquare.id=='h8') { bit = 4; }
            else if (this.square.id=='a8' || newSquare.id=='a8') { bit = 8; }
            document.castling &= ~bit;
        }
    },
    
    updateEnPassantInfo : function(newSquare, file, rank) {
        if (this.piece_code == 'P' && Math.abs(this.rank-rank)==2) {
            document.enPassantTarget = 
                document.getElementById(getId(file,rank+(this.side=="B"?1:-1)));
        } else {
            document.enPassantTarget=null;
        }
    },
        
    getSAN : function(from, isCapture, castle, promotion) {
 	var piececode = promotion?"P":this.piece_code;
        if (castle) {
            return this.file==6? "O-O": "O-O-O";
        } else {
            return (
              piececode=="P"?(isCapture?getFileCode(from.file):""):this.piece_code) 
            + this.getSANDisambiguation(from, isCapture)
            + (isCapture?"x":"") 
            + this.square.id
            + (promotion?"=Q":"")
            + (kingIsInCheck(this.side=="W"?"B":"W")?"+":"");
        }
    },  

    getSANDisambiguation : function(from, isCapture) {
        var squares = document.getElementById("board").childNodes;
        var disambig = "";
        for (var i=0; i<64; i++) {
            if (squares[i] != this.square
                && squares[i].piece != undefined && squares[i].piece 
                && squares[i].piece.piece_code.toString() == this.piece_code.toString()
                && squares[i].piece.side==this.side) {
                from.square.piece = "X";
                if (squares[i].piece.pieceCanMove(this.file,this.rank)) {
                    if (squares[i].piece.file != from.file) {
                        disambig = getFileCode(from.file);
                    } else {
                        disambig = from.rank+1;
                    }
                }
                from.square.piece = null;
            }
        }
        return disambig;
    },

    isBestMove : function(from, isCapture, castle, promotion) {
        var san=this.getSAN(from, isCapture, castle, promotion);
        if (document.epd.bm != undefined && document.epd.bmlist == undefined) {
            document.epd.bmlist = document.epd.bm.split(/\s+/);
        }
        var found = false;
        for (var bm in document.epd.bmlist) {
            if (san == document.epd.bmlist[bm]) {
                found = true;
                break;
            }
        }
        document.tries += 1;
        if (!found) {
            logMessage("Sorry, "+san+" is not the solution. Try again.");
            return false;
        } else {
            var article = document.epd.bmlist.length>1? 'a': 'the';
            document.solved = 1;
            logMessage('<span id="congratulations">Congratulations!</span> '
                        +san+" is "+article+" solution!");
            window.setTimeout("nextPosition()",1500);
            return true;
        }
    },
        
    // This generates the Standard Algebraic Notation (SAN) for the move and
    // adds it to the HTML for the "movelist" element.
    addMoveToList : function(from, isCapture, castle, promotion) {
        var moveList = document.getElementById("movelist");
        if (moveList) {
            if (document.turn == "B") {
                moveList.innerHTML += " " + document.move_number++ +".";
            } else if (document.half_moves++ == 0) {
                moveList.innerHTML += document.move_number++ + "..";
            }
            moveList.innerHTML += " "+this.getSAN(from, isCapture, castle, promotion);
            document.half_moves++;
        }
    }
}

// This static method is registered as a mousedown handler on the HTML elements
// represented by ChessPieces. On mousedown it registers event handlers 
// on document to track the mouse during the drag.  In this code "this" 
// is the HTMLElement object.
ChessPiece.mouseDown = function(event) {
    ChessPiece.util.addListener(document, 'mousemove', ChessPiece.mouseMove );
    ChessPiece.util.addListener(document, 'mouseup', ChessPiece.mouseUp);
    document.onmousemove = function() { return false; }
    if (this.piece != null) {
        document.piece = this.piece;
        this.piece.dragStart(event);
    }
}


// This static method is set as the event handler for mousemove events 
// on "document" once a drag of a ChessPiece is started by the user.
// This just passes the event along to the ChessPiece for the element being
// dragged.   "this" == document at this point.
ChessPiece.mouseMove = function(event) {
    if (this.piece) {
        return this.piece.dragMove(event);
    }
}


// This static method is the event handler for mouseup events on "document", 
// registered at the start of the drag by startDrag. "this" == document.
ChessPiece.mouseUp = function(event) {
    ChessPiece.util.removeListener(document, 'mousemove', ChessPiece.mouseMove);
    ChessPiece.util.removeListener(document, 'mouseup',   ChessPiece.mouseUp);
    document.onmousemove = null;
    if (this.piece) {
        this.piece.dragEnd(event);
        this.piece = null;
    }
}

ChessPiece.util = {
    addListener : function(element, type, func) {
        if (!element) 
            return;
        if (element.addEventListener) {
            element.addEventListener(type, func, false);
        } else if (element.attachEvent) {
            if (!element._listeners) {
                element._listeners = new Array();
            }
            if (!element._listeners[type]) {
                element._listeners[type] = new Array();
            }
            var workaroundFunc = function() {
                func.apply(element, new Array());
            }
            element._listeners[type][func] = workaroundFunc;
            element.attachEvent('on' + type, workaroundFunc);
        }
   },

   removeListener : function (element, type, func) {
       if (element.removeEventListener) {
           element.removeEventListener(type, func, false);
       } else if (element.detachEvent) {
           if (element._listeners 
               && element._listeners[type] 
               && element._listeners[type][func]) {
               element.detachEvent('on' + type, element._listeners[type][func]);
           }
       }
    },

    readStyle : function(element, property) {
        if (element.style[property]) {
            return element.style[property];
        } else if (element.currentStyle) {
            return element.currentStyle[property];
        } else if (document.defaultView && document.defaultView.getComputedStyle) {
            var style = document.defaultView.getComputedStyle(element, null);
            return style.getPropertyValue(property);
        } else {
            return null
        }
    },

    topLeftPosition : function(element) {
        var left = parseInt(ChessPiece.util.readStyle(element, "left"));
        var left = isNaN(left) ? 0 : left;
        var top = parseInt(ChessPiece.util.readStyle(element, "top"));
        var top = isNaN(top) ? 0 : top;
        return new Coordinate(left, top);
    },

    topLeftOffset : function(element) {
        var offset = new Coordinate(element.offsetLeft, element.offsetTop); 
        var parent = element.offsetParent;
        while (parent) {
            offset = offset.plus(new Coordinate(parent.offsetLeft, parent.offsetTop));
            parent = parent.offsetParent;
        }
        return offset
    }
}

function DragEvent(type, event, square) {
    if (!event) {
        event = window.event;
    }
    if (event.target) {
        if (event.target.nodeType == 3) {
            event.target = event.target.parentNode;
        }
    } else if (event.srcElement) {
        event.target = event.srcElement;
    }

    this.type = type
    this.square = square;
    this.mousePosition =  new Coordinate(event.clientX, event.clientY);
    if (event.pageX >= 0 || event.pageX < 0) {
        this.mouseOffset =  new Coordinate(event.pageX, event.pageY);
    } else {
        var x, y;
        if (window.pageXOffset) {
            x = window.pageXOffset;
            y = window.pageYOffset;
        } else if (document.documentElement) {
            x = document.body.scrollLeft + document.documentElement.scrollLeft;
            y = document.body.scrollTop + document.documentElement.scrollTop;
        } else if (document.body.scrollLeft >= 0) {
            x = document.body.scrollLeft;
            y =  document.body.scrollTop;
        } else {
            x = 0;
            y = 0;
        }
        this.mouseOffset = this.mousePosition.plus(new Coordinate(x,y));
    } 
    this.topLeftPosition = ChessPiece.util.topLeftPosition(square.piece);
    this.topLeftOffset = ChessPiece.util.topLeftOffset(square.piece);
}

function Coordinate(x, y) {
    this.x = isNaN(x) ? 0 : x;
    this.y = isNaN(y) ? 0 : y;
    this.toString = function() { return "(" + this.x + "," + this.y + ")";  }
}

Coordinate.prototype = {
    plus : function(that) { 
        return new Coordinate(this.x + that.x, this.y + that.y) ;
    },
    minus : function(that) { 
        return new Coordinate(this.x - that.x, this.y - that.y) ;
    },
    toString : function() {
        return "["+this.x+","+this.y+"]"
    }
}

function isOccupied(file,rank) {
    var square = document.getElementById(getId(file,rank));
    occupied = square && !(typeof square.piece == "undefined" || square.piece == null);
    return occupied;
}


function kingIsInCheck(side) {

    // First, find the king.
    var squares = document.getElementById("board").childNodes;
    var kingSquare = null;
    for (var i=0; i<squares.length; i++) {
        if (typeof squares[i].piece!="undefined" && squares[i].piece 
            && squares[i].piece.side==side && squares[i].piece.piece_code=="K") {
            kingSquare = squares[i];
        break;
        }
    }
    if (kingSquare == null) {
        alert("Probable bug: where is the "+side+" King?");
        return 1;
    }      

    // Now loop through the squares again and check whether capturing
    // the king would be a legal move.  If so, the king is in check.
    var otherSide = side=="W"?"B":"W";  
    for (var i=0; i<squares.length; i++) {
        if (typeof squares[i].piece!="undefined" && squares[i].piece 
            && squares[i].piece.side==otherSide) {
            var piece = squares[i].piece;
            var file = kingSquare.piece.file;
            var rank = kingSquare.piece.rank;
            if (piece.isLegalMove(kingSquare,file,rank)!="f") {
                return 1;
            }
        }
    }
    return 0; 
}   

// This returns true if castling is allowed.
function castlingAvailable(side, KorQ) {
    var bit=(side=='W'?(KorQ=='K'?1:2):(KorQ='K'?4:8));
    return (document.castling & bit)!=0;
}

function getBoardOffset() {
    return ChessPiece.util.topLeftOffset(document.getElementById("board"));
}
  
// Puts the HTML for the board in the "board" div
function createChessboard() {
    var board=document.getElementById("board");
    var boardHTML = "";
    for (var i=0;i<64;i++) {
    var f = i%8;
    var r = Math.floor(i/8);
    var id = getId(getFile(f),getRank(r));
        boardHTML += '<li id="'+id+'" class="'
      +(r%2?(i%2?"lsquare":"dsquare"):(i%2?"dsquare":"lsquare"))+'"/>';
    }
    boardHTML += "</ul>";
    boardHTML += '<div class="clear"></div>';
    board.innerHTML = boardHTML;

    var squares	= document.getElementById("board").childNodes;
    for(var i=0;i<64;i++) {
	squares[i].index = i;
	squares[i].file = getFile(i%8);
	squares[i].rank = getRank(Math.floor(i/8));
    }
	
    if (document.epd.id != undefined) {
        var caption = document.getElementById('caption');
        if (caption != null) {
            caption.innerHTML = '<div class="caption">'+document.epd.id+'</div>';
        }
    }
    document.chessboardInited = true;
}

// Puts a position in FEN/EPD format onto the board.
function setPosition(epd) {
    if (epd == null) 
        return;

    // Split the EPD string.
    var fields = epd.split(/\s+/);
    var position=fields[0];

    document.turn = fields[1].toUpperCase();
    document.castling = 0;
    if (fields[2].indexOf('K') != -1)
        document.castling |= 1;
    if (fields[2].indexOf('Q') != -1)
        document.castling |= 2;
    if (fields[2].indexOf('k') != -1)
        document.castling |= 4;
    if (fields[2].indexOf('q') != -1)
        document.castling |= 8;
    if (fields[3] == "-") {
        document.enPassantTarget = null;
    } else {
        document.enPassantTarget = document.getElementById(fields[3]);
    }

    if (0) {
       document.halfMoveClock = fields[4];
       document.move_number = fields[5];
    } else {
        document.move_number = 0;
        documentHalfMoveClock = 0;
    }
    if (document.move_number == 0) 
        document.move_number = 1;
    document.half_moves = 0;

    // Parse the attributes.
    if (fields.length > 4) {
        var attribute="";
        var value="";
        for (var f=4; f<fields.length; f++) {
            if (attribute=="") {
                attribute=fields[f];
                value="";
            } else {
                var last = fields[f].length-1;
                var lastchar = fields[f].substring(last);
                if (value!="")
                    value += " ";
                if (lastchar==";") {
                    fields[f] = fields[f].substring(0,last);
                    value += fields[f];
                    setAttribute(attribute, value);     
                    attribute="";
                } else {
                    value += fields[f];
                }
            }
        }
        if (attribute != "") {
            setAttribute(attribute, value);
        }
    }   
    if (document.chessboardInited == undefined || !document.chessboardInited) {
        document.flipped = document.turn=="B";
        createChessboard();
    } else {    
        clearPosition();
    }

    // Parse the piece placement string, creating ChessPiece objects
    // and putting images on the board.
    var sq = 0;
    var empty = 0;
    var piece = "";
    var squares = document.getElementById("board").childNodes;
      for (var i=0; i<position.length; i++) {
        var c =position.charAt(i);

        switch(c) {
            case '/':
                break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                empty += parseInt(c);
                break;

            case 'R':
            case 'N':
            case 'B':
            case 'Q':
            case 'K':
            case 'P':
                side = 'W';
                piece = new String(c);
                break;

            case 'r': 
            case 'n':
            case 'b':
            case 'q':
            case 'k':
            case 'p':
                side = 'B';
                piece = new String(c).toUpperCase();      
                break;
        }
        if (piece) {
            sq += empty;
            var file = sq%8;
            var rank = 7-Math.floor(sq/8);
            var square = getSquare(file, rank); 
            square.innerHTML = '<img src='+images_dir+'/'+side+piece+'.gif>';
            new ChessPiece(square, side, piece);
            sq++;
            empty = 0;
            piece = null;
        }
    }
    setTurnIndicator();
}

// Returns a FEN string for the current position.
function getCurrentPosition( ) {
    var squares = document.getElementById("board").childNodes;
    var fen = ""
    var rank_squares = 0, empty = 0;

    for (var r=7;r>=0;r--) {
    for (var f=0;f<8;f++) {
        var square = document.getElementById(getId(f,r));
            if (square.piece) {
                if (empty) {
                    fen += empty;
                    empty = 0;
                }
                var piece = square.piece;
                fen += piece.side=="B"?piece.piece_code.toLowerCase():piece.piece_code;
             } else {
                empty++;
             }
        }
        if (empty) {
            fen += empty;
        }
        fen += '/';
        rank_squares = 0;
        empty = 0;
    }
    fen += " "+document.turn.toLowerCase();
    castling = getCastlingFEN();
    fen += " "+(castling==""?"-":castling);
    if (document.enPassantTarget && document.enPassantTarget!="") {
        fen += " "+document.enPassantTarget;
    } else {
        fen += " -";
    }
    //fen += " "+document.halfMoveClock;
    //fen += " "+document.move_number;
    return fen;
}

// This converts the castling information back to the FEN string
function getCastlingFEN() {
    var fen = "";
    if (document.castling & 1)
        fen += 'K';
    if (document.castling & 2)
        fen += 'Q';
    if (document.castling & 4)
        fen += 'k';
    if (document.castling & 8)
        fen += 'q';
    return fen;
}

function getSquare(file, rank) {
    return document.getElementById(getId(file,rank));
}

function getFile(f) {
    return document.flipped?7-f:f;
}

function getRank(r) {
    return document.flipped?r:7-r;
}

function getId(f,r) {
    return getFileCode(f)+(r+1);
}

function getFileCode(f) {
    var files= new String('abcdefgh');
    return files.substr(f,1);
}
    
function setAttribute(attribute,value) {
    if (document.epd == undefined)
        document.epd = new Object();
    eval("document.epd."+attribute+"='"+value+"'");
}

function setTurnIndicator() {
    var indicator = document.getElementById("turnindicator");
    var board = document.getElementById("board");
    var boardOffset = getBoardOffset();
    var fileletters = document.getElementById("fileletters");
    var rownumbers = document.getElementById("rownumbers");

    indicator.innerHTML = document.turn=="W"?'White to Move':'Black to Move';
    if (false) { 	
        indicator.style.top=document.flipped
         ?(document.turn=="B"? (boardOffset.y+430)+"px": boardOffset.y+"px")
         :(document.turn=="W"? (boardOffset.y+430)+"px": boardOffset.y+"px");
        indicator.style.left= (boardOffset.x+460)+"px";
    }
    if (document.flipped) {
        fileletters.innerHTML="h g f e d c b a";
        rownumbers.innerHTML="1 2 3 4 5 6 7 8";
    } else {
        fileletters.innerHTML="a b c d e f g h";
        rownumbers.innerHTML="8 7 6 5 4 3 2 1";
    }
}

// This dispays a message in the HTML of an element with the id "messages"
function logMessage(message) {
    var messages = document.getElementById("messages");
    messages.innerHTML = message;
}

// Clears the messages
function clearMessages() {
    var messages = document.getElementById("messages");
    messages.innerHTML = '&nbsp;';
}

function logCurrentPosition() {
    document.getElementById("messages").innerHTML += "<br>"+getCurrentPosition();
}

// Empties the board
function clearPosition() {
    var squares = document.getElementById("board").childNodes;
    for (var sq=0; sq<64; sq++) {
       squares[sq].innerHTML = '';          
       squares[sq].piece = null;
    }
}

// Gets the current index in the problem set from a cookie
function getCurrentPositionIndex(problemSet,userName) {
    var cookies = document.cookie;
    var cookieName = "PS_"+userName+"_"+problemSet;
    var pos = cookies.indexOf(cookieName+"=");
    var value = 0;
    if (pos != -1) {
        var start = pos+cookieName.length+1;
        var end = cookies.indexOf(';',start);
        if (end == -1)
            end = cookies.length;
        value = cookies.substring(start,end);
    }
    return value;
}

// Stores the current index in the problem set in a cookie
function storeCurrentPositionIndex(problemSet, userName, currentPosition) {
    var cookieName = "PS_"+userName+"_"+problemSet;
    var nextYear = new Date();
    nextYear.setFullYear(nextYear.getFullYear()+1);
    var cookieString = cookieName+"="+currentPosition+"; expires="+nextYear.toGMTString();
    document.cookie = cookieString;
}

// Show the solution.
function showSolution() {
    var solution = document.epd.pv != undefined? document.epd.pv: document.epd.bm; 	
    logMessage('The solution is: '+solution
    +'.<br/> Click Next to go to the next problem.');
    document.lookedAtSolution = true;
}

function firstPosition() {
    document.scoreUpdatedByServer = true;
    new Ajax.Request('hwajax',{
      method: 'get',
      parameters: "cmd=score;ps="+escape(problemSet),
      onComplete: function (req) {
	if (document.scoreUpdatedByServer) {
	  score.innerHTML = req.responseText;
        }
      }
    });
    document.problemCount=0;
    nextPosition();
}
    
function nextPosition() {
    var movelist = document.getElementById("movelist");
    var messages = document.getElementById("messages");
    var score = document.getElementById("score");

    // Let the server log the result of the last problem.
    if (document.epd != undefined
        && document.epd 
        && document.epd.id != undefined 
        && document.epd.id 
        && document.tries != undefined
        && document.tries >= 1) {

	var now = new Date();
	var milliseconds = now.getTime() - document.clockStart.getTime();
    	new Ajax.Request('hwajax',{
            method: 'get',
            parameters: "cmd=next;ps="+escape(problemSet)+";id="+escape(document.epd.id)
                        +";t="+document.tries+";s="+document.solved+";ms="+milliseconds,
            onComplete: function (req) {
		if (document.scoreUpdatedByServer) {
		    score.innerHTML = req.responseText;
                }
            }
    	});
    }

    // Clear the previous problem, and update the running score display
    if (movelist) 
        movelist.innerHTML="";

    if (score && !document.scoreUpdatedByServer) {
    	if (document.problemCount == undefined) {
            document.problemCount = 0;
            document.solvedCount = 0;
        }
        if (document.solved && document.tries<=2) 
            document.solvedCount++;
        score.innerHTML = "Score: "+document.solvedCount+"/"+document.problemCount;
    }

    // Get the next problem
    document.epd = null;
    document.chessboardInited = false;

    var currentPosition = getCurrentPositionIndex(problemSet, userName);
    if (currentPosition >= positions.length || currentPosition<0) 
	currentPosition = 0;
    setPosition(positions[currentPosition++]);
    storeCurrentPositionIndex(problemSet, userName, currentPosition);	
    document.ply = 0;
    document.tries = 0;
    document.solved = 0;
    document.clockStart = new Date();
    document.lookedAtSolution = false;
    document.problemCount++;
    document.getElementById('btnSolution').style.display=
  	(document.epd.bm == undefined? 'none': 'inline')

    var task="";
    if (document.epd != undefined 
       && document.epd.Task != undefined) {
        task=document.epd.Task;	
    } else {
	task="Make the best move.";
    }
    if (messages)
        messages.innerHTML=document.epd.id+" "+task+" To move, drag the piece with your mouse.";
}

function resetPosition() {
    var currentPosition = getCurrentPositionIndex(problemSet, userName);
    setPosition(positions[currentPosition-1]);
    logMessage("");
}


function getPV() {
    if (typeof document.epd.pv == "string") {
      var pv = document.epd.pv; 
      var moves = pv.split(" ");
      var next_move = moves[document.ply];
      if (next_move == undefined || next_move == "") {
	logMessage('<span id="congratulations">Congratulations!</span> You found the correct solution.');
    	document.solved = 1;
        document.tries += 1;
        window.setTimeout("nextPosition()",1500);
      } else if (document.epd.Variant == undefined || document.epd.Variant != "maze") {
	makeMove(document.turn,moves[document.ply]);
      }
    } else {
     new Ajax.Request('hwajax',{
        method: 'get',
        parameters: 'cmd=pv;pos='+escape(getCurrentPosition()),
        onComplete: function (req) {
          var response = req.responseText;
          var pv = response.substr(4,response.indexOf("</pv>")-4);
	  var moves = pv.split(" ");
          if (moves[0]=="") {
	    if (kingIsInCheck(document.turn)) {
               logMessage('<span id="congratulations">Congratulations!</span> Checkmate.');
               document.solved = 1;
               document.tries += 1;
               window.setTimeout("nextPosition()",1500);
             } else {
	       logMessage("<b>Stalemate. Try again.</b>");
	       document.tries += 1;
	       window.setTimeout("resetPosition()",1500);	
             }
          } else {
	     makeMove(document.turn, moves[0]);
	  }
        }
      });
   }
}
var sanMovePattern=/([RNBQK]?)([a-h]?[1-8]?)(x?)([a-h][1-8])+((=)([RNBQ]))?/;

function makeMove(side, san) {
    if (side=="B") {
	if (san=="O-O") 
	    san = "Kg8";
        else if (san=="O-O-O")
	    san = "Kb8";
    } else {
	if (san=="O-O")
	    san = "Kg1";
        else if (san=="O-O-O")
	    san = "Kb1";
    }

    var move_data = sanMovePattern.exec(san);
    var toSquare = document.getElementById(move_data[4]);
    var piece_code = move_data[1];
    var disambig = move_data[2];
    var squares = document.getElementById("board").childNodes;		


    if (piece_code=="") 
        piece_code = "P";	

    for (var i=0; i<squares.length; i++) {
	if (typeof squares[i].piece!="undefined" 
            && squares[i].piece
            && squares[i].piece.side==side 
            && squares[i].piece.piece_code==piece_code
             && (disambig=="" 
               || squares[i].rank+1==disambig 
               || getFileCode(squares[i].file)==disambig)) {

	    var piece = squares[i].piece;
	    var file = toSquare.file;
	    var rank = toSquare.rank;
	    var legalMove = piece.isLegalMove(toSquare,file,rank);
	    var legalCastle = 0;
            if (legalMove=="f" && piece_code=="K") {
            	legalCastle = piece.isLegalCastle(file, rank);
            	if (legalCastle)
                    legalMove="t";
            }       
	    if (legalMove!="f") {
                piece.move(toSquare, file, rank, legalCastle, 0);
                piece.square.innerHTML = '';
                toSquare.innerHTML = '<img src='+images_dir+'/'+piece.side+piece_code+'.gif>';
                piece.piece = toSquare.childNodes[0];
                toSquare.piece.addMoveToList(piece, 0, 0,0);
	        document.ply++;
                setTurnIndicator();
	        return;
	    } 
        }
    }
    alert(san+" is not a legal move for "+side);		
}

function showScores() {
    document.location="/hwtopscores?ps="+problemSet;
}




