/*
    DroidFish - An Android chess program.
    Copyright (C) 2011  Peter Österlund, peterosterlund2@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.petero.droidfish.gamelogic;

import java.util.ArrayList;
import java.util.List;

import junit.framework.TestCase;

public class PositionTest extends TestCase {

    public PositionTest() {
    }

    public void testGetPiece() throws ChessParseError {
        Position pos = new Position();
        int result = pos.getPiece(0);
        assertEquals(result, Piece.EMPTY);

        pos = TextIO.readFEN(TextIO.startPosFEN);
        result = pos.getPiece(0);
        assertEquals(result, Piece.WROOK);
        for (int x = 0; x < 8; x++) {
            for (int y = 0; y < 2; y++) {
                int p1 = pos.getPiece(Position.getSquare(x, y));
                int p2 = pos.getPiece(Position.getSquare(x, 7-y));
                int bwDiff = Piece.BPAWN - Piece.WPAWN;
                assertEquals(p2, p1 + bwDiff);
            }
        }
    }

    public void testGetIndex() {
        for (int x = 0; x < 8; x++) {
            for (int y = 0; y < 8; y++) {
                int sq = Position.getSquare(x, y);
                int x2 = Position.getX(sq);
                int y2 = Position.getY(sq);
                assertEquals(x, x2);
                assertEquals(y, y2);
            }
        }
    }

    public void testSetPiece() {
        Position instance = new Position();
        assertEquals(Piece.EMPTY, instance.getPiece(Position.getSquare(0, 0)));
        instance.setPiece(Position.getSquare(3, 4), Piece.WKING);
        assertEquals(Piece.WKING, instance.getPiece(Position.getSquare(3, 4)));
    }

    public void testMakeMove() throws ChessParseError {
        Position pos = TextIO.readFEN(TextIO.startPosFEN);
        Position origPos = new Position(pos);
        assertTrue(pos.equals(origPos));
        Move move = new Move(Position.getSquare(4,1), Position.getSquare(4,3), Piece.EMPTY);
        UndoInfo ui = new UndoInfo();
        pos.makeMove(move, ui);
        assertEquals(pos.whiteMove, false);
        assertEquals(-1, pos.getEpSquare());
        assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(4,1)));
        assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(4,3)));
        assertTrue(!pos.equals(origPos));
        int castleMask = (1 << Position.A1_CASTLE) |
                         (1 << Position.H1_CASTLE) |
                         (1 << Position.A8_CASTLE) |
                         (1 << Position.H8_CASTLE);
        assertEquals(castleMask,pos.getCastleMask());
        pos.unMakeMove(move, ui);
        assertEquals(pos.whiteMove, true);
        assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(4,1)));
        assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(4,3)));
        assertTrue(pos.equals(origPos));

        String fen = "r1bqk2r/2ppbppp/p1n2n2/1pP1p3/B3P3/5N2/PP1P1PPP/RNBQK2R w KQkq b6 0 2";
        pos = TextIO.readFEN(fen);
        assertEquals(fen, TextIO.toFEN(pos));
        origPos = new Position(pos);
        assertEquals(Position.getSquare(1,5), pos.getEpSquare());

        // Test capture
        move = new Move(Position.getSquare(0, 3), Position.getSquare(1,4), Piece.EMPTY);
        pos.makeMove(move, ui);
        assertEquals(-1, pos.getEpSquare());
        assertEquals(Piece.WBISHOP, pos.getPiece(Position.getSquare(1,4)));
        assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(0,3)));
        pos.unMakeMove(move, ui);
        assertTrue(pos.equals(origPos));

        // Test castling
        move = new Move(Position.getSquare(4, 0), Position.getSquare(6,0), Piece.EMPTY);
        pos.makeMove(move, ui);
        assertEquals(Piece.WROOK, pos.getPiece(Position.getSquare(5,0)));
        assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(7,0)));
        castleMask = (1 << Position.A8_CASTLE) |
                     (1 << Position.H8_CASTLE);
        assertEquals(castleMask,pos.getCastleMask());
        assertEquals(-1, pos.getEpSquare());
        pos.unMakeMove(move, ui);
        assertTrue(pos.equals(origPos));

        // Test castling rights (king move)
        move = new Move(Position.getSquare(4, 0), Position.getSquare(4,1), Piece.EMPTY);
        pos.makeMove(move, ui);
        castleMask = (1 << Position.A8_CASTLE) |
                     (1 << Position.H8_CASTLE);
        assertEquals(castleMask,pos.getCastleMask());
        assertEquals(-1, pos.getEpSquare());
        pos.unMakeMove(move, ui);
        assertTrue(pos.equals(origPos));

        // Test castling rights (rook move)
        move = new Move(Position.getSquare(7, 0), Position.getSquare(6,0), Piece.EMPTY);
        pos.makeMove(move, ui);
        castleMask = (1 << Position.A1_CASTLE) |
                     (1 << Position.A8_CASTLE) |
                     (1 << Position.H8_CASTLE);
        assertEquals(castleMask,pos.getCastleMask());
        assertEquals(-1, pos.getEpSquare());
        pos.unMakeMove(move, ui);
        assertTrue(pos.equals(origPos));

        // Test en passant
        move = new Move(Position.getSquare(2, 4), Position.getSquare(1,5), Piece.EMPTY);
        pos.makeMove(move, ui);
        assertEquals(Piece.WPAWN, pos.getPiece(Position.getSquare(1,5)));
        assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(2,4)));
        assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,4)));
        pos.unMakeMove(move, ui);
        assertTrue(pos.equals(origPos));

        // Test castling rights loss when rook captured
        pos.setPiece(Position.getSquare(6,2), Piece.BKNIGHT);
        pos.setWhiteMove(false);
        Position origPos2 = new Position(pos);
        move = new Move(Position.getSquare(6,2), Position.getSquare(7,0), Piece.EMPTY);
        pos.makeMove(move, ui);
        castleMask = (1 << Position.A1_CASTLE) |
                     (1 << Position.A8_CASTLE) |
                     (1 << Position.H8_CASTLE);
        assertEquals(castleMask,pos.getCastleMask());
        assertEquals(-1, pos.getEpSquare());
        pos.unMakeMove(move, ui);
        assertTrue(pos.equals(origPos2));
    }

    public void testPromotion() throws ChessParseError {
        String fen = "r1bqk2r/1Pppbppp/p1n2n2/2P1p3/B3P3/5N2/Pp1P1PPP/R1BQK2R w KQkq - 0 1";
        Position pos = TextIO.readFEN(fen);
        assertEquals(fen, TextIO.toFEN(pos));
        Position origPos = new Position(pos);
        assertEquals(origPos, pos);

        Move move = new Move(Position.getSquare(1, 6), Position.getSquare(0,7), Piece.WQUEEN);
        UndoInfo ui = new UndoInfo();
        pos.makeMove(move, ui);
        assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,6)));
        assertEquals(Piece.WQUEEN, pos.getPiece(Position.getSquare(0,7)));
        pos.unMakeMove(move, ui);
        assertEquals(origPos, pos);

        move = new Move(Position.getSquare(1, 6), Position.getSquare(1,7), Piece.WKNIGHT);
        ui = new UndoInfo();
        pos.makeMove(move, ui);
        assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,6)));
        assertEquals(Piece.WKNIGHT, pos.getPiece(Position.getSquare(1,7)));
        pos.unMakeMove(move, ui);
        assertEquals(origPos, pos);

        pos.setWhiteMove(false);
        origPos = new Position(pos);

        move = new Move(Position.getSquare(1, 1), Position.getSquare(2, 0), Piece.BROOK);
        ui = new UndoInfo();
        pos.makeMove(move, ui);
        assertEquals(Piece.EMPTY, pos.getPiece(Position.getSquare(1,1)));
        assertEquals(Piece.BROOK, pos.getPiece(Position.getSquare(2,0)));
        pos.unMakeMove(move, ui);
        assertEquals(origPos, pos);
    }

    public void testMoveCounters() throws ChessParseError {
        String fen = "r1bqk2r/2ppbppp/p1n2n2/1pP1p3/B3P3/5N2/PP1P1PPP/RNBQK2R w KQkq b6 0 7";
        Position pos = TextIO.readFEN(fen);

        Move move = TextIO.stringToMove(pos, "Nc3");
        UndoInfo ui = new UndoInfo();
        pos.makeMove(move, ui);
        assertEquals(1, pos.halfMoveClock);
        assertEquals(7, pos.fullMoveCounter);
        pos.unMakeMove(move, ui);

        move = TextIO.stringToMove(pos, "O-O");
        pos.makeMove(move, ui);
        assertEquals(1, pos.halfMoveClock);     // Castling does not reset 50 move counter
        assertEquals(7, pos.fullMoveCounter);
        pos.unMakeMove(move, ui);

        move = TextIO.stringToMove(pos, "a3");
        pos.makeMove(move, ui);
        assertEquals(0, pos.halfMoveClock);     // Pawn move resets 50 move counter
        assertEquals(7, pos.fullMoveCounter);
        pos.unMakeMove(move, ui);

        move = TextIO.stringToMove(pos, "Nxe5");
        pos.makeMove(move, ui);
        assertEquals(0, pos.halfMoveClock);     // Capture move resets 50 move counter
        assertEquals(7, pos.fullMoveCounter);
        pos.unMakeMove(move, ui);

        move = TextIO.stringToMove(pos, "cxb6");
        pos.makeMove(move, ui);
        assertEquals(0, pos.halfMoveClock);     // EP capture move resets 50 move counter
        assertEquals(7, pos.fullMoveCounter);
        pos.unMakeMove(move, ui);

        move = TextIO.stringToMove(pos, "Kf1");
        pos.makeMove(move, ui);
        assertEquals(1, pos.halfMoveClock);     // Loss of castling rights does not reset 50 move counter
        assertEquals(7, pos.fullMoveCounter);
        pos.unMakeMove(move, ui);

        Move firstMove = TextIO.stringToMove(pos, "Nc3");
        UndoInfo firstUi = new UndoInfo();
        pos.makeMove(move, firstUi);
        move = TextIO.stringToMove(pos, "O-O");
        pos.makeMove(move, ui);
        assertEquals(2, pos.halfMoveClock);
        assertEquals(8, pos.fullMoveCounter);   // Black move increases fullMoveCounter
        pos.unMakeMove(move, ui);
        pos.unMakeMove(firstMove, firstUi);

        fen = "8/8/8/4k3/8/8/2p5/5K2 b - - 47 68";
        pos = TextIO.readFEN(fen);
        move = TextIO.stringToMove(pos, "c1Q");
        pos.makeMove(move, ui);
        assertEquals(0, pos.halfMoveClock);     // Pawn promotion resets 50 move counter
        assertEquals(69, pos.fullMoveCounter);
    }

    public void testDrawRuleEquals() throws ChessParseError {
        Position pos = TextIO.readFEN(TextIO.startPosFEN);
        Position origPos = new Position(pos);
        UndoInfo ui = new UndoInfo();
        pos.makeMove(TextIO.stringToMove(pos, "Nf3"), ui);
        assertEquals(false, pos.drawRuleEquals(origPos));
        pos.makeMove(TextIO.stringToMove(pos, "Nf6"), ui);
        assertEquals(false, pos.drawRuleEquals(origPos));
        pos.makeMove(TextIO.stringToMove(pos, "Ng1"), ui);
        assertEquals(false, pos.drawRuleEquals(origPos));
        pos.makeMove(TextIO.stringToMove(pos, "Ng8"), ui);
        assertEquals(true, pos.drawRuleEquals(origPos));
        assertEquals(false, pos.equals(origPos));       // Move counters have changed

        String fen = "r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1";
        pos = TextIO.readFEN(fen);
        origPos = new Position(pos);
        pos.makeMove(TextIO.stringToMove(pos, "Ke2"), ui);
        assertEquals(false, pos.drawRuleEquals(origPos));
        pos.makeMove(TextIO.stringToMove(pos, "Be7"), ui);
        assertEquals(false, pos.drawRuleEquals(origPos));
        pos.makeMove(TextIO.stringToMove(pos, "Ke1"), ui);
        assertEquals(false, pos.drawRuleEquals(origPos));
        pos.makeMove(TextIO.stringToMove(pos, "Bf8"), ui);
        assertEquals(false, pos.drawRuleEquals(origPos));   // Not equal, castling rights lost

        pos = TextIO.readFEN(TextIO.startPosFEN);
        pos.makeMove(TextIO.stringToMove(pos, "c4"), ui);
        pos.makeMove(TextIO.stringToMove(pos, "a6"), ui);
        pos.makeMove(TextIO.stringToMove(pos, "c5"), ui);
        pos.makeMove(TextIO.stringToMove(pos, "b5"), ui);
        assertEquals(Position.getSquare(1, 5), pos.getEpSquare());
        origPos = new Position(pos);
        pos.makeMove(TextIO.stringToMove(pos, "Nc3"), ui);
        pos.makeMove(TextIO.stringToMove(pos, "Nc6"), ui);
        pos.makeMove(TextIO.stringToMove(pos, "Nb1"), ui);
        pos.makeMove(TextIO.stringToMove(pos, "Nb8"), ui);
        assertEquals(false, pos.drawRuleEquals(origPos));   // Not equal, en passant rights lost
    }

    public void testHashCode() throws ChessParseError {
        Position pos = TextIO.readFEN(TextIO.startPosFEN);
        long h1 = pos.zobristHash();
        assertEquals(h1, pos.computeZobristHash());
        UndoInfo ui = new UndoInfo();
        Move move = TextIO.stringToMove(pos, "e4");
        pos.makeMove(move, ui);
        assertTrue(h1 != pos.zobristHash());
        pos.unMakeMove(move, ui);
        assertTrue(h1 == pos.zobristHash());

        pos.setWhiteMove(!pos.whiteMove);
        long h4 = pos.zobristHash();
        assertEquals(h4, pos.computeZobristHash());
        assertTrue(h1 != pos.zobristHash());
        pos.setWhiteMove(!pos.whiteMove);
        assertTrue(h1 == pos.zobristHash());

        pos.setCastleMask(0);
        assertTrue(h1 != pos.zobristHash());

        pos = TextIO.readFEN("rnbqkbnr/pppp1ppp/8/2P1p3/8/8/PP1PPPPP/RNBQKBNR b KQkq - 0 1");
        h1 = pos.zobristHash();
        assertEquals(h1, pos.computeZobristHash());

        String[] moves = {
            "b5", "Nc3", "Nf6", "Nb1", "Ng8", "Nc3", "Nf6", "Nb1", "Ng8", "Nc3", "d5",
            "cxd6", "Qxd6", "h4", "Be6", "h5", "Nc6", "h6", "o-o-o", "hxg7", "Nf6", "gxh8Q", "Be7"
        };
        List<UndoInfo> uiList = new ArrayList<>();
        List<Long> hashList = new ArrayList<>();
        List<Move> moveList = new ArrayList<>();
        for (int i = 0; i < moves.length; i++) {
            uiList.add(new UndoInfo());
            Move m = TextIO.stringToMove(pos, moves[i]);
            moveList.add(m);
            pos.makeMove(m, uiList.get(i));
            long h = pos.zobristHash();
            assertEquals(h, pos.computeZobristHash());
            hashList.add(h);
        }
        assertTrue(!hashList.get(0).equals(hashList.get(4)));
        assertTrue(hashList.get(4).equals(hashList.get(8)));
        for (int i = moves.length - 1; i >= 0; i--) {
            pos.unMakeMove(moveList.get(i), uiList.get(i));
            long h = pos.zobristHash();
            assertEquals(h, pos.computeZobristHash());
            assertEquals(h, i > 0 ? (long)hashList.get(i - 1) : h1);
        }
    }

    public void testGetKingSq() throws ChessParseError {
        Position pos = TextIO.readFEN(TextIO.startPosFEN);
        assertEquals(TextIO.getSquare("e1"), pos.getKingSq(true));
        assertEquals(TextIO.getSquare("e8"), pos.getKingSq(false));
        pos = TextIO.readFEN("r1bq1bnr/ppppkppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQ - 0 4");
        assertEquals(TextIO.getSquare("e1"), pos.getKingSq(true));
        assertEquals(TextIO.getSquare("e7"), pos.getKingSq(false));
        UndoInfo ui = new UndoInfo();
        pos.makeMove(TextIO.stringToMove(pos, "o-o"), ui);
        assertEquals(TextIO.getSquare("g1"), pos.getKingSq(true));
        assertEquals(TextIO.getSquare("e7"), pos.getKingSq(false));
        pos.makeMove(TextIO.stringToMove(pos, "Kd6"), ui);
        assertEquals(TextIO.getSquare("g1"), pos.getKingSq(true));
        assertEquals(TextIO.getSquare("d6"), pos.getKingSq(false));
    }

    public void testNullMove() throws ChessParseError {
        Move nullMove = new Move(0, 0, 0);
        UndoInfo ui = new UndoInfo();

        Position pos = TextIO.readFEN(TextIO.startPosFEN);
        Position origPos = new Position(pos);
        pos.makeMove(nullMove, ui);
        assertEquals(false, pos.whiteMove);
        assertEquals(true, pos.a1Castle());
        assertEquals(Piece.WROOK, pos.getPiece(0));
        pos.unMakeMove(nullMove, ui);
        assertTrue(pos.equals(origPos));

        pos = TextIO.readFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w Kkq - 5 1");
        origPos = new Position(pos);
        pos.makeMove(nullMove, ui);
        assertEquals(false, pos.whiteMove);
        assertEquals(false, pos.a1Castle());
        assertEquals(Piece.WROOK, pos.getPiece(0));
        assertEquals(0, pos.halfMoveClock);
        pos.unMakeMove(nullMove, ui);
        assertTrue(pos.equals(origPos));

        pos = TextIO.readFEN("7k/8/8/8/8/8/4P3/K7 w - - 5 1");
        origPos = new Position(pos);
        pos.makeMove(nullMove, ui);
        assertEquals(false, pos.whiteMove);
        assertEquals(false, pos.a1Castle());
        assertEquals(0, pos.halfMoveClock);
        assertEquals(0, pos.getKingSq(true));
        assertEquals(63, pos.getKingSq(false));

        UndoInfo ui2 = new UndoInfo();
        pos.makeMove(nullMove, ui2);
        assertEquals(true, pos.whiteMove);
        assertEquals(false, pos.a1Castle());
        assertEquals(0, pos.halfMoveClock);
        assertEquals(0, pos.getKingSq(true));
        assertEquals(63, pos.getKingSq(false));

        pos.unMakeMove(nullMove, ui2);
        assertEquals(false, pos.whiteMove);
        assertEquals(false, pos.a1Castle());
        assertEquals(0, pos.halfMoveClock);
        assertEquals(0, pos.getKingSq(true));
        assertEquals(63, pos.getKingSq(false));

        pos.unMakeMove(nullMove, ui);
        assertTrue(pos.equals(origPos));

        pos = TextIO.readFEN("5k2/8/4R1K1/8/8/8/8/8 b - - 5 1");
        origPos = new Position(pos);
        pos.makeMove(nullMove, ui);
        assertEquals(true, pos.whiteMove);
        assertEquals(false, pos.a1Castle());
        assertEquals(0, pos.halfMoveClock);

        pos.unMakeMove(nullMove, ui);
        assertTrue(pos.equals(origPos));
    }
}
