#ifndef INCLUDE_GUARD_SEARCH_H
#define INCLUDE_GUARD_SEARCH_H

#include <iostream>
#include <iomanip>
#include <vector>
#include <chrono>
#include <atomic>

#include "types.h"
#include "Move.h"
#include "Board.h"

namespace Search {

struct State {
    enum class Type { perft, search, ponder };

    enum Type search;
    int depth;
    unsigned wtime;
    unsigned btime;
    unsigned winc;
    unsigned binc;
    unsigned movetime;
    MoveList searchmoves;

    State() = default;
    State(const State& state)
        : search{Type::search}
        , depth{state.depth}
        , wtime{state.wtime}
        , btime{state.btime}
        , winc{state.winc}
        , binc{state.binc}
        , movetime{state.movetime}
        , searchmoves() { };
};

extern State state;
extern std::atomic<bool> isRunning;

/**
 * Initialization of search specific (global) variables
 */
void init(unsigned ttSize = 32);

/**
 * Diagnostic routine to get the allocated TT size in MB
 */
double ttSize();

/**
 * Resets all search internal variables to initial configuration state
 */
void newgame();

/**
 * Performance Test Subroutine, simple perft function without any I/O.
 *
 * Used in `perft.cpp` testing utility (therefore accessable).
 *
 * @param board Reference root node board position.
 * @param depth search depth.
 *
 * @return number of moves/nodes from root node of search depth.
 */
unsigned perft_subroutine(Board& board, int depth);

/**
 * Board wrapper in combination with evaluation to abstract the different
 * position evaluation options (HCE Board evaluate and NNUE evaluate)
 *
 * @note The NNUE evaluation is removed from this code base!
 */
template <class CRTP>
class GameState : public Board {
public:
    GameState(const Board& pos) : Board(pos) { };

    Score eval() { return static_cast<CRTP*>(this)->eval(); }
};

/**
 * Specialized CRTP derived HCE Board evaluation position
 */
class BoardState : public GameState<BoardState> {
public:
    BoardState(const Board& pos) : GameState<BoardState>(pos) { };

    Score eval() const { return this->evaluate(); }

};

/**
 * Search routine class handling all search relevant data and executes the search
 *
 * This is a Principal Variabtion Search with alpha-beta pruning and a
 * transposition table implemented in an iterative deepening framework.
 *
 * There are two Evaluation types, ether the HCE Board evaluation `PVS<Board>`
 * or the NNUE evaluation `PVS<NNUE>`.
 *
 * @note The NNUE evaluation is removed from this code base!
 */
template <typename Evaluator>
class PVS {
public:
    PVS(const std::vector<Board>&, const State&);

    Score operator()();

    Move bestMove() const { return _pv[0]; }

    // TODO: there is a BUG collecting the PV!
    const MoveList& pv() const { return _pv; }

    // Analytic routine to get the number of searched leave nodes
    unsigned nodes() const { return _nodes; }

private:
    using clock = std::chrono::high_resolution_clock;
    using milliseconds = std::chrono::milliseconds;
    using time_point = decltype(clock::now());
    using Position = BoardState;

    // Root search position (HCE Board state)
    Position _root;
    // Max search depth (excluding QSearch)
    int _max_depth;

    // Selective search counter (needs tracking)
    int _seldepth;
    // Number of nodes visited (leave nodes)
    unsigned _nodes;
    // Set to exit the search as soon as possible (breaks out of all recursions)
    bool _isStopped;
    // Start time of the search (for break condition check)
    time_point _start_time;
    // Middle time between _start_time and _end_time used to check if an
    time_point _mid_time;
    // Time till the search should stop (if now() is bigger, stop the search)
    time_point _end_time;
    // Moves to be searched, if empty all legal moves are searched
    MoveList _searchmoves;
    // Principal Variation
    MoveList _pv;
    // Killer Moves (initialized to contain !move entries)
    Move _killers[2][MoveList::max_size()];
    // History Heuristic Table (for move ordering)
    Score _historyTable[2][6][64];   // [color][piece][to Square]
    // Number of elements in `_history`
    size_t _historySize;
    // Game history list storing previous board hashes for repetition detection
    u64 _history[100 + MoveList::max_size()];
    // Increasing age for TT entry
    static unsigned _age;

    // adds a position to the history
    void pushHistory(const Position& pos) {
        _history[_historySize++] = pos.hash();
    }
    // and removes the last position from the history
    void popHistory() { --_historySize; }

    // two-fold repetition detection
    bool isRepetition(const Position& pos) const {
        u64 hash = pos.hash();

        for (size_t i = _historySize - 1; i-- > 0; ) {
            if (_history[i] == hash) {
                return true;
            }
        }
        return false;
    }

    // Principal Variation Search routine (for not root nodes)
    Score pvs(const Position& pos, int depth, int ply,
              Score alpha, Score beta, Score color, MoveList& pv_line);
    // Quiesence Search
    Score qSearch(const Position& pos, int ply,
                  Score alpha, Score beta, Score color);
};

/**
 * Starts searching on the given board position.
 */
void start(std::vector<Board>& game, struct State& config);

/**
 * Stops all workers.
 *
 * Tells all workers to stop as soon as possible and closes working threads.
 *
 * @note: This function blocks
 */
void stop();


} /* namespace Search */

#endif /* INCLUDE_GUARD_SEARCH_H */
