//Renata Kopecna

#include <angularcorr.hh>
#include <sstream>      // std::istringstream

#include <options.hh>
#include <event.hh>
#include <values.hh>
#include <helpers.hh>
#include <paths.hh>
#include <design.hh>

#include <bu2kstarmumu_loader.hh>
#include <bu2kstarmumu_parameters.hh>
#include <bu2kstarmumu_pdf.hh>

#include <spdlog.h>

#include <TFitResult.h>
#include <TH2D.h>
#include <TROOT.h>
#include <TChain.h>
#include <TFile.h>
#include <TRandom3.h>
#include <TMatrixD.h>
#include <TStyle.h>


//-------------------------------
//     scan angular acceptance max. order of legendre
//-------------------------------


void logYears(std::vector<int>years, std::vector< std::vector<fcnc::event> > eves, int loadedSize){

    for(UInt_t y = 0; y < years.size(); y++){
        spdlog::info("Events in run {0:d} : {1:d}", years.at(y) , eves.at(y).size());
    }
    spdlog::info( "------------------------");
    spdlog::info("Events in total: {0:d}", loadedSize);
    return;
}

void logYears(int run, int evtsSize, int loadedSize){
    spdlog::info("Events in run {0:d} : {1:d}", run, evtsSize);
    spdlog::info("------------------------");
    spdlog::info("Events in total : {0:d}", loadedSize);
}

//-------------------------------
//     scan angular acceptance
//-------------------------------

std::vector<int> get_scan_low(bool quickTest, bool only_1D_chi2){
    //q2, thetak, thetal, phi
    if (quickTest) return {6,5,5,2};//use small range for quick tests on format or algorithms:
    if (only_1D_chi2) return {2,2,2,2};//use huge range for quick tests of 1D chi2:
    return {6,5,2,1};
}

std::vector<int> get_scan_high(bool quickTest, bool only_1D_chi2){
    //q2, thetak, thetal, phi
    if (quickTest) return {6,10,5,2};//use small range for quick tests on format or algorithms:
    if (only_1D_chi2) return {9,9,9,9};//use huge range for quick tests of 1D chi2:
    return {8,7,4,3};
}

std::vector<int> get_scan_range(std::vector<int> scan_low, std::vector<int> scan_high){
    std::vector<int> scan_range;
    for_indexed(auto item: scan_low){
        //If high >= low, raise an exception
        assert(scan_high.at(i) >= item);
        scan_range.push_back(scan_high.at(i) -item + 1);
    }
    return scan_range;
}

int sanityCheck(std::vector<fcnc::event>events, fcnc::options opts, bool PHSP, int bin, bool norm){
    //events: input events
    //opts: general options
    //PHSP: using PHSP or sigMC?
    //bin: # of bin in q2, when all, set to -1
    //norm: Normalize the plots?

    //Create parameter set
    fcnc::bu2kstarmumu_parameters * leParameters = new fcnc::bu2kstarmumu_parameters(&opts);
    //create PDF
    fcnc::bu2kstarmumu_pdf * lePDF = new fcnc::bu2kstarmumu_pdf(&opts, leParameters);
    std::vector<fcnc::event> *leEvents= new std::vector<fcnc::event>;

    opts.weighted_fit = true; //Jesus efin christ
    int nAngleBins = 20;

    for(auto meas: events){
        leEvents->push_back(meas);
    }
    leParameters->use_default();
    leParameters->take_current_as_start();

    lePDF->load_coeffs_eff_phsp_4d();
    opts.multiply_eff = true;
    opts.weighted_fit = true;
    lePDF->update_cached_efficiencies(leParameters, leEvents);
    lePDF->update_cached_normalization(leParameters);

    //check the distributions of weighted events
    TH1D * ctl = new TH1D ("h_ctl", "h_ctl", nAngleBins, CTL_MIN, CTL_MAX);
    TH1D * ctk = new TH1D ("h_ctk", "h_ctk", nAngleBins, CTK_MIN, CTK_MAX);
    TH1D * phi = new TH1D ("h_phi", "h_phi", nAngleBins, PHI_MIN, PHI_MAX);
    TH1D * q2  = new TH1D ("h_q2" , "h_q2" , nAngleBins, Q2_MIN_RANGE, Q2_MAX_RANGE);
    TH2D * ctkq2  = new TH2D ("h_ctkq2" , "h_ctkq2",  nAngleBins, CTK_MIN, CTK_MAX, nAngleBins, Q2_MIN_RANGE, Q2_MAX_RANGE);
    TH2D * ctlq2  = new TH2D ("h_ctlq2" , "h_ctlq2",  nAngleBins, CTL_MIN, CTL_MAX, nAngleBins, Q2_MIN_RANGE, Q2_MAX_RANGE);
    TH2D * phiq2  = new TH2D ("h_phiq2" , "h_phiq2",  nAngleBins, PHI_MIN, PHI_MAX, nAngleBins, Q2_MIN_RANGE, Q2_MAX_RANGE);
    TH1D * ctl_noW = new TH1D ("ctl_noW", "ctl_noW", nAngleBins, CTL_MIN, CTL_MAX);
    TH1D * ctk_noW = new TH1D ("ctk_noW", "ctk_noW", nAngleBins, CTK_MIN, CTK_MAX);
    TH1D * phi_noW = new TH1D ("phi_noW", "phi_noW", nAngleBins, PHI_MIN, PHI_MAX);
    TH1D * q2_noW  = new TH1D ("q2_noW" , "q2_noW" , nAngleBins, Q2_MIN_RANGE, Q2_MAX_RANGE);
    TH1D* hweight  = new TH1D("hweight",  ";w;",     100,        0.0, 1.0/EFF_CUTOFF);


    for(auto &meas: *leEvents){
        if(meas.weight < 0.0){
            spdlog::warn("negative weight event:\tm={0:f} \t\tctl={1:f} \tctk={2:f}\tphi={3:f} \tweight={4:f}", meas.m, meas.costhetal, meas.costhetak, meas.phi, meas.weight);
        }        
        ctl->Fill(meas.costhetal, meas.weight);
        ctk->Fill(meas.costhetak, meas.weight);
        phi->Fill(meas.phi,       meas.weight);
        q2 ->Fill(meas.q2,        meas.weight);
        ctkq2->Fill(meas.costhetak, meas.q2, meas.weight);
        ctlq2->Fill(meas.costhetal, meas.q2, meas.weight);
        phiq2->Fill(meas.phi, meas.q2, meas.weight);

        ctl_noW->Fill(meas.costhetal);
        ctk_noW->Fill(meas.costhetak);
        phi_noW->Fill(meas.phi);
        q2_noW ->Fill(meas.q2);
        if (meas.weight > 30){
            spdlog::warn("Event with very high weight {0:f} found!", meas.weight);
            print_event(meas);
        }

        hweight->Fill(meas.weight);

    }
    //phi->GetYaxis()->SetRangeUser(5000,7000);


    int tagNumber =1*opts.eff_order_q2+
                   100*opts.eff_order_costhetak+
                   10000* opts.eff_order_costhetal+
                   1000000*opts.eff_order_phi ;


    //Normalize to one

    if (norm){
        ctl->Scale(nAngleBins/ctl->Integral());
        ctk->Scale(nAngleBins/ctk->Integral());
        phi->Scale(nAngleBins/phi->Integral());
        q2->Scale(nAngleBins/q2->Integral());
        ctkq2->Scale(nAngleBins*nAngleBins/ctkq2->Integral());
        ctlq2->Scale(nAngleBins*nAngleBins/ctlq2->Integral());
        phiq2->Scale(nAngleBins*nAngleBins/phiq2->Integral());
    }

    std::string subfolder = get_angCorrPath(false);
    std::string tag = (PHSP ? "" : "MC_") + (bin==-1 ? "" : "_bin"+std::to_string(bin)) + tag_Run(opts.run) +"_" + std::to_string(tagNumber);
    if (PHSP){
        //Get the fitted lines
        TF1 *fitLine_ctl = fitStraightLine(ctl, "fitLine_ctl", CTL_MIN, CTL_MAX);
        TF1 *fitLine_ctk = fitStraightLine(ctk, "fitLine_ctk", CTK_MIN, CTK_MAX);
        TF1 *fitLine_phi = fitStraightLine(phi, "fitLine_phi", PHI_MIN, PHI_MAX);
        TF1 *fitLine_q2  = fitStraightLine(q2,  "fitLine_q2",  Q2_MIN_RANGE, Q2_MAX_RANGE);
        //and plot them
        plotAndSaveWithLine(ctl,fitLine_ctl,"c_ctl",subfolder+"debug_ctl_"+tag,"eps");
        plotAndSaveWithLine(ctk,fitLine_ctk,"c_ctk",subfolder+"debug_ctk_"+tag,"eps");
        plotAndSaveWithLine(phi,fitLine_phi,"c_phi",subfolder+"debug_phi_"+tag,"eps");
        plotAndSaveWithLine(q2, fitLine_q2, "c_q2" ,subfolder+"debug_q2_"+tag, "eps");
    }
    else{
        plotAndSave(ctl,"c_ctl",subfolder+"debug_ctl_"+tag,"eps");
        plotAndSave(ctk,"c_ctk",subfolder+"debug_ctk_"+tag,"eps");
        plotAndSave(phi,"c_phi",subfolder+"debug_phi_"+tag,"eps");
        plotAndSave(q2, "c_q2" ,subfolder+"debug_q2_"+tag, "eps");
    }
    plotAndSave(ctkq2, "c_ctkq2" ,subfolder+"debug_ctkq2_"+tag, "eps");
    plotAndSave(ctlq2, "c_ctlq2" ,subfolder+"debug_ctlq2_"+tag, "eps");
    plotAndSave(phiq2, "c_phiq2" ,subfolder+"debug_phiq2_"+tag, "eps");
    plotAndSave(hweight, "c_weight" ,subfolder+"debug_weights_"+tag, "eps");

    std::string outputPath = subfolder + "debug_angles"+ tag + ".root";
    TFile *output = new TFile(outputPath.c_str(),"RECREATE");
    output->cd();
    ctl->Write("",TObject::kWriteDelete);
    ctk->Write("",TObject::kWriteDelete);
    phi->Write("",TObject::kWriteDelete);
    q2->Write("",TObject::kWriteDelete);
    ctkq2->Write("",TObject::kWriteDelete);
    ctlq2->Write("",TObject::kWriteDelete);
    phiq2->Write("",TObject::kWriteDelete);
    hweight->Write("",TObject::kWriteDelete);
    output->Close();


    if (norm){ //save only when the option of normalization is on
        plotAndSave(ctl_noW,"c_ctl_noW",subfolder+"debug_ctl_noW_"+tag,"eps");
        plotAndSave(ctk_noW,"c_ctk_noW",subfolder+"debug_ctk_noW_"+tag,"eps");
        plotAndSave(phi_noW,"c_phi_noW",subfolder+"debug_phi_noW_"+tag,"eps");
        plotAndSave(q2_noW, "c_q2_noW" ,subfolder+"debug_q2_noW_"+tag, "eps");
    }

    delete ctl;
    delete ctk;
    delete phi;
    delete q2;
    delete ctlq2;
    delete ctkq2;
    delete phiq2;
    delete ctl_noW;
    delete ctk_noW;
    delete phi_noW;
    delete q2_noW;
    return 1;
}

int sanityCheck_MC(fcnc::options opts){

    spdlog::info("Checking MC distribution after correction");

    //load MC
    std::vector<fcnc::event> events;

    if (opts.run == 1){
        events = fcnc::load_events(get_theFCNCpath(1,1), "Events", -1);
    }
    else if (opts.run == 2){
        events = fcnc::load_events(get_theFCNCpath(1,2), "Events", -1);
    }
    else {
        std::vector<fcnc::event> events1 = fcnc::load_events(get_theFCNCpath(1,1), "Events", -1);
        std::vector<fcnc::event> events2 = fcnc::load_events(get_theFCNCpath(1,2), "Events", -1);
        events = {merge_two_evtVecs(events1,events2)};
    }
    for (unsigned int b = 0; b < opts.get_nQ2bins(); b++){
        std::vector<fcnc::event> filtered_events;
        for (auto &evt: events){

            if (evt.costhetal > opts.ctl_max) continue;
            if (evt.costhetal < opts.ctl_min) continue;
            if (evt.costhetak > opts.ctk_max) continue;
            if (evt.costhetak < opts.ctk_min) continue;
            if (evt.phi > opts.phi_max) continue;
            if (evt.phi < opts.phi_min) continue;
            if (evt.q2 > opts.TheQ2binsmax[b]) continue;
            if (evt.q2 < opts.TheQ2binsmin[b]) continue;
            filtered_events.push_back(evt);
        }
        sanityCheck(filtered_events, opts, false, b, false);
    }

    return 0;
}
std::vector< std::vector<fcnc::event> > select_event(fcnc::options opts,std::vector<int> years){
    //Load and check size of events in both runs

    std::vector<fcnc::event>events[2] = {
        fcnc::load_events(get_theFCNCpath(3,1), "Events", -1),
        fcnc::load_events(get_theFCNCpath(3,2), "Events", -1)
    };
    assert(events[0].size());
    assert(events[1].size());
    spdlog::debug("Load angular corrections per two years?\t{0:b}",opts.angacccorrpertwoyears);
    spdlog::debug("Load angular corrections for both runs?\t{0:b}",opts.angacccorrbothruns);

    std::vector<fcnc::event> loaded_events;
    if (opts.angacccorrbothruns)         loaded_events = merge_two_evtVecs(events[0], events[1]);
    else if (opts.angacccorrpertwoyears) loaded_events = events[1];
    else                                 loaded_events = events[opts.run-1];

    std::vector< std::vector<fcnc::event> > eves;

    if (opts.angacccorrperyear || opts.angacccorrpertwoyears){
        spdlog::debug("Seperate sample into {0:d} sub-samples. One per year!",years.size());
        //create indidividual vectors for every year
        std::vector<fcnc::event>eve_year(years.size());
        //fill events into the correct vector
        for(auto ev: loaded_events){
            for(uint y = 0; y < years.size(); y++){
                if(ev.year == years.at(y)) eves.at(y).push_back(ev);
            }
        }

        //print the corresponding numbers in every vector:
        logYears(years, eves, loaded_events.size());
    }
    else eves.push_back(loaded_events);

    //If the correction is done by two years only, merge the two vectors into one
    if (opts.angacccorrpertwoyears){
        eves = {merge_two_evtVecs(eves.at(0),eves.at(1))};
        spdlog::debug("Size of the final vector is {0:d}",eves.size());
        spdlog::debug("Size of the final vector at(0) is {0:d}",eves.at(0).size());
    }

    return eves;

}

int get_flat_Q2_weights(){

    spdlog::info("Start preparing weights for flat-flat PHSP.");
    std::string PHSPpath = getSelectedTuplePath(4,2017,2);

    TFile *output = new TFile(CONFIG_PHSP_WEIGHT.c_str(),"RECREATE");
    output->cd();
    spdlog::debug("Created " + std::string(output->GetPath()));
    TH1D *flatq2weights = new TH1D("flatq2weights","flatq2weights",100,Q2_MIN_RANGE,Q2_MAX_RANGE);
    TH1D *originalQ2 = new TH1D("originalQ2","originalQ2",100,Q2_MIN_RANGE,Q2_MAX_RANGE);


    spdlog::debug("Reading tree DecayTree from " + PHSPpath);
    TChain *tree = new TChain("DecayTree");
    tree->Add(PHSPpath.c_str());
    spdlog::debug("Events in tree DecayTree:\t{0:d}", tree->GetEntries());

    double Q2;
    tree->SetBranchStatus("Q2",1);
    tree->SetBranchAddress("Q2",&Q2);

    for (int e = 0; e<tree->GetEntries(); e++){
        //spdlog::trace("Q2 for event {0:d}:\t{1:f}",e,Q2);
        tree->GetEntry(e);
        originalQ2->Fill(Q2/1e6); //Fill q2 in GeV instead of MeV
    }


    for (int b =1; b <= originalQ2->GetNbinsX(); b++){
        flatq2weights->SetBinContent(b,1.0/originalQ2->GetBinContent(b)); //Create the weights
    }
    flatq2weights->Scale(flatq2weights->GetNbinsX()/ flatq2weights->Integral()); //Normalize the weights
    output->cd();
    originalQ2->Write("",TObject::kWriteDelete);
    flatq2weights->Write("",TObject::kWriteDelete);
    output->Close();
    spdlog::info("Done preparing weights for flat-flat PHSP.");

    return 0;
}

int scan_max_order_angular_ccorrection(fcnc::options opts,
                                       bool assumePhiEven,
                                       bool quickTest, bool test_4times1D,
                                       bool checkSignificance, bool runMinuit,
                                       bool checkFactorization, bool do3Dmoments){
    get_flat_Q2_weights();
    //only use chi2 from 1D projections
    opts.only_4_1D_chi2    = test_4times1D;
    //prevent any plot printing for the scan
    opts.write_eps         = quickTest || test_4times1D;
    //Set year/run options
    set_ang_year_options(opts);

    //define the range to scan through the parameters:
    //format: {q2, theta_K, theta_L, phi}
    std::vector<int> scan_low  = get_scan_low(quickTest, opts.only_4_1D_chi2);
    std::vector<int> scan_high = get_scan_high(quickTest, opts.only_4_1D_chi2);
    std::vector<int> scan_range = get_scan_range(scan_low, scan_high);

    spdlog::debug("Scan range:" + convert_vector_to_string(scan_range));

    //construct results in 2D histogram:
    const UInt_t nBinsX = scan_range.at(1)*scan_range.at(2); // cos(theta_K) and cos(theta_L)
    const UInt_t nBinsY = scan_range.at(0)*scan_range.at(3); // q^2 and phi

    //load phase-space MC events
    std::vector<int> years = get_years(opts.run,false, false);
    std::vector< std::vector<fcnc::event> > eves = select_event(opts, years);

    //object for exporting values from the parametrization process:
    fcnc::values valu;
    spdlog::debug("Eves vector size: {0:d}", eves.size());       

    for_indexed(auto evts: eves){

        if(opts.angacccorrperyear ){
            opts.year = years.at(i);
            spdlog::debug("[YEAR]\t\t{0:d}", opts.year);
        }
        else opts.year = -1;

        std::string sample_suffix;
        if (opts.angacccorrbothruns)   sample_suffix = "Run1and2";
        else if(opts.angacccorrperyear)sample_suffix = std::to_string(opts.year);
        else                           sample_suffix = "Run"+std::to_string(opts.run);

        TH2D * chi2histo = new TH2D(("ScanLegendreOrders_Chi2result_"+sample_suffix).c_str(),
                                    ("#chi^{2} scan of max. order of polynoms ("+sample_suffix+");#bf{#theta_{K}} - #theta_{L};#bf{q^{2}} - #phi").c_str(),
                                    nBinsX, -0.5, nBinsX - 0.5,
                                    nBinsY, -0.5, nBinsY - 0.5);

        TH2D * pvaluehisto = new TH2D(("ScanLegendreOrders_Pvalueresult_"+sample_suffix).c_str(),
                                      ("p-value scan of max. order of polynoms ("+sample_suffix+");#bf{#theta_{K}} - #theta_{L};#bf{q^{2}} - #phi").c_str(),
                                      nBinsX, -0.5, nBinsX - 0.5,
                                      nBinsY, -0.5, nBinsY - 0.5);


        //label the axese
        labelAngularScan(scan_low, scan_range, chi2histo);
        labelAngularScan(scan_low, scan_range, pvaluehisto);

        /*
        //individual histograms for each 1D projection:
        TH2D * dist_4_1D[4];
        std::string svar[4] = {"ctk", "ctl", "phi", "q2"};

        if(opts.only_4_1D_chi2){
            //was only needed to proof that all four 1D projections are indipendent.
            //keep the code for possible re-checks:
            for(int h = 0; h < 4; h++){
                dist_4_1D[h] = new TH2D(("ScanLegendreOrders_Chi2result_"+svar[h]+"_"+sample_suffix).c_str(),
                                        (svar[h]+": #chi^{2} scan of max. order of polynoms ("+sample_suffix+");#bf{#theta_{K}} - #theta_{L};#bf{q^{2}} - #phi").c_str(),
                                        nBinsX, -0.5, nBinsX - 0.5,
                                        nBinsY, -0.5, nBinsY - 0.5);
                //label the x-axis
                labelAngularScan(scan_low, scan_range, dist_4_1D[h]);
            }
        }
        */



        //determine max scan range:
        int range_low  = *min_element(std::begin(scan_low),std::end(scan_low));
        //for_indexed(auto item: scan_low) if(scan_low[c] < range_low)range_low = scan_low[c];
        int range_high = *max_element(std::begin(scan_high),std::end(scan_high));

        //get the histogram for all four varialbes:
        TH2D * chi2_4_1D = new TH2D(("Scan_Chi2result_"+sample_suffix).c_str(),
                                    ("#chi^{2} scan of max. order of polynoms ("+sample_suffix+");max. order;variable").c_str(),
                                    range_high - range_low + 1, range_low - 0.5, range_high + 0.5,
                                    4, -0.5, 3.5);
        //label the x-axis
        for(int b = range_low; b <= range_high; b++){
            chi2_4_1D->GetXaxis()->SetBinLabel(b - range_low + 1, (std::to_string(b)).c_str());
        }
        //label the y-axis
        chi2_4_1D->GetYaxis()->SetBinLabel(1, LATEX_Q2_CH());
        chi2_4_1D->GetYaxis()->SetBinLabel(2, LATEX_TK_CH());
        chi2_4_1D->GetYaxis()->SetBinLabel(3, LATEX_TL_CH());
        chi2_4_1D->GetYaxis()->SetBinLabel(4, LATEX_PHI_CH());

        if(opts.only_4_1D_chi2){  //Screams in python
            for(int i = 0; i < 4; i++){
                for(int j = scan_low.at(i); j <= scan_high.at(i); j++){

                    //set to max order to current scan value and all others to min value
                    if(i == 0)opts.eff_order_q2        = j;
                    else      opts.eff_order_q2        = scan_low.at(0);
                    if(i == 1)opts.eff_order_costhetak = j;
                    else      opts.eff_order_costhetak = scan_low.at(1);
                    if(i == 2)opts.eff_order_costhetal = j;
                    else      opts.eff_order_costhetal = scan_low.at(2);
                    if(i == 3)opts.eff_order_phi       = j;
                    else      opts.eff_order_phi       = scan_low.at(3);

                    spdlog::debug("[SCAN]\t\tq2 {0:d} \tcos(t_K) {1:d} \tcos(t_L)  {2:d} \tphi  {3:d}", opts.eff_order_q2, opts.eff_order_costhetak, opts.eff_order_costhetal , opts.eff_order_phi);

                    //All Unbiased parameters!
                    fcnc::bu2kstarmumu_parameters params(&opts);
                    //pdf
                    fcnc::bu2kstarmumu_pdf prob(&opts, &params);

                    //prob.parametrize_eff_phsp(selected);
                    int tagNumber = i*10+j;
                    spdlog::debug("Tag number:\t{0:d}", tagNumber);
                    prob.parametrize_eff_phsp_4d(evts, &valu,tagNumber, assumePhiEven, checkSignificance, runMinuit, checkFactorization, do3Dmoments);
                    //Tag for all possible combinations when testing

                    //save the one chi2 value:
                    double chi2 = 0.;
                    if(i == 0)     chi2 = valu.chi2_4_1D_q2;
                    else if(i == 1)chi2 = valu.chi2_4_1D_ctk;
                    else if(i == 2)chi2 = valu.chi2_4_1D_ctl;
                    else           chi2 = valu.chi2_4_1D_phi;
                    int ndof = 1;
                    if(i == 0)     ndof = valu.ndof_4_1D_q2;
                    else if(i == 1)ndof = valu.ndof_4_1D_ctl;
                    else if(i == 2)ndof = valu.ndof_4_1D_ctl;
                    else           ndof = valu.ndof_4_1D_phi;
                    if(chi2 / ndof < 2.) chi2_4_1D->SetBinContent(j - range_low + 1, i + 1, chi2 / ndof);
                }
            }
        }
        else{
            for(int qq = 0; qq < scan_range.at(0); qq++){
                for(int tk = 0; tk < scan_range.at(1); tk++){
                    for(int tl = 0; tl < scan_range.at(2); tl++){
                        for(int fi = 0; fi < scan_range.at(3); fi++){

                            //change max order of polynoms according to the scan:
                            opts.eff_order_q2        = qq+scan_low.at(0);
                            opts.eff_order_costhetak = tk+scan_low.at(1);
                            opts.eff_order_costhetal = tl+scan_low.at(2);
                            opts.eff_order_phi       = fi+scan_low.at(3);

                             int tagNumber = 1*opts.eff_order_q2+
                                            10*opts.eff_order_costhetak+
                                            100* opts.eff_order_costhetal+
                                            1000*opts.eff_order_phi ;
                                            //This works fine assuming all the orders are <10!

                            spdlog::info("[SCAN]\t\tq2 {0:d} \tcos(t_K) {1:d} \tcos(t_L)  {2:d} \tphi  {3:d}", opts.eff_order_q2, opts.eff_order_costhetak, opts.eff_order_costhetal , opts.eff_order_phi);
                            spdlog::debug("Tag number:\t{0:d}", tagNumber);

                            //All Unbiased parameters!
                            fcnc::bu2kstarmumu_parameters params(&opts);
                            //pdf
                            fcnc::bu2kstarmumu_pdf prob(&opts, &params);

                            //prob.parametrize_eff_phsp(selected);
                            prob.parametrize_eff_phsp_4d(evts, &valu, tagNumber, assumePhiEven, checkSignificance, runMinuit, checkFactorization, do3Dmoments);

                            double chi2 = valu.angacccorr_chi2;
                            int ndof    = valu.angacccorr_ndof;

                            chi2histo->SetBinContent(tk * scan_range.at(2) + tl + 1,
                                                     qq * scan_range.at(3) + fi + 1,  chi2 / ndof);

                            pvaluehisto->SetBinContent(tk * scan_range.at(2) + tl + 1,
                                                       qq * scan_range.at(3) + fi + 1,
                                                       TMath::Prob(chi2, ndof));
                            //P(chi2,ndof) represents the probability that the observed Chi-squared
                            //for a correct model should be less than the value chi2.
                            //The returned probability corresponds to 1-P(chi2,ndof),
                            //which denotes the probability that an observed Chi-squared exceeds
                            //the value chi2 by chance, even for a correct model.
                        }
                    }
                }
            }//loop over max. orders
        }

        //save the histogram with chi2/ndof output
        TCanvas * cChi2Ndof = new TCanvas(("cChi2Ndof_"+sample_suffix).c_str(), ("cChi2Ndof_"+sample_suffix).c_str());
        TCanvas * cPvalue   = new TCanvas(("cPvalue_"+sample_suffix).c_str(), ("cPvalue_"+sample_suffix).c_str());

        gStyle->SetPaintTextFormat("1.3f");
        std::string plotName = "plots/angular/test/ScanChi2MaxOrderPolynom" + sample_suffix+std::string(quickTest ? "_quickTest" : "");
        if(opts.only_4_1D_chi2){
            replace(plotName,"Polynom","Polynom_4_1D_");
            cChi2Ndof->cd();
            cChi2Ndof->SetLogz(false);
            chi2_4_1D->GetZaxis()->SetRangeUser(-1.0,2.5);
            chi2_4_1D->Draw("TEXTCOLZ");
            cChi2Ndof->SaveAs((plotName+".eps").c_str(), "eps");
        }
        else{
            cChi2Ndof->cd();
            chi2histo->GetZaxis()->SetRangeUser(0.98, 1.05);
            chi2histo->Draw("TEXTCOLZ");
            cChi2Ndof->SaveAs((plotName + ".eps").c_str(), "eps");
            cChi2Ndof->SetLogz();
            cChi2Ndof->SaveAs((plotName+"_log.eps").c_str(), "eps");

            replace(plotName,"Chi2Max","PvalueMax");
            cPvalue->cd();
            //pvaluehisto->GetZaxis()->SetRangeUser(1e-8, 2.5e-2);
            pvaluehisto->Draw("TEXTCOLZ");

            cPvalue->SaveAs((plotName+".eps").c_str(), "eps");
            cPvalue->SetLogz();
            cPvalue->SaveAs((plotName+"_log.eps").c_str(), "eps");


            replace(plotName,"PvalueMax","Max");
            TFile * f = new TFile((plotName+".root").c_str(), "UPDATE");
            f->cd();
            chi2histo  ->Write("", TObject::kWriteDelete);
            pvaluehisto->Write("", TObject::kWriteDelete);
            f->Close();
        }

        delete cChi2Ndof;
        delete cPvalue;

        delete chi2histo;
        delete pvaluehisto;

    }

    //end program after scan
    return 0;

}

//-------------------------------
//     fit angular acceptance
//-------------------------------
int get_angular_acceptance(fcnc::options opts, bool testSaving){

    //Mostly useless here, but in case it is needed quickly get the correction to PHSP from genLvl MC
    //Needed to correct for flat q2
    get_flat_Q2_weights();

    spdlog::info("[START] Determination of angular acceptance corrections");
    //Set year/run options
    set_ang_year_options(opts);

    //load phase-space MC
    std::vector<int> years = get_years(opts.run,false, false);
    std::vector< std::vector<fcnc::event> > eves = select_event(opts, years);

    //object for exporting values from the parametrization process:
    fcnc::values valu;

    for(UInt_t y = 0; y < (opts.angacccorrperyear ? years.size() : 1); y++){

        opts.year = opts.angacccorrperyear ? years.at(y) : -1;
        if(opts.angacccorrperyear) spdlog::debug("[YEAR]\t\t{0:d}",years.at(y));

        //All Unbiased parameters!
        fcnc::bu2kstarmumu_parameters params(&opts);
        //pdf
        fcnc::bu2kstarmumu_pdf prob(&opts, &params);

        //prob.parametrize_eff_phsp(selected);
        prob.parametrize_eff_phsp_4d(eves.at(y), &valu, 0, IS_PHI_EVEN, false, false, false, false);

        //save the coefficients to .dat file
        prob.save_coeffs_eff_phsp_4d();


        //Plot the corrected distributions
        sanityCheck(eves.at(y), opts, true, -1, true);

        //test succesfull saving by loading and checking the coefficients:
        if(testSaving){
            //load the coefficients from file:
            std::vector<double>testCoeffs;
            testCoeffs = prob.read_coeffs_eff_phsp_4d();
            //check if the correct number of coeffs is saved and reloaded
            assert(testCoeffs.size() == prob.coeffs_eff_4d.size());

            //test if all coeffs are identical
            for(UInt_t c = 0; c < prob.coeffs_eff_4d.size(); c++){
                if(TMath::Abs(testCoeffs.at(c) - prob.coeffs_eff_4d.at(c)) > 1.0e-14){
                    spdlog::warn(" Coeff #{0:d} unequal.",c+1);
                    spdlog::warn(" Saved: {0:f}", prob.coeffs_eff_4d.at(c));
                    spdlog::warn(" Loaded: {0:f}", testCoeffs.at(c));
                    spdlog::warn(" Difference: {0:f}", testCoeffs.at(c) - prob.coeffs_eff_4d.at(c));
                }
            }
        }


        npolynom npol(&opts);
        assert(prob.coeffs_eff_4d.size() == npol.getSize());

        //this can be varying parameter, or can be fixed for example to the middle of the bin
        //double q2 = params->eff_q2();
        TRandom3* rnd = new TRandom3(432759);
        std::vector<fcnc::event> negativeeff;
        const bool testdata = false;
        const bool testtoy = false;
        unsigned int ntest = 100000;
        if (testdata){
            std::vector<fcnc::event> dataevents = fcnc::filterResonances(fcnc::load_events( get_theFCNCpath(0, opts.run), "Events", ntest));
            ntest = 0;//dataevents.size();
            for (unsigned int l=0; l<dataevents.size(); l++){
                fcnc::event meas = dataevents.at(l);
                if ( (meas.q2 < Q2_MIN_RANGE)
                     || (meas.q2 > Q2_MAX_RANGE)
                     || (meas.m < opts.m_low || meas.m > opts.m_high) )
                    continue;

                continue;
                ntest++;

                double eff = 0.0;
                unsigned int bin = 0;
                double costhetal = meas.costhetal;
                double costhetak = meas.costhetak;
                double phi = meas.phi;
                double q2 = meas.q2;
                for (unsigned int h = 0; h < npol.q2; h++){
                    for (unsigned int i = 0; i < npol.ctl; i++){
                        for (unsigned int j = 0; j < npol.ctk; j++){
                            for (unsigned int k = 0; k < npol.phi; k++){
                                bin = npol.get_bin_in4D(h,i,j,k);
                                eff += prob.coeffs_eff_4d.at(bin)*pow(q2, h)*pow(costhetal, i)*pow(costhetak, j)*pow(phi, k);
                            }
                        }
                    }
                }
                if (eff < 0.0){
                    meas.weight = eff;
                    negativeeff.push_back(meas);
                }
            }
        }
        else if (testtoy){
            for (unsigned int l=0; l<ntest; l++){
                if (l % (ntest/100) == 0) spdlog::debug("{0:d}%", l/(ntest/100));
                double eff = 0.0;
                unsigned int bin = 0;
                double costhetal = CTL_MIN + rnd->Rndm()*CTL_RANGE();
                double costhetak = CTK_MIN + rnd->Rndm()*CTK_RANGE();
                double phi       = PHI_MIN + rnd->Rndm()*PHI_RANGE();
                double q2        = Q2_MIN_RANGE + rnd->Rndm()*Q2_RANGE_FULL();
                for (unsigned int h = 0; h < npol.q2; h++){
                    for (unsigned int i = 0; i < npol.ctl; i++){
                        for (unsigned int j = 0; j < npol.ctk; j++){
                            for (unsigned int k = 0; k < npol.phi; k++){
                                bin = npol.get_bin_in4D(h,i,j,k);
                                eff += prob.coeffs_eff_4d.at(bin)*pow(q2, h)*pow(costhetal, i)*pow(costhetak, j)*pow(phi, k);
                            }
                        }
                    }
                }
                if (eff < 0.0){
                    fcnc::event meas;
                    meas.costhetal = costhetal;
                    meas.costhetak = costhetak;
                    meas.phi = phi;
                    meas.q2 = q2;
                    meas.weight = eff;
                    negativeeff.push_back(meas);
                }
                else if (eff > 3.0) spdlog::debug("LARGE {0:f} {1:f} {2:f} {3:f} {4:f}", costhetal, costhetak, phi, q2, eff);
            }
        }
        if(testtoy || testdata){
            spdlog::info( "looping over {0:d}  events"+ std::string(testtoy ? " (TOYS)" : " (DATA)"),ntest);
            for (unsigned int i=0; i<negativeeff.size();i++){
                const fcnc::event& meas = negativeeff.at(i);
                spdlog::debug(  "{0:f} {1:f} {2:f} {3:f} {4:f} {5:f}", meas.costhetal, meas.costhetak, meas.phi, meas.q2, meas.m, meas.weight);
            }
            spdlog::debug( "negative fraction {0:f}", double(negativeeff.size())/ntest);
        }

        if(opts.only_4_1D_chi2){
            spdlog::debug( "Results from >>>> " + std::string(opts.year == -1 ? (opts.angacccorrbothruns ? "Run1+2" : "Run"+std::to_string(opts.run)) : std::to_string(opts.year)) + " <<<< data set:");
            spdlog::debug( "Reduced chi2 of 4*1D projections:\t {0:f} ({1:f}/{2:f})",
                           valu.chi2_4_1D/ valu.ndof_4_1D, valu.chi2_4_1D,valu.ndof_4_1D);
            spdlog::debug( "Reduced chi2 of 4*1D projections: ctk \t {0:f} ({1:f}/{2:f})",
                           valu.chi2_4_1D_ctk/valu.ndof_4_1D_ctk, valu.chi2_4_1D_ctk, valu.ndof_4_1D_ctk);
            spdlog::debug( "Reduced chi2 of 4*1D projections: ctl\t {0:f} ({1:f}/{2:f})",
                           valu.chi2_4_1D_ctl/valu.ndof_4_1D_ctl, valu.chi2_4_1D_ctl, valu.ndof_4_1D_ctl);
            spdlog::debug( "Reduced chi2 of 4*1D projections: phi\t {0:f} ({1:f}/{2:f})",
                           valu.chi2_4_1D_phi/valu.ndof_4_1D_phi, valu.chi2_4_1D_phi, valu.ndof_4_1D_phi);
            spdlog::debug( "Reduced chi2 of 4*1D projections: q2\t {0:f} ({1:f}/{2:f})",
                           valu.chi2_4_1D_q2/valu.ndof_4_1D_q2, valu.chi2_4_1D_q2, valu.ndof_4_1D_q2 );
        }
    }

    return 0;
}


//-------------------------------
//     fit angular resolution
//-------------------------------
int get_angular_resolution(fcnc::options opts, std::vector<int> years){
    //Don't forget to set the full ctk range when doing this!

    //create resolution histograms for all three angles (x - x_true)
    const unsigned int leBins = 50;
    const double leRange = 0.12;


    spdlog::info("Determine angular resolutions from signal MC true information!");
    gROOT->SetBatch(kTRUE);
    gROOT->SetStyle("Plain");
    set_gStyle();

    assert(years.size());
    opts.reject_identical_muon_TRUEID = true;
    fcnc::bu2kstarmumu_loader loader(&opts);

    double resolution[3][years.size()];

    for_indexed(auto yr: years){
        spdlog::info("Evaluate year {0:d}", yr);
        //load tuples:
        std::vector<fcnc::event> MCevents = loader.read_full_tuple(yr,
                                                                   getSelectedTuplePath(1, get_yearFromRun(yr),
                                                                                        yr),
                                                                   get_inputTree_name(1),
                                                                   true, false, false, -1);

        std::vector<fcnc::event> MCtrue   = loader.read_full_tuple(yr,
                                                                   getSelectedTuplePath(1, get_yearFromRun(yr),
                                                                                        yr),
                                                                   get_inputTree_name(1),
                                                                   true, true, false, -1);

        assert(MCevents.size() == MCtrue.size());

        //create resolution histograms for all three angles (x - x_true)

        TCanvas * cReso = nullptr;
        std::string sangle[3] = {"ctk", "ctl", "phi"};
        std::string latexangle[3] = {LATEX_CTK,LATEX_CTL,LATEX_PHI};

        TH1D * h[3];
        for(unsigned int i = 0; i < 3; i++){
            h[i] = new TH1D(("h"+sangle[i]+"_"+std::to_string(yr)).c_str(),
                            (";"+latexangle[i]+" - "+latexangle[i]+"_{true};Events").c_str(),
                            leBins, -leRange, +leRange);
        }

        //fill histograms:
        for(unsigned int e = 0; e < MCevents.size(); e++){
            fcnc::event measMC   = MCevents.at(e);
            fcnc::event measTRUE = MCtrue.at(e);

            h[0]->Fill(measMC.costhetak - measTRUE.costhetak);
            h[1]->Fill(measMC.costhetal - measTRUE.costhetal);
            h[2]->Fill(measMC.phi - measTRUE.phi);
        }

        spdlog::info("Entries in histograms: {0:f}", h[0]->Integral());

        TF1 * fGauss = new TF1("theGaussian", "[0]*([2]*exp(-0.5*((x-[1])/[3])**2)+(1.-[2])*exp(-0.5*((x-[1])/[4])**2))", -leRange, +leRange);

        fGauss->SetLineStyle(kDashed);
        fGauss->SetLineColor(kMagenta - 3);

        //Set init parameters differently for each variable
        double initSigma[3]      = {0.0125, 0.005, 0.025};
        double sigmaLowRange[3]  = {0.008,  0.001, 0.01};
        double sigmaHighRange[3] = {0.020,  0.01,  0.04};
        for(int j = 0; j < 3; j++){
            fGauss->SetParameter(0, MCevents.size());
            fGauss->SetParameter(1, 0.);
            fGauss->SetParameter(2, 0.5);
            fGauss->SetParLimits(2, 0.0, 1.0);
            //Set the init resolution extra for ctl, as it is very narrow compared to phi and ctk

            fGauss->SetParameter(3, initSigma[j]);
            fGauss->SetParLimits(3, sigmaLowRange[j], sigmaHighRange[j]);
            fGauss->SetParameter(4, initSigma[j]);
            fGauss->SetParLimits(4, sigmaLowRange[j], sigmaHighRange[j]);
            TFitResultPtr fr;
            do{
                h[j]->GetListOfFunctions()->Clear();
                fr = h[j]->Fit(fGauss, "MS+Q");
                spdlog::info("Fit status = {0:d}", fr->Status());
            } while (!fr->IsValid());
            TMatrixD covmat = fr->GetCovarianceMatrix();
            if (spdlog::default_logger_raw()->level()==spdlog::level::trace) covmat.Print();

            double rel_frac  = fGauss->GetParameter(2);
            double drel_frac = fGauss->GetParError(2);
            double sigma1  = fGauss->GetParameter(3);
            double dsigma1 = fGauss->GetParError(3);
            double sigma2  = fGauss->GetParameter(4);
            double dsigma2 = fGauss->GetParError(4);

            double eff_sigma  = sqrt(rel_frac*sigma1*sigma1 + (1.-rel_frac)*sigma2*sigma2);
            double deff_sigma = 1./(2.*eff_sigma) * sqrt(
                        TMath::Power(2*rel_frac*sigma1*dsigma1, 2)
                        + TMath::Power(2*(1.-rel_frac)*sigma2*dsigma2, 2)
                        + TMath::Power((sigma1*sigma1-sigma2*sigma2)*drel_frac, 2)
                        + 2 * 2*rel_frac*sigma1 * 2*(1.-rel_frac)*sigma2 * covmat[3][4]
                    + 2 * 2*rel_frac*sigma1 * (sigma1*sigma1-sigma2*sigma2) * covmat[3][2]
                    + 2 * 2*(1.-rel_frac)*sigma2 * (sigma1*sigma1-sigma2*sigma2) * covmat[4][2]);

            cReso = new TCanvas((sangle[j]+"_reso_"+std::to_string(yr)).c_str(),
                                (sangle[j]+"_reso_"+std::to_string(yr)).c_str(), 1600, 1200);
            TLatex * leg = getPrettyTex(0.06, 13);

            std::ostringstream sRMS;
            sRMS << std::fixed << std::setprecision(4) << "#sigma = " << eff_sigma << " #pm " << deff_sigma;
            resolution[j][i] = eff_sigma;

            cReso->SetMargin(0.125,0.05,0.115,0.05);
            cReso->cd();
            h[j]->GetYaxis()->SetTitleOffset(1.15);
            h[j]->GetYaxis()->SetTitleSize(0.05);
            h[j]->GetYaxis()->SetLabelSize(0.05);
            h[j]->GetXaxis()->SetTitleSize(0.05);
            h[j]->GetXaxis()->SetLabelSize(0.05);
            h[j]->Draw("E1");
            leg->DrawLatex(0.15,0.84, sRMS.str().c_str());
            leg->DrawLatex(0.7,0.88, "LHCb MC");
            leg->DrawLatex(0.7,0.82, std::to_string(yr).c_str());

            cReso->Print(get_angResoPlot_path(sangle[j],yr,"eps").c_str(), "eps");
            cReso->SaveAs(get_angResoPlot_path(sangle[j],yr,"root").c_str());

            delete h[j];
        }
    }

    //print out resolutions:
    spdlog::info("resolution: {");
    for(int i = 0; i < 3; i++){
        std::cout << "{";
        for(UInt_t y = 0; y < years.size(); y++){
            std::cout << std::fixed << std::setprecision(6) << resolution[i][y];
            if(y < years.size() - 1)
                std::cout << ", ";
        }
        std::cout << "}" << std::endl;
    }
    return 0;
}
