//Renata Kopecna

#include <fitter.hh>
#include <paths.hh>

#include <event.hh>
#include <parameters.hh>
#include <pdf.hh>
#include <plotter.hh>
#include <options.hh>
#include <helpers.hh>
#include <funcs.hh>
#include <design.hh>
#include <constants.hh>

#include <TH1D.h>
#include <TMatrixD.h>
#include <TMatrixDSym.h>
#include <TRandom3.h>
#include <TVector.h>
#include <TDecompChol.h>

#include <spdlog.h>

#include <fstream>
#include <iostream>
#include <complex>
#include <assert.h>
#include <thread>
#include <mutex>

//Log of the covariance matrix status
void printCovMatrixStatus(int status, bool squared){
    std::string hesse = "Hesse";
    if (squared) hesse = "Squared Hesse";
    if      (status==0) spdlog::warn(hesse + " returns 0: Hesse not calculated.");
    else if (status==1) spdlog::warn(hesse + " returns 1: Approximation only.");
    else if (status==2) spdlog::warn(hesse + " returns 2: Full, forced pos def");
    else if (status==3) spdlog::info(hesse + " returns 3: ALL GOOD");
    else                spdlog::warn(hesse + " returns {0:d} -> SOMETHING VERY WRONG", status);
    return;
}

//Migrad status log so one doesn't have to look it up all the time
void printMigradStatus(int status){
    if      (status==0)  spdlog::info("MIGRAD returns 0: ALL GOOD");
    else if (status==1)  spdlog::warn("MIGRAD returns 1: Blank command, ignored.");
    else if (status==2)  spdlog::warn("MIGRAD returns 2: Command line unreadable, ignored.");
    else if (status==3)  spdlog::warn("MIGRAD returns 3: Unknown command, ignored");
    else if (status==4)  spdlog::warn("MIGRAD returns 4: Abnormal termination, MIGRAD not converged or something.");
    else if (status==9)  spdlog::warn("MIGRAD returns 9: Reserved.");
    else if (status==10) spdlog::warn("MIGRAD returns 10: End command.");
    else if (status==11) spdlog::warn("MIGRAD returns 11: Exit/Stop command.");
    else if (status==12) spdlog::warn("MIGRAD returns 12: Return command.");
    else                 spdlog::warn("MIGRAD returns {0:d} -> SOMETHING VERY WRONG", status);
    return;
}


//set plotter
void fcnc::fitter::set_plotters(plotter* plot) {
    std::vector<plotter* > p;
    p.push_back(plot);
    plots = p;
    return;
}

///set plotters
void fcnc::fitter::set_plotters(std::vector<plotter*> plotters) {
    plots = plotters;
    return;
}
///define common parameters for all pdfs
void fcnc::fitter::set_common_parameters(std::vector<std::string> common_pars) {
    common_params = common_pars;
    return;
}
///is the parameter with name one of the common parameters
bool fcnc::fitter::is_common(std::string name) {
    for (unsigned int i=0; i<common_params.size(); i++){
        if (common_params.at(i) == name) return true;
    }
    return false;
}
///is this the first occurence of the parameter?
bool fcnc::fitter::is_first(std::string name, unsigned int idx_i, unsigned int idx_j) {
    for (unsigned int i = 0; i < params.size(); i++){
        for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
            if (name == params.at(i)->get_parameter(j)->get_name()){
                if (i==idx_i && j==idx_j) return true;
                else                      return false;
            }
        }
    }
    spdlog::critical("[FATAL]\t\tThe parameter '"+name+"' was not found!");
    assert(0);
    return true;
}

///returns the nth minuit index for parameter with name
int fcnc::fitter::nth_minuit_index(std::string name, unsigned int n) {
    assert(n);
    assert(n <= params.size());
    unsigned int start_param = 0;
    for (unsigned int i = 0; i < params.size(); i++){
        for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
            if (name == params.at(i)->get_parameter(j)->get_name()){
                if (n == 1) return start_param+j;
                else        n--;
            }
        }
        start_param += minuit_one_fitter->params.at(i)->nparameters();
    }
    return -1;
}

///returns the first minuit index for parameter with name
int fcnc::fitter::first_minuit_index(std::string name) {
    return nth_minuit_index(name, 1);
}

///returns wether this is an analysis with blinded parameters
bool fcnc::fitter::is_blind() {
    for (unsigned int i = 0; i < params.size(); i++){
        for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
            if (params.at(i)->get_parameter(j)->is_blind()){
                return true;
            }
        }
    }
    return false;
}

//WHAT THE FUCK IS THIS DOING HERE?
int fcnc::fitter::fit(pdf* probs, parameters* parms, std::vector<event>* ev, std::string index){
    std::vector<pdf *> the_probs;
    the_probs.push_back(probs);

    std::vector<parameters *> the_params;
    the_params.push_back(parms);

    std::vector<std::vector<event>* > the_events;
    the_events.push_back(ev);

    return fit(the_probs, the_params, the_events, index);
}

void fcnc::fitter::init(pdf* probs, parameters* parms, std::vector<event>* ev, std::string index){
    std::vector<pdf *> the_probs;
    the_probs.push_back(probs);

    std::vector<parameters *> the_params;
    the_params.push_back(parms);

    std::vector<std::vector<event>* > the_events;
    the_events.push_back(ev);

    return init(the_probs, the_params, the_events, index);
}

void fcnc::fitter::minuit_one_fcn(Int_t &npar, Double_t *grad, Double_t &lh, Double_t *parameters, Int_t iflag){
    //set the parameter sets according to minuits current parameter values stored in the params pointer
    //do not forget to set common parameters correctly. these have no directly corresponding minuit index, use first_index instead
    unsigned int start_param = 0;
    for (unsigned int i = 0; i < minuit_one_fitter->params.size(); i++){
        for (unsigned int j = 0; j < minuit_one_fitter->params.at(i)->nparameters(); j++){
            unsigned int index = start_param + j;
            std::string par_name = minuit_one_fitter->params.at(i)->get_parameter(j)->get_name();
            if (minuit_one_fitter->is_common(par_name))
                minuit_one_fitter->params.at(i)->get_parameter(j)->set_value(parameters[minuit_one_fitter->first_minuit_index(par_name)]);
            else
                minuit_one_fitter->params.at(i)->get_parameter(j)->set_value(parameters[index]);
        }
        start_param += minuit_one_fitter->params.at(i)->nparameters();
    }
    lh = minuit_one_fitter->likelihood();
    return;
}

fcnc::fitter::fitter(options* o):
    opts(o)
{
    minuit_one = new TMinuit(1000);//params->nparameters());
    minuit_one->SetFCN(&(minuit_one_fcn));
    //this makes minuit use highest precision
    int errorcode;
    double strategy_list[1];// = {2.0}
    strategy_list[0] = opts->minuit_strategy;
    minuit_one->mnexcm("SET STR", strategy_list, 1, errorcode);
    minuit_one->SetMaxIterations(40000);
    //need to set this value to 0.5 if you do not use -2*ln(LH)
    minuit_one->SetErrorDef(1.0);//default
    spdlog::info("Machine precision: {0:f}", minuit_one->fEpsmac); //8.88178e-16
    //verbose minuit
    //minuit_one->SetPrintLevel(o->print_level);
    //minuit_one->SetPrintLevel(-1);
    empirical_constant = 0.0;
}

fcnc::fitter::~fitter(){
    delete minuit_one;
}

void fcnc::fitter::init(std::vector<pdf*> probs, std::vector<parameters*> parms, std::vector< std::vector<event> * > ev, std::string index){
    square_weights = false;
    //set all important pointers and initialize
    minuit_one_fitter = this;
    //this could be different for every fit (feldman-cousins)
    int errorcode;
    double strategy_list[1];// = {2.0}
    strategy_list[0] = opts->minuit_strategy;
    minuit_one->mnexcm("SET STR", strategy_list, 1, errorcode);

    //prob = probability;
    pdfs = probs;
    params = parms;
    events = ev;

    reset_param_values();
    //this was originally before the reset... put here to catch reset of swave
    for (unsigned int i=0; i<pdfs.size(); i++)
        pdfs.at(i)->init(params.at(i));

    //update cached efficiencies in three situations
    //1. q2 fixed and regular acceptance
    //2. unfolded fit to determine the weight=1/eff
    //3. per event norm
    //cache fis
    if (opts->cache_fis){
        spdlog::info("Caching fis");
        for (unsigned int i = 0; i < pdfs.size(); i++)
            pdfs.at(i)->update_cached_integrated_fis(params.at(i));
    }
    for (unsigned int i = 0; i < pdfs.size(); i++){
        parameter* effq2 = params.at(i)->get_parameter("eff_q2");
        bool q2fixed = false;
        if (effq2) q2fixed = (effq2->get_step_size() == 0.0);
        if ((q2fixed && opts->use_angular_acc) || opts->weighted_fit || opts->use_event_norm){
            pdfs.at(i)->update_cached_efficiencies(params.at(i), events.at(i));
        }
    }
    //per event xis
    if (opts->use_event_norm){
        for (unsigned int i = 0; i < pdfs.size(); i++){
            pdfs.at(i)->update_cached_xis(params.at(i), events.at(i));
        }
    }

}

int fcnc::fitter::fit(std::vector<pdf*> probs, std::vector<parameters*> parms, std::vector< std::vector<event> * > ev, std::string index){
    //can only use either squared hesse or minos
    if(opts->minos_errors && opts->squared_hesse){
        spdlog::critical("Cannot use both MINOS and squared HESSE for uncertainty deterimination. Choose one!");
        assert(0);
    }
    square_weights = false;
    //set all important pointers and initialize
    minuit_one_fitter = this;
    //this could be different for every fit (feldman-cousins)
    int errorcode;
    double strategy_list[1];// = {2.0}
    strategy_list[0] = opts->minuit_strategy;
    minuit_one->mnexcm("SET STR", strategy_list, 1, errorcode);

    //prob = probability;
    pdfs = probs;
    params = parms;
    events = ev;

    //make sure to reduce output for blind analysis
    if (is_blind())          minuit_one->SetPrintLevel(-1);
    else if (spdlog_debug()) minuit_one->SetPrintLevel(0);
    else                     minuit_one->SetPrintLevel(-1);

    reset_param_values();
    //this was originally before the reset... put here to catch reset of swave
    for (unsigned int i=0; i<pdfs.size(); i++){
        pdfs.at(i)->init(params.at(i));
    }

    //update cached efficiencies in three situations
    //1. q2 fixed and regular acceptance
    //2. unfolded fit to determine the weight=1/eff
    //3. per event norm
    //cache fis
    if (opts->cache_fis){
        spdlog::info("Caching fis");
        for (unsigned int i = 0; i < pdfs.size(); i++){
            pdfs.at(i)->update_cached_integrated_fis(params.at(i));
        }
    }
    for (unsigned int i = 0; i < pdfs.size(); i++){
        parameter* effq2 = params.at(i)->get_parameter("eff_q2");
        bool q2fixed = false;
        if (effq2) q2fixed = (effq2->get_step_size() == 0.0);
        if ((q2fixed && opts->use_angular_acc) || opts->weighted_fit || opts->use_event_norm){
            pdfs.at(i)->update_cached_efficiencies(params.at(i), events.at(i));
        }
    }
    //per event xis
    if (opts->use_event_norm){
        for (unsigned int i = 0; i < pdfs.size(); i++){
            pdfs.at(i)->update_cached_xis(params.at(i), events.at(i));
        }
    }

    //get empirical factor
    empirical_constant = 0.0;
    if (opts->shift_lh){
        unsigned int the_size=0;
        for (unsigned int i=0; i<ev.size(); i++) the_size += ev.at(i)->size();
        empirical_constant = likelihood()/the_size;
    }

    spdlog::debug("Determined empirical constant of: {0:f}", empirical_constant);

    int result = 0;
    unsigned int varied_params = 0;
    //determine the number of varied parameters
    //count only parameters that are not common among the PDFs!
    for (unsigned int i = 0; i < params.size(); i++){
        for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
            if (params.at(i)->get_parameter(j)->get_step_size() != 0.0 && !is_common(params.at(i)->get_parameter(j)->get_name())){
                varied_params++;
                spdlog::debug("[PDF{0:d}]\tPAR='"+params.at(i)->get_parameter(j)->get_name()+"'\trange=[{1:f} - {2:f}]", i+1, params.at(i)->get_parameter(j)->get_min(), params.at(i)->get_parameter(j)->get_max());
            }
        }
    }

    spdlog::debug("Count number of varied parameters: {0:d} PDF(s)", params.size());
    spdlog::debug("Number of varied, non-common parameters: {0:d}", varied_params);
    //assert(!(varied_params % params.size()));

    //add the parameters to the counter, that are common AND varied
    for (unsigned int i = 0; i < common_params.size(); i++){
        bool is_varied = false;
        for (unsigned int j = 0; j < params.size(); j++){
            for (unsigned int k = 0; k < params.at(j)->nparameters(); k++){
                if (common_params.at(i) == params.at(j)->get_parameter(k)->get_name() && params.at(j)->get_parameter(k)->get_step_size() != 0.0){
                    is_varied = true;                    
                    spdlog::debug("[COMMON]\tPAR='"+params.at(j)->get_parameter(k)->get_name()+"'\trange=[{0:f} - {1:f}]", params.at(j)->get_parameter(k)->get_min(), params.at(j)->get_parameter(k)->get_max());
                    break;
                }
            }
            if(is_varied)break;
        }
        if (is_varied){
            varied_params++;
        }
    }

    spdlog::info("Including common parameters, number of varied parameters is: {0:d}", varied_params);

    //open texFile
    std::ofstream myFile;
    if (index != ""){
        open_Latex_noteFile(latex_fitterFile(index), myFile);
    }


    //Use Markov Chain Monte Carlo //TODO when bored, move this to a separate function
    if (opts->use_mcmc){
        unsigned int nlength = 10000;
        //just run a single chain
        std::vector<TVectorD> chain;//(nlength);
        for (unsigned int i=0; i<nlength; i++){
            chain.push_back(TVectorD(varied_params));
        }
        TVectorD current(varied_params);
        //fill in starting values
        //TMatrixDSym S1(nparams);
        //S1.IdentityMatrix();
        //S1 *= 0.1;
        TMatrixDSym SNminusone(varied_params);
        SNminusone.UnitMatrix();
        //SNminusone *= 0.1; //might be more appropriate for the problem

        unsigned int idx = 0;
        for (unsigned int i = 0; i < params.size(); i++){
            for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
                if (params.at(i)->get_parameter(j)->get_step_size() != 0.0 && !is_common(params.at(i)->get_parameter(j)->get_name())){
                    current[idx] = params.at(i)->get_parameter(j)->get_value();
                    SNminusone(idx, idx) = params.at(i)->get_parameter(j)->get_step_size();//this is actually quite clever
                    idx++;
                }
            }
        }
        spdlog::debug("nrows {0:d}\tvaried_params {1:d}", current.GetNrows(), varied_params);
        for(int i=0; i<current.GetNrows(); i++) spdlog::debug("{0:d}", current[i]);

        TVectorD last(current);

        double alphastar = 0.234;
        double last_llh = likelihood();
        double minimum_llh = last_llh;
        TVectorD minimum(current);

        TMatrixDSym SN(SNminusone);
        TRandom3 * rnd = new TRandom3();
        spdlog::info("Starting Markov chain");
        //produce markov chain of the desired length
        for (unsigned int i=0; i<nlength; i++){
            if ((i*100)%nlength == 0) std::cout << i*100/double(nlength) << "%" << std::endl;
            //should optionally repeat this, until values are in parameter range
            TVectorD WN(varied_params);
            bool finished = false;
            while (!finished){//repeat until we are inside the boundaries
                //get random vector
                for (int j=0; j<WN.GetNrows(); j++){
                    WN[j] = rnd->Gaus(0.0, 1.0);//could also use students t distribution
                }
                //shift depends on SNminusone
                TVectorD SW(SNminusone*WN);
                current = last + SW;
                finished = true;
                //this will disallow parameters outside the allowed range
                const bool constrain_parameters = true;
                if (constrain_parameters){
                    idx = 0;
                    for (unsigned int j = 0; j < params.size(); j++){
                        for (unsigned int k = 0; k < params.at(j)->nparameters(); k++){
                            if (params.at(j)->get_parameter(k)->get_step_size() != 0.0 && !is_common(params.at(j)->get_parameter(k)->get_name())){
                                if (current[idx] > params.at(j)->get_parameter(k)->get_max() || current[idx] < params.at(j)->get_parameter(k)->get_min()){
                                    finished = false;
                                }
                                idx++;
                            }
                        }
                    }
                }
            }
            spdlog::debug("event {0:d}", i);
            //convert index back to parameter* and set to current point
            idx = 0;
            for (unsigned int j = 0; j < params.size(); j++){
                for (unsigned int k = 0; k < params.at(j)->nparameters(); k++){
                    if (params.at(j)->get_parameter(k)->get_step_size() != 0.0 && !is_common(params.at(j)->get_parameter(k)->get_name())){
                        params.at(j)->get_parameter(k)->set_value(current[idx]);
                        idx++;
                    }
                }
            }
            for (unsigned int j=0; j<pdfs.size(); j++){//check if this is needed!
                pdfs.at(j)->init(params.at(j));
            }
            //compute likelihood of current point
            double current_llh = likelihood();
            double r = rnd->Rndm();
            double alpha = std::min(1.0, exp(0.5*(last_llh-current_llh)));//llhs are in fact Sum_i -2*log(P_i)
            //debug output
            if (spdlog_debug()){
                for (int i=0; i<current.GetNrows(); i++) std::cout << current[i] << " ";
                std::cout << std::endl;
            }
            //check if current point is accepted
            spdlog::debug("accept event?");
            if (r < alpha){
                chain.at(i) = current;
                last = current;
                last_llh = current_llh;
            }
            else{
                chain.at(i) = last;
                //last stays last
            }
            //debugging output
            spdlog::debug("current_llh {0:f}\t last_llh {1:f}" + std::string((r<alpha) ? " accepted " : " rejected "), current_llh, last_llh);
                    //check wether a new likelihood minimum is found and set if this is the case
            if (current_llh < minimum_llh){
                minimum = current;
                minimum_llh = current_llh;
            }
            //update SN
            TMatrixDSym SNminusoneT(SNminusone);
            SNminusoneT.T();
            double etan = std::min(1.0, varied_params*pow(double(i), -2.0/3.0));
            TMatrixDSym WNWNT(varied_params);
            WNWNT.Zero();
            for (int row = 0; row < WNWNT.GetNrows(); row++){
                for (int col = 0; col < WNWNT.GetNcols(); col++){
                    WNWNT[row][col] = WN[row]*WN[col]/WN.Norm2Sqr();
                }
            }
            //TMatrixDSym SNSNT(identity + WNWNT*etan*(alpha-alphastar));
            TMatrixDSym SNSNT(varied_params);
            SNSNT.UnitMatrix();
            SNSNT += WNWNT*etan*(alpha-alphastar);
            SNSNT = SNSNT.Similarity(SNminusone);
            TDecompChol chol(SNSNT);
            bool success = chol.Decompose();
            assert(success);
            TMatrixD SNT = chol.GetU();
            TMatrixD SN(SNT);
            SN.T();
            for (int row = 0; row < SN.GetNrows(); row++){
                for (int col = 0; col < SN.GetNcols(); col++){
                    SNminusone(row,col) = SN(row,col);
                }
            }
        }
        //best spot found
        spdlog::info("lowest point found at llh of {0:f}\n", minimum_llh);
        if (spdlog_info()){
            for (int i=0; i<minimum.GetNrows(); i++) std::cout << minimum[i] << " ";
            std::cout << std::endl;
        }
        //mean
        spdlog::info("mean point at");
        TVectorD mean(varied_params);
        mean.Zero();
        for (unsigned int i=0; i<nlength; i++){
            TVectorD v(chain.at(i));
            v *= 1.0/double(nlength);
            mean += v;
        }
        if (spdlog_info()){
            for (int i=0; i<mean.GetNrows(); i++) std::cout << mean[i] << std::endl;
            std::cout << std::endl;
        }
        //rms
        spdlog::info("rms is ");
        TVectorD rms(varied_params);
        rms.Zero();
        for (unsigned int i=0; i<nlength; i++){
            TVectorD v(chain.at(i)-mean);
            v.Sqr();
            v *= 1.0/double(nlength);
            rms += v;
        }
        rms.Sqrt();
        if (spdlog_info()){
            for (int i=0; i<rms.GetNrows(); i++) std::cout << rms[i] << " ";
            std::cout << std::endl;
        }
        //get covariance matrix estimate from matrix SN
        spdlog::debug("matrix SNminousone");
        if (spdlog_debug()){
            for (int row = 0; row < SNminusone.GetNrows(); row++){
                for (int col = 0; col < SNminusone.GetNcols(); col++) std::cout << std::setw(8) << SNminusone(row,col) << " ";
                std::cout << std::endl;
            }
        }
        TMatrixD SNminusoneT(SNminusone);
        SNminusoneT.T();
        TMatrixD cov = SNminusone*SNminusoneT;
        spdlog::debug("matrix cov");
        if (spdlog_debug()){
            for (int row = 0; row < cov.GetNrows(); row++){
                for (int col = 0; col < cov.GetNcols(); col++) std::cout << std::setw(8) << cov(row,col) << " ";
                std::cout << std::endl;
            }
        }
        spdlog::debug("errors from matrix cov");

        if (spdlog_debug()){
            for (int row = 0; row < cov.GetNrows(); row++)std::cout << sqrt(cov(row,row)) << " ";
            std::cout << std::endl;
        }
        //proper estimate of best point and uncertainties from projections
        unsigned int nbins = 1000;
        TH1D* histos[varied_params];
        idx = 0;
        for (unsigned int j = 0; j < params.size(); j++){
            for (unsigned int k = 0; k < params.at(j)->nparameters(); k++){
                if (params.at(j)->get_parameter(k)->get_step_size() != 0.0 && !is_common(params.at(j)->get_parameter(k)->get_name())){
                    std::ostringstream hname;
                    hname << "hist" << idx;
                    histos[idx] = new TH1D(hname.str().c_str(), ";;", nbins,
                                           params.at(j)->get_parameter(k)->get_min(),
                                           params.at(j)->get_parameter(k)->get_max());
                    idx++;
                }
            }
        }
        //loop over chain and fill histos
        for (unsigned int i=0; i<nlength; i++){
            TVectorD v(chain.at(i));
            for (int row = 0; row < v.GetNrows(); row++) histos[row]->Fill(v[row]);
        }
        if (true){//smooth all histos
            const unsigned int ntimes = 1;
            for (unsigned int i=0; i<varied_params; i++) histos[i]->Smooth(ntimes);
        }
        if (spdlog_debug()){ //If in debug mode, just save the histos for now
            for (unsigned int i=0; i<varied_params; i++){                
                plotAndSave(histos[i],"c"+std::to_string(i),std::string(histos[i]->GetName()),"eps");
            }
        }
        //get maximum from histos (for now no smoothing) and add up highes bins until 1 sigma
        //probably want to add from highest bin, no?
        TVectorD max(varied_params);
        TVectorD eup(varied_params);
        TVectorD edown(varied_params);
        for (unsigned int i=0; i<varied_params; i++){
            int maxbin = histos[i]->GetMaximumBin();
            double x = histos[i]->GetBinCenter(maxbin);
            double left = histos[i]->GetBinLowEdge(maxbin);
            double right = histos[i]->GetBinLowEdge(maxbin+1);
            double sum = histos[i]->GetBinContent(maxbin);
            histos[i]->SetBinContent(maxbin, 0.0);
            bool finished = sum/nlength > ONE_SIGMA;
            while (!finished){
                maxbin = histos[i]->GetMaximumBin();
                double nx = histos[i]->GetBinCenter(maxbin);
                sum += histos[i]->GetBinContent(maxbin);
                histos[i]->SetBinContent(maxbin, 0.0);
                if (nx > right) right = histos[i]->GetBinLowEdge(maxbin+1);
                if (nx < left)  left = histos[i]->GetBinLowEdge(maxbin);
                finished = sum/nlength > ONE_SIGMA;
            }
            max[i] = x;
            eup[i] = right-x;
            edown[i] = left-x;
            delete histos[i];
        }
        //output of the newly determined mean and errors

        if (spdlog_info()){
            spdlog::info("max");
            for (int i=0; i<max.GetNrows(); i++) std::cout << max[i] << " ";
            std::cout << std::endl;
            spdlog::info("eup");
            for (int i=0; i<eup.GetNrows(); i++) std::cout << eup[i] << " ";
            std::cout << std::endl;
            spdlog::info("edown");
            for (int i=0; i<edown.GetNrows(); i++) std::cout << edown[i] << " ";
            std::cout << std::endl;
        }
        idx = 0;
        for (unsigned int j = 0; j < params.size(); j++){
            for (unsigned int k = 0; k < params.at(j)->nparameters(); k++){
                if (params.at(j)->get_parameter(k)->get_step_size() != 0.0 && !is_common(params.at(j)->get_parameter(k)->get_name())){
                    params.at(j)->get_parameter(k)->set_value(max[idx]);
                    params.at(j)->get_parameter(k)->set_error(0.5*(fabs(eup[idx])+fabs(edown[idx])));
                    params.at(j)->get_parameter(k)->set_error_up(eup[idx]);
                    params.at(j)->get_parameter(k)->set_error_down(edown[idx]);
                    //params.at(j)->get_parameter(k)->set_value(mean[idx]);
                    //params.at(j)->get_parameter(k)->set_error(sqrt(cov(idx,idx)));
                    idx++;
                }
            }
        }
        //MCMC always returns status OK
        return 300;//this means all ok
    }
    //When using no Markov Chain MC: Nominal fit procedure
    else{  //TODO when bored, move this to a separate function
        spdlog::debug("Starting Fit Procedure");
        int errorcode;
        //double eps[1] = {1.0e-10};
        //minuit_one->mnexcm("SET EPS", eps, 1, errorcode);
        //double strategy_list[2] = {0,0.1};

        double migrad_options[2] = {MIG_MAX_CALLS, MIG_TOLERANCE}; //Maxcalls and tolerance
        // The default tolerance is 0.1, and the minimization will stop when the estimated vertical distance to the minimum (EDM) is less than 0.001*[tolerance]*UP(see SET ERR)
        if (opts->simplex_prerun){
            spdlog::info("Running Simplex");
            minuit_one->mnexcm("SIM", migrad_options, 2, errorcode);
        }
        minuit_one->mnexcm("MIG", migrad_options, 2, errorcode);
        result = errorcode;
        printMigradStatus(result);
        spdlog::warn("EDM: {0:f}", minuit_one->fEDM);
        if (opts->hesse_postrun){
            spdlog::info("Starting hesse");
            minuit_one->mnhess();
            if (spdlog_debug()) minuit_one->mnmatu(1); //Print the correlation matrix, they are annoying so turned off for now
        }
        int nfree, ntot, status_cov;
        double m_fcn, m_edm, up;
        minuit_one->mnstat(m_fcn, m_edm, up, nfree, ntot, status_cov);
        //status_cov  0=not calculated, 1=approximation, 2=full but forced pos. def., 3=full accurate
        result += 100*status_cov;
        if (opts->hesse_postrun){ //Hesse calculates the 2nd derivative of the likelihood profile to assign the correct systematics
            printCovMatrixStatus(status_cov,false);
        }
        double tmp_cov[varied_params * varied_params];
        //spdlog::info("nvaried params = " << varied_params);;
        minuit_one->mnemat(tmp_cov, varied_params);
        if (opts->weighted_fit && opts->asymptotic){//perform correction according to 1911.01303 Eq. 18
            spdlog::debug("Starting an assymptotic treatment.");
            assert(!(opts->squared_hesse));//TODOCL i think hesse_postrun is fine (and will lead to better estimate for the weighted Hessian)

            //weighted hesse matrix is available as tmp_cov
            //need to iterate over all events
            //need to evaluate first derivatives at central values
            //events in different pdfs are independently distributed
            //just should count every event separately
            TMatrixDSym V(varied_params, tmp_cov);//weighted covariance matrix
            TMatrixDSym C(varied_params);//, 0.0);//tmp_cov_sq);
            for (unsigned int k=0; k<varied_params; k++){
                for (unsigned int l=0; l<varied_params; l++){
                    C(k,l) = 0.0;
                }
            }
            //save best fit values
            spdlog::debug("Saving best fit values.");
            std::vector<std::string> param_names;
            std::vector<double> param_values;
            std::vector<double> eps(varied_params, 1.0e-5);
            for (unsigned int i = 0; i < params.size(); i++){
                for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
                    if (params.at(i)->get_parameter(j)->get_step_size() != 0.0){
                        //if the parameter is common, only add name once
                        if (!is_common(params.at(i)->get_parameter(j)->get_name()) || is_first(params.at(i)->get_parameter(j)->get_name(), i, j)){
                            double v,e;
                            std::string par_name = params.at(i)->get_parameter(j)->get_name();
                            unsigned int idx = first_minuit_index(par_name);
                            minuit_one->GetParameter(idx, v, e);
                            param_names.push_back(par_name);
                            param_values.push_back(v);
                            spdlog::debug(par_name+": {0:f}", v);
                        }
                    }
                }
            }
            //the optimized version varies the parameter in the outer loop and performs the update_cached_normalisation only once for + and -
            //loop over data samples

            spdlog::debug("Looping over data samples.");

            std::vector<double> lh_extended_nsig(events.size(),0.0);
            std::vector<double> lh_extended_nbkg(events.size(),0.0);
            std::vector<double> lh_extended_nsigbar(events.size(),0.0);
            std::vector<double> lh_extended_nbkgbar(events.size(),0.0);
            for (unsigned int i = 0; i<events.size(); i++){
                //save derivative for every event, every param
                if (i%10==0) spdlog::trace("Event {0:d}",i);
                std::vector<std::vector<double> > derivs; //first idx varied param, second index event
                for (unsigned int k=0; k<varied_params; k++){
                    //only want to do this for params.at(i), other data samples use the other param set
                    fcnc::parameter* varied = params.at(i)->get_parameter(param_names.at(k));
                    if (!varied){
                        std::vector<double> derivs_k(events.at(i)->size(),0.0);
                        derivs.push_back(derivs_k);
                        continue;
                    }
                    //set varied parameter +  everywhere
                    varied->set_value(param_values.at(k)+eps.at(k));
                    //cache integrals
                    pdfs.at(i)->update_cached_normalization(params.at(i));
                    if (opts->use_event_norm) pdfs.at(i)->update_cached_normalization(params.at(i), events.at(i));

                    //TODOCL

                    spdlog::debug("Getting likelihood.");
                    likelihood();//this is supposed to only get the extended term, for the + varied parameters, should be fast enough //This doesn't do anything!
                    //loop over lh_plus events
                    std::vector<double> loglh_plus_k(events.at(i)->size(),0.0);
                    for (unsigned int j = 0; j < events.at(i)->size(); j++){
                        //for every event do finite differences
                        const fcnc::event meas = events.at(i)->at(j);
                        double probability = 0.0;
                        probability = pdfs.at(i)->prob(params.at(i), meas);
                        if (probability > 0.0) loglh_plus_k.at(j) = -TMath::Log(probability);
                        //TODOCL
                        if (meas.cp_conjugate){ //TODOCL CHECK!! //LEONS COMMENT meas.cp_conjugate corresponds to n_sig !!!!!!!
                            loglh_plus_k.at(j) += -TMath::Log(lh_extended_nsig.at(i)+lh_extended_nbkg.at(i));
                        }
                        else{
                            loglh_plus_k.at(j) += -TMath::Log(lh_extended_nsigbar.at(i)+lh_extended_nbkgbar.at(i));
                        }
                    }

                    //set varied parameter -  everywhere
                    varied->set_value(param_values.at(k)-eps.at(k));
                    //cache integrals
                    pdfs.at(i)->update_cached_normalization(params.at(i));
                    if (opts->use_event_norm) pdfs.at(i)->update_cached_normalization(params.at(i), events.at(i));

                    //TODOCL
                    likelihood();//this is supposed to only get the extended term, for the - varied parameters, should be fast enough

                    //loop over lh_minus events
                    std::vector<double> loglh_minus_k(events.at(i)->size(),0.0);
//                    for (unsigned int j = 0; j < events.at(i)->size(); j++){
//                        //for every event do finite differences
//                        const fcnc::event meas = events.at(i)->at(j);
//                        double probability = 0.0;
//                        probability = pdfs.at(i)->prob(params.at(i), meas);
//                        if (probability > 0.0) loglh_minus_k.at(j) = -TMath::Log(probability);

//                        //TODOCL
//                        if (meas.cp_conjugate){ //TODOCL CHECK!!  //LEONS COMMENT meas.cp_conjugate corresponds to n_sig !!!!!!!
//                            loglh_minus_k.at(j) += -TMath::Log(lh_extended_nsig.at(i)+lh_extended_nbkg.at(i));
//                        }
//                        else{
//                            loglh_minus_k.at(j) += -TMath::Log(lh_extended_nsigbar.at(i)+lh_extended_nbkgbar.at(i));
//                        }
//                    }

                    //calculate derivatives for all events
                    spdlog::debug("Calculating derivatives for all events");
                    std::vector<double> derivs_k(events.at(i)->size(),0.0);
                    for (unsigned int j = 0; j < events.at(i)->size(); j++){
                        derivs_k.at(j) = (loglh_plus_k.at(j)-loglh_minus_k.at(j))/(2.0*eps.at(k));
                    }
                    spdlog::trace("Derivatives:+ "+convert_vector_to_string(derivs_k));
                    derivs.push_back(derivs_k);

                    //reset varied parameter  everywhere
                    varied->set_value(param_values.at(k));
                    //cache integrals
                    pdfs.at(i)->update_cached_normalization(params.at(i));
                    if (opts->use_event_norm){
                        pdfs.at(i)->update_cached_normalization(params.at(i), events.at(i));
                    }

                }

                //afterwards determine derivative via finite differences

                spdlog::debug("Calculating derivatives for finite differences");
                //and save matrix of squared first derivatives
                for (unsigned int j = 0; j < events.at(i)->size(); j++){
                    //for every event do finite differences
                    const fcnc::event meas = events.at(i)->at(j);
                    for (unsigned int k=0; k<varied_params; k++){
                        for (unsigned int l=0; l<varied_params; l++){
                            C(k,l) += meas.weight*meas.weight*derivs.at(k).at(j)*derivs.at(l).at(j);
                        }
                    }
                }
            }

            TMatrixD VCV(V,TMatrixD::kMult,TMatrixD(C,TMatrixD::kMult,V));
            TMatrixDSym VCVsym(varied_params);
            spdlog::debug("printing corrected covariance matrix");
            for (unsigned int i=0; i<varied_params; i++){
                for (unsigned int j=0; j<varied_params; j++) {
                    if (spdlog_debug()) std::cout << std::scientific << std::setw(9) << std::setprecision(2) << VCV(i,j) << " ";
                    VCVsym(i,j) = 0.5*(VCV(i,j)+VCV(j,i));//no check for now
                    tmp_cov[varied_params*j + i] = VCVsym(i,j);//save corrected covariance matrix
                }
                if (spdlog_debug()) std::cout << std::endl;
            }
        }

        if (opts->weighted_fit && opts->squared_hesse){
            square_weights = true;
            spdlog::info("Starting hesse with squared weights");
            minuit_one->mnhess();
            //check that this went fine
            double m_sq_fcn, m_sq_edm, sq_up;
            int status_sq_cov;
            minuit_one->mnstat(m_sq_fcn, m_sq_edm, sq_up, nfree, ntot, status_sq_cov);
            printCovMatrixStatus(status_sq_cov,true);
            //change status to invalied if squared weighted fit failed
            if (status_cov != status_sq_cov){
                result -= 100*status_cov;
                result += 100*((status_cov < status_sq_cov) ? status_cov : status_sq_cov);
            }
            //  minuit_one->mnmatu(1);//this also sets something internal in minuit! getparameter seems to get its error from here!
            double tmp_cov_sq[varied_params * varied_params];
            minuit_one->mnemat(tmp_cov_sq, varied_params);
            //lets use root classes for the matrix inversion
            TMatrixDSym V(varied_params, tmp_cov);//tmp_cov
            TMatrixDSym C(varied_params, tmp_cov_sq);
            double det=0.0;
            C.Invert(&det);
            TMatrixD VCV(V,TMatrixD::kMult,TMatrixD(C,TMatrixD::kMult,V));
            TMatrixDSym VCVsym(varied_params);
            spdlog::info("printing corrected covariance matrix");
            for (unsigned int i=0; i<varied_params; i++){
                for (unsigned int j=0; j<varied_params; j++){
                    if (spdlog_debug()){
                        std::cout << std::scientific << std::setw(9) << std::setprecision(2) << VCV(i,j) << " ";
                    }
                    VCVsym(i,j) = 0.5*(VCV(i,j)+VCV(j,i));//no check for now
                    tmp_cov[varied_params*j + i] = VCVsym(i,j);//save corrected covariance matrix
                }
                if (spdlog_debug()) std::cout << std::endl;
            }
            square_weights = false;
        }

        //to get the correct latex names
        std::vector<std::string> varied_names;
        for (unsigned int i = 0; i < params.size(); i++){
            for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
                if (params.at(i)->get_parameter(j)->get_step_size() != 0.0){//if the parameter is common, only add name once
                    if (!is_common(params.at(i)->get_parameter(j)->get_name()) || is_first(params.at(i)->get_parameter(j)->get_name(), i, j)){
                        varied_names.push_back(params.at(i)->get_parameter(j)->get_description());
                    }
                }
            }
        }

        //printing error matrix
        if (spdlog_debug()){
            spdlog::debug("printing error matrix");
            for (unsigned int x = 0; x < varied_params; x++) std::cout << "\tvar" << x << " ";
            std::cout << std::endl;
            for (unsigned int y = 0; y < varied_params; y++){
                std::cout << "var" << y;
                for (unsigned int x = 0; x < varied_params; x++){
                    std::cout << " " << std::scientific << std::setw(9) << std::setprecision(2) << tmp_cov[varied_params*y + x];
                }
                std::cout << std::endl;
            }
        }
        //calculating correlation matrix from error matrix
        double cor[varied_params*varied_params];
        for (unsigned int x = 0; x < varied_params; x++){
            for (unsigned int y = 0; y < varied_params; y++){
                cor[varied_params*y + x] = tmp_cov[varied_params*y + x]
                        /(sqrt(fabs(tmp_cov[varied_params*x + x]))*sqrt(fabs(tmp_cov[varied_params*y + y])));
            }
        }
        //printing correlation matrix

        if (index != ""){
            spdlog::info("printing correlation matrix");
            myFile << "Run: " << opts->run << std::endl;
            myFile << "\\begin{tabular}{c";
            for (unsigned int x = 0; x < varied_params; x++){
                myFile << "c";
            }
            myFile << "} \\hline" << std::endl;
            myFile << "     ";
            for (unsigned int x = 0; x < varied_params; x++){
                myFile << " & $" << varied_names.at(x) << "$";
            }
            myFile << "\\\\ \\hline" << std::endl;

            bool upper_half = true;
            for (unsigned int y = 0; y < varied_params; y++){
                myFile << std::setw(20) << "$" << varied_names.at(y) << "$";
                for (unsigned int x = 0; x < varied_params; x++){
                    if (x >= y || !upper_half){
                        if (fabs(cor[varied_params*y + x]) >= 0.005){
                            myFile << " & " << std::fixed << std::setw(5) << std::setprecision(2) << cor[varied_params*y + x];
                        }
                        else myFile << " & " << std::setw(5) << "-";
                    }
                    else myFile << " & " << std::setw(5) << " ";
                }
                myFile << " \\\\ " << std::endl;
            }
            myFile <<"\\hline \\end{tabular}" << std::endl;
        }


        //saves correlations for all parameters
        for (unsigned int i = 0; i < params.size(); i++){
            for (unsigned int j=0; j < params.at(i)->nparameters(); j++){
                if (params.at(i)->get_parameter(j)->get_step_size() != 0.0){
                    params.at(i)->get_parameter(j)->correlations.clear();
                    unsigned int apary = 0;
                    for (unsigned int y=0; y < varied_params; y++){
                        if (varied_names.at(y) == params.at(i)->get_parameter(j)->get_description()){
                            apary = y;
                            break;
                        }
                    }
                    for (unsigned int x=0; x < varied_params; x++)
                        params.at(i)->get_parameter(j)->correlations.push_back(cor[varied_params*apary + x]);
                }
            }
        }

        //minos error analysis
        if (opts->minos_errors && (result%100) == 0){//migrad is successfull
            spdlog::info("Starting Minos error analysis");
            std::vector<int> indices;
            for (unsigned int i = 0; i < params.size(); i++){
                for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
                    if (params.at(i)->get_parameter(j)->get_step_size() != 0.0 && params.at(i)->get_parameter(j)->get_minos()){
                        std::string par_name = params.at(i)->get_parameter(j)->get_name();
                        unsigned int idx = 0;
                        if(is_common(par_name) || opts->use_individual_param_names) idx = first_minuit_index(par_name);
                        else idx = nth_minuit_index(par_name, i+1);
                        indices.push_back(idx);
                    }
                }
            }
            if (indices.size() > 0){//run minos just for subset of parameters
                spdlog::info("MINOS SUBSET");
                int errorcode;
                /*
                double migrad_options[indices.size()+1];
                migrad_options[0] = 100000000.0;
                for (unsigned int i = 0; i < indices.size(); i++)
                  //migrad_options[i+1] = indices.at(i);
                  migrad_options[i+1] = indices.at(i)+1;
                minuit_one->mnexcm("MINO", migrad_options, indices.size()+1, errorcode);
                */
                for (unsigned int i = 0; i < indices.size(); i++){
                    double migrad_options[2];
                    migrad_options[0] = 100000000.0;
                    migrad_options[1] = indices.at(i)+1;
                    minuit_one->mnexcm("MINO", migrad_options, 2, errorcode);
                }
            }
            else minuit_one->mnmnos();//do minos error analysis on all parameters

            //write MINOS errors to parameters
            for (unsigned int i = 0; i < params.size(); i++){
                for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
                    double e_up, e_down, parab, gcc;
                    if (params.at(i)->get_parameter(j)->get_step_size() != 0.0){
                        std::string par_name = params.at(i)->get_parameter(j)->get_name();
                        unsigned int idx = 0;
                        if(is_common(par_name) || opts->use_individual_param_names)  idx = first_minuit_index(par_name);
                        else                                                         idx = nth_minuit_index(par_name, i+1);
                        minuit_one->mnerrs(idx, e_up, e_down, parab, gcc);
                        params.at(i)->get_parameter(j)->set_error_up(e_up);
                        params.at(i)->get_parameter(j)->set_error_down(e_down);
                    }
                }
            }
        }

        //finally extract fitted parameters from minuit
        for (unsigned int i = 0; i < params.size(); i++){
            for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
                double v,e;
                std::string par_name = params.at(i)->get_parameter(j)->get_name();
                unsigned int idx;
                if(is_common(par_name) || opts->use_individual_param_names) idx = first_minuit_index(par_name);
                else idx = nth_minuit_index(par_name, i + 1);

                spdlog::trace("[PDF{0:d}]\tPAR{1:d}:\t{2:s}\tTMinuit_idx: {3:d}", i, j, par_name, idx);

                minuit_one->GetParameter(idx, v, e);
                params.at(i)->get_parameter(j)->set_value(v);
                params.at(i)->get_parameter(j)->set_error(e);


                if (opts->weighted_fit){
                    //this method is approximate for parameters with limited range!
                    int iidx = minuit_one->fNiofex[idx] - 1;
                    if (iidx>=0){
                        e = sqrt(fabs(tmp_cov[iidx*varied_params+iidx]));
                        params.at(i)->get_parameter(j)->set_error(e);
                    }
                }
            }
        }

        if (spdlog_trace()){
            for (unsigned int i = 0; i < params.size(); i++){
                params.at(i)->print_parameters();
            }
        }

        if (opts->project){
            for (unsigned int i = 0; i<plots.size(); i++)
                plots.at(i)->plot_data(pdfs.at(i), params.at(i), events.at(i), index);
        }
        spdlog::info("Fit procedure finished");
    }

    //Close Latex file
    if (index != "") myFile.close();

    return result;
}

void fcnc::fitter::define_parameter(int i, const parameter & p){
    if (p.get_unlimited()){
        minuit_one->DefineParameter(i, p.get_mn_name(), p(), p.get_step_size(), 0.0, 0.0);
    }
    else{
        minuit_one->DefineParameter(i, p.get_mn_name(), p(), p.get_step_size(), p.get_min(), p.get_max());
    }
}

void fcnc::fitter::reset_param_values(){
    spdlog::debug("Resetting parameter values");
    minuit_one->mncler();

    unsigned int start_param = 0;
    for (unsigned int i = 0; i < params.size(); i++){
        for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
            if (opts->reset_start){
                params.at(i)->get_parameter(j)->reset_start();
            }
            std::string par_name = params.at(i)->get_parameter(j)->get_name();
            if (!is_common(par_name) || is_first(par_name, i, j)){
                define_parameter(start_param+j, *(params.at(i)->get_parameter(j)));
            }
        }
        start_param += params.at(i)->nparameters();
    }
}

double fcnc::fitter::likelihood(){
    lh_tot = 0.0;
    //this is called on every parameter change
    //this can never hurt, also needed in case of weighted/unfolded fit
    for (unsigned int i = 0; i < pdfs.size(); i++){
        pdfs.at(i)->update_cached_normalization(params.at(i));//should do this, but should buffer f1, update f1 on fit command
        if(opts->use_event_norm){
            pdfs.at(i)->update_cached_normalization(params.at(i), events.at(i));
        }
    }

    //threading the likelihood determination on multiple CPU cores
    if(opts->ncores > 1){
        std::vector<std::thread*> threads;
        for(unsigned int i = 0; i < opts->ncores; i++){
            std::thread* t = new std::thread(std::bind( &fitter::likelihood_thread, this, i));
            threads.push_back(t);
        }
        for(unsigned int i = 0; i < opts->ncores; i++) threads[i]->join();
        for(unsigned int i = 0; i < opts->ncores; i++) delete threads[i];
    }
    else{
        likelihood_thread(0);
    }

    if (spdlog_trace()){
        unsigned int start_param = 0;
        for(unsigned int i = 0; i < params.size(); i++){
            for(unsigned int j = 0; j < params.at(i)->nparameters(); j++){
                if(params.at(i)->get_parameter(j)->get_step_size() != 0.0){
                    std::string par_name = params.at(i)->get_parameter(j)->get_name();
                    if(!is_common(par_name) || is_first(par_name, i, j)){
                        if(!params.at(i)->get_parameter(j)->is_blind()) std::cout << par_name << ":" << params.at(i)->get_parameter(j)->get_value() << " ";
                        else                                            std::cout << par_name << ":" << "XXX" << " ";
                    }
                }
            }
            start_param += params.at(i)->nparameters();
        }
        std::cout << std::endl;
    }

    //there is an additional term for parameters which have been measured previously
    for (unsigned int i = 0; i < params.size(); i++){
        for (unsigned int j = 0; j < params.at(i)->nparameters(); j++){
            const parameter* param = params.at(i)->get_parameter(j);
            if (param->get_gaussian_constraint()){
                if (!is_common(param->get_name()) || is_first(param->get_name(), i, j)){
                    double mean = param->get_previous_measurement();
                    double x = param->get_value();
                    double dup = fabs(param->get_previous_error_high());
                    double ddown = fabs(param->get_previous_error_low());
                    double sigma = (x>mean ? dup : ddown);
                    //spdlog::trace("mean {0:f}\tx {1:f}\tdup {2:f}\tddown {3:f}\tsigma {4:f}",mean,x,dup,ddown,sigma);

                    if (x>mean+dup)        sigma = dup;
                    else if (x<mean-ddown) sigma = ddown;
                    else                   sigma = ddown + (dup-ddown)*(x-(mean-ddown))/(dup+ddown);
                    lh_tot += (-2.0*(-(x-mean)*(x-mean)/(2.0*sigma*sigma)));
                }
            }
        }
    }
    if (opts->chisquaredstudy){
        double chi2 = 0.0;
        for (unsigned int i = 0; i < pdfs.size(); i++) chi2 += pdfs.at(i)->chi2(params.at(i));
        lh_tot += chi2;
    }
    if (opts->extended_ml){
        double lh_ext = 0.0;
        for(unsigned int i = 0; i < params.size(); i++){
            double nobs = 0.0;
            parameter* pnsig = params.at(i)->get_parameter("n_sig");
            parameter* pnbkg = params.at(i)->get_parameter("n_bkg");
            assert(pnsig && pnbkg);
            double nsig = pnsig->get_value();
            double nbkg = pnbkg->get_value();
            if(opts->weighted_fit){
                for(unsigned int j = 0; j<events.at(i)->size(); j++){
                    nobs += events.at(i)->at(j).weight;
                }
            }
            else{
                nobs = events.at(i)->size();
            }
            double prob = TMath::Poisson(nsig+nbkg, nobs);
            if(prob == 0.0) prob = 1e-100;
            lh_ext += -2.0*TMath::Log( prob );
            if (std::isinf(lh_ext) || std::isnan(lh_ext)){
                std::cout << "[PDF" << i << "]\t\tlh_ext = " << lh_ext << "\t prob = " << prob << "\t -2log(prob) = " << -2.0*TMath::Log( prob ) << std::endl;
                std::cout << "[PDF" << i << "]\t\tnsig = " << nsig << "\t n_bkg = " << nbkg << "\t nobs = " << nobs << std::endl;
                assert(0);
            }
        }
        if (std::isinf(lh_ext)){
            spdlog::error("{0:0.16f}", lh_ext);
            spdlog::error("lh (w/o lh_ext) = {0:0.16f}", lh_tot);
            assert(0);
        }
        lh_tot += lh_ext;
    }

    if (std::isinf(lh_tot || std::isnan(lh_tot))){
        spdlog::error("lh = {0:0.16f}", lh_tot);
        assert(0);
    }
    if (spdlog_trace()) std::cout << "[debug]\t" << std::setprecision(16) << "lh = " << lh_tot << "\r" << std::flush;
    return lh_tot;
}

void fcnc::fitter::likelihood_thread(int thread_id){

    double probability = 0.0;
    event meas;
    long double result = 0.0;

    for (unsigned int i = 0; i<events.size(); i++){
        unsigned int min, max;
        unsigned int evts_per_thread = events.at(i)->size()/opts->ncores;
        //is too small for last vector!
        min = evts_per_thread * thread_id;
        max = evts_per_thread * (thread_id+1);
        int last_thread = opts->ncores - 1;
        if (thread_id == last_thread){//last thread needs to take all posibly remaining events
            max = events.at(i)->size();
        }
        std::vector<long double> vec;
        vec.reserve(max - min);
        for (unsigned int j = min; j < max; j++){
            meas = events.at(i)->at(j);
            probability = pdfs.at(i)->prob(params.at(i), meas);
            if (probability < 0.0 || std::isnan(probability) || std::isinf(probability)){
                std::lock_guard<std::mutex> lock(fitter_mutex);
                spdlog::info("Event probability is {0:d}", probability);
                print_event(meas);
                params.at(i)->print_parameters();
                assert(0);
            }
            if (probability == 0.0){
                probability = 1.0;//ignore event
            }
            if (probability != 0.0){
                if (opts->weighted_fit){
                    if (square_weights) vec.push_back(-2.0 * meas.weight * meas.weight * TMath::Log(probability) - empirical_constant);//todo: does the empirical constant pose a problem here?
                    else vec.push_back(-2.0 * meas.weight * TMath::Log(probability) - empirical_constant);
                }
                else vec.push_back(-2.0 * TMath::Log(probability) - empirical_constant);
            }
            else if (!std::isnan(probability)){
                spdlog::error("Event probability is {0:d}", probability);
                print_event(meas);
                assert(0);
            }
            else{//is inf?
                vec.push_back(8.0);//is inf?//use something like prob max? log(p_min)
            }
        }
        result += add_results<std::vector<long double>::iterator>(vec.begin(), vec.end());
    }
    if (opts->ncores > 1){
        std::lock_guard<std::mutex> lock(fitter_mutex);
        lh_tot += result;
    }
    else{
        lh_tot += result;
    }
}

//boost::mutex fitter::fitter_mutex;
std::mutex fcnc::fitter::fitter_mutex;

fcnc::fitter* fcnc::fitter::minuit_one_fitter;
