#include "ramjet.h"
#include "quadboard.h"
#include "state.h"
#include "move.h"
#include "eval.h"
#include "hash.h"
#include "position.h"
#include "history.h"
#include "sort.h"
#include "game.h"
#include "pv.h"
#include "options.h"
#include "params.h"
#include "stats.h"
#include "search.h"

// send a message to the GUI
void src_print_uci(const SEARCH* src, MSGTYPE mt)
{
    char uci[MT_MAX] = "info";

    switch(mt) {

    case MT_DEPTH: // current depth
        if(src->opt.om == OM_FULL && time_diff(src->stat.start) > 1000) {
            stat_print_uci_depth(append_space(uci), &src->stat);
            print_line(src->opt.log, uci);
        }
        break;

    case MT_SPEED: // nodes, time and speed
        if(src->opt.om == OM_FULL && time_diff(src->stat.start) > 1000) {
            stat_print_uci_speed(append_space(uci), &src->stat);
            print_line(src->opt.log, uci);
        }
        break;

    case MT_CURRMV: // current move
        if(src->opt.om == OM_FULL && time_diff(src->stat.start) > 1000) {
            stat_print_uci_curr_mv(append_space(uci), &src->stat, gm_get_ply(&src->gm, 0), src->opt.c960);
            print_line(src->opt.log, uci);
        }
        break;

    case MT_PV: // new PV
        if(src->opt.om >= OM_PV) {
            stat_print_uci_depth(append_space(uci), &src->stat);
            POSITION pos = *gm_get_ply(&src->gm, 0);
            pv_print_uci(append_space(uci), &src->pv, pos, src->opt.c960);
            stat_print_uci_speed(append_space(uci), &src->stat);
            print_line(src->opt.log, uci);
        }
        break;

    case MT_BEST: // best and ponder moves
        if(src->opt.om >= OM_BASIC) {
            stat_print_uci_speed(append_space(uci), &src->stat);
            print_line(src->opt.log, uci);
            POSITION pos = *gm_get_ply(&src->gm, 0);
            pv_print_uci_best(uci, &src->pv, pos, src->opt.c960);
            print_line(src->opt.log, uci);
        }
        break;
    }
}

// decide when to stop searching
int src_halt(SEARCH* src, int root)
{
    // bail out when the stint is not over
    if(!root && !is_stint_over(&src->stat)) return 0;
    stat_set_stint_ref(&src->stat);

    // aborted search
    if(src->par.sm == SM_HALT) return 1;

    // time elapsed
    MILLISEC ms = time_diff(src->stat.start);

    // set the stint length to about 1 second by successive doublings
    if(ms < 667) stat_double_stint_len(&src->stat);

    // hard constraints
    if(ms >= src->par.time || src->stat.nodes >= src->par.nodes ||
                     (root && src->stat.depth >= src->par.depth)) {
        src->par.sm = SM_HALT;
        return 1;
        // TODO: wait for stop or ponder hit signal
    }

    // tournament mode
    if(src->par.sm == SM_SMART && ((root && ms >= src->par.time / 30) ||
                                   (!root && ms >= src->par.time / 5))) {
        src->par.sm = SM_HALT;
        return 1;
        // TODO: mate score (from PV), only move (smart move in general)
    }

    // send the current speed to the GUI
    src_print_uci(src, MT_SPEED);

    return 0;
}

// quiescence search
EVAL src_quiesc(SEARCH* src, int ply, int depth, EVAL alpha, EVAL beta)
{
    MOVE mv[256];
    int leg, i;

    // current node
    POSITION* pos = gm_get_ply(&src->gm, ply);
    PLAYER pl = ST_GET_PL(pos->st);
    int chk = is_k_in_check(&pos->qb, pl);

    // stand-pat
    EVAL scr = alpha;
    if(!chk) {
        scr = eval(&pos->qb, pos->st);
        if(scr >= beta) return scr; // TODO: max limit on quiescence plies
        if(scr > alpha) alpha = scr;
    }

    // pseudo-legal moves
    int n = pos_gen_mv(pos, mv);
    i = sort_forcing(pos, mv, n, alpha - scr - 200); // delta pruning margin 200
    if(!chk) n = i;

    // analyse each move
    for(leg = 0, i = 0; i < n; ++i) {

        // depth-first search
        scr = alpha;
        if(gm_make_mv(&src->gm, ply, mv[i])) {
            scr = -src_quiesc(src, ply + 1, depth - 1, -beta, -alpha);
            stat_nodes_inc(&src->stat);
            ++leg;
        }

        // abort search
        if(src->par.sm == SM_HALT) return alpha;

        // alpha-beta
        if(scr >= beta) return beta;
        if(scr > alpha) alpha = scr;
    }

    // checkmate detection
    if(leg == 0 && chk)
        return EVAL_MATE_PLY(ply);

    return alpha;
}

// main search routine
EVAL src_negamax(SEARCH* src, int ply, int depth, EVAL alpha, EVAL beta)
{
    MOVE mv[256];
    int leg, i;
    EVAL scr;

    // current node
    POSITION* pos = gm_get_ply(&src->gm, ply);
    PLAYER pl = ST_GET_PL(pos->st);
    int chk = is_k_in_check(&pos->qb, pl);

    // initialise the current PV
    pv_clear(&src->pv, ply);

    // fifty-move rule and repetition detection
    if(ST_GET_50(pos->st) > 100 || is_repeat(&src->gm, ply))
        return EVAL_DRAW;

    // quiescence search at the leaves
    if(depth <= 0) return src_quiesc(src, ply, depth, alpha, beta);

    // emergency stop
    if(src_halt(src, 0)) return alpha;

    // probe the hash table
    mv[0] = MV_NULL;
    if(hash_probe(&src->ht, pos->key, &mv[0], &scr, ply, depth, alpha, beta) == HP_PRUNE)
        return scr;

    // null move
    if(depth >= 3 && !chk && eval(&pos->qb, pos->st) >= beta) {

        // cut when null window search fails high
        gm_make_null_mv(&src->gm, ply);
        scr = -src_negamax(src, ply + 1, depth - 3, -beta, -beta + 1);
        if(scr >= beta) return beta;
    }

    // internal iterative deepening
    if(depth >= 4 && mv[0] == MV_NULL && alpha < beta - 1) {
        src_negamax(src, ply, depth - 3, alpha, beta);
        mv[0] = pv_get_mv(&src->pv, ply);
    }

    // more initialisations
    HASHFLAG hf = HF_ALPHA;
    SORTSTATE ss = SS_START;

    // analyse each move
    for(leg = 0, i = 0; sort_more_mv(pos, mv, &src->hy, &ss, i); ++i) {

        // depth-first PVS search
        scr = alpha;
        if(gm_make_mv(&src->gm, ply, mv[i])) {
            scr = EVAL_MAX;
            if(leg > 0 && alpha < beta - 1)
                scr = -src_negamax(src, ply + 1, depth - 1, -alpha - 1, -alpha);
            if(scr > alpha)
                scr = -src_negamax(src, ply + 1, depth - 1, -beta, -alpha);
            stat_nodes_inc(&src->stat);
            ++leg;
        }

        // abort search
        if(src->par.sm == SM_HALT) return alpha;

        // beta cut
        if(scr >= beta) {
            hash_insert(&src->ht, pos->key, mv[i], beta, ply, depth, HF_BETA);
            history_update(&src->hy, pos, mv[i], depth);
            return beta;
        }

        // improve alpha
        if(scr > alpha) {
            alpha = scr;
            pv_save(&src->pv, ply, mv[i], scr);
            hf = HF_EXACT;
        }
    }

    // checkmate / stalemate detection
    if(leg == 0) {
        if(chk)
            return EVAL_MATE_PLY(ply);
        else
            return EVAL_DRAW;
    }

    // store the position in the hash table
    hash_insert(&src->ht, pos->key, pv_get_mv(&src->pv, ply), alpha, ply, depth, hf);

    return alpha;
}

// root search routine, pass SEARCH* as argument
void src_root(void* arg)
{
    MOVE mv[256];
    int leg, i;

    // root node
    SEARCH* src = (SEARCH*) arg;
    POSITION* pos = gm_get_ply(&src->gm, 0);

    // initialisations
    stat_clear(&src->stat);
    hash_clear(&src->ht);
    history_clear(&src->hy);
    pv_clear(&src->pv, 0);

    // pseudo-legal moves
    int n = pos_gen_mv(pos, mv);
    sort_forcing(pos, mv, n, 0); // TODO: is it worth sorting here?

    // iterative deepening
    while(!src_halt(src, 1)) {

        // increment the depth
        stat_depth_inc(&src->stat);
        src_print_uci(src, MT_DEPTH);

        // reset the bounds
        EVAL alpha = -EVAL_MAX;
        EVAL beta = EVAL_MAX;

        // analyse each move
        for(leg = 0, i = 0; i < n; ++i) {

            // skip illegal moves
            EVAL scr = alpha;
            if(gm_make_mv(&src->gm, 0, mv[i])) {

                // announce the current move
                stat_set_curr_mv(&src->stat, mv[i], leg + 1);
                src_print_uci(src, MT_CURRMV);

                // depth-first PVS search
                scr = EVAL_MAX;
                if(leg > 0)
                    scr = -src_negamax(src, 1, src->stat.depth - 1, -alpha - 1, -alpha);
                if(scr > alpha)
                    scr = -src_negamax(src, 1, src->stat.depth - 1, -beta, -alpha);
                stat_nodes_inc(&src->stat);
                ++leg;
            }

            // abort search
            if(src->par.sm == SM_HALT) break;

            // new PV
            if(scr > alpha) {
                alpha = scr;
                sort_2_top(mv, i);
                pv_save(&src->pv, 0, mv[0], scr);
                src_print_uci(src, MT_PV);
            }
        }

        // store the position in the hash table
        hash_insert(&src->ht, pos->key, pv_get_mv(&src->pv, 0), pv_get_scr(&src->pv, 0), 0, src->stat.depth, HF_EXACT);
    }

    // announce the best and ponder moves
    src_print_uci(src, MT_BEST);
}
