#include "intervention.h"


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void parseWhatHappens(InterventionsInfo& iif,
                     UserVarsInfo& uvif,
                     Intervention intervention, 
                     double &totPopSize, 
                     double currentTime){
    
    // now we need to parse the "what_happens" intervention
    // TODO: raise exception, malformed what_happens specification
    if(!isValidEquation(intervention.what_happens)){
        throw std::runtime_error("The intervention was wrongfully specified.\n");
    }

    bool totalPopFlag = false;

    symbol_table_t symbol_table;

    for(auto& iterator : uvif.userVars) {
        symbol_table.add_variable(iterator.first, iterator.second);
    }
    
    for(auto& iterator : iif.mapGenoToPop) {
        symbol_table.add_variable(iterator.first, iterator.second);
    } 

    double N = totPopSize;
    double T = currentTime;

    symbol_table.add_constant("N", N);//We reserve N to total population size
    symbol_table.add_constant("T", T); //Pass current time to exprtk
    symbol_table.add_constants();

    expression_t expression;
    expression.register_symbol_table(symbol_table);
    
    //Variables needed for tokeninzing
    std::vector <std::string> tokens;
    std::stringstream check1(intervention.what_happens); 
    std::string intermediate; 

    // Tokenizing whathappens '=' 
    while(getline(check1, intermediate, '=')){ 
        tokens.push_back(intermediate); 
    } 
    // now we see if the operation affects total population or genotype popolation
    std::string leftMostWhatHappens = tokens[0];
    leftMostWhatHappens.erase(std::remove(leftMostWhatHappens.begin(), leftMostWhatHappens.end(), ' '), leftMostWhatHappens.end());
    if(leftMostWhatHappens.compare("N") == 0)
        totalPopFlag = true;

    // we use the right-most side of the equation
    std::string rightMostWhatHappens = tokens[1];
    parser_t parser_rwh;
    if (!parser_rwh.compile(rightMostWhatHappens, expression)){
        // error control, just in case the parsing it's not correct
        Rcpp::Rcout << "\nexprtk parser error: \n" << std::endl;

        for (std::size_t i = 0; i < parser_rwh.error_count(); ++i){
            typedef exprtk::parser_error::type error_t;
            error_t error = parser_rwh.get_error(i);
            // FIXMEmaybe: Use warning or error to capture it easily in tests?
            REprintf("Error[%02zu] Position: %02zu Type: [%14s] Msg: %s Expression: %s\n",
                i,
                error.token.position,
                exprtk::parser_error::to_str(error.mode).c_str(),
                error.diagnostic.c_str(),
                rightMostWhatHappens.c_str());
        }
        std::string errorMessage = "The expression was imposible to parse.";
        throw std::invalid_argument(errorMessage);
    } else{
        // value cant have decimals (can't exist a half-cell)
        double res = floor(expression.value());

        // once the value is calculated, we must assure if the operation is for the total population
        // or for some specific-genotype
        if (totalPopFlag && (res > N)) {
            // TODO: Throw exception of some kind, this CANNOT happen by any means
            throw std::runtime_error("You have specified an intervention that is not allowed.");
        } 

        // check that user does not create a WhatHappens that creates population: n_A = n_A * 2
        if(!totalPopFlag){
            try { //check with the left part of the equation finding the value for the n_*
                const double& value = iif.mapGenoToPop.at(leftMostWhatHappens);
                if(res > value){
                    Rcpp::Rcerr << "In intervention:" << intervention.id << " with WhatHappens: " << intervention.what_happens << ". You cannot intervene to generate more population.";
                    throw std::runtime_error("You have specified an intervention that is not allowed.");
                }
            }
            catch (const std::out_of_range&) {
                Rcpp::Rcout << "Key \"" << leftMostWhatHappens.c_str() << "\" not found" << std::endl;
            }
        }

        if(totalPopFlag && res == N){ // this case is absurd, but it might happen, we just return.
            return;
        } else if(totalPopFlag && (res < N)){// reduce total amount of population using hipergeometric distribution
            reduceTotalPopulation(iif, res, totPopSize);
        } else { // update new value for genotype
            std::map<std::string, double>::iterator it = iif.mapGenoToPop.find(leftMostWhatHappens); 
            if(it != iif.mapGenoToPop.end()){
                it->second = res; 
            }
        }
    }
}