/*
    CuckooChess - A java 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 chess;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;

public class TextIOTest {

    public TextIOTest() {
    }

    @BeforeClass
    public static void setUpClass() {
    }

    @AfterClass
    public static void tearDownClass() {
    }

    @Before
    public void setUp() {
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testReadFEN() throws ChessParseError {
        System.out.println("readFEN");
        String fen = "rnbqk2r/1p3ppp/p7/1NpPp3/QPP1P1n1/P4N2/4KbPP/R1B2B1R b kq - 0 1";
        Position pos = TextIO.readFEN(fen);
        assertEquals(fen, TextIO.toFEN(pos));
        assertEquals(pos.getPiece(Position.getSquare(0, 3)), Piece.WQUEEN);
        assertEquals(pos.getPiece(Position.getSquare(4, 7)), Piece.BKING);
        assertEquals(pos.getPiece(Position.getSquare(4, 1)), Piece.WKING);
        assertEquals(pos.whiteMove, false);
        assertEquals(pos.a1Castle(), false);
        assertEquals(pos.h1Castle(), false);
        assertEquals(pos.a8Castle(), true);
        assertEquals(pos.h8Castle(), true);

        fen = "8/3k4/8/5pP1/1P6/1NB5/2QP4/R3K2R w KQ f6 1 2";
        pos = TextIO.readFEN(fen);
        assertEquals(fen, TextIO.toFEN(pos));
        assertEquals(1, pos.halfMoveClock);
        assertEquals(2, pos.fullMoveCounter);

        // Must have exactly one king
        boolean wasError = testFENParseError("8/8/8/8/8/8/8/kk1K4 w - - 0 1");
        assertEquals(true, wasError);

        // Must not be possible to capture the king
        wasError = testFENParseError("8/8/8/8/8/8/8/k1RK4 w - - 0 1");
        assertEquals(true, wasError);
        
        // Make sure bogus en passant square information is removed
        fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1";
        pos = TextIO.readFEN(fen);
        assertEquals(-1, pos.getEpSquare());
        
        // Test for too many rows (slashes)
        wasError = testFENParseError("8/8/8/8/4k3/8/8/8/KBN5 w - - 0 1");
        assertEquals(true, wasError);
        
        // Test for too many columns
        wasError = testFENParseError("8K/8/8/8/4k3/8/8/8 w - - 0 1");
        assertEquals(true, wasError);
        
        // Pawns must not be on first/last rank
        wasError = testFENParseError("kp6/8/8/8/8/8/8/K7 w - - 0 1");
        assertEquals(true, wasError);
        
        wasError = testFENParseError("kr/pppp/8/8/8/8/8/KBR w");
        assertEquals(false, wasError);  // OK not to specify castling flags and ep square
        
        wasError = testFENParseError("k/8/8/8/8/8/8/K");
        assertEquals(true, wasError);   // Error side to move not specified
        
        wasError = testFENParseError("");
        assertEquals(true, wasError);

        wasError = testFENParseError("    |");
        assertEquals(true, wasError);

        wasError = testFENParseError("1B1B4/6k1/7r/7P/6q1/r7/q7/7K b - - acn 6; acs 0;");
        assertEquals(false, wasError);  // Extra stuff after FEN string is allowed
    }

    /** Tests if trying to parse a FEN string causes an error. */
    private boolean testFENParseError(String fen) {
        boolean wasError;
        wasError = false;
        try {
            TextIO.readFEN(fen);
        } catch (ChessParseError err) {
            wasError = true;
        }
        return wasError;
    }
    
    @Test
    public void testMoveToString() throws ChessParseError {
        System.out.println("moveToString");
        Position pos = TextIO.readFEN(TextIO.startPosFEN);
        assertEquals(TextIO.startPosFEN, TextIO.toFEN(pos));
        Move move = new Move(Position.getSquare(4, 1), Position.getSquare(4, 3),
                Piece.EMPTY);
        boolean longForm = true;
        String result = TextIO.moveToString(pos, move, longForm);
        assertEquals("e2-e4", result);

        move = new Move(Position.getSquare(6, 0), Position.getSquare(5, 2), Piece.EMPTY);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("Ng1-f3", result);
        
        move = new Move(Position.getSquare(4, 7), Position.getSquare(2, 7),
                Piece.EMPTY);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("O-O-O", result);

        String fen = "1r3k2/2P5/8/8/8/4K3/8/8 w - - 0 1";
        pos = TextIO.readFEN(fen);
        assertEquals(fen, TextIO.toFEN(pos));
        move = new Move(Position.getSquare(2,6), Position.getSquare(1,7), Piece.WROOK);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("c7xb8R+", result);

        move = new Move(Position.getSquare(2,6), Position.getSquare(2,7), Piece.WKNIGHT);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("c7-c8N", result);
        
        move = new Move(Position.getSquare(2,6), Position.getSquare(2,7), Piece.WQUEEN);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("c7-c8Q+", result);
    }

    @Test
    public void testMoveToStringMate() throws ChessParseError {
        System.out.println("moveToStringMate");
        Position pos = TextIO.readFEN("3k4/1PR5/3N4/8/4K3/8/8/8 w - - 0 1");
        boolean longForm = true;

        Move move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WROOK);
        String result = TextIO.moveToString(pos, move, longForm);
        assertEquals("b7-b8R+", result);    // check
        
        move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WQUEEN);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("b7-b8Q#", result);    // check mate
        
        move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WKNIGHT);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("b7-b8N", result);

        move = new Move(Position.getSquare(1, 6), Position.getSquare(1, 7), Piece.WBISHOP);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("b7-b8B", result);     // stalemate
    }

    @Test
    public void testMoveToStringShortForm() throws ChessParseError {
        System.out.println("moveToStringShortForm");
        String fen = "r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1";
        Position pos = TextIO.readFEN(fen);
        assertEquals(fen, TextIO.toFEN(pos));
        boolean longForm = false;
        
        Move move = new Move(Position.getSquare(4,5), Position.getSquare(4,3), Piece.EMPTY);
        String result = TextIO.moveToString(pos, move, longForm);
        assertEquals("Qee4", result);   // File disambiguation needed

        move = new Move(Position.getSquare(2,5), Position.getSquare(4,3), Piece.EMPTY);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("Qc6e4", result);  // Full disambiguation needed

        move = new Move(Position.getSquare(2,3), Position.getSquare(4,3), Piece.EMPTY);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("Q4e4", result);   // Row disambiguation needed

        move = new Move(Position.getSquare(2,3), Position.getSquare(2,0), Piece.EMPTY);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("Qc1+", result);   // No disambiguation needed

        move = new Move(Position.getSquare(0,1), Position.getSquare(0,0), Piece.BQUEEN);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("a1Q", result);    // Normal promotion

        move = new Move(Position.getSquare(0,1), Position.getSquare(1,0), Piece.BQUEEN);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("axb1Q#", result); // Capture promotion and check mate

        move = new Move(Position.getSquare(0,1), Position.getSquare(1,0), Piece.BKNIGHT);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("axb1N", result);  // Capture promotion

        move = new Move(Position.getSquare(3,6), Position.getSquare(4,4), Piece.EMPTY);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("Ne5", result);    // Other knight pinned, no disambiguation needed

        move = new Move(Position.getSquare(7,6), Position.getSquare(7,4), Piece.EMPTY);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("h5", result);     // Regular pawn move
        
        move = new Move(Position.getSquare(5,7), Position.getSquare(3,7), Piece.EMPTY);
        result = TextIO.moveToString(pos, move, longForm);
        assertEquals("Rfd8", result);     // File disambiguation needed
    }

    @Test
    public void testStringToMove() throws ChessParseError {
        System.out.println("stringToMove");
        Position pos = TextIO.readFEN("r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1");

        Move mNe5 = new Move(Position.getSquare(3, 6), Position.getSquare(4, 4), Piece.EMPTY);
        Move m = TextIO.stringToMove(pos, "Ne5");
        assertEquals(mNe5, m);
        m = TextIO.stringToMove(pos, "ne");
        assertEquals(mNe5, m);
        m = TextIO.stringToMove(pos, "N");
        assertEquals(null, m);
        
        Move mQc6e4 = new Move(Position.getSquare(2, 5), Position.getSquare(4, 3), Piece.EMPTY);
        m = TextIO.stringToMove(pos, "Qc6-e4");
        assertEquals(mQc6e4, m);
        m = TextIO.stringToMove(pos, "Qc6e4");
        assertEquals(mQc6e4, m);
        m = TextIO.stringToMove(pos, "Qce4");
        assertEquals(null, m);
        m = TextIO.stringToMove(pos, "Q6e4");
        assertEquals(null, m);

        Move maxb1Q = new Move(Position.getSquare(0, 1), Position.getSquare(1, 0), Piece.BQUEEN);
        m = TextIO.stringToMove(pos, "axb1Q");
        assertEquals(maxb1Q, m);
        m = TextIO.stringToMove(pos, "axb1Q#");
        assertEquals(maxb1Q, m);
        m = TextIO.stringToMove(pos, "axb1Q+");
        assertEquals(null, m);
        
        Move mh5= new Move(Position.getSquare(7, 6), Position.getSquare(7, 4), Piece.EMPTY);
        m = TextIO.stringToMove(pos, "h5");
        assertEquals(mh5, m);
        m = TextIO.stringToMove(pos, "h7-h5");
        assertEquals(mh5, m);
        m = TextIO.stringToMove(pos, "h");
        assertEquals(null, m);

        pos = TextIO.readFEN("r1b1k2r/1pqpppbp/p5pn/3BP3/8/2pP4/PPPBQPPP/R3K2R w KQkq - 0 12");
        m = TextIO.stringToMove(pos, "bxc3");
        assertEquals(TextIO.getSquare("b2"), m.from);
        m = TextIO.stringToMove(pos, "Bxc3");
        assertEquals(TextIO.getSquare("d2"), m.from);
        m = TextIO.stringToMove(pos, "bxc");
        assertEquals(TextIO.getSquare("b2"), m.from);
        m = TextIO.stringToMove(pos, "Bxc");
        assertEquals(TextIO.getSquare("d2"), m.from);
        
        // Test castling. o-o is a substring of o-o-o, which could cause problems.
        pos = TextIO.readFEN("5k2/p1pQn3/1p2Bp1r/8/4P1pN/2N5/PPP2PPP/R3K2R w KQ - 0 16");
        Move kCastle = new Move(Position.getSquare(4,0), Position.getSquare(6,0), Piece.EMPTY);
        Move qCastle = new Move(Position.getSquare(4,0), Position.getSquare(2,0), Piece.EMPTY);
        m = TextIO.stringToMove(pos, "o");
        assertEquals(null, m);
        m = TextIO.stringToMove(pos, "o-o");
        assertEquals(kCastle, m);
        m = TextIO.stringToMove(pos, "O-O");
        assertEquals(kCastle, m);
        m = TextIO.stringToMove(pos, "o-o-o");
        assertEquals(qCastle, m);
        
        // Test 'o-o+'
        pos.setPiece(Position.getSquare(5,1), Piece.EMPTY);
        pos.setPiece(Position.getSquare(5,5), Piece.EMPTY);
        m = TextIO.stringToMove(pos, "o");
        assertEquals(null, m);
        m = TextIO.stringToMove(pos, "o-o");
        assertEquals(kCastle, m);
        m = TextIO.stringToMove(pos, "o-o-o");
        assertEquals(qCastle, m);
        m = TextIO.stringToMove(pos, "o-o+");
        assertEquals(kCastle, m);
        
        // Test d8=Q+ syntax
        pos = TextIO.readFEN("1r3r2/2kP2Rp/p1bN1p2/2p5/5P2/2P5/P5PP/3R2K1 w - -");
        m = TextIO.stringToMove(pos, "d8=Q+");
        Move m2 = TextIO.stringToMove(pos, "d8Q");
        assertEquals(m2, m);
    }

    @Test
    public void testGetSquare() throws ChessParseError {
        System.out.println("getSquare");
        assertEquals(Position.getSquare(0, 0), TextIO.getSquare("a1"));
        assertEquals(Position.getSquare(1, 7), TextIO.getSquare("b8"));
        assertEquals(Position.getSquare(3, 3), TextIO.getSquare("d4"));
        assertEquals(Position.getSquare(4, 3), TextIO.getSquare("e4"));
        assertEquals(Position.getSquare(3, 1), TextIO.getSquare("d2"));
        assertEquals(Position.getSquare(7, 7), TextIO.getSquare("h8"));
    }

    @Test
    public void testSquareToString() {
        System.out.println("squareToString");
        assertEquals("a1", TextIO.squareToString(Position.getSquare(0, 0)));
        assertEquals("h6", TextIO.squareToString(Position.getSquare(7, 5)));
        assertEquals("e4", TextIO.squareToString(Position.getSquare(4, 3)));
    }

    @Test
    public void testAsciiBoard() throws ChessParseError {
        System.out.println("asciiBoard");
        Position pos = TextIO.readFEN("r4rk1/2pn3p/2q1q1n1/8/2q2p2/6R1/p4PPP/1R4K1 b - - 0 1");
        String aBrd = TextIO.asciiBoard(pos);
//        System.out.print(aBrd);
        assertEquals(12, aBrd.length() - aBrd.replaceAll("\\*", "").length()); // 12 black pieces
        assertEquals(3, aBrd.length() - aBrd.replaceAll("\\*Q", " ").length()); // 3 black queens
        assertEquals(3, aBrd.length() - aBrd.replaceAll(" P", " ").length()); // 3 white pawns
    }
    
    @Test
    public void testUciStringToMove() throws ChessParseError {
        System.out.println("stringToMove");
        Position pos = TextIO.readFEN(TextIO.startPosFEN);
        Move m = TextIO.uciStringToMove("e2e4");
        assertEquals(TextIO.stringToMove(pos, "e4"), m);
        m = TextIO.uciStringToMove("e2e5");
        assertEquals(new Move(12, 12+8*3, Piece.EMPTY), m);

        m = TextIO.uciStringToMove("e2e5q");
        assertEquals(null, m);

        m = TextIO.uciStringToMove("e7e8q");
        assertEquals(Piece.WQUEEN, m.promoteTo);
        m = TextIO.uciStringToMove("e7e8r");
        assertEquals(Piece.WROOK, m.promoteTo);
        m = TextIO.uciStringToMove("e7e8b");
        assertEquals(Piece.WBISHOP, m.promoteTo);
        m = TextIO.uciStringToMove("e2e1n");
        assertEquals(Piece.BKNIGHT, m.promoteTo);
        m = TextIO.uciStringToMove("e7e8x");
        assertEquals(null, m);  // Invalid promotion piece
        m = TextIO.uciStringToMove("i1i3");
        assertEquals(null, m);  // Outside board
    }
}
