#ifndef INCLUDE_GUARD_BOARD_H
#define INCLUDE_GUARD_BOARD_H

#include <string>
#include <array>
#include <functional> // for std::hash

#include "types.h"

// Forward declarations
class Move;
class MoveList;

// pseudo-random-number-generator to fill Zobrist lookup hash table
static constexpr u64 rot(u64 val, int shift) {
    return (val << shift) | (val >> (64 - shift));
}
static constexpr u64 random(u64& a, u64& b, u64& c, u64& d) {
    u64 e = a - rot(b, 7);
    a = b ^ rot(b, 13);
    b = c + rot(d, 37);
    c = d + e;
    d = e + a;
    return d;
}


/**
 * BitBoard indexing;
 * @verbatim
 *   rank                      rankIndex
 *    |            index            |
 *    v +-------------------------+ v
 *    8 |  0  1  2  3  4  5  6  7 | 0
 *    7 |  8  9 10 11 12 13 14 15 | 1
 *    6 | 16 17 18 19 20 21 22 23 | 2
 *    5 | 24 25 26 27 28 29 30 31 | 3
 *    4 | 32 33 34 35 36 37 38 39 | 4
 *    3 | 40 41 42 43 44 45 46 47 | 5
 *    2 | 48 49 50 51 52 53 54 55 | 6
 *    1 | 56 57 58 59 60 61 62 63 | 7
 *      +-------------------------+
 *         a  b  c  d  e  f  g  h <- file
 *         0  1  2  3  4  5  6  7 <- fileIndex
 * @endverbatim
 */
class Board {
public:
    Board()
        : _castle{true, true, true, true}
        , _enPassant{static_cast<Index>(64)}
        , _halfMoveClock{0}
        , _plyCount{1}
        , _bitBoard{
            0xFFFF000000000000, // white
            0x000000000000FFFF, // black
            0x00FF00000000FF00, // pawns
            0x4200000000000042, // knights
            0x2400000000000024, // bishops
            0x8100000000000081, // rooks
            0x0800000000000008, // queens
            0x1000000000000010, // kings
        } {
            _hash = rehash(); // This makes it non default constructible
        };                    // but the main performance requirement is in the
                              // copy constructor and assignment operator

    // copy constructor
    Board(const Board& board)
        : _castle{board._castle}
        , _enPassant{board._enPassant}
        , _halfMoveClock{board._halfMoveClock}
        , _plyCount{board._plyCount}
        , _hash{board._hash}
    {
        for (int i = 0; i < 8; i++)
            _bitBoard[i] = board._bitBoard[i];
    };
    // copy assignment operator
    Board& operator=(const Board& board) = default;

    // accessor functions for not directly given values
    enum piece piece(const Index) const;
    enum piece color(const Index) const;
    // accessor functions for internal states (private, only altered by Board)
    u64 bb(enum piece piece) const { return _bitBoard[piece]; }
    bool castleRight(enum piece color, enum location side) const;
    Index enPassant() const { return _enPassant; }
    unsigned halfMoveClock() const { return _halfMoveClock; }
    unsigned plyCount() const { return _plyCount; }
    u64 knightMoves(Index from) const { return knightMoveLookup[from]; }
    u64 kingMoves(Index from) const { return kingMoveLookup[from]; }

    // Ply Count interpretation of who's move it is
    bool isWhiteTurn() const { return static_cast<bool>(_plyCount % 2); }
    enum piece sideToMove() const {
        return static_cast<enum piece>(!(_plyCount % 2));
    }

    // Formatting functions of pieces, squares, board, ...?
    char formatPiece(Index fileIndex, Index rankIndex) const;
    char formatPiece(Index squareIndex) const;
    std::string format() const;

    // Get board hash (Zobrist-hash), for complete computation see `rehash`
    u64 hash() const { return _hash; };
    // Get FEN string from board
    std::string fen(const bool withCounts = true) const;
    // Set board state given by FEN string
    void init(const std::string& fen, bool& parseError);
    // Complete (new) calculation of Zobrist hash for the current board position
    u64 rehash() const;

    // Validate a move (all moves from input checked before passed to `make`)
    // and returns a legal (extended with all move info's) or non-move if case of
    // illegal moves.
    Move isLegal(const Move) const;
    // Make a given move (assumed legal, otherwise undefined behavior)
    void make(const Move); // passing move by value

    // Generate all legal moves
    void moves(MoveList&, bool = false) const;
    void moves(MoveList&, const enum piece, bool = false) const;

    // Check if current side to move is in check
    bool isCheck() const;
    // Check if the current position is a quiet position (no piece is attacked)
    bool isQuiet() const;
    // Checks if the position is a terminal position, meaning if the game ended
    // by mate, stale mate or the 50 modes rule. Three-Fold repetition is NOT
    // checked, therefore a seperate game history is required which the board
    // does NOT track.
    bool isTerminal() const;
    // Checks if there is sufficient mating material on the board, meaning if it
    // possible for any side to deliver a check mate. More specifically, it
    // checks if the pieces on the board are KK, KNK or KBK.
    bool isInsufficient() const;
    // TODO: validate further possibilities like KBKB for two same color bishobs?!

    // Board evaluation, gives a heuristic score of the current board position
    // viewed from whites side
    Score evalMaterial(enum piece) const;
    Score evalPawns(enum piece) const;
    Score evalKingSafety(enum piece) const;
    Score evalRooks(enum piece) const;
    Score evaluate() const;
    // Static Exchange Evaluation
    Score see(const Move) const;

    // Subroutines for move generation, ...
    u64 attacks(const enum piece, const u64) const;
    u64 pinned(const enum piece, const u64, const u64) const;
    u64 checkers(const enum piece, const u64, const u64) const;

    // Equality operator used for comparing boards (exact equality in contrast
    // to hash equality)
    friend bool operator==(const Board& lhs, const Board& rhs) {
        bool isEqual = true;
        for (int i = 0; i < 8; i++) {
            isEqual &= (lhs._bitBoard[i] == rhs._bitBoard[i]);
        }

        return isEqual
            && (lhs._castle.whiteKingSide  == rhs._castle.whiteKingSide)
            && (lhs._castle.whiteQueenSide == rhs._castle.whiteQueenSide)
            && (lhs._castle.blackKingSide  == rhs._castle.blackKingSide)
            && (lhs._castle.blackQueenSide == rhs._castle.blackQueenSide)
            && (lhs._enPassant == rhs._enPassant);
    }
    friend bool operator!=(const Board& lhs, const Board& rhs) {
        return !(lhs == rhs);
    }

private:
    struct {
        bool whiteKingSide  : 1;
        bool whiteQueenSide : 1;
        bool blackKingSide  : 1;
        bool blackQueenSide : 1;
    } _castle;
    Index _enPassant;
    unsigned _halfMoveClock;
    unsigned _plyCount;
    u64 _hash;
    u64 _bitBoard[8];

    // Lookup table for "random" hash value for piece-square, ... used in
    // Zobrist board hashing.
    static constexpr std::array<u64, 781> hashTable = []() {
        std::array<u64, 781> table{};

        u64 a = 0xABCD1729, b = 0, c = 0, d = 0;
        for (int i = -11; i < 781; i++) {
            u64 rand = random(a, b, c, d);
            if (i >= 0) table[i] = rand;
        }
        return table;
    }();
    // Lookup helper for Zobrist hash lookup table
    // hash index for 6 pieces of both colors for all squares
    constexpr Index hashIndex(enum piece piece, enum piece color, Index sq) const {
        return 12 * sq + (6 * (color == black)) + piece - 2;
    }
    // hash index the e.p. target square (file)
    constexpr Index hashIndex(Index sq) const {
        return 768 + (sq % 8);
    }

    enum piece sqPiece(const u64) const;
};

// Inject specialization hash<Board> into standard name space
namespace std {
    template <>
    struct hash<Board> {
        using result_type = u64;

        u64 operator()(const Board& board) const noexcept {
            return board.hash();
        };
    };
}

#endif /* INCLUDE_GUARD_BOARD_H */
