// UCI (Univeral Chess Interface) `R` binding to the `SchachHoernchen` engine
#include <string>
#include <vector>
#include <sstream>
#include <iterator>

#include <Rcpp.h>

#include "SchachHoernchen/Move.h"
#include "SchachHoernchen/Board.h"
#include "SchachHoernchen/uci.h"
#include "SchachHoernchen/search.h"

namespace UCI_State {
    std::vector<Board> game(1);
};

//' Converts a FEN string to a Board (position) or return the current internal state
// [[Rcpp::export(rng = false)]]
Board board(const std::string& fen = "") {
    if (fen == "") {
        return UCI_State::game.back();
    } else if (fen == "startpos") {
        return Board();
    }
    Board pos;
    bool parseError = false;
    pos.init(fen, parseError);
    if (parseError) {
        Rcpp::stop("Parse parsing FEN");
    }
    return pos;
}

// [[Rcpp::export(name = "print.board", rng = false)]]
void print_board(const std::string& fen = "") {
    UCI::printBoard(board(fen));
}

// [[Rcpp::export(name = "print.moves", rng = false)]]
void print_moves(const std::string& fen = "") {
    UCI::printMoves(board(fen));
}

// [[Rcpp::export(name = "print.bitboards", rng = false)]]
void print_bitboards(const std::string& fen = "") {
    UCI::printBitBoards(board(fen));
}

// [[Rcpp::export(rng = false)]]
Board position(
    const Board& pos,
    const std::vector<std::string>& moves,
    const bool san = false
) {
    // Build UCI command (without "position")
    std::stringstream cmd;
    cmd << "fen " << pos.fen() << " ";
    if (moves.size()) {
        cmd << "moves ";
        std::copy(moves.begin(), moves.end(), std::ostream_iterator<std::string>(cmd, " "));
    }

    // set UCI internal flag to interprate move input in from-to format or SAN
    UCI::readSAN = san;

    // and invoke UCI position command handler on the internal game state
    bool parseError = false;
    UCI::position(UCI_State::game, cmd, parseError);
    if (parseError) {
        Rcpp::stop("Parse Error");
    }

    return UCI_State::game.back();
}

// [[Rcpp::export(rng = false)]]
void perft(const int depth = 6) {
    // Enforce a depth limit, this is very restrictive but we only want to
    // use it as a toolbox and _not_ as a strong chess engine
    if (8 < depth) {
        Rcpp::stop("In `R` search is limited to depth 8");
    } else if (depth <= 0) {
        cout << "Nodes searched: 0" << std::endl;
        return;
    }

    // Get current set position
    Board pos(UCI_State::game.back());

    // Get all legal moves
    MoveList moves;
    pos.moves(moves);

    // Setup counter for total nr. of moves
    Index totalCount = 0;

    Board copy(pos);
    for (Move move : moves) {
        // continue traversing
        pos.make(move);
        Index nodeCount = Search::perft_subroutine(pos, depth - 1);
        totalCount += nodeCount;
        pos = copy; // unmake move

        // report moves of node
        cout << move << ": " << nodeCount << std::endl;
    }

    cout << std::endl << "Nodes searched: " << totalCount << std::endl;
}

// [[Rcpp::export(rng = false)]]
Move go(
    const int depth = 6
) {
    // Enforce a depth limit, this is very restrictive but we only want to
    // use it as a toolbox and _not_ as a strong chess engine
    if (8 < depth) {
        Rcpp::stop("In `R` search is limited to depth 8");
    }

    // Setup search configuration
    Search::State config;
    config.depth = depth < 1 ? 1 : depth;

    // sets worker thread stop condition to false (before dispatch) which in this
    // context is the main thread. Only need to ensure its running.
    Search::isRunning.store(true, std::memory_order_release);

    // Construct a search object
    Search::PVS<Board> search(UCI_State::game, config);

    // and start the search
    search();

    // return best move
    return search.bestMove();
}

// [[Rcpp::export(rng = false)]]
void ucinewgame() {
    Search::newgame();
    UCI_State::game.clear();
    UCI_State::game.emplace_back();
}
