#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <vector>
#include <tuple>
#include <algorithm>
#include <cassert>

#include "types.h"
#include "Move.h"
#include "Board.h"
#include "uci.h"
#include "search.h"
#if __cplusplus > 201703L
    #include <syncstream>
    using osyncstream = std::osyncstream;
#else
    #include "syncstream.h"
#endif

// Inject into std namespace
namespace std {
    ostream& operator<<(ostream& out, const Move& move) {
        if (!move) {
            out << "!move";
            return out;
        }

        if (UCI::writeLAN) {
            switch (move.piece()) {
                case pawn:   break;
                case knight: out << 'N'; break;
                case bishop: out << 'B'; break;
                case rook:   out << 'R'; break;
                case queen:  out << 'Q'; break;
                case king:   out << 'K'; break;
                default:     out << '?';
            }
        }

        out << static_cast<char>('a' + (move.from() % 8))
            << static_cast<char>('8' - (move.from() / 8));

        if (UCI::writeLAN) {
            switch (move.victim()) {
                case pawn:   out << 'x';  break;
                case knight: out << "xN"; break;
                case bishop: out << "xB"; break;
                case rook:   out << "xR"; break;
                case queen:  out << "xQ"; break;
                default:     out << '-';
            }
        }

        out << static_cast<char>('a' + (move.to() % 8))
            << static_cast<char>('8' - (move.to() / 8));

        if (UCI::writeLAN) {
            switch (move.promote()) {
                case rook:   out << 'R'; break;
                case knight: out << 'N'; break;
                case bishop: out << 'B'; break;
                case queen:  out << 'Q'; break;
                default: break;
            }
        } else {
            switch (move.promote()) {
                case rook:   out << 'r'; break;
                case knight: out << 'n'; break;
                case bishop: out << 'b'; break;
                case queen:  out << 'q'; break;
                default: break;
            }
        }

        return out;
    }

    ostream& operator<<(ostream& out, const MoveList& moveList) {
        if (moveList.size()) {
            out << moveList[0];
        }

        for (unsigned i = 1; i < moveList.size(); i++) {
            out << ' ' << moveList[i];
        }

        return out;
    }
}


namespace UCI {

// General UCI protocol configuration
bool readSAN = false;   // if moves should be read in SAN
bool writeLAN = false;  // moves written in Long Algebraic Notation

Index parseSquare(const std::string& str, bool& parseError) {
    if (str.length() != 2) {
        parseError = true;
        return static_cast<Index>(64);
    }
    if (str[0] < 'a' || 'h' < str[0] || str[1] < '1' || '8' < str[1]) {
        parseError = true;
        return static_cast<Index>(64);
    }

    Index fileIndex = static_cast<Index>(str[0] - 'a');
    Index rankIndex = static_cast<Index>('8' - str[1]);

    return 8 * rankIndex + fileIndex;
}

Move parseMove(const std::string& str, bool& parseError) {
    // check length <from><to> = 4 plus 1 if promotion
    if ((str.length() < 4) || (5 < str.length())) {
        parseError = true;
        return Move(0);
    }

    // parse <from><to> squares
    Index fromSq = parseSquare(str.substr(0, 2), parseError);
    if (parseError) { return Move(0); }
    Index   toSq = parseSquare(str.substr(2, 2), parseError);
    if (parseError) { return Move(0); }

    // handle promotions
    if (str.length() == 5) {
        switch (str[4]) {
            case 'r': return Move(fromSq, toSq, rook);
            case 'n': return Move(fromSq, toSq, knight);
            case 'b': return Move(fromSq, toSq, bishop);
            case 'q': return Move(fromSq, toSq, queen);
            default: parseError = true; break;
        }
    }

    return Move(fromSq, toSq);
}

const std::string formatMove(const Move move) {
    // redirect parsing to `<<` overload
    std::ostringstream out;
    out << move;
    // return as a string
    return out.str();
}

// <SAN piece moves>   ::= <Piece symbol>[<from file>|<from rank>|<from square>]['x']<to square>
// <SAN pawn captures> ::= <from file>[<from rank>] 'x' <to square>[<promoted to>]
// <SAN pawn push>     ::= <to square>[<promoted to>]
Move parseSAN(const std::string& str, const Board& pos, bool& parseError) {
    // check length, at least 2 characters for pawn pushes and max 6 plus 2
    // additional characters as move annotations (for example '!?', '+' or '#').
    if ((str.length() < 2) || (9 < str.length())) {
        parseError = true;
        return Move(0);
    }

    // Start with extracting the piece type.
    // The first letter (strictly speaking upper case) gives the moving
    // piece type or a pawn if not given.
    enum piece color = pos.isWhiteTurn() ? white : black;
    enum piece piece = pawn;
    switch(str[0]) {
        // Piece moves (all normal piece moves)
        case 'R': piece = rook;   break;
        case 'N': piece = knight; break;
        case 'B': piece = bishop; break;
        case 'Q': piece = queen;  break;
        case 'K': piece = king;   break;
        // Castling
        case 'O': case '0':
            if (startsWith(str, "O-O-O") || startsWith(str, "0-0-0")) {
                if (color == white) {
                    return Move(e1, c1);
                } else {
                    return Move(e8, c8);
                }
            } else if (startsWith(str, "O-O") || startsWith(str, "0-0")) {
                if (color == white) {
                    return Move(e1, g1);
                } else {
                    return Move(e8, g8);
                }
            } else {
                parseError = true;
                return Move(0);
            }
            break;
        // Pawn moves always begin with from/to file
        case 'a': case 'b': case 'c': case 'd':
        case 'e': case 'f': case 'g': case 'h':
            break;
        default:
            parseError = true;
            return Move(0);
    }

    // Normalize moves.
    // Drop annotation symbols '?', '+', ... as well as '=', '/' for promotions.
    // Thats quite generes but as long as the reminder leads to pseudo legal
    // moves, I'm happy.
    char part[6];
    int part_length = 0;
    for (char c : str) {
        switch (c) {
            case '+': case '#': case '!': case '?': case '=': case '/':
                continue;
            default:
                part[part_length++] = c;
        }
        if (part_length >= 6) {
            break;
        }
    }

    if (piece == pawn) {
        // Pawn push
        if ((part_length == 2) || (part_length == 3)) {
            Index toSq = parseSquare(std::string{part[0], part[1]}, parseError);
            if (parseError) { return Move(0); }
            u64 from;
            if (color == white) {
                from = fill7dump<Down>(bitMask<Square>(toSq), ~pos.bb(pawn));
                from &= pos.bb(white);
            } else {
                from = fill7dump<Up>(bitMask<Square>(toSq), ~pos.bb(pawn));
                from &= pos.bb(black);
            }
            if (bitCount(from) != 1) {
                parseError = true;
                return Move(0);
            }
            Index fromSq = bitScanLS(from);
            if (part_length == 3) {
                switch(toLower(part[2])) {
                    case 'q': return Move(fromSq, toSq, queen);
                    case 'r': return Move(fromSq, toSq, rook);
                    case 'b': return Move(fromSq, toSq, bishop);
                    case 'n': return Move(fromSq, toSq, knight);
                    default:
                        parseError = true;
                        return Move(0);
                }
            }
            return Move(fromSq, toSq);
        // Pawn Capture
        } else if ((part_length == 4) || (part_length == 5)) {
            if (part[1] != 'x') {
                parseError = true;
                return Move(0);
            }
            Index toSq = parseSquare(std::string{part[2], part[3]}, parseError);
            if (parseError) { return Move(0); }
            u64 from;
            if (('a' <= part[0]) && (part[0] <= 'h')) {
                from = bitMask<File>(static_cast<Index>(str[0] - 'a'));
            } else {
                parseError = true;
                return Move(0);
            }
            if (color == white) {
                from &= shift<Down>(bitMask<Rank>(toSq));
                from &= pos.bb(white) & pos.bb(pawn);
            } else {
                from &= shift<Up>(bitMask<Rank>(toSq));
                from &= pos.bb(black) & pos.bb(pawn);
            }
            if (bitCount(from) != 1) {
                parseError = true;
                return Move(0);
            }
            Index fromSq = bitScanLS(from);
            if (part_length == 5) {
                switch(toLower(part[4])) {
                    case 'q': return Move(fromSq, toSq, queen);
                    case 'r': return Move(fromSq, toSq, rook);
                    case 'b': return Move(fromSq, toSq, bishop);
                    case 'n': return Move(fromSq, toSq, knight);
                    default:
                        parseError = true;
                        return Move(0);
                }
            }
            return Move(fromSq, toSq);
        }
        // Illegal length, ether 2, 3 for pawn push or 4, 5 for pawn captures
        parseError = true;
        return Move(0);
    }

    // Only normal piece moves remain (castling and pawn moves handled above)
    if ((part_length < 3) || (6 < part_length)) {
        parseError = true;
        return Move(0);
    }
    Index toSq = parseSquare(
        std::string{part[part_length - 2], part[part_length - 1]}, parseError);
    if (parseError) { return Move(0); }
    u64 to = bitMask<Square>(toSq);
    u64 from = pos.bb(color) & pos.bb(piece);
    u64 empty = ~(pos.bb(white) | pos.bb(black));
    switch(piece) {
        case queen:
            from &= fill7dump<Left>     (to, empty)
                  | fill7dump<Right>    (to, empty)
                  | fill7dump<Up>       (to, empty)
                  | fill7dump<Down>     (to, empty)
                  | fill7dump<LeftUp>   (to, empty)
                  | fill7dump<RightUp>  (to, empty)
                  | fill7dump<LeftDown> (to, empty)
                  | fill7dump<RightDown>(to, empty);
            break;
        case rook:
            from &= fill7dump<Left> (to, empty)
                  | fill7dump<Right>(to, empty)
                  | fill7dump<Up>   (to, empty)
                  | fill7dump<Down> (to, empty);
            break;
        case bishop:
            from &= fill7dump<LeftUp>   (to, empty)
                  | fill7dump<RightUp>  (to, empty)
                  | fill7dump<LeftDown> (to, empty)
                  | fill7dump<RightDown>(to, empty);
            break;
        case knight:
            from &= pos.knightMoves(toSq);
            break;
        case king:
            from &= pos.kingMoves(toSq);
            break;
        default:
            assert(false && "Unreachable!");
    }
    // Capture Move
    if (part[part_length - 3] == 'x') {
        Index fromSq;
        switch (part_length) {
            case 4:
                break;
            case 5:
                if (('1' <= part[1]) && (part[1] <= '8')) {
                    from &= bitMask<Rank>(8 * static_cast<Index>('8' - part[1]));
                } else if (('a' <= part[1]) && (part[1] <= 'h')) {
                    from &= bitMask<File>(static_cast<Index>(part[1] - 'a'));
                } else {
                    parseError = true;
                    return Move(0);
                }
                break;
            case 6:
                fromSq = parseSquare(std::string{part[1], part[2]}, parseError);
                if (parseError) { return Move(0); }
                from &= bitMask<Square>(fromSq);
                break;
            default:
                parseError = true;
                return Move(0);
        }
    // Non Capture / Quiet Move
    } else {
        Index fromSq;
        switch (part_length) {
            case 3:
                break;
            case 4:
                if (('1' <= part[1]) && (part[1] <= '8')) {
                    from &= bitMask<Rank>(8 * static_cast<Index>('8' - part[1]));
                } else if (('a' <= part[1]) && (part[1] <= 'h')) {
                    from &= bitMask<File>(static_cast<Index>(part[1] - 'a'));
                } else {
                    parseError = true;
                    return Move(0);
                }
                break;
            case 5:
                fromSq = parseSquare(std::string{part[1], part[2]}, parseError);
                if (parseError) { return Move(0); }
                from &= bitMask<Square>(fromSq);
                break;
            default:
                parseError = true;
                return Move(0);
        }
    }
    if (bitCount(from) != 1) {
        // If there is still an umbiguity, some candidates might be pinned.
        // Remove pinned candidates unable to reach the target square.
        u64 cKing = pos.bb(color) & pos.bb(king);
        Index cKingSq = bitScanLS(cKing);
        u64 pinMask = pos.pinned((color == white) ? black : white, cKing, empty);
        for (u64 can = from; can; can &= can - 1) { // can ... candidates
            Index canSq = bitScanLS(can);
            // check if current candidate is pinned
            if ((can & -can) & pinMask) {
                // validate if tha candidate is able to reach the target square
                if (!(lineMask(cKingSq, canSq) & to)) {
                    // Remove candidate
                    from ^= can & -can;
                }
            }
        }
        // If there are still multiple candidates or none -> parse error
        if (bitCount(from) != 1) {
            parseError = true;
            return Move(0);
        }
    }
    // Finaly, create the move
    return Move(bitScanLS(from), toSq);
}

void parseMoves(std::istream& istream, const Board& pos, MoveList& moveList,
    bool& parseError
) {
    // setup movelist of all legal moves to validate move lagality
    MoveList legalMoves;
    pos.moves(legalMoves);
    // holds input tokens to be parsed as moves
    std::string word;
    istream >> std::skipws;
    // For each input token
    while (istream >> word) {
        // parse token as a move (depending on the move format)
        Move move;
        if (readSAN) {
            move = parseSAN(word, pos, parseError);
        } else {
            move = parseMove(word, parseError);
        }
        // stop in case of a parse error
        if (parseError) { return; }
        // validate legality of the (successfully parsed) move and add the
        // generated (not the parsed move) to the move list containing all the
        // additional move information
        bool isLegal = false;
        for (Move legal : legalMoves) {
            if ((move.from()    == legal.from())
            &&  (move.to()      == legal.to())
            &&  (move.promote() == legal.promote()))
            {
                isLegal = true;
                moveList.push_back(legal);
                break;
            }
        }
        // if the parse move was not found in the legal moves list stop and
        // report a parse error
        if (!isLegal) {
            parseError = true;
            return;
        }
    }
}


char formatPiece(enum piece piece) {
    switch (piece) {
        case rook:   return 'R';
        case knight: return 'N';
        case bishop: return 'B';
        case queen:  return 'Q';
        case king:   return 'K';
        case pawn:   return 'P';
        default:     return '?';
    }
}

void printBoard(const Board& board) {
    osyncstream out(cout);

    const char* rankNames = "87654321";
    const char* fileNames = "abcdefgh";

    for (Index line = 0; line < 17; line++) {
        if (line % 2) {
            Index rankIndex = line / 2;
            out << rankNames[rankIndex];
            for (Index fileIndex = 0; fileIndex < 8; fileIndex++) {
                Index squareIndex = 8 * rankIndex + fileIndex;
                if (board.color(squareIndex) == black) {
                    out << " | \033[1m\033[94m"
                        << formatPiece(board.piece(squareIndex)) << "\033[0m";
                } else if (board.color(squareIndex) == white) {
                    out << " | \033[1m\033[97m"
                        << formatPiece(board.piece(squareIndex)) << "\033[0m";
                } else {
                    out << " |  ";
                }
            }
            out << " |";
        } else {
            out << "  +---+---+---+---+---+---+---+---+";
        }
        out << "     ";

        switch (line) {
            case 1:
                out << "How's turn:         "
                    << (board.isWhiteTurn() ? "white" : "black");
                break;
            case 2:
                out << "Move Count:         " << ((board.plyCount() + 1U) / 2U);
                break;
            case 3:
                out << "Half move clock:    " << board.halfMoveClock();
                break;
            case 4:
                out << "Castling Rights:    ";
                if (board.castleRight(white, KingSide))  { out << 'K'; };
                if (board.castleRight(white, QueenSide)) { out << 'Q'; };
                if (board.castleRight(black, KingSide))  { out << 'k'; };
                if (board.castleRight(black, QueenSide)) { out << 'q'; };
                break;
            case 5:
                out << "en-passange target: ";
                if (board.enPassant() < 64) {
                    out << static_cast<char>('a' + (board.enPassant() % 8))
                        << static_cast<char>('8' - (board.enPassant() / 8));
                }
                else { out << "-"; };
                break;
            case 6:
                out << "evaluate:           " << std::dec << board.evaluate();
                break;
            case 7:
                out << "hash:               " << std::hex << board.hash() << std::dec;
                break;
            default:
                break;
        }
        out << "\033[0K\n"; // clear rest of line (remove potential leftovers)
    }
    out << " ";
    for (Index fileIndex = 0; fileIndex < 8; fileIndex++) {
        out << "   " << fileNames[fileIndex];
    }
    out << std::endl;
}
void printBitBoards(const Board& board) {
    osyncstream out(cout);

    std::string lRanks("  8\n  7\n  6\n  5\n  4\n  3\n  2\n  1");
    std::string rRanks("8\n7\n6\n5\n4\n3\n2\n1");

    out << std::endl << "     "
        << std::left << std::setw(18) << "white"
        << std::left << std::setw(18) << "black"
        << std::left << std::setw(18) << "pawns"
        << std::left << std::setw(18) << "kings"
        << std::endl << "     " << std::setfill('0') << std::hex
        << std::right << std::setw(16) << board.bb(white) << "  "
        << std::right << std::setw(16) << board.bb(black) << "  "
        << std::right << std::setw(16) << board.bb(pawn) << "  "
        << std::right << std::setw(16) << board.bb(king) << "  "
        << std::left << std::setfill(' ') << std::dec << std::endl
        << aside(
            lRanks,
            rbits(board.bb(white),  '\n', ' '),
            rbits(board.bb(black),  '\n', ' '),
            rbits(board.bb(pawn),   '\n', ' '),
            rbits(board.bb(king),   '\n', ' '),
            rRanks
        ) << std::endl
        << "     a b c d e f g h   a b c d e f g h"
        << "   a b c d e f g h   a b c d e f g h" << std::endl;
    out << std::endl << "     "
        << std::left << std::setw(18) << "rooks"
        << std::left << std::setw(18) << "knights"
        << std::left << std::setw(18) << "bishops"
        << std::left << std::setw(18) << "queens"
        << std::endl << "     " << std::setfill('0') << std::hex
        << std::right << std::setw(16) << board.bb(rook) << "  "
        << std::right << std::setw(16) << board.bb(knight) << "  "
        << std::right << std::setw(16) << board.bb(bishop) << "  "
        << std::right << std::setw(16) << board.bb(queen) << "  "
        << std::left << std::setfill(' ') << std::dec << std::endl
        << aside(
            lRanks,
            rbits(board.bb(rook),   '\n', ' '),
            rbits(board.bb(knight), '\n', ' '),
            rbits(board.bb(bishop), '\n', ' '),
            rbits(board.bb(queen),  '\n', ' '),
            rRanks
        ) << std::endl
        << "     a b c d e f g h   a b c d e f g h"
        << "   a b c d e f g h   a b c d e f g h" << std::endl;

    const enum piece color = board.isWhiteTurn() ? white : black;
    const enum piece enemy = (color == white) ? black : white;
    const u64 empty = ~(board.bb(white) | board.bb(black));
    const u64 cKing = board.bb(color) & board.bb(king);

    out << std::endl << "     "
        << std::left << std::setw(18) << "attacks"
        << std::left << std::setw(18) << "pinned"
        << std::left << std::setw(18) << "checkers"
        << std::endl
        << aside(
            lRanks,
            rbits(board.attacks(enemy, empty),   '\n', ' '),
            rbits(board.pinned(enemy, cKing, empty), '\n', ' '),
            rbits(board.checkers(enemy, cKing, empty), '\n', ' '),
            rRanks
        ) << std::endl
        << "     a b c d e f g h   a b c d e f g h"
        << "   a b c d e f g h" << std::endl;
}

void printEval(const Board& board) {
    osyncstream out(cout);

    // Partial Scores
    Score pScoreWhite, pScoreBlack;

    out << "             |  White  |  Black  |  Total\n"
        << "-------------+---------+---------+---------\n"
        << " Material    | ";
    pScoreWhite = board.evalMaterial(white);
    pScoreBlack = board.evalMaterial(black);
    out << std::setw(7) << std::right << pScoreWhite << " | "
        << std::setw(7) << std::right << pScoreBlack << " | "
        << std::setw(7) << std::right << pScoreWhite - pScoreBlack << "\n"
        << " Pawns       | ";
    pScoreWhite = board.evalPawns(white);
    pScoreBlack = board.evalPawns(black);
    out << std::setw(7) << std::right << pScoreWhite << " | "
        << std::setw(7) << std::right << pScoreBlack << " | "
        << std::setw(7) << std::right << pScoreWhite - pScoreBlack << "\n"
        << " King Safety | ";
    pScoreWhite = board.evalKingSafety(white);
    pScoreBlack = board.evalKingSafety(black);
    out << std::setw(7) << std::right << pScoreWhite << " | "
        << std::setw(7) << std::right << pScoreBlack << " | "
        << std::setw(7) << std::right << pScoreWhite - pScoreBlack << "\n"
        << " Rooks       | ";
    pScoreWhite = board.evalRooks(white);
    pScoreBlack = board.evalRooks(black);
    out << std::setw(7) << std::right << pScoreWhite << " | "
        << std::setw(7) << std::right << pScoreBlack << " | "
        << std::setw(7) << std::right << pScoreWhite - pScoreBlack << "\n";

    out << "\nTotal: " << board.evaluate() << std::endl;
}

void printMoves(const Board& board, bool capturesOnly) {
    MoveList moveList;
    board.moves(moveList, capturesOnly);
    for (Move& move : moveList) {
        move.setScore(move.calcScore());
    }
    std::sort(moveList.begin(), moveList.end());
    osyncstream out(cout);
    if (capturesOnly) {
        out << "info string captures";
    } else {
        out << "info string moves";
    }
    for (Move& move : moveList) {
        out << ' ' << move;
    }
    out << std::endl;
}


// position [fen <fenstring> | startpos ] moves <move1> <move2> .... <moveN>
//          \______________________ remainder in cmd ______________________/
void position(std::vector<Board>& game, std::istream& cmd, bool& parseError) {
    std::string word;

    // setup a new game in case of a parse error (no changes in case of an error)
    std::vector<Board> newGame;

    // Setup position (and consume moves cmd)
    Board pos;
    cmd >> word;
    if (word == "startpos") {
        // Consume and check "moves" word
        if (cmd >> word && word != "moves") {
            parseError = true;
            return;
        }
        // set position to start position
        pos = Board();
        newGame.push_back(pos);
    } else if (word == "fen") {
        std::string fen;
        while (cmd >> word && word != "moves") {
            fen += word + " ";
        }
        pos.init(fen, parseError);
        if (parseError) { return; }
        newGame.push_back(pos);
    } else if (word == "this") { // (UCI extention)
        if (game.empty()) {
            parseError = true;
            return;
        }
        pos = game.back();
        // Consume and check "moves" word
        if (cmd >> word && word != "moves") {
            parseError = true;
            return;
        }
        // keep the game
        newGame = game;
    } else {
        parseError = true;
        return;
    }

    // Apply (legal) moves (if any) and append new positions
    while (cmd >> word) {
        Move move;
        if (readSAN) {
            move = UCI::parseSAN(word, pos, parseError);
        } else {
            move = UCI::parseMove(word, parseError);
        }
        // validate legality (and extend the move with piece, victim, ... info)
        move = pos.isLegal(move); // validate move legality and extend move info
        if (parseError || !move) {
            parseError = true;
            return;
        }
        pos.make(move);
        newGame.push_back(pos);
    }

    // Finaly replace game with new game (no errors occured)
    game = newGame;
}

void setoption(std::istream& cmd, bool& parseError) {
    std::string word;

    // Consume "name"
    if ((!(cmd >> word)) || (word != "name")) {
        parseError = true;
        return;
    }

    // read option id
    std::string id = "";
    while ((cmd >> word) && (word != "value")) {
        id += word;
    }

    // set option value
    if (id == "Hash") {
        Index ttSize = 0;
        cmd >> ttSize;
        if ((0 < ttSize) && (ttSize < 1025)) {
            Search::init(ttSize);
        } else {
            parseError = true;
        }
    } else if (id == "readSAN") {
        cmd >> word;
        if (word == "true") {
            readSAN = true;
        } else if (word == "false") {
            readSAN = false;
        } else {
            parseError = true;
        }
    } else if (id == "writeLAN") {
        cmd >> word;
        if (word == "true") {
            writeLAN = true;
        } else if (word == "false") {
            writeLAN = false;
        } else {
            parseError = true;
        }
    } else {
        parseError = true;
    }
}

// go <cmd>
// with <cmd>;
//      ...
// TODO: implement
//
void go(std::vector<Board>& game, std::istream& cmd, bool& parseError) {
    std::string word;
    Search::State config(Search::state);

    std::string IGNORED; // TODO: complete UCI implementation

    while ((cmd >> word) && !parseError) {
        if (word == "perft") {
            config.search = Search::State::Type::perft;
            if (!(cmd >> config.depth)) {
                parseError = true;
            }
        }
        else if (word == "searchmoves") {
            parseMoves(cmd, game.back(), config.searchmoves, parseError);
        }
        else if (word == "depth")     { cmd >> config.depth; }
        else if (word == "wtime")     { cmd >> config.wtime; }
        else if (word == "btime")     { cmd >> config.btime; }

        else if (word == "winc")      { cmd >> config.winc; }
        else if (word == "binc")      { cmd >> config.binc; }
        else if (word == "movestogo") { cmd >> IGNORED; }
        else if (word == "nodes")     { cmd >> IGNORED; }
        else if (word == "mate")      { cmd >> IGNORED; }
        else if (word == "movetime")  { cmd >> config.movetime; }
        else if (word == "ponder")    { cmd >> IGNORED; }
        else if (word == "infinite")  {
            config.depth = MoveList::max_size(); // max PV line length
            config.wtime = 24 * 60 * 60 * 1000; // 1 day in milliseconds
            config.btime = 24 * 60 * 60 * 1000; // 1 day in milliseconds
        }
        else {
            parseError = true;
        }
    }
    if (parseError) {
        return;
    }

    // Write new global settings to Search state
    Search::state.wtime = config.wtime;
    Search::state.btime = config.btime;
    Search::state.winc = config.winc;
    Search::state.binc = config.binc;

    Search::start(game, config);
}

void print(std::vector<Board>& game, std::istream& cmd, bool& parseError) {
    Board& board = game.back();
    std::string word;

    // Get print command (or set default)
    if (!(cmd >> word)) {
        word = "board";
    }

         if (word == "board")    { printBoard(board); }
    else if (word == "game")     { for (Board& pos : game) { printBoard(pos); } }
    else if (word == "moves")    { printMoves(board); }
    else if (word == "eval")     { printEval(board); }
    else if (word == "captures") { printMoves(board, true); }
    else if (word == "bits")     { printBitBoards(board); }
    else if (word == "fen")      {
        osyncstream(cout) << "info string fen "
                               << board.fen() << std::endl; }
    else { parseError = true; }
}

void stdin_listen(std::vector<Board>& game) {
    // read line by line from stdin and dispatch appropriate command handler
    std::string line;
    while (getline(std::cin, line)) {
        // a game consists of at least one position
        assert(game.size());

        std::istringstream cmd(line);
        std::string cmdName;

        // Extract command name (skip white spaces)
        cmd >> std::skipws >> cmdName;

        // Dispatch commands (or handle directly)
        bool parseError = false; // tracks comand argument parse status
             if (cmdName == "quit")       { Search::stop(); break; } // stop stdin loop -> shutdown
        else if (cmdName == "exit")       { Search::stop(); break; } // quit alias
        else if (cmdName == "stop")       { Search::stop(); }
        else if (cmdName == "ucinewgame") { Search::newgame(); }
        else if (cmdName == "uci")  {
            osyncstream(cout)
                << "id name Schach Hoernchen"
                   "\nid author Daniel Kapla"
                   "\noption name Hash type spin default 32 min 1 max 1024"
                   "\noption name readSAN type check default false"
                   "\noption name writeLAN type check default false"
                   "\nuciok"                // Ready (there are no options yet)
                << std::endl;
        }
        else if (cmdName == "isready")    { cout << "readyok" << std::endl; }
        else if (cmdName == "setoption")  {      setoption(cmd, parseError); }
        else if (cmdName == "go")         {       go(game, cmd, parseError); }
        else if (cmdName == "position")   { position(game, cmd, parseError); }
        // UCI Extention (not part of the UCI protocol)
        else if (cmdName == "d")          {    print(game, cmd, parseError); } // print alias (as in stockfish)
        else if (cmdName == "print")      {    print(game, cmd, parseError); }
        else if (cmdName == "getoptions") {
            osyncstream(cout)
                << "option name Hash value " << std::setprecision(2)
                        << Search::ttSize() << " MB"
                << "\noption name readSAN value "  << std::boolalpha << readSAN
                << "\noption name writeLAN value " << std::boolalpha << writeLAN
                << std::endl;
        }
        else if (cmdName == "help" || cmdName == "?") {
            osyncstream(std::cerr)
                << "Commands:\n"
                   "  uci\n\033[2m"
                   "  responds with self identification/options and finishes "
                     "with uciok\033[0m\n"
                   "  ucinewgame\n\033[2m"
                   "  starts a new game; resets all internal game states\033[0m\n"
                   "  isready\n\033[2m"
                   "  Should be responding 'readyok' immediately\033[0m\n"
                   "  position\n"
                   "    position startpos [moves <move1> [<move2> ...]]\n"
                   "    position fen <fen> [moves <move1> [<move2> ...]]\n"
                   "    position this moves <move1> [<move2> ...]\n"
                   "  go\n"
                   "    go perft <depth>\n"
                   "    go [depth <depth>] [searchmoves <move> [<move> ...]]\n"
                   "  stop\n\033[2m"
                   "  Stops what ever the engine is doing right now as "
                     "soon as possible\033[0m\n"
                   "  print\n"
                   "    d\n"
                   "    print [board]\033[2m\n"
                   "    Prity prints the board with state information\033[0m\n"
                   "    print game\033[2m\n"
                   "    Prity prints entire game history\033[0m\n"
                   "    print moves\033[2m\n"
                   "    Gives a list of all legal moves sorted by move ordering "
                       "heuristic\033[0m\n"
                   "    print captures\033[2m\n"
                   "    Same as print moves but captures only\033[0m\n"
                   "    print bits\033[2m\n"
                   "    Prints the internal bit-boards plus attacks, pinnned and "
                       "checkers bit-boards\033[0m\n"
                   "    print fen\033[2m\n"
                   "    Print FEN string of current internal state\033[0m\n"
                   "  quit\n"
                   "  exit\033[2m\n"
                   "  Shutdown the engine as soon as possible\033[0m\n"
                   "  setoption\n"
                   "    setoption name Hash value <size>\n"
                   "    setoption name readSAN value [true | false]\n"
                   "    setoption name writeLAN value [true | false]\n"
                   "  getoptions\n\033[2m"
                   "  Prints set values of all options\033[0m\n"
                << std::endl; // TODO: complete help!!!
        } else {
            osyncstream(std::cerr)
                << "info string error Unknown command!" << std::endl;
        }

        if (parseError) {
            osyncstream(std::cerr)
                << "info string error Missformed or illegal command!"
                << std::endl;
        }
    }

    osyncstream(cout) << "info string shutdown" << std::endl;
}

} /* namespace UCI */
