/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * Copyright (C) 2010-2013 Robert Ancell
 *
 * 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. See http://www.gnu.org/copyleft/gpl.html the full text of the
 * license.
 */

class GNOMEChess
{
    static int test_count = 0;
    static int failure_count = 0;

    private static void test_good_move (string fen, string move, string result_fen,
                                        ChessResult result = ChessResult.IN_PROGRESS,
                                        ChessRule rule = ChessRule.CHECKMATE,
                                        bool verify_san = false)
    {
        ChessState state = new ChessState (fen);
        test_count++;
        if (!state.move (move))
        {
            stderr.printf ("%d. FAIL %s + %s is an invalid move\n", test_count, fen, move);
            failure_count++;
            return;
        }

        if (state.get_fen () != result_fen)
        {
            stderr.printf ("%d. FAIL %s + %s has state %s not %s\n", test_count, fen, move, state.get_fen (), result_fen);
            failure_count++;
            return;
        }

        // We don't typically want to test this since get_san returns exactly one canonical SAN,
        // but some test cases want to verify that slightly different notations are accepted.
        if (verify_san && state.last_move.get_san () != move)
        {
            stderr.printf ("%d. FAIL %s + %s has SAN move %s\n", test_count, fen, move, state.last_move.get_san ());
            failure_count++;
            return;
        }

        ChessRule move_rule;
        var move_result = state.get_result (out move_rule);
        if (move_result != result || move_rule != rule)
        {
            stderr.printf ("%d. FAIL %s + %s has result %s not %s\n", test_count, fen, move, move_result.to_string (), result.to_string ());
            failure_count++;
            return;
        }

        stderr.printf ("%d. PASS %s + %s is valid\n", test_count, fen, move);
    }

    private static void test_bad_move (string fen, string move)
    {
        ChessState state = new ChessState (fen);
        test_count++;
        if (state.move (move, false))
        {
            stderr.printf ("%d. FAIL %s + %s is valid\n", test_count, fen, move);
            failure_count++;
            return;
        }

        stderr.printf ("%d. PASS %s + %s is invalid\n", test_count, fen, move);
    }

    public static int main (string[] args)
    {
        /* Pawn move */
        test_good_move ("8/8/8/8/8/8/P7/8 w - - 0 1", "a3",
                        "8/8/8/8/8/P7/8/8 b - - 0 1");

        /* Pawn march */
        test_good_move ("8/8/8/8/8/8/P7/8 w - - 0 1", "a4",
                        "8/8/8/8/P7/8/8/8 b - a3 0 1");

        /* Pawn march only allowed from baseline */
        test_bad_move ("8/8/8/8/8/P7/8/8 w - - 0 1", "a2a5");

        /* Pawn promotion */
        test_good_move ("8/P7/8/8/8/8/8/8 w - - 0 1", "a8=Q",
                        "Q7/8/8/8/8/8/8/8 b - - 0 1");
        test_good_move ("8/P7/8/8/8/8/8/8 w - - 0 1", "a7a8q",
                        "Q7/8/8/8/8/8/8/8 b - - 0 1");
        test_good_move ("8/P7/8/8/8/8/8/8 w - - 0 1", "a7a8N",
                        "N7/8/8/8/8/8/8/8 b - - 0 1");

        /* En passant */
        test_good_move ("8/8/8/pP6/8/8/8/8 w - a6 0 1", "bxa6",
                        "8/8/P7/8/8/8/8/8 b - - 0 1");

        /* Can't en passant if wasn't allowed */
        test_bad_move ("8/8/8/pP6/8/8/8/8 w - - 0 1", "b5a6");

        /* Can't capture en passant unless we are a pawn */
        test_good_move ("8/8/8/pQ6/8/8/8/8 w - a6 0 1", "Qa6",
                        "8/8/Q7/p7/8/8/8/8 b - - 1 1");
        test_bad_move ("8/8/8/pQ6/8/8/8/8 w - a6 0 1", "bxa6");

        /* Castle kingside */
        test_good_move ("8/8/8/8/8/8/8/4K2R w K - 0 1", "O-O",
                        "8/8/8/8/8/8/8/5RK1 b - - 1 1");

        /* Castle queenside */
        test_good_move ("8/8/8/8/8/8/8/R3K3 w Q - 0 1", "O-O-O",
                        "8/8/8/8/8/8/8/2KR4 b - - 1 1");

        /* Can't castle if pieces moved */
        test_bad_move ("8/8/8/8/8/8/8/4K2R w - - 0 1", "O-O");

        /* Can't castle if piece misplaced (shouldn't occur as then the castle flag would not be there) */
        test_bad_move ("8/8/8/8/8/8/8/4K3 w K - 0 1", "O-O");
        test_bad_move ("8/8/8/8/8/8/8/5K2 w K - 0 1", "O-O");

        /* Can't castle when in check */
        test_bad_move ("4r3/8/8/8/8/8/8/4K2R w K - 0 1", "O-O");

        /* Can't move across square that would put into check */
        test_bad_move ("5r2/8/8/8/8/8/8/4K2R w K - 0 1", "O-O");
        test_bad_move ("8/8/8/8/8/8/6p1/4K2R w K - 0 1", "O-O");
        test_bad_move ("8/8/8/8/8/8/4p3/R3K3 w Q - 0 1", "O-O-O");

        /* Can't move into check */
        test_bad_move ("4r3/8/8/8/8/8/4R3/4K3 w - - 0 1", "e2f2");

        /* Check */
        test_good_move ("k7/8/8/8/8/8/8/1R6 w - - 0 1", "Ra1+",
                        "k7/8/8/8/8/8/8/R7 b - - 1 1");

        /* Checkmate */
        test_good_move ("k7/8/8/8/8/8/1R6/1R6 w - - 0 1", "Ra1#",
                        "k7/8/8/8/8/8/1R6/R7 b - - 1 1", ChessResult.WHITE_WON, ChessRule.CHECKMATE);

        /* Not checkmate (piece can be moved to intercept) */
        test_good_move ("k7/7r/8/8/8/8/1R6/1R6 w - - 0 1", "Ra1+",
                        "k7/7r/8/8/8/8/1R6/R7 b - - 1 1", ChessResult.IN_PROGRESS);

        /* Stalemate */
        test_good_move ("k7/8/7R/8/8/8/8/1R6 w - - 0 1", "Rh7",
                        "k7/7R/8/8/8/8/8/1R6 b - - 1 1", ChessResult.DRAW, ChessRule.STALEMATE);

        /* Insufficient material - King vs. King */
        test_good_move ("k7/7p/7K/8/8/8/8/8 w - - 0 1", "Kxh7",
                        "k7/7K/8/8/8/8/8/8 b - - 0 1", ChessResult.DRAW, ChessRule.INSUFFICIENT_MATERIAL);

        /* Insufficient material - King and knight vs. King */
        test_good_move ("k7/7p/7K/8/8/8/8/7N w - - 0 1", "Kxh7",
                        "k7/7K/8/8/8/8/8/7N b - - 0 1", ChessResult.DRAW, ChessRule.INSUFFICIENT_MATERIAL);
        /* Sufficient if a knight on each side */
        test_good_move("k7/7p/7K/8/8/8/8/6Nn w - - 0 1", "Kxh7",
                       "k7/7K/8/8/8/8/8/6Nn b - - 0 1", ChessResult.IN_PROGRESS);
        /* A bishop would suffice as well */
        test_good_move("k7/7p/7K/8/8/8/8/6Nb w - - 0 1", "Kxh7",
                       "k7/7K/8/8/8/8/8/6Nb b - - 0 1", ChessResult.IN_PROGRESS);

        /* Insufficient material - King and n same-color bishops vs. King and n same-color bishops */
        test_good_move("k2b1b1b/6bp/7K/4B1B1/8/8/8/8 w - - 0 1", "Kxh7",
                       "k2b1b1b/6bK/8/4B1B1/8/8/8/8 b - - 0 1", ChessResult.DRAW, ChessRule.INSUFFICIENT_MATERIAL);
        /* Sufficient if the players' bishops are on opposite colors */
        test_good_move("k2b1b1b/6bp/7K/5B1B/8/8/8/8 w - - 0 1", "Kxh7",
                       "k2b1b1b/6bK/8/5B1B/8/8/8/8 b - - 0 1", ChessResult.IN_PROGRESS);
        /* Still sufficient with only one bishop per player, opposite colors */
        test_good_move("k6b/6bp/7K/7B/8/8/8/8 w - - 0 1", "Kxh7",
                       "k6b/6bK/8/7B/8/8/8/8 b - - 0 1", ChessResult.IN_PROGRESS);
        /* Sufficient if one player has a bishop on each color */
        test_good_move("k2b1b1b/6bp/7K/6BB/8/8/8/8 w - - 0 1", "Kxh7",
                       "k2b1b1b/6bK/8/6BB/8/8/8/8 b - - 0 1", ChessResult.IN_PROGRESS);

        // FIXME: Need to be able to test claim draw

        /* Claim draw due to 50 move rule */
        //test_good_move ("p7/8/8/8/8/8/8/P7 w - - 100 1", "draw",
        //                "p7/8/8/8/8/8/8/P7 w - - 100 1", ChessResult.DRAW, ChessRule.FIFTY_MOVES);

        /* Need 100 halfmoves for 50 move rule */
        //test_bad_move ("p7/8/8/8/8/8/8/P7 w - - 99 1", "draw");

        /* Three fold repetition */
        // FIXME: Need a test for three fold repetition

        stdout.printf ("%d/%d tests successful\n", test_count - failure_count, test_count);

        return failure_count;
    }
}
