/*
 
    Texel - A UCI chess engine.
 
    Copyright (C) 2012-2014  Peter Ă–sterlund, peterosterlund2@gmail.com
 
 
 
    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/>.
 
*/
 
 
 
/*
 
 * game.cpp
 
 *
 
 *  Created on: Feb 25, 2012
 
 *      Author: petero
 
 */
 
 
 
#include "game.hpp"
 
#include "moveGen.hpp"
 
#include "textio.hpp"
 
#include "util/timeUtil.hpp"
 
 
 
#include <iostream>
 
#include <iomanip>
 
#include <cassert>
 
 
 
 
 
Game::Game(const std::shared_ptr<Player>& whitePlayer,
 
           const std::shared_ptr<Player>& blackPlayer) {
 
    this->whitePlayer = whitePlayer;
 
    this->blackPlayer = blackPlayer;
 
    handleCommand("new");
 
}
 
 
 
bool
 
Game::processString(const std::string& str) {
 
    if (handleCommand(str))
 
        return true;
 
    if (getGameState() != ALIVE)
 
        return false;
 
 
 
    Move m = TextIO::stringToMove(pos, str);
 
    if (m.isEmpty())
 
        return false;
 
 
 
    UndoInfo ui;
 
    pos.makeMove(m, ui);
 
    TextIO::fixupEPSquare(pos);
 
    if (currentMove < (int)moveList.size()) {
 
        moveList.erase(moveList.begin() + currentMove, moveList.end());
 
        uiInfoList.erase(uiInfoList.begin() + currentMove, uiInfoList.end());
 
        drawOfferList.erase(drawOfferList.begin() + currentMove, drawOfferList.end());
 
    }
 
    moveList.push_back(m);
 
    uiInfoList.push_back(ui);
 
    drawOfferList.push_back(pendingDrawOffer);
 
    pendingDrawOffer = false;
 
    currentMove++;
 
    return true;
 
}
 
 
 
std::string
 
Game::getGameStateString() {
 
    switch (getGameState()) {
 
    case ALIVE:
 
        return "";
 
    case WHITE_MATE:
 
        return "Game over, white mates!";
 
    case BLACK_MATE:
 
        return "Game over, black mates!";
 
    case WHITE_STALEMATE:
 
    case BLACK_STALEMATE:
 
        return "Game over, draw by stalemate!";
 
    case DRAW_REP:
 
    {
 
        std::string ret = "Game over, draw by repetition!";
 
        if (drawStateMoveStr.length() > 0)
 
            ret += " [" + drawStateMoveStr + "]";
 
        return ret;
 
    }
 
    case DRAW_50:
 
    {
 
        std::string ret = "Game over, draw by 50 move rule!";
 
        if (drawStateMoveStr.length() > 0)
 
            ret += " [" + drawStateMoveStr + "]";
 
        return ret;
 
    }
 
    case DRAW_NO_MATE:
 
        return "Game over, draw by impossibility of mate!";
 
    case DRAW_AGREE:
 
        return "Game over, draw by agreement!";
 
    case RESIGN_WHITE:
 
        return "Game over, white resigns!";
 
    case RESIGN_BLACK:
 
        return "Game over, black resigns!";
 
    default:
 
        assert(false);
 
        return "";
 
    }
 
}
 
 
 
Move
 
Game::getLastMove() {
 
    Move m;
 
    if (currentMove > 0)
 
        m = moveList[currentMove - 1];
 
    return m;
 
}
 
 
 
Game::GameState
 
Game::getGameState() {
 
    MoveList moves;
 
    MoveGen::pseudoLegalMoves(pos, moves);
 
    MoveGen::removeIllegal(pos, moves);
 
    if (moves.size == 0) {
 
        if (MoveGen::inCheck(pos))
 
            return pos.isWhiteMove() ? BLACK_MATE : WHITE_MATE;
 
        else
 
            return pos.isWhiteMove() ? WHITE_STALEMATE : BLACK_STALEMATE;
 
    }
 
    if (insufficientMaterial())
 
        return DRAW_NO_MATE;
 
    if (resignState != ALIVE)
 
        return resignState;
 
    return drawState;
 
}
 
 
 
bool
 
Game::haveDrawOffer() {
 
    if (currentMove > 0)
 
        return drawOfferList[currentMove - 1];
 
    else
 
        return false;
 
}
 
 
 
bool
 
Game::handleCommand(const std::string& moveStr) {
 
    if (moveStr == "new") {
 
        moveList.clear();
 
        uiInfoList.clear();
 
        drawOfferList.clear();
 
        currentMove = 0;
 
        pendingDrawOffer = false;
 
        drawState = ALIVE;
 
        resignState = ALIVE;
 
        pos = TextIO::readFEN(TextIO::startPosFEN);
 
        whitePlayer->clearTT();
 
        blackPlayer->clearTT();
 
        activateHumanPlayer();
 
        return true;
 
    } else if (moveStr == "undo") {
 
        if (currentMove > 0) {
 
            pos.unMakeMove(moveList[currentMove - 1], uiInfoList[currentMove - 1]);
 
            currentMove--;
 
            pendingDrawOffer = false;
 
            drawState = ALIVE;
 
            resignState = ALIVE;
 
            return handleCommand("swap");
 
        } else {
 
            std::cout << "Nothing to undo" << std::endl;
 
        }
 
        return true;
 
    } else if (moveStr == "redo") {
 
        if (currentMove < (int)moveList.size()) {
 
            pos.makeMove(moveList[currentMove], uiInfoList[currentMove]);
 
            currentMove++;
 
            pendingDrawOffer = false;
 
            return handleCommand("swap");
 
        } else {
 
            std::cout << "Nothing to redo" << std::endl;
 
        }
 
        return true;
 
    } else if (moveStr == "swap" || moveStr == "go") {
 
        std::swap(whitePlayer, blackPlayer);
 
        return true;
 
    } else if (moveStr == "list") {
 
        listMoves();
 
        return true;
 
    } else if (startsWith(moveStr, "setpos ")) {
 
        std::string fen = moveStr.substr(moveStr.find_first_of(' ') + 1);
 
        try {
 
            Position newPos(TextIO::readFEN(fen));
 
            handleCommand("new");
 
            pos = newPos;
 
            activateHumanPlayer();
 
        } catch (const ChessParseError& ex) {
 
            std::cout << "Invalid FEN: " << fen << " (" << ex.what() << ")" << std::endl;
 
        }
 
        return true;
 
    } else if (moveStr == "getpos") {
 
        std::string fen(TextIO::toFEN(pos));
 
        std::cout << fen << std::endl;
 
        return true;
 
    } else if (startsWith(moveStr, "draw ")) {
 
        if (getGameState() == ALIVE) {
 
            std::string drawCmd = moveStr.substr(moveStr.find_first_of(' ') + 1);
 
            return handleDrawCmd(drawCmd);
 
        } else {
 
            return true;
 
        }
 
    } else if (moveStr == "resign") {
 
        if (getGameState()== ALIVE) {
 
            resignState = pos.isWhiteMove() ? RESIGN_WHITE : RESIGN_BLACK;
 
            return true;
 
        } else {
 
            return true;
 
        }
 
    } else if (startsWith(moveStr, "book")) {
 
        std::string bookCmd = moveStr.substr(moveStr.find_first_of(' ') + 1);
 
        return handleBookCmd(bookCmd);
 
    } else if (startsWith(moveStr, "time")) {
 
        std::string timeStr = moveStr.substr(moveStr.find_first_of(' ') + 1);
 
        int timeLimit;
 
        if (!str2Num(timeStr, timeLimit)) {
 
            std::cout << "Can not parse number: " << timeStr << std::endl;
 
            return false;
 
        }
 
        whitePlayer->timeLimit(timeLimit, timeLimit);
 
        blackPlayer->timeLimit(timeLimit, timeLimit);
 
        return true;
 
    } else if (startsWith(moveStr, "perft ")) {
 
        std::string depthStr = moveStr.substr(moveStr.find_first_of(' ') + 1);
 
        int depth;
 
        if (!str2Num(depthStr, depth)) {
 
            std::cout << "Can not parse number: " << depthStr << std::endl;
 
            return false;
 
        }
 
        S64 t0 = currentTimeMillis();
 
        U64 nodes = perfT(pos, depth);
 
        S64 t1 = currentTimeMillis();
 
        double t = (t1 - t0) * 1e-3;
 
        std::stringstream ss;
 
        ss.precision(3);
 
        std::cout << "perft(" << depth << ") = " << nodes << ", t="
 
                  << (ss << std::fixed << t).rdbuf()
 
                  << "s" << std::endl;
 
        return true;
 
    } else {
 
        return false;
 
    }
 
}
 
 
 
void
 
Game::activateHumanPlayer() {
 
    if (!(pos.isWhiteMove() ? whitePlayer : blackPlayer)->isHumanPlayer())
 
        std::swap(whitePlayer, blackPlayer);
 
}
 
 
 
void
 
Game::getPosHistory(std::vector<std::string> ret) {
 
    ret.clear();
 
    Position pos(this->pos);
 
    for (int i = currentMove; i > 0; i--)
 
        pos.unMakeMove(moveList[i - 1], uiInfoList[i - 1]);
 
    ret.push_back(TextIO::toFEN(pos)); // Store initial FEN
 
 
 
    std::string moves;
 
    UndoInfo ui;
 
    for (size_t i = 0; i < moveList.size(); i++) {
 
        const Move& move = moveList[i];
 
        std::string strMove(TextIO::moveToString(pos, move, false));
 
        moves += ' ';
 
        moves += strMove;
 
        pos.makeMove(move, ui);
 
    }
 
    ret.push_back(moves); // Store move list string
 
 
 
    int numUndo = (int)moveList.size() - currentMove;
 
    ret.push_back(num2Str(numUndo));
 
}
 
 
 
std::string
 
Game::getMoveListString(bool compressed) {
 
    std::string ret;
 
 
 
    // Undo all moves in move history.
 
    Position pos(this->pos);
 
    for (int i = currentMove; i > 0; i--)
 
        pos.unMakeMove(moveList[i - 1], uiInfoList[i - 1]);
 
 
 
    // Print all moves
 
    std::string whiteMove;
 
    std::string blackMove;
 
    for (int i = 0; i < currentMove; i++) {
 
        const Move& move = moveList[i];
 
        std::string strMove = TextIO::moveToString(pos, move, false);
 
        if (drawOfferList[i])
 
            strMove += " (d)";
 
        if (pos.isWhiteMove()) {
 
            whiteMove = strMove;
 
        } else {
 
            blackMove = strMove;
 
            if (whiteMove.length() == 0)
 
                whiteMove = "...";
 
            if (compressed) {
 
                ret += num2Str(pos.getFullMoveCounter()) + ". " + whiteMove +
 
                       " " + blackMove + " ";
 
            } else {
 
                std::stringstream ss;
 
                ss << std::setw(3) << pos.getFullMoveCounter() << ".  "
 
                   << std::setw(10) << std::left << whiteMove << " "
 
                   << std::setw(10) << std::left << blackMove << std::endl;
 
                ret += ss.str();
 
            }
 
            whiteMove.clear();
 
            blackMove.clear();
 
        }
 
        UndoInfo ui;
 
        pos.makeMove(move, ui);
 
    }
 
    if ((whiteMove.length() > 0) || (blackMove.length() > 0)) {
 
        if (whiteMove.length() == 0)
 
            whiteMove = "...";
 
        if (compressed) {
 
            ret += num2Str(pos.getFullMoveCounter()) + ". " + whiteMove +
 
                   " " + blackMove + " ";
 
        } else {
 
            std::stringstream ss;
 
            ss << std::setw(3) << pos.getFullMoveCounter() << ".  "
 
               << std::setw(10) << std::left << whiteMove << " "
 
               << std::setw(10) << std::left << blackMove << std::endl;
 
            ret += ss.str();
 
        }
 
    }
 
    std::string gameResult = getPGNResultString();
 
    if (gameResult != "*") {
 
        ret += gameResult;
 
        if (!compressed)
 
            ret += '\n';
 
    }
 
    return ret;
 
}
 
 
 
std::string
 
Game::getPGNResultString() {
 
    std::string gameResult = "*";
 
    switch (getGameState()) {
 
    case ALIVE:
 
        break;
 
    case WHITE_MATE:
 
    case RESIGN_BLACK:
 
        gameResult = "1-0";
 
        break;
 
    case BLACK_MATE:
 
    case RESIGN_WHITE:
 
        gameResult = "0-1";
 
        break;
 
    case WHITE_STALEMATE:
 
    case BLACK_STALEMATE:
 
    case DRAW_REP:
 
    case DRAW_50:
 
    case DRAW_NO_MATE:
 
    case DRAW_AGREE:
 
        gameResult = "1/2-1/2";
 
        break;
 
    }
 
    return gameResult;
 
}
 
 
 
/** Return a list of previous positions in this game, back to the last "zeroing" move. */
 
void
 
Game::getHistory(std::vector<Position>& posList) {
 
    posList.clear();
 
    Position pos(this->pos);
 
    for (int i = currentMove; i > 0; i--) {
 
        if (pos.getHalfMoveClock() == 0)
 
            break;
 
        pos.unMakeMove(moveList[i - 1], uiInfoList[i - 1]);
 
        posList.push_back(pos);
 
    }
 
    std::reverse(posList.begin(), posList.end());
 
}
 
 
 
void
 
Game::listMoves() {
 
    std::string movesStr = getMoveListString(false);
 
    std::cout << movesStr;
 
}
 
 
 
bool
 
Game::handleDrawCmd(std::string drawCmd) {
 
    if (startsWith(drawCmd, "rep") || startsWith(drawCmd, "50")) {
 
        bool rep = startsWith(drawCmd, "rep");
 
        Move m;
 
        size_t idx = drawCmd.find_first_of(' ');
 
        std::string ms;
 
        if (idx != drawCmd.npos) {
 
            ms = drawCmd.substr(idx + 1);
 
            if (ms.length() > 0)
 
                m = TextIO::stringToMove(pos, ms);
 
        }
 
        bool valid;
 
        if (rep) {
 
            valid = false;
 
            std::vector<Position> oldPositions;
 
            if (!m.isEmpty()) {
 
                UndoInfo ui;
 
                Position tmpPos(pos);
 
                tmpPos.makeMove(m, ui);
 
                oldPositions.push_back(tmpPos);
 
            }
 
            oldPositions.push_back(pos);
 
            Position tmpPos(pos);
 
            for (int i = currentMove - 1; i >= 0; i--) {
 
                tmpPos.unMakeMove(moveList[i], uiInfoList[i]);
 
                oldPositions.push_back(tmpPos);
 
            }
 
            int repetitions = 0;
 
            Position firstPos = oldPositions[0];
 
            for (size_t i = 0; i < oldPositions.size(); i++) {
 
                if (oldPositions[i].drawRuleEquals(firstPos))
 
                    repetitions++;
 
            }
 
            if (repetitions >= 3) {
 
                valid = true;
 
            }
 
        } else {
 
            Position tmpPos(pos);
 
            if (!m.isEmpty()) {
 
                UndoInfo ui;
 
                tmpPos.makeMove(m, ui);
 
            }
 
            valid = tmpPos.getHalfMoveClock() >= 100;
 
        }
 
        if (valid) {
 
            drawState = rep ? DRAW_REP : DRAW_50;
 
            drawStateMoveStr.clear();
 
            if (!m.isEmpty())
 
                drawStateMoveStr = TextIO::moveToString(pos, m, false);
 
        } else {
 
            pendingDrawOffer = true;
 
            if (!m.isEmpty())
 
                processString(ms);
 
        }
 
        return true;
 
    } else if (startsWith(drawCmd, "offer ")) {
 
        pendingDrawOffer = true;
 
        size_t idx = drawCmd.find_first_of(' ');
 
        if (idx != drawCmd.npos) {
 
            std::string ms = drawCmd.substr(idx + 1);
 
            if (!TextIO::stringToMove(pos, ms).isEmpty())
 
                processString(ms);
 
        }
 
        return true;
 
    } else if (drawCmd == "accept") {
 
        if (haveDrawOffer())
 
            drawState = DRAW_AGREE;
 
        return true;
 
    } else {
 
        return false;
 
    }
 
}
 
 
 
bool
 
Game::handleBookCmd(const std::string& bookCmd) {
 
    if (bookCmd == "off") {
 
        whitePlayer->useBook(false);
 
        blackPlayer->useBook(false);
 
        return true;
 
    } else if (bookCmd == "on") {
 
        whitePlayer->useBook(true);
 
        whitePlayer->useBook(true);
 
        return true;
 
    }
 
    return false;
 
}
 
 
 
bool
 
Game::insufficientMaterial() {
 
    if (pos.pieceTypeBB(Piece::WQUEEN) != 0) return false;
 
    if (pos.pieceTypeBB(Piece::WROOK)  != 0) return false;
 
    if (pos.pieceTypeBB(Piece::WPAWN)  != 0) return false;
 
    if (pos.pieceTypeBB(Piece::BQUEEN) != 0) return false;
 
    if (pos.pieceTypeBB(Piece::BROOK)  != 0) return false;
 
    if (pos.pieceTypeBB(Piece::BPAWN)  != 0) return false;
 
    int wb = BitBoard::bitCount(pos.pieceTypeBB(Piece::WBISHOP));
 
    int wn = BitBoard::bitCount(pos.pieceTypeBB(Piece::WKNIGHT));
 
    int bb = BitBoard::bitCount(pos.pieceTypeBB(Piece::BBISHOP));
 
    int bn = BitBoard::bitCount(pos.pieceTypeBB(Piece::BKNIGHT));
 
    if (wb + wn + bb + bn <= 1)
 
        return true;    // King + bishop/knight vs king is draw
 
    if (wn + bn == 0) {
 
        // Only bishops. If they are all on the same color, the position is a draw.
 
        U64 bMask = pos.pieceTypeBB(Piece::WBISHOP) | pos.pieceTypeBB(Piece::BBISHOP);
 
        if (((bMask & BitBoard::maskDarkSq) == 0) ||
 
                ((bMask & BitBoard::maskLightSq) == 0))
 
            return true;
 
    }
 
    return false;
 
}
 
 
 
U64
 
Game::perfT(Position& pos, int depth) {
 
    if (depth == 0)
 
        return 1;
 
    U64 nodes = 0;
 
    MoveList moves;
 
    MoveGen::pseudoLegalMoves(pos, moves);
 
    MoveGen::removeIllegal(pos, moves);
 
    if (depth == 1)
 
        return moves.size;
 
    UndoInfo ui;
 
    for (int mi = 0; mi < moves.size; mi++) {
 
        const Move& m = moves[mi];
 
        pos.makeMove(m, ui);
 
        nodes += perfT(pos, depth - 1);
 
        pos.unMakeMove(m, ui);
 
    }
 
    return nodes;
 
}