#include <parameters.hh>
#include <constants.hh>
#include <helpers.hh>
#include <funcs.hh>
#include <fstream>

#include <iomanip>
#include <iostream>

#include <paths.hh>
#include <spdlog.h>
/**
 * @file parameters.cc
 * @author Renata Kopecna
 * @date 2021-03-02
 *
 */

void fcnc::parameter::set_values_n_errors(double v, double e){
    value = v;
    start_value = v;
    previous_measurement = v;

    error = e;
    error_down = e;
    error_up = e;
    previous_error_low = e;
    previous_error_high = e;
    //step_size = e; //not correct
}
void fcnc::parameter::reset_correlations(){
    correlations.clear();
}
void fcnc::parameter::set_correlation(double corr, unsigned int idx){
    while(correlations.size() <= idx) correlations.push_back(0.0);
    correlations.at(idx) = corr;
}
void fcnc::parameter::set_correlations(std::vector<double> corrs, unsigned int first, unsigned int last, double scalefactor){
    reset_correlations();
    assert(first < last);
    assert(last < corrs.size());
    for(unsigned int idx = first; idx <= last; idx++){
        correlations.push_back(scalefactor * corrs.at(idx));
    }
}

///set blinding string
void fcnc::parameter::set_blinding_string(std::string s) {
    blinding_string = s;
}
void fcnc::parameter::set_blinding(bool b, double b_scale, bool is_angle, std::string b_string) {
    blind = b;
    blinding_scale = b_scale;
    blinding_string = b_string;
    //this is new, apparently the shi(f)t needs to be subtracted, who knew?
    /* removed by David 18/07/18, as the functions are commented out in funcs.cc anyhow...
    if (is_angle)
      blinding_delta = -evaluate_unblind_uniform(0.0, blinding_string.c_str(), blinding_scale);
    else
      blinding_delta = -evaluate_unblind_uniform_angle(0.0, blinding_string.c_str(), blinding_scale);
*/
    //new blinding: just add
    TRandom3 * rn = new TRandom3(0);
    blinding_delta = rn->Gaus(0., 1.);
    delete rn;
}

std::string fcnc::parameter::get_root_name() const {
    std::string rootstring = get_description();
    size_t j;
    while ((j = rootstring.find("\\mathrm ")) != std::string::npos) rootstring.replace(j,8,"");
    while ((j = rootstring.find("\\")) != std::string::npos) rootstring.replace(j,1,"#");
    return rootstring;
}


///The parameters class stores a collection of parameters used in the analysis. Every analysis should implement its own set e.g. bs2jpsiphi_parameters
void fcnc::parameters::add_parameter(parameter* param) {
    param->set_index(params.size());
    param->set_parent(this);
    params.push_back(param);
}

bool fcnc::parameters::is_blind() const {
    for (unsigned int i = 0; i<params.size(); i++){
        if (params.at(i)->is_blind()) return true;
    }
    return false;
}

unsigned int fcnc::parameters::nparameters() const{
    return params.size();
}

///Methos returns a pointer to parameter with index i
fcnc::parameter* fcnc::parameters::get_parameter(unsigned int i) const{
    return params.at(i);
}

///returns parameter with specific name
fcnc::parameter* fcnc::parameters::get_parameter(std::string name) const{
    parameter* res=0;
    for (unsigned int i =0; i<nparameters();i++)
        if (get_parameter(i)->get_name() == name){
            res = get_parameter(i);
            break;
        }
    return res;
}

///Resets all parameters to their start values
void fcnc::parameters::reset_parameters(){
    for (unsigned int i = 0; i < nparameters(); i++) params.at(i)->reset_start();
}

///Method that prints the parameter set, the current values, errors and the deviation from the start value in sigma
void fcnc::parameters::print_parameters(bool latex_output) const{

    std::ofstream myFile;
    open_Latex_noteFile(latex_params(), myFile);
    if (!latex_output){
        if (spdlog_trace()){
            std::cout << std::endl << "Parameters" << std::endl;
            spdlog::info("Blinded Parameters: ");
            for (unsigned int j = 0; j < nparameters(); j++){
                if (params[j]->is_blind()) std::cout << params[j]->get_name() << " ";
            }
            std::cout << std::endl;

            spdlog::info("The {0:d} Parameters: ", nparameters());
            for (unsigned int j = 0; j < nparameters(); j++) {
                std::string parname(params[j]->get_name());//the_minimizer->fCpnam[j]);
                std::cout.setf(std::ios::left);
                double nsigma = 0.0;
                if (params[j]->get_error() > 0.0){
                    nsigma = (params[j]->get_value() - params[j]->get_start_value())/params[j]->get_error();
                }
                double value = params[j]->get_value();
                //	if (params[j]->is_blind())
                //	  value += params[j]->blinding_delta;//todo
                double error = params[j]->get_error();
                double error_up = params[j]->get_error_up();
                double error_down = params[j]->get_error_down();
                if (params[j]->is_blind()){
                    value += params[j]->blinding_delta * (error_up + error_down) / 2.;
                }
                if (error_up == error_down){
                    std::cout << std::setw(12) << parname
                                 //<< std::setw(12) << (params[j]->is_blind() ? "(*)" : format_value(value, error))
                              << std::setw(12) << (format_value(value, error))
                              << std::setw(12) << "+-"
                              << std::setw(12) << format_error(error)
                              << std::setw(12) << (params[j]->is_blind() ? " (*)": "")
                              << " " << (params[j]->is_blind() ? "(*)" : format_double(nsigma)) << " sigma" << std::endl;
                }
                else {
                    std::cout << std::setw(12) << parname
                                 //<< std::setw(12) << (params[j]->is_blind() ? "(*)" : format_value(value, error))
                              << std::setw(12) << (format_value(value, error))
                              << std::setw(12) << " " << format_error(error_down)
                              << std::setw(12) << " " << format_error(error_up)
                              << std::setw(12) << (params[j]->is_blind() ? "(*)": "")
                              << " " << (params[j]->is_blind() ? "(*)" : format_double(nsigma)) << " sigma" << std::endl;
                }
            }
        }
    }
    else {
        if (params[0]->get_error_up() == params[0]->get_error_down()){
           myFile << "\\begin{tabular}{|c|c|c|} \\hline" << std::endl;
            myFile << "parameter & result & $\\sigma$ from nominal \\\\ \\hline \\hline" << std::endl;
        }
        else{
            myFile << "\\begin{tabular}{|c|c|c|c|c|} \\hline" << std::endl;
            myFile << "parameter & result & $\\sigma_\\text{down}$ & $\\sigma_\\text{up}$ & $\\sigma$ from nominal \\\\ \\hline \\hline" << std::endl;
        }
        for (unsigned int j = 0; j < nparameters(); j++){
            std::string pardesc(params[j]->get_description());//the_minimizer->fCpnam[j]);
            std::string partex("");
            for (std::string::iterator it = pardesc.begin(); it != pardesc.end(); it++){ //Replace the ROOT tex by actual latex //TODO: maybe do this properly
                if (*it != '#') partex += *it;
                else            partex += "\\";
            }
            double value = params[j]->get_value();
            if (params[j]->is_blind()) value += params[j]->blinding_delta;//todo
            double error = params[j]->get_error();
            if (error == 0.0) continue; //Do not print fixed params into latex
            double error_up = params[j]->get_error_up();
            double error_down = params[j]->get_error_down();
            double nsigma = 0.0;
            if (error > 0.0)  nsigma = (value - params[j]->get_start_value())/error;
            std::cout.setf(std::ios::left);

            if (error_up == error_down){
                myFile << "$" << std::setw(20) << partex << "$ & $"
                             //<< std::setw(12) << (params[j]->is_blind() ? "(*)" : format_value(value, error))
                          << std::setw(12) << ((params[j]->get_step_size() == 0.0 && params[j]->is_blind()) ? "" : format_value(value, error))
                          << "\\pm " << std::setw(12) << format_error(error) << (params[j]->is_blind() ? "(*)": "") << "$ & "
                          << (params[j]->is_blind() ? "(*)" : format_double(nsigma)) << "\\\\" << std::endl;
            }
            else{
                myFile << "$" << std::setw(20) << partex << "$ & $"
                          << std::setw(12)
                             //<< (params[j]->is_blind() ? "(*)" : format_value(value, error))
                          << (format_value(value, error))
                          << "\\pm "
                          << std::setw(12) << format_error(error) << (params[j]->is_blind() ? "(*)": "") << "$ & "
                          << std::setw(12) << format_error(error_down)<< " & "
                          << std::setw(12) << "+" << format_error(error_up) << " & "
                          << (params[j]->is_blind() ? "(*)" : format_double(nsigma)) << "\\\\" << std::endl;
            }
        }
        myFile << "\\hline" << std::endl;
        myFile << "\\end{tabular}" << std::endl;
    }
}

///fix all parameters
void fcnc::parameters::fix_parameters() {
    for (unsigned int i = 0; i < params.size(); i++) params.at(i)->set_step_size(0.0);
}
///set all parameters to current values! Careful with this, please.
void fcnc::parameters::take_current_as_start() {
    for (unsigned int i = 0; i < params.size(); i++) {
        parameter* p = params.at(i);
        p->set_start_value(p->get_value());
    }
}


