/*  DreamChess
**
**  DreamChess is the legal property of its developers, whose names are too
**  numerous to list here. Please refer to the COPYRIGHT file distributed
**  with this source distribution.
**
**  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/>.
*/

#include <stdio.h>
#include <stdlib.h>

#include "board.h"
#include "move.h"
#include "move_data.h"
#include "eval.h"

static int
min(int a, int b)
{
    if (a < b)
        return a;
    else
        return b;
}

static int
max(int a, int b)
{
    if (a > b)
        return a;
    else
        return b;
}

static int
eval_king_tropism(board_t *board, int side)
{
    int score = 0;
    int square;
    /* Avoid compiler warnings. */
    int king_rank = 0;
    int king_file = 0;
    int piece_rank;
    int piece_file;
    if (side == SIDE_WHITE)
    {
        for (square = 0; square < 64; square++)
            if (board->bitboard[BLACK_KING] & square_bit[square])
            {
                king_rank = square >> 3;
                king_file = square & 7;
                break;
            }
        /* printf("King on square: %i\n", square); */
        for (square = 0; square < 64; square++)
        {
            piece_rank = square >> 3;
            piece_file = square & 7;

            if (board->bitboard[WHITE_ROOK] & square_bit[square])
                score -= min(abs(king_rank - piece_rank),
                             abs(king_file - piece_file)) << 1;
            else
                if (board->bitboard[WHITE_KNIGHT] & square_bit[square])
                    score += 5 - abs(king_rank - piece_rank)
                             - abs(king_file - piece_file);
                else
                    if (board->bitboard[WHITE_QUEEN] & square_bit[square])
                        score -= min(abs(king_rank - piece_rank),
                                     abs(king_file - piece_file));
        }
    }
    else
    {
        for (square = 0; square < 64; square++)
            if (board->bitboard[WHITE_KING] & square_bit[square])
            {
                king_rank = square >> 3;
                king_file = square & 7;
                break;
            }
        /* printf("King on square: %i\n", square); */
        for (square = 0; square < 64; square++)
        {
            piece_rank = square >> 3;
            piece_file = square & 7;

            if (board->bitboard[BLACK_ROOK] & square_bit[square])
                score -= min(abs(king_rank - piece_rank),
                             abs(king_file - piece_file)) << 1;
            else
                if (board->bitboard[BLACK_KNIGHT] & square_bit[square])
                    score += 5 - abs(king_rank - piece_rank)
                             - abs(king_file - piece_file);
                else
                    if (board->bitboard[BLACK_QUEEN] & square_bit[square])
                        score -= min(abs(king_rank - piece_rank),
                                     abs(king_file - piece_file));
        }
    }

    return score;
}

static int
eval_rook_bonus(board_t *board, eval_data_t *eval_data, int side)
{
    int score = 0;
    int square;
    bitboard_t bitboard = board->bitboard[ROOK + side];
    if (side == SIDE_WHITE)
    {
        for (square = 0; square < 64; square++)
            if (bitboard & square_bit[square])
            {
                int piece_rank = square >> 3;
                int piece_file = square & 7;

                if (piece_rank == 6)
                    score += 22;

                if (eval_data->max_pawn_file_bins[piece_file] == 0)
                {
                    if (eval_data->min_pawn_file_bins[piece_file] == 0)
                        score += 10;
                    else
                        score += 4;
                }

                if (square < eval_data->max_passed_pawns[piece_file])
                    score += 25;
                bitboard ^= square_bit[square];

                if (!bitboard)
                    return score;
            }
    }
    else
    {
        for (square = 63; square >= 0; square--)
            if (bitboard & square_bit[square])
            {
                int piece_rank = square >> 3;
                int piece_file = square & 7;

                if (piece_rank == 1)
                    score += 22;

                if (eval_data->max_pawn_file_bins[piece_file] == 0)
                {
                    if (eval_data->min_pawn_file_bins[piece_file] == 0)
                        score += 10;
                    else
                        score += 4;
                }

                if (square > eval_data->max_passed_pawns[piece_file])
                    score += 25;
                bitboard ^= square_bit[square];

                if (!bitboard)
                    return score;
            }
    }

    /* Avoid compiler warnings. */
    return 0;
}

static int
eval_development(board_t *board, int side)
{
    int score = 0;
    int square;
    bitboard_t bitboard;

    if (side == SIDE_WHITE)
    {
        if (board->bitboard[WHITE_PAWN] & square_bit[SQUARE_D2])
            score -= 15;
        if (board->bitboard[WHITE_PAWN] & square_bit[SQUARE_E2])
            score -= 15;

        bitboard = board->bitboard[WHITE_KNIGHT] |
                   board->bitboard[WHITE_BISHOP];

        for (square = SQUARE_A1; square <= SQUARE_H1; square++)
        {
            if (bitboard & square_bit[square])
                score -= 10;
        }

        bitboard = board->bitboard[WHITE_QUEEN];

        if (bitboard && !(bitboard & square_bit[SQUARE_D1]))
        {
            int count = 0;
            if (board->bitboard[WHITE_ROOK] & square_bit[SQUARE_A1])
                count++;
            if (board->bitboard[WHITE_ROOK] & square_bit[SQUARE_H1])
                count++;
            if (board->bitboard[WHITE_KNIGHT] & square_bit[SQUARE_B1])
                count++;
            if (board->bitboard[WHITE_KNIGHT] & square_bit[SQUARE_G1])
                count++;
            if (board->bitboard[WHITE_BISHOP] & square_bit[SQUARE_C1])
                count++;
            if (board->bitboard[WHITE_BISHOP] & square_bit[SQUARE_F1])
                count++;
            /* if (board->bitboard[WHITE_KING] & square_bit[SQUARE_E1])
                count++; */

            score -= count << 3;
        }

        if (board->bitboard[BLACK_QUEEN])
        {
            if (board->castle_flags & WHITE_HAS_CASTLED)
                score += 10;
            else
                if ((board->castle_flags & WHITE_CAN_CASTLE_KINGSIDE) &&
                        (board->castle_flags & WHITE_CAN_CASTLE_QUEENSIDE))
                    score -= 24;
                else
                    if (board->castle_flags & WHITE_CAN_CASTLE_KINGSIDE)
                        score -= 40;
                    else
                        if (board->castle_flags & WHITE_CAN_CASTLE_QUEENSIDE)
                            score -= 80;
                        else
                            score -= 120;
        }
    }
    else
    {
        if (board->bitboard[BLACK_PAWN] & square_bit[SQUARE_D7])
            score -= 15;
        if (board->bitboard[BLACK_PAWN] & square_bit[SQUARE_E7])
            score -= 15;

        bitboard = board->bitboard[BLACK_KNIGHT] |
                   board->bitboard[BLACK_BISHOP];

        for (square = SQUARE_A8; square <= SQUARE_H8; square++)
        {
            if (bitboard & square_bit[square])
                score -= 10;
        }

        bitboard = board->bitboard[BLACK_QUEEN];

        if (bitboard && !(bitboard & square_bit[SQUARE_D8]))
        {
            int count = 0;
            if (board->bitboard[BLACK_ROOK] & square_bit[SQUARE_A8])
                count++;
            if (board->bitboard[BLACK_ROOK] & square_bit[SQUARE_H8])
                count++;
            if (board->bitboard[BLACK_KNIGHT] & square_bit[SQUARE_B8])
                count++;
            if (board->bitboard[BLACK_KNIGHT] & square_bit[SQUARE_G8])
                count++;
            if (board->bitboard[BLACK_BISHOP] & square_bit[SQUARE_C8])
                count++;
            if (board->bitboard[BLACK_BISHOP] & square_bit[SQUARE_F8])
                count++;
            /* if (board->bitboard[BLACK_KING] & square_bit[SQUARE_E8])
                count++; */

            score -= count << 3;
        }

        if (board->bitboard[WHITE_QUEEN])
        {
            if (board->castle_flags & BLACK_HAS_CASTLED)
                score += 10;
            else
                if ((board->castle_flags & BLACK_CAN_CASTLE_KINGSIDE) &&
                        (board->castle_flags & BLACK_CAN_CASTLE_QUEENSIDE))
                    score -= 24;
                else
                    if (board->castle_flags & BLACK_CAN_CASTLE_KINGSIDE)
                        score -= 40;
                    else
                        if (board->castle_flags & BLACK_CAN_CASTLE_QUEENSIDE)
                            score -= 80;
                        else
                            score -= 120;
        }
    }

    return score;
}

static int
eval_bad_bishops(board_t *board, eval_data_t *eval_data, int side)
{
    int square;
    int score = 0;
    bitboard_t bitboard = board->bitboard[BISHOP + side];

    if (!bitboard)
        return 0;

    for (square = 0; square < 64; square++)
    {
        if (bitboard & square_bit[square])
        {
            int piece_rank = square >> 3;
            int piece_file = square & 7;

            if ((piece_rank & 1) == (piece_file & 1))
                score -= eval_data->max_pawn_color_bins[0] << 3;
            else
                score -= eval_data->max_pawn_color_bins[1] << 3;

            bitboard ^= square_bit[square];

            if (!bitboard)
                return score;
        }
    }

    return 0;
}

static int
eval_pawn_structure(board_t *board, eval_data_t *eval_data, int side)
{
    int score = 0;
    int bin;

    for (bin = 0; bin < 8; bin++)
        if (eval_data->max_pawn_file_bins[bin] > 1)
            score -= 8;

    if ((eval_data->max_pawn_file_bins[0] > 0) &&
            eval_data->max_pawn_file_bins[1] == 0)
        score -= 15;
    if ((eval_data->max_pawn_file_bins[7] > 0) &&
            eval_data->max_pawn_file_bins[6] == 0)
        score -= 15;

    for (bin = 1; bin < 7; bin++)
        if ((eval_data->max_pawn_file_bins[bin] > 0) &&
                (eval_data->max_pawn_file_bins[bin - 1] == 0) &&
                (eval_data->max_pawn_file_bins[bin + 1] == 0))
            score -= 15;

    if (eval_data->max_total_pawns == 8)
        score -= 10;

    score -= 8 * eval_data->pawn_rams;

    if (side == SIDE_WHITE)
    {
        for (bin = 0; bin < 8; bin++)
            if (eval_data->max_passed_pawns[bin] > 0)
                score += (eval_data->max_passed_pawns[bin] >> 3) *
                         (eval_data->max_passed_pawns[bin] >> 3);
    }
    else
    {
        for (bin = 0; bin < 8; bin++)
            if (eval_data->max_passed_pawns[bin] < 63)
                score += (7 - (eval_data->max_passed_pawns[bin] >> 3)) *
                         (7 - (eval_data->max_passed_pawns[bin] >> 3));
    }

    return score;
}

static void
analyze_pawn_structure(board_t *board, eval_data_t *eval_data, int side)
{
    int i;
    for (i = 0; i < 8; i++)
    {
        eval_data->max_pawn_file_bins[i] = 0;
        eval_data->min_pawn_file_bins[i] = 0;
    }

    eval_data->max_pawn_color_bins[0] = 0;
    eval_data->max_pawn_color_bins[1] = 0;
    eval_data->pawn_rams = 0;
    eval_data->max_total_pawns = 0;

    if (side == SIDE_WHITE)
    {
        int square;

        for (i = 0; i < 8; i++)
        {
            eval_data->max_most_advanced[i] = 0;
            eval_data->min_most_backward[i] = 0;
            eval_data->max_passed_pawns[i] = 0;
        }

        for (square = 8; square <= 55; square++)
        {
            if (board->bitboard[WHITE_PAWN] & square_bit[square])
            {
                int piece_rank = square >> 3;
                int piece_file = square & 7;

                eval_data->max_pawn_file_bins[piece_file]++;
                eval_data->max_total_pawns++;
                eval_data->max_most_advanced[piece_file] = square;

                if ((piece_rank & 1) == (piece_file & 1))
                    eval_data->max_pawn_color_bins[0]++;
                else
                    eval_data->max_pawn_color_bins[1]++;

                if (board->bitboard[BLACK_PAWN] & square_bit[square + 8])
                    eval_data->pawn_rams++;
            }
            else
                if (board->bitboard[BLACK_PAWN] & square_bit[square])
                {
                    int piece_file = square & 7;
                    eval_data->min_pawn_file_bins[piece_file]++;
                    eval_data->min_most_backward[piece_file] = square;
                }
        }

        if (eval_data->max_most_advanced[0] >
                max(eval_data->min_most_backward[0],
                    eval_data->min_most_backward[1]))
            eval_data->max_passed_pawns[0] = eval_data->max_most_advanced[0];

        if (eval_data->max_most_advanced[7] >
                max(eval_data->min_most_backward[6],
                    eval_data->min_most_backward[7]))
            eval_data->max_passed_pawns[7] = eval_data->max_most_advanced[7];

        for (i = 1; i < 7; i++)
            if ((eval_data->max_most_advanced[i] >
                    eval_data->min_most_backward[i-1]) &&
                    (eval_data->max_most_advanced[i] >
                     eval_data->min_most_backward[i]) &&
                    (eval_data->max_most_advanced[i] >
                     eval_data->min_most_backward[i+1]))
                eval_data->max_passed_pawns[i] =
                    eval_data->max_most_advanced[i];
    }
    else
    {
        int square;

        for (i = 0; i < 8; i++)
        {
            eval_data->max_most_advanced[i] = 63;
            eval_data->min_most_backward[i] = 63;
            eval_data->max_passed_pawns[i] = 63;
        }

        for (square = 55; square >= 8; square--)
        {
            if (board->bitboard[BLACK_PAWN] & square_bit[square])
            {
                int piece_rank = square >> 3;
                int piece_file = square & 7;

                eval_data->max_pawn_file_bins[piece_file]++;
                eval_data->max_total_pawns++;
                eval_data->max_most_advanced[piece_file] = square;

                if ((piece_rank & 1) == (piece_file & 1))
                    eval_data->max_pawn_color_bins[0]++;
                else
                    eval_data->max_pawn_color_bins[1]++;

                if (board->bitboard[WHITE_PAWN] & square_bit[square - 8])
                    eval_data->pawn_rams++;
            }
            else
                if (board->bitboard[WHITE_PAWN] & square_bit[square])
                {
                    int piece_file = square & 7;
                    eval_data->min_pawn_file_bins[piece_file]++;
                    eval_data->min_most_backward[piece_file] = square;
                }
        }

        if (eval_data->max_most_advanced[0] <
                min(eval_data->min_most_backward[0],
                    eval_data->min_most_backward[1]))
            eval_data->max_passed_pawns[0] = eval_data->max_most_advanced[0];

        if (eval_data->max_most_advanced[7] <
                min(eval_data->min_most_backward[6],
                    eval_data->min_most_backward[7]))
            eval_data->max_passed_pawns[7] = eval_data->max_most_advanced[7];

        for (i = 1; i < 7; i++)
            if ((eval_data->max_most_advanced[i] <
                    eval_data->min_most_backward[i-1]) &&
                    (eval_data->max_most_advanced[i] <
                     eval_data->min_most_backward[i]) &&
                    (eval_data->max_most_advanced[i] <
                     eval_data->min_most_backward[i+1]))
                eval_data->max_passed_pawns[i] =
                    eval_data->max_most_advanced[i];
    }
}

static int
board_eval_material(board_t *board, int side)
{
    int mat_total;

    if (board->material_value[SIDE_BLACK] ==
            board->material_value[SIDE_WHITE])
        return 0;

    mat_total = board->material_value[SIDE_WHITE] +
                board->material_value[SIDE_BLACK];

    if (board->material_value[SIDE_BLACK] >
            board->material_value[SIDE_WHITE])
    {
        int mat_diff = board->material_value[SIDE_BLACK] -
                       board->material_value[SIDE_WHITE];
        int val = (2400 < mat_diff? 2400 : mat_diff) +
                  (mat_diff * (12000 - mat_total) *
                   board->num_pawns[SIDE_BLACK])
                  / (6400 * (board->num_pawns[SIDE_BLACK ] + 1 ));
        if (side == SIDE_BLACK)
            return val;
        else
            return -val;
    }
    else
    {
        int mat_diff = board->material_value[SIDE_WHITE] -
                       board->material_value[SIDE_BLACK];
        int val = (2400 < mat_diff? 2400 : mat_diff) +
                  (mat_diff * (12000 - mat_total) *
                   board->num_pawns[SIDE_WHITE])
                  / (6400 * (board->num_pawns[SIDE_WHITE] + 1 ));
        if (side == SIDE_WHITE)
            return val;
        else
            return -val;
    }
}

int
board_eval_quick(board_t *board, int side)
{
    int eval = board_eval_material(board, side);
    if (board->current_player == side)
        return eval;
    else
        return -eval;
}

int
board_eval_complete(board_t *board, int side, int alpha, int beta)
{
    eval_data_t eval_data;
    int eval2;
    int eval1 = board_eval_material(board, side);

    if (board->current_player != side)
        eval1 = -eval1;
#if 0
    if (eval1 - 200 >= beta)
        return beta;

    if (eval1 + 200 <= alpha)
        return alpha;
#endif
    analyze_pawn_structure(board, &eval_data, side);
    eval2 = eval_pawn_structure(board, &eval_data, side) +
            eval_bad_bishops(board, &eval_data, side) +
            eval_development(board, side) +
            eval_rook_bonus(board, &eval_data, side) +
            eval_king_tropism(board, side) + 192; /* Add 192 to have the starting position score 0 */

    if (board->current_player == side)
        return eval1 + eval2;
    else
        return eval1 - eval2;
}

