#include "ABCD.h"

ABCD::ABCD(int maxDepth, EvaluationFunction* ef)
{
    _maxDepth = maxDepth;
    _ef = ef;
    _maxBranching = 0;
    _minBranching = 999;
    _avgBranching = 0;
    _nodesExpanded = 0;
    _timeout = false;
    _downSamplingTimes = 0;
    TIME_LIMIT = LoadConfigInt("ABCD", "time_limit");
    MAX_SAMPLING = LoadConfigInt("ABCD", "downsampling");
}

playerActions_t ABCD::start(bool player, GameState gs)
{
    _start = clock();
    float alpha = std::numeric_limits<float>::min();
    float beta = std::numeric_limits<float>::max();
    bool maxplayer = player;
    bool minplayer = !player;
    //LOG("Starting ABCD... " << player);
    gameNode_t bestMove = loop(gs, maxplayer, minplayer, alpha, beta, _maxDepth, maxplayer);
    double searchTime = double(clock()-_start)/CLOCKS_PER_SEC;
    //LOG("Start: " << _start << " end: " << clock() << " totalTime: " << searchTime << " NodesExpanded: " << _nodesExpanded);
    //LOG("ABCD: " << bestMove.evaluation << " in " << double(clock()-start)/CLOCKS_PER_SEC);
    // save stats
    unsigned int numberOfGroups = gs.friendlyUnits.size() + gs.enemyUnits.size();
    stats.groupTime[numberOfGroups] += searchTime;
	stats.groupFrequency[numberOfGroups]++;
    stats.groupBranchingMin[numberOfGroups] += _minBranching;
    stats.groupBranchingMax[numberOfGroups] += _maxBranching;
    stats.groupBranchingAvg[numberOfGroups] += _avgBranching/_nodesExpanded;
    stats.groupTimeouts[numberOfGroups] += _timeout;
    stats.groupDownSamplings[numberOfGroups] += _downSamplingTimes;
    LOG("Groups: " << numberOfGroups << " min: " << _minBranching << " max: " << _maxBranching << " avg: " << stats.groupBranchingAvg[numberOfGroups]
    << " seconds: " << searchTime << " tiemout: " << _timeout << " downSamplings: " << _downSamplingTimes);

    return bestMove.action;
}

ABCD::gameNode_t ABCD::loop(GameState gs, bool maxplayer, bool minplayer, float alpha, float beta, int depthLeft, int nextPlayerInSimultaneousNode)
{
//     std::string spaces;
//     for(int i=0;i<_maxDepth-depthLeft;i++){
//         spaces += " ";
//     }
    //LOG(spaces << "ABCD depthLeft: " << depthLeft);

    if(!_timeout) { // check tiemout
        _timeout = double(clock()-_start)/CLOCKS_PER_SEC > TIME_LIMIT;
    }

    if (depthLeft<=0 || gs.winner()!=-1 || _timeout) {
		// Evaluate the current state
        //LOG(spaces << "ABCD end. Using evaluation function");
        float evaluation = _ef->evaluate(maxplayer, minplayer, gs, _maxDepth-depthLeft);
        gameNode_t bestNode = {(playerActions_t)NULL,evaluation};
        //LOG(spaces << "Node score: " << bestNode.evaluation);
        //LOG(gs.toString());
        return bestNode;
    }
        
    int toMove = -1;
    int sampling = 0;
    if (gs.canExecuteAnyAction(maxplayer)) {
        if (gs.canExecuteAnyAction(minplayer)) {
            toMove = nextPlayerInSimultaneousNode;
            nextPlayerInSimultaneousNode = 1 - nextPlayerInSimultaneousNode;
        } else {
            toMove = maxplayer;
        }
    } else {
        if (gs.canExecuteAnyAction(minplayer)) toMove = minplayer;
    }

    if (toMove == (int)maxplayer) {
        ActionGenerator actions = ActionGenerator(gs, maxplayer);
        _maxBranching = std::max((double)actions._size,_maxBranching);
        _minBranching = std::min((double)actions._size,_minBranching);
        _avgBranching += actions._size;
        _nodesExpanded++;
#ifdef NOVA_GUI
        //LOG("[Current] Depth: "<<depthLeft<<" nodes expanded: "<<_nodesExpanded<<" branching: "<<actions._size);
#endif
        gameNode_t best = {(playerActions_t)NULL, NULL};
        playerActions_t next;
        do {
            next = actions.getNextAction();
            if (!next.empty()) {
                sampling++;
                if (sampling > MAX_SAMPLING) {
                    _downSamplingTimes++;
                    return best;
                }
                GameState gs2 = gs.cloneIssue(next, maxplayer);
                gameNode_t tmp = loop(gs2, maxplayer, minplayer, alpha, beta, depthLeft-1, nextPlayerInSimultaneousNode);
                alpha = std::max(alpha,tmp.evaluation);
                if (best.action.empty() || tmp.evaluation>best.evaluation) {
                    best = tmp;
                    best.action = next;
                }
                if (beta<=alpha) {
                    //LOG(spaces << "Ready to return best alpha (pruned)");
                    //LOG(gameNodeString(spaces, best, gs));
                    return best;
                }
            }
        } while(!next.empty());
        //LOG(spaces << "Ready to return best alpha");
        //LOG(gameNodeString(spaces, best, gs));
        return best;
    } else if (toMove == (int)minplayer) {
        ActionGenerator actions = ActionGenerator(gs, minplayer);
        _maxBranching = std::max((double)actions._size,_maxBranching);
        _minBranching = std::min((double)actions._size,_minBranching);
        _avgBranching += actions._size;
        _nodesExpanded++;
        gameNode_t best = {(playerActions_t)NULL, NULL};
        playerActions_t next;
        do {
            next = actions.getNextAction();
            if (!next.empty()) {
                sampling++;
                if (sampling > MAX_SAMPLING) {
                    _downSamplingTimes++;
                    return best;
                }
                GameState gs2 = gs.cloneIssue(next, minplayer);
                gameNode_t tmp = loop(gs2, maxplayer, minplayer, alpha, beta, depthLeft-1, nextPlayerInSimultaneousNode);
                beta = std::min(beta,tmp.evaluation);
                if (best.action.empty() || tmp.evaluation<best.evaluation) {
                    best = tmp;
                    best.action = next;
                }
                if (beta<=alpha) {
                    //LOG(spaces << "Ready to return best beta (pruned)");
                    //LOG(gameNodeString(spaces, best, gs));
                    return best;
                }
            }
        } while(!next.empty());
        //LOG(spaces << "Ready to return best beta");
        //LOG(gameNodeString(spaces, best, gs));
        return best;
    } else {
		DEBUG("ABCD error (none of the players can move)");
        GameState gs2 = gs;
        while(gs2.winner()==-1 && 
              !gs2.gameover() && 
              !gs2.canExecuteAnyAction(maxplayer) && 
              !gs2.canExecuteAnyAction(minplayer)) gs2.moveForward();
        //LOG(spaces << "Next loop after moving forward");
        return loop(gs2, maxplayer, minplayer, alpha, beta, depthLeft, nextPlayerInSimultaneousNode);
    }
}

std::string ABCD::gameNodeString(std::string spaces, gameNode_t node, GameState gs)
{
    playerActions_t bestActions = node.action;
    std::stringstream tmp;
    uint8_t orderId;
	uint8_t targetRegionId;
    for(playerActions_t::const_iterator i = bestActions.begin(); i!=bestActions.end(); ++i) {
// 		orderId = (*i).second.first;
// 		targetRegionId = (*i).second.second;
        // Unpacking playerActions_t (00000000 TTTTTTTT AAAAAAAA GGGGGGGG)
        int playerAction = (*i);
        orderId = (playerAction >> 8) & 0xFF;
        targetRegionId = playerAction >> 16;
        tmp << spaces << "Score: " << node.evaluation << ", action: " << gs.getAbstractOrderName((int)orderId) << " region: " << (int)targetRegionId;
    }
    return tmp.str();
}