/*
 
    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/>.
 
*/
 
 
 
/*
 
 * enginecontrol.cpp
 
 *
 
 *  Created on: Mar 4, 2012
 
 *      Author: petero
 
 */
 
 
 
#define _GLIBCXX_USE_NANOSLEEP
 
 
 
#include "enginecontrol.hpp"
 
#include "util/random.hpp"
 
#include "searchparams.hpp"
 
#include "book.hpp"
 
#include "textio.hpp"
 
#include "parameters.hpp"
 
#include "moveGen.hpp"
 
#include "util/logger.hpp"
 
#include "numa.hpp"
 
 
 
#include <iostream>
 
#include <memory>
 
#include <chrono>
 
 
 
 
 
EngineControl::SearchListener::SearchListener(std::ostream& os0)
 
    : os(os0)
 
{
 
}
 
 
 
void
 
EngineControl::SearchListener::notifyDepth(int depth) {
 
//    std::lock_guard<std::mutex> L(Logger::getLogMutex());
 
    os << "info depth " << depth << std::endl;
 
}
 
 
 
void
 
EngineControl::SearchListener::notifyCurrMove(const Move& m, int moveNr) {
 
//    std::lock_guard<std::mutex> L(Logger::getLogMutex());
 
    os << "info currmove " << moveToString(m) << " currmovenumber " << moveNr << std::endl;
 
}
 
 
 
void
 
EngineControl::SearchListener::notifyPV(int depth, int score, int time, U64 nodes, int nps, bool isMate,
 
                                        bool upperBound, bool lowerBound, const std::vector<Move>& pv,
 
                                        int multiPVIndex, U64 tbHits) {
 
//    std::lock_guard<std::mutex> L(Logger::getLogMutex());
 
    std::string pvBuf;
 
    for (size_t i = 0; i < pv.size(); i++) {
 
        pvBuf += ' ';
 
        pvBuf += moveToString(pv[i]);
 
    }
 
    std::string bound;
 
    if (upperBound) {
 
        bound = " upperbound";
 
    } else if (lowerBound) {
 
        bound = " lowerbound";
 
    }
 
    os << "info depth " << depth << " score " << (isMate ? "mate " : "cp ")
 
       << score << bound << " time " << time << " nodes " << nodes
 
       << " nps " << nps;
 
    if (tbHits > 0)
 
        os << " tbhits " << tbHits;
 
    if (multiPVIndex >= 0)
 
        os << " multipv " << (multiPVIndex + 1);
 
    os << " pv" << pvBuf << std::endl;
 
}
 
 
 
void
 
EngineControl::SearchListener::notifyStats(U64 nodes, int nps, U64 tbHits, int time) {
 
    os << "info nodes " << nodes << " nps " << nps;
 
    if (tbHits > 0)
 
        os << " tbhits " << tbHits;
 
    os << " time " << time << std::endl;
 
}
 
 
 
EngineControl::EngineControl(std::ostream& o)
 
    : os(o),
 
      shouldDetach(true),
 
      tt(8),
 
      pd(tt),
 
      randomSeed(0)
 
{
 
    Numa::instance().bindThread(0);
 
    hashParListenerId = UciParams::hash->addListener([this]() {
 
        setupTT();
 
    });
 
    clearHashParListenerId = UciParams::clearHash->addListener([this]() {
 
        tt.clear();
 
        ht.init();
 
    }, false);
 
    et = Evaluate::getEvalHashTables();
 
}
 
 
 
EngineControl::~EngineControl() {
 
    UciParams::hash->removeListener(hashParListenerId);
 
    UciParams::hash->removeListener(clearHashParListenerId);
 
}
 
 
 
void
 
EngineControl::startSearch(const Position& pos, const std::vector<Move>& moves, const SearchParams& sPar) {
 
    stopSearch();
 
    setupPosition(pos, moves);
 
    computeTimeLimit(sPar);
 
    ponder = false;
 
    infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0);
 
    searchMoves = sPar.searchMoves;
 
    startThread(minTimeLimit, maxTimeLimit, maxDepth, maxNodes);
 
}
 
 
 
void
 
EngineControl::startPonder(const Position& pos, const std::vector<Move>& moves, const SearchParams& sPar) {
 
    stopSearch();
 
    setupPosition(pos, moves);
 
    computeTimeLimit(sPar);
 
    ponder = true;
 
    infinite = false;
 
    startThread(-1, -1, -1, -1);
 
}
 
 
 
void
 
EngineControl::ponderHit() {
 
    std::shared_ptr<Search> mySearch;
 
    {
 
        std::lock_guard<std::mutex> L(threadMutex);
 
        mySearch = sc;
 
    }
 
    if (mySearch) {
 
        if (onePossibleMove) {
 
            if (minTimeLimit > 1) minTimeLimit = 1;
 
            if (maxTimeLimit > 1) maxTimeLimit = 1;
 
        }
 
        mySearch->timeLimit(minTimeLimit, maxTimeLimit);
 
    }
 
    infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0);
 
    ponder = false;
 
}
 
 
 
void
 
EngineControl::stopSearch() {
 
    stopThread();
 
}
 
 
 
void
 
EngineControl::newGame() {
 
    randomSeed = Random().nextU64();
 
    tt.clear();
 
    ht.init();
 
}
 
 
 
/**
 
 * Compute thinking time for current search.
 
 */
 
void
 
EngineControl::computeTimeLimit(const SearchParams& sPar) {
 
    minTimeLimit = -1;
 
    maxTimeLimit = -1;
 
    maxDepth = -1;
 
    maxNodes = -1;
 
    if (sPar.infinite) {
 
        minTimeLimit = -1;
 
        maxTimeLimit = -1;
 
        maxDepth = -1;
 
    } else {
 
        if (sPar.depth > 0)
 
            maxDepth = sPar.depth;
 
        if (sPar.mate > 0) {
 
            int md = sPar.mate * 2 - 1;
 
            maxDepth = maxDepth == -1 ? md : std::min(maxDepth, md);
 
        }
 
        if (sPar.nodes > 0)
 
            maxNodes = sPar.nodes;
 
 
 
        if (sPar.moveTime > 0) {
 
             minTimeLimit = maxTimeLimit = sPar.moveTime;
 
        } else if (sPar.wTime || sPar.bTime) {
 
            int moves = sPar.movesToGo;
 
            if (moves == 0)
 
                moves = 999;
 
            moves = std::min(moves, static_cast<int>(timeMaxRemainingMoves)); // Assume at most N more moves until end of game
 
            bool white = pos.isWhiteMove();
 
            int time = white ? sPar.wTime : sPar.bTime;
 
            int inc  = white ? sPar.wInc : sPar.bInc;
 
            const int margin = std::min(static_cast<int>(bufferTime), time * 9 / 10);
 
            int timeLimit = (time + inc * (moves - 1) - margin) / moves;
 
            minTimeLimit = (int)(timeLimit * minTimeUsage * 0.01);
 
            if (UciParams::ponder->getBoolPar()) {
 
                const double ponderHitRate = timePonderHitRate * 0.01;
 
                minTimeLimit = (int)ceil(minTimeLimit / (1 - ponderHitRate));
 
            }
 
            maxTimeLimit = (int)(minTimeLimit * clamp(moves * 0.5, 2.5, static_cast<int>(maxTimeUsage) * 0.01));
 
 
 
            // Leave at least 1s on the clock, but can't use negative time
 
            minTimeLimit = clamp(minTimeLimit, 1, time - margin);
 
            maxTimeLimit = clamp(maxTimeLimit, 1, time - margin);
 
        }
 
    }
 
}
 
 
 
void
 
EngineControl::startThread(int minTimeLimit, int maxTimeLimit, int maxDepth, int maxNodes) {
 
    Search::SearchTables st(tt, kt, ht, *et);
 
    sc = std::make_shared<Search>(pos, posHashList, posHashListSize, st, pd, nullptr, treeLog);
 
    sc->setListener(std::make_shared<SearchListener>(os));
 
    sc->setStrength(UciParams::strength->getIntPar(), randomSeed);
 
    std::shared_ptr<MoveList> moves(std::make_shared<MoveList>());
 
    MoveGen::pseudoLegalMoves(pos, *moves);
 
    MoveGen::removeIllegal(pos, *moves);
 
    if (searchMoves.size() > 0)
 
        moves->filter(searchMoves);
 
    onePossibleMove = false;
 
    if ((moves->size < 2) && !infinite) {
 
        onePossibleMove = true;
 
        if (!ponder) {
 
            if (maxTimeLimit > 0) {
 
                maxTimeLimit = clamp(maxTimeLimit/100, 1, 100);
 
                minTimeLimit = clamp(minTimeLimit/100, 1, 100);
 
            } else {
 
                if ((maxDepth < 0) || (maxDepth > 2))
 
                    maxDepth = 2;
 
            }
 
        }
 
    }
 
    pd.addRemoveWorkers(UciParams::threads->getIntPar() - 1);
 
    pd.wq.resetSplitDepth();
 
    pd.startAll();
 
    sc->timeLimit(minTimeLimit, maxTimeLimit);
 
    tt.nextGeneration();
 
    bool ownBook = UciParams::ownBook->getBoolPar();
 
    bool analyseMode = UciParams::analyseMode->getBoolPar();
 
    int maxPV = (infinite || analyseMode) ? UciParams::multiPV->getIntPar() : 1;
 
    int minProbeDepth = UciParams::minProbeDepth->getIntPar();
 
    if (analyseMode) {
 
        Evaluate eval(*et);
 
        int evScore = eval.evalPosPrint(pos) * (pos.isWhiteMove() ? 1 : -1);
 
        std::stringstream ss;
 
        ss.precision(2);
 
        ss << std::fixed << (evScore / 100.0);
 
        os << "info string Eval: " << ss.str() << std::endl;
 
    }
 
    auto f = [this,ownBook,analyseMode,moves,maxDepth,maxNodes,maxPV,minProbeDepth]() {
 
        Numa::instance().bindThread(0);
 
        Move m;
 
        if (ownBook && !analyseMode) {
 
            Book book(false);
 
            book.getBookMove(pos, m);
 
        }
 
        if (m.isEmpty())
 
            m = sc->iterativeDeepening(*moves, maxDepth, maxNodes, false, maxPV, false, minProbeDepth);
 
        while (ponder || infinite) {
 
            // We should not respond until told to do so. Just wait until
 
            // we are allowed to respond.
 
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
 
        }
 
        Move ponderMove = getPonderMove(pos, m);
 
        std::lock_guard<std::mutex> L(threadMutex);
 
        os << "bestmove " << moveToString(m);
 
        if (!ponderMove.isEmpty())
 
            os << " ponder " << moveToString(ponderMove);
 
        os << std::endl;
 
        if (shouldDetach) {
 
            engineThread->detach();
 
            pd.stopAll();
 
            pd.fhInfo.reScale();
 
        }
 
        engineThread.reset();
 
        sc.reset();
 
        for (auto& p : pendingOptions)
 
            setOption(p.first, p.second, false);
 
        pendingOptions.clear();
 
    };
 
    shouldDetach = true;
 
    {
 
        std::lock_guard<std::mutex> L(threadMutex);
 
        engineThread = std::make_shared<std::thread>(f);
 
    }
 
}
 
 
 
void
 
EngineControl::stopThread() {
 
    std::shared_ptr<std::thread> myThread;
 
    {
 
        std::lock_guard<std::mutex> L(threadMutex);
 
        myThread = engineThread;
 
        if (myThread) {
 
            sc->timeLimit(0, 0);
 
            infinite = false;
 
            ponder = false;
 
            shouldDetach = false;
 
        }
 
    }
 
    if (myThread)
 
        myThread->join();
 
    pd.stopAll();
 
    pd.fhInfo.reScale();
 
}
 
 
 
void
 
EngineControl::setupTT() {
 
    int hashSizeMB = UciParams::hash->getIntPar();
 
    U64 nEntries = hashSizeMB > 0 ? ((U64)hashSizeMB) * (1 << 20) / sizeof(TranspositionTable::TTEntry)
 
                                  : (U64)1024;
 
    int logSize = 0;
 
    while (nEntries > 1) {
 
        logSize++;
 
        nEntries /= 2;
 
    }
 
    logSize++;
 
    while (true) {
 
        try {
 
            logSize--;
 
            if (logSize <= 0)
 
                break;
 
            tt.reSize(logSize);
 
            break;
 
        } catch (const std::bad_alloc& /*ex*/) { // Pierre-Marie Baty -- unreferenced variable
 
        }
 
    }
 
}
 
 
 
void
 
EngineControl::setupPosition(Position pos, const std::vector<Move>& moves) {
 
    UndoInfo ui;
 
    posHashList.resize(200 + moves.size());
 
    posHashListSize = 0;
 
    for (size_t i = 0; i < moves.size(); i++) {
 
        const Move& m = moves[i];
 
        posHashList[posHashListSize++] = pos.zobristHash();
 
        pos.makeMove(m, ui);
 
        if (pos.getHalfMoveClock() == 0)
 
            posHashListSize = 0;
 
    }
 
    this->pos = pos;
 
}
 
 
 
/**
 
 * Try to find a move to ponder from the transposition table.
 
 */
 
Move
 
EngineControl::getPonderMove(Position pos, const Move& m) {
 
    Move ret;
 
    if (m.isEmpty())
 
        return ret;
 
    UndoInfo ui;
 
    pos.makeMove(m, ui);
 
    TranspositionTable::TTEntry ent;
 
    ent.clear();
 
    tt.probe(pos.historyHash(), ent);
 
    if (ent.getType() != TType::T_EMPTY) {
 
        ent.getMove(ret);
 
        MoveList moves;
 
        MoveGen::pseudoLegalMoves(pos, moves);
 
        MoveGen::removeIllegal(pos, moves);
 
        bool contains = false;
 
        for (int mi = 0; mi < moves.size; mi++)
 
            if (moves[mi].equals(ret)) {
 
                contains = true;
 
                break;
 
            }
 
        if  (!contains)
 
            ret = Move();
 
    }
 
    pos.unMakeMove(m, ui);
 
    return ret;
 
}
 
 
 
std::string
 
EngineControl::moveToString(const Move& m) {
 
    if (m.isEmpty())
 
        return "0000";
 
    std::string ret = TextIO::squareToString(m.from());
 
    ret += TextIO::squareToString(m.to());
 
    switch (m.promoteTo()) {
 
    case Piece::WQUEEN:
 
    case Piece::BQUEEN:
 
        ret += 'q';
 
        break;
 
    case Piece::WROOK:
 
    case Piece::BROOK:
 
        ret += 'r';
 
        break;
 
    case Piece::WBISHOP:
 
    case Piece::BBISHOP:
 
        ret += 'b';
 
        break;
 
    case Piece::WKNIGHT:
 
    case Piece::BKNIGHT:
 
        ret += 'n';
 
        break;
 
    default:
 
        break;
 
    }
 
    return ret;
 
}
 
 
 
void
 
EngineControl::printOptions(std::ostream& os) {
 
    std::vector<std::string> parNames;
 
    Parameters::instance().getParamNames(parNames);
 
    for (const auto& pName : parNames) {
 
        std::shared_ptr<Parameters::ParamBase> p = Parameters::instance().getParam(pName);
 
        switch (p->type) {
 
        case Parameters::CHECK: {
 
            const Parameters::CheckParam& cp = dynamic_cast<const Parameters::CheckParam&>(*p.get());
 
            os << "option name " << cp.name << " type check default "
 
               << (cp.defaultValue?"true":"false") << std::endl;
 
            break;
 
        }
 
        case Parameters::SPIN: {
 
            const Parameters::SpinParam& sp = dynamic_cast<const Parameters::SpinParam&>(*p.get());
 
            os << "option name " << sp.name << " type spin default "
 
               << sp.getDefaultValue() << " min " << sp.getMinValue()
 
               << " max " << sp.getMaxValue() << std::endl;
 
            break;
 
        }
 
        case Parameters::COMBO: {
 
            const Parameters::ComboParam& cp = dynamic_cast<const Parameters::ComboParam&>(*p.get());
 
            os << "option name " << cp.name << " type combo default " << cp.defaultValue;
 
            for (size_t i = 0; i < cp.allowedValues.size(); i++)
 
                os << " var " << cp.allowedValues[i];
 
            os << std::endl;
 
            break;
 
        }
 
        case Parameters::BUTTON:
 
            os << "option name " << p->name << " type button" << std::endl;
 
            break;
 
        case Parameters::STRING: {
 
            const Parameters::StringParam& sp = dynamic_cast<const Parameters::StringParam&>(*p.get());
 
            os << "option name " << sp.name << " type string default "
 
               << sp.defaultValue << std::endl;
 
            break;
 
        }
 
        }
 
    }
 
}
 
 
 
void
 
EngineControl::setOption(const std::string& optionName, const std::string& optionValue,
 
                         bool deferIfBusy) {
 
    Parameters& params = Parameters::instance();
 
    if (deferIfBusy) {
 
        std::lock_guard<std::mutex> L(threadMutex);
 
        if (engineThread) {
 
            if (params.getParam(optionName))
 
                pendingOptions[optionName] = optionValue;
 
            return;
 
        }
 
    }
 
    std::shared_ptr<Parameters::ParamBase> par = params.getParam(optionName);
 
    if (par && par->type == Parameters::STRING && optionValue == "<empty>")
 
        params.set(optionName, "");
 
    else
 
        params.set(optionName, optionValue);
 
}