//Renata Kopecna

#include <mainfit.hh>

#include <fstream>
#include <sstream>      // std::istringstream

#include <event.hh>
#include <fitter.hh>
#include <folder.hh>
#include <paths.hh>
#include <funcs.hh>
#include <design.hh>
#include <helpers.hh>
#include <constants.hh>

#include <bu2kstarmumu_generator.hh>
#include <bu2kstarmumu_plotter.hh>
#include <bu2kstarmumu_parameters.hh>

#include <spdlog.h>

#include <TStyle.h>

int mainfit(fcnc::options opts, basic_params params, bool fitReference, bool fitToy, bool likelihoodScan, bool FeldmanCousin){

    //TODO
    bool fitData     = !fitToy;
    //TODO: add a test where I cna try this on a toy file

    if(fitData)     spdlog::info("[FIT]\tFit signal data"); //signal data << FINAL FIT!!
    if(fitReference)spdlog::info("[FIT]\tFit reference channel: J/psi K*+"); //reference channel only

    //--------------------------------
    //      Set all the options
    //--------------------------------

    //TODO: declare maybe in the header or elsewhere
    const bool UseBinnedFit     = !fitReference;
    const bool SimultaneousFit  = true;
    const bool FixAngularBckgnd = false; //Fix the background to the shape of the upper mass sideband
    const bool constrainBmass   = false;
    const bool FixedSwave       = !fitReference; //TODO
    const bool ConstrainedSwave = false; //TODO
    const bool ConstrainedFS    = false; //TODO

    const bool LoadFSfrom2Dfit    = !fitReference && !(opts.systematic > -1);
    //const bool JpsiWithRareStats  = false; //Try to fit the Jpsi fit with only a subsample of events //TODO
    const double fractionOfStats  = 1.0; //Use only a fraction of statistics, eg for half set this to 0.5

    //TODO: set the fraction of stats accordingly if JpsiWithRareStats; will make my life easier

    const bool NP                 = false;  //TODO
    const bool Blind              = fitData && !fitReference;

    const bool plotPulls = true; //Do you wanna pull plots with or without pulls?
    bool plotSignalRegion = fitReference; //Plot only the region around B mass, makes nicer plots

    const unsigned int nBins = UseBinnedFit ? opts.get_nQ2bins() : 1;

    if (fitToy && params.polarity!= 0){
        spdlog::warn("Your polarity is not set to both ofr whatever reason when fitting a toy!");
        spdlog::warn("Setting the polarity to 0!");
        params.polarity = 0;
    }
    //------------------------------------------------------------------------------------------------

    if((int)ConstrainedSwave + (int) ConstrainedFS + (int) LoadFSfrom2Dfit > 1){
        //If at least two of those, then crash
        spdlog::error("Use only one option to constrain FS:");
        spdlog::error("Constrained sWave" + boolToString(ConstrainedSwave));
        spdlog::error("Constrained FS" + boolToString(ConstrainedFS));
        spdlog::error("Load FS from 2D fit" + boolToString(LoadFSfrom2Dfit));
        assert(0);
    }

    opts.fit_full_angular_bkg = true; //fold also the bkg?
    opts.individual_penalties = false;

    //If blinded, don't plot anything
    opts.write_eps = !Blind;
    opts.write_pdf = false;
    opts.write_C   = false;

    //Fit both angles and mass
    opts.only_angles = false;
    opts.only_Bmass = false;

    //Fit swave?
    opts.swave = true;

    //Use weights
    opts.weighted_fit = true;

    //What framework?
    opts.shift_lh = false;
    opts.hesse_postrun = true;
    opts.squared_hesse = true;//params.folding>-1;
    opts.minos_errors = false;// params.folding==-1;

    //Flat background?
    opts.flat_bkg = false;
    //What order of bkg
    opts.bkg_order_costhetak = 5;

    opts.fit_mkpi = true; //Use fit to Kpi mass?
    opts.use_mkpi = false;  //Do the 5D fit? //TODO: probably doesn't work now
    opts.simple_mkpi = false;
    opts.isobar = false;
    opts.asymptotic = false; //Use the improved hesse calculation?

    //Check that sWave is on when fitting Kpi mass
    assert(!(opts.fit_mkpi && !opts.swave));
    assert(!(opts.use_mkpi && !opts.swave));

    //Use angular acceptance corrections
    opts.update_efficiencies = true;

    //generate nametag for root file to save results:
    std::string results_file = final_result_name(fitReference, false, params,
                                                 SimultaneousFit, nBins, params.Run,
                                                 fitToy, fractionOfStats,
                                                 NP, -1); //FitOnlyRun is not reflected in opts here TODO


    //Set the available PDFs: this depends on the selected Run for now, so if only one is selected, run over one pdf, if both, use two
    std::vector<UInt_t> pdf_idx;
    if (params.Run == 1 || params.Run == 12) pdf_idx.push_back(1);
    if (params.Run == 2 || params.Run == 12) pdf_idx.push_back(2);

    //--------------------------------
    //      Load data
    //--------------------------------
    spdlog::info("[FIT]\tLoading data...");
    std::vector<std::vector<fcnc::event>>events;

    if(fitToy){
        events.push_back(fcnc::load_events(get_finalToys_file(fitReference,nBins, SimultaneousFit, params,1),"Events", -1)); //Run1
        events.push_back(fcnc::load_events(get_finalToys_file(fitReference,nBins, SimultaneousFit, params,2),"Events", -1)); //Run2
    }
    else{
        std::vector<fcnc::event> tmp = fcnc::load_events(get_theFCNCpath(0,1), "Events", -1); //Run1
        if (!fitReference) events.push_back(fcnc::filterResonances(tmp));
        else               events.push_back(tmp);
        tmp = fcnc::load_events(get_theFCNCpath(0,2), "Events", -1); //Run2
        if (!fitReference) events.push_back(fcnc::filterResonances(tmp));
        else               events.push_back(tmp);
    }

    //check that the number of pdfs is the same as number of event vectors
    if (pdf_idx.size() != events.size()){
        spdlog::error("Something went very wrong when loading the events and setting the pdfs.");
        spdlog::error("The number of PDFs != number of event vectors: {0:d} vs {1:d}",pdf_idx.size(), events.size());
        return 5;
    }

    //we are good to go, now; how many individual pdfs are used?
    const UInt_t nPDFs = pdf_idx.size();

    //Control print of the loaded events
    UInt_t N_tot = 0;
    for (UInt_t n = 0; n < nPDFs; n++){
        spdlog::debug("Event vector {0:d}:\t"+(Blind?"Larger 1 ":std::to_string(events.at(n).size())),n);
        if (events.at(n).size()==0){
            spdlog::error("Empty event vector!");
            return 404;
        }
        N_tot += events.at(n).size();
    }
    spdlog::info("Total number of used events:\t{0:d}", N_tot);

    //Set fit ranges //TODO: just move it to init angle parameters
    double angleRange  = 1.0;
    double PprimeRangeScale = 10.0;
    if (params.usePprime) angleRange *= PprimeRangeScale;
    double angleStepSize  = 0.1;

    //get signal fractions and event numbers
    //TODO: fix this when you know whether we need the eventNumbers here specifically
    double sig_frac[nBins];

    unsigned int event_numbers[nBins][nPDFs];

    for(unsigned int b = 0; b < nBins; b++){
        for(unsigned int n = 0; n < nPDFs; n++){            
            //get event numbers and signal fraction from global function, given the total number of events
            //TODO: check what to do with JpsiWithRareStats
            EventNumbers(b, n, sig_frac[b], event_numbers[b][n], N_tot, nBins, nPDFs);
            event_numbers[b][n] *= fractionOfStats;
        }
    }

    //Control print
    for(unsigned int b = 0; b < nBins; b++){
        for(unsigned int n = 0; n < nPDFs; n++){
            spdlog::debug("q2bin={0:d}\tPDF={1:d}\tf_sig={2:f}\tN={3:d}", b, n, sig_frac[b], event_numbers[b][n]);
        }
    }

    //create the fitter, plotter, parameteres and pdfs
    fcnc::fitter f(&opts);
    fcnc::folder fldr(&opts);
    fcnc::options theOptions[nPDFs];
    fcnc::bu2kstarmumu_plotter * thePlotter[nPDFs];
    std::vector<fcnc::parameters*> theParams [nBins];
    std::vector<fcnc::pdf*>        theProbs  [nBins];
    std::vector< std::vector<fcnc::event>* >    selection[nBins];

    //these parameters are common:
    std::vector<std::string> common_params = param_string(opts, false); //set MC to true to avoid background there
    spdlog::info("Shared parameters: " + convert_vector_to_string(common_params));
    //set common parameters:
    if(SimultaneousFit) f.set_common_parameters(common_params);

    //Get the name of files needed for constraining
    std::string signalMCFile = final_result_name_MC(params, nBins, false, false, SimultaneousFit, false, false);
    std::string refrenceMCFile =  final_result_name_MC(params, 1, true, false, SimultaneousFit, false, true);
    bool takeAngBkgFromRef = true;
    std::string upperMassBkgFile = final_result_name_bkg(takeAngBkgFromRef ? 1 : nBins, takeAngBkgFromRef, false, true, false, params);
    std::string upperMassKpiBkgFile = final_result_name_bkg(takeAngBkgFromRef ? 1 : nBins, takeAngBkgFromRef, false, true, true, params);
    std::string RefMassFile = final_result_name_mass(true,1,true,params,params.Run);

    for(UInt_t n = 0; n < nPDFs; n++){
        spdlog::debug("PDF {0:d}", n);
        UInt_t idx = pdf_idx.at(n);
        opts.update_angle_ranges(); //Set angles in options back to defaults
        opts.update_efficiencies = true; //This ensures the acceptance weights to be taken into account.
        //Set the label
        if (fitToy) opts.plot_label = "Toy sample";
        else        opts.plot_label = "LHCb data";
        opts.name = std::to_string(idx+1) + "_"+ get_eps_label(fitReference,false,false,fitToy, nBins, -1,-SimultaneousFit,params); //TODO: FIX THIS!!!

        //Set the options and init the plotter
        theOptions[n] = opts;
        theOptions[n].run = pdf_idx.at(n); //Set proper run to options
        thePlotter[n] = new fcnc::bu2kstarmumu_plotter(&theOptions[n]);

        for(unsigned int b = 0; b < nBins; b++){
            theOptions[n].q2_min =  theOptions[n].TheQ2binsmin.at(b) ;
            theOptions[n].q2_max =  theOptions[n].TheQ2binsmax.at(b);

            //create parameter sets
            fcnc::bu2kstarmumu_parameters * leParameters = new fcnc::bu2kstarmumu_parameters(&theOptions[n]);

            //create PDFs
            fcnc::bu2kstarmumu_pdf * lePDF = new fcnc::bu2kstarmumu_pdf(&theOptions[n], leParameters);

            //define center of q2bin as effective q2 by hand
            leParameters->eff_q2.init_fixed(bin_center_q2(theOptions[n],b));

            //Init mass
            std::string fileName_dataMass = final_result_name_mass(true, 1, true, params, params.Run);
            leParameters->init_Bmass(fileName_dataMass,pdf_idx.at(n),0.5,fixConstr(false,constrainBmass));


            //initiate the sig/bkg fraction for mass fit
            leParameters->f_sig.init(fitReference ? 0.8 : 0.3, 0.0, 1.0, 0.05);

            //Init mass parameters
            leParameters->init_mass_parameters(n,nBins,b,0.01);            
            leParameters->fix_param_from_rootfile(fitReference ? refrenceMCFile : signalMCFile,
                                                 {"alpha_1","alpha_2","n_1","n_2"}, idx, b);
            //Fix the mass mean to the one from reference channel
            if (!fitReference) leParameters->init_Bmass(RefMassFile, pdf_idx.at(n), 0.0, fixConstr(true,false));

            //Init the ratio of sigmas in signal MC/reference MC
            if (!fitReference) leParameters->m_scale.init_fixed(get_sigmaRatio_fromMC(params,nBins,b,pdf_idx.at(n)));
            else               leParameters->m_scale.init_fixed(1.0);

            //Init mass background
            leParameters->init_mass_background_parameters(nBins,b,opts.fit_lambda);

            //(de)activate S-wave            
            double swaveStepSize = opts.swave && !FixedSwave ? 0.1 : 0.0;

            //Init the Kstar mass fit
            if(opts.fit_mkpi || opts.use_mkpi){
                //p-wave                
                leParameters->init_mkpi_pWave_parameters(fitReference,0.0);
                leParameters->init_kpi_background_parameters(fitReference,0.05);
                //s-wave
                if (opts.swave) leParameters->init_mkpi_sWave_parameters(fitReference, 0.0);
            }

            //Init the sWave
            if (opts.swave){
                leParameters->init_sWave_parameters(swaveStepSize);
                leParameters->get_param_from_rootfile(RefMassFile, {"FS"}, idx, b,fixConstr(!fitReference,ConstrainedFS));
            }

            //Add angular parameters
            leParameters->init_angular_parameters(nBins,b,angleStepSize,angleRange, Blind);

            //Add background to angular observables

            if(!theOptions[n].flat_bkg){ //If bkg not flat, init based on upper mass sideband fit
                leParameters->get_param_from_rootfile(upperMassBkgFile,
                                                      PAR_BKG_STRING(opts.folding,opts.bkg_order_costhetal,opts.bkg_order_costhetak),
                                                      params.Run,b,fixConstr(FixAngularBckgnd,false));
                if (params.folding ==4){
                    leParameters->cbkgctk2.init(-1.75,-3.0,1.5,0.05);
                    leParameters->cbkgctk4.init(-1.75,-3.0,1.5,0.05);
                }
            }

            //make sure all configured values are also the start_value:
            leParameters->take_current_as_start();

            //Add the parameters and the pdf into the vectors
            theParams[b].push_back(leParameters);
            theProbs [b].push_back(lePDF);

            spdlog::info("[PDF{0:d}]\tSaved PDF and parameters!", n);

            //Load the events
            std::vector<fcnc::event> * leEvents = new std::vector<fcnc::event>;

            //choose events from the event vector and sort corresponding to q2bin or non-resonant:
            //If use all events, just keep the numbers as they are
            UInt_t NNN = events[n].size()*fractionOfStats; //  UInt_t NNN = fractionOfStats == 1 ? events[n].size() :  0.5 + event_numbers[b][n];

            //This randomly pre-selected list of events is used, if not all events are wanted (eg if JpsiWithRareStats)
            std::vector<UInt_t> event_idxs(NNN);

            if(fractionOfStats != 1.0){
                event_idxs = fcnc::GetNOutOfM(NNN, events[n].size(), 0, false, false);
            }
            else{
                std::iota (std::begin(event_idxs), std::end(event_idxs), 0); //This just puts 0,1,2,3,... into a vector
            }
            spdlog::info("Running fitter with a total number of events of: {0:d}", event_idxs.size());

            for_indexed(auto e: event_idxs){
                //if (i%2!=0) continue;
                fcnc::event meas = events[n].at(e);
                //Cut on B mass
                if(meas.m < B_MASS_LOW || meas.m > B_MASS_HIGH) continue;
                //Select either magUp or magDown
                if(params.polarity==1  && meas.magnet > 0) continue;
                if(params.polarity==-1 && meas.magnet < 0) continue;

                //Select only events in the given bin
                if(meas.q2 < theOptions[n].TheQ2binsmin.at(b) || meas.q2 > theOptions[n].TheQ2binsmax.at(b))  continue;

                //Cut on Kpi mass
                if(meas.mkpi < opts.mkpi_min || meas.mkpi > opts.mkpi_max) continue;

                //Fold if needed
                if(!filterFldFour(&meas, &theOptions[n])) continue; //remove ctk events
                if(!opts.full_angular)  fldr.fold(&meas);

                leEvents->push_back(meas);
            }

            //Load the acceptance correction
            lePDF->load_coeffs_eff_phsp_4d();
            lePDF->update_cached_normalization(leParameters);
            lePDF->update_cached_efficiencies(leParameters, leEvents);

            spdlog::info("[PDF{0:d}]\tFinished selecting the events: {1:d}",n, leEvents->size());

            //save event vector in vector
            selection[b].push_back(leEvents);

            if(selection[b].back()->size() > 0){
                spdlog::info("[PDF{0:d}]\t[BIN{1:d}]\tDone!", n, b);
            }
            else{
                spdlog::critical("No events found for PDF={0:d} and q2-bin={1:d}. Exit!",n,b);
                assert(0);
            }
        } //end loop over bins
        theOptions[n].update_efficiencies = false; //Prevent multiple weights
    } //end loop over  PDFs

    //Measure the time for the fit:
    runTime timer = runTime();

    //Save the fit results
    std::vector<int>fit_results[nBins];
    std::vector<double>f_sigs[nBins];
    std::vector<double>f_sigserr[nBins];
    std::vector<UInt_t>evts_cntr[nBins];

    //--------------------------------
    //          FIT
    //--------------------------------
    spdlog::info("[FIT]\tFit started.");

    for(unsigned int b = 0; b < nBins; b++){

        //Start the clock
        timer.start();
        time_t startTime = time(0);

        spdlog::info("[START]\tStart the fit for bin #{0:d}", b);

        //fit the events:
        const bool do_fit = !FeldmanCousin; //TODO: ask David wtf
        int fitresult = 0;
        if(do_fit){
            if(!SimultaneousFit){
                for(UInt_t n = 0; n < nPDFs; n++){
                    UInt_t idx = pdf_idx.at(n);
                    spdlog::info("[FIT{0:d}]\tRunning the fitter...", idx);
                    fitresult = f.fit(theProbs[b].at(n), theParams[b].at(n), selection[b].at(n));
                    fit_results[b].push_back(fitresult);
                }
            }
            else{
                spdlog::info("[FIT]\tFitting the following events simultaenously:");
                for(UInt_t s = 0; s < selection[b].size(); s++) spdlog::info("PDF #{0:d}: {1:d}", pdf_idx.at(s), selection[b].at(s)->size());
                fitresult = f.fit(theProbs[b], theParams[b], selection[b]);
                fit_results[b].push_back(fitresult);
                spdlog::info("Q2BIN={0:d}\tLLH={1:f}", b, f.likelihood());

                //run likelihood profile scan //TODO: fix and implement
                if(likelihoodScan) return 5;

                //run feldman cousins //TODO: fix and implement
                if(FeldmanCousin) return 5;
            }
        }
        else{ //in case of not fit, just update the pdfs....?
            for(UInt_t n = 0; n < nPDFs; n++){
                theProbs[b].at(n)->update_cached_efficiencies(theParams[b].at(n), selection[b].at(n));
                fit_results[b].push_back(fitresult);
            }
        }

        //Stop the clock
        timer.stop(startTime);


        //save signal fraction and event number for each bin and each pdf:
        for(UInt_t n = 0; n < nPDFs; n++){
            f_sigs[b]   .push_back(((fcnc::bu2kstarmumu_parameters *) theParams[b].at(n))->f_sig.get_value());
            f_sigserr[b].push_back(((fcnc::bu2kstarmumu_parameters *) theParams[b].at(n))->f_sig.get_error());
            evts_cntr[b].push_back(selection[b].at(n)->size());
        }

        //plot each set of pdfs with the data points:
        bool do_plot = !(likelihoodScan || FeldmanCousin);
        if (do_plot){
            for(UInt_t n = 0; n < nPDFs; n++){
                std::string eps_label = get_eps_label(fitReference,false, false, fitToy,
                                                      params.nBins, b,
                                                      params.polarity,
                                                      SimultaneousFit, theOptions[n]);

                theOptions[n].q2_label = q2_label(opts.TheQ2binsmin.at(b), opts.TheQ2binsmax.at(b));

                spdlog::info("[PLOT]\t"+eps_label);
                thePlotter[n]->SetPulls(plotPulls);
                if (plotSignalRegion) thePlotter[n]->plot_data((fcnc::bu2kstarmumu_pdf*)theProbs[b].at(n), (fcnc::bu2kstarmumu_parameters*)theParams[b].at(n), selection[b].at(n), get_MainFitPlot_path(), eps_label, plotSignalRegion);
                thePlotter[n]->plot_data((fcnc::bu2kstarmumu_pdf*)theProbs[b].at(n), (fcnc::bu2kstarmumu_parameters*)theParams[b].at(n), selection[b].at(n), get_MainFitPlot_path(), eps_label+"_incBkg", false);
            }

            //plot both PDFs together
            std::vector<fcnc::bu2kstarmumu_pdf*> * prober = (std::vector<fcnc::bu2kstarmumu_pdf*> *)  & theProbs[b];

            std::string eps_label = get_eps_label(fitReference,false, false, fitToy,
                                                  params.nBins, b,
                                                  params.polarity,
                                                  SimultaneousFit, opts);
            std::vector<fcnc::bu2kstarmumu_parameters*> * paramser = (std::vector<fcnc::bu2kstarmumu_parameters*> *) & theParams[b];
            thePlotter[0]->SetPulls(plotPulls);
            if (plotSignalRegion) thePlotter[0]->plot_added_pdfs(prober, paramser, & selection[b], get_MainFitPlot_path(), eps_label, plotSignalRegion); //TODO add prefix to the plots
            thePlotter[0]->plot_added_pdfs(prober, paramser, & selection[b], get_MainFitPlot_path(), eps_label+"_incBkg", false);


        }//end do_plot
    }//end loop over bins for fitter

    if(likelihoodScan || FeldmanCousin) return 5; //Not implemented yet

    //--------------------------------
    //        Print & Save
    //--------------------------------

    //Print running time
    timer.print(nBins);


    if (!Blind) print_all_parameters(nBins, pdf_idx, theParams, spdlog::level::debug);

    //Save the fit results into a txt file in case
    for(UInt_t n = 0; n < nPDFs; n++){
        for(unsigned int b = 0; b < nBins; b++){
            //Print all fit results
            for(unsigned int b = 0; b < nBins; b++){
                spdlog::info("[BIN{0:d}]:\tFitresult: {1:d}", b, fit_results[n].at(b));
            }
            std::string txtFile = get_MainFitResult_path() + "fitresult_"
                    + get_eps_label(fitReference,false, false, fitToy,
                                    params.nBins, b,
                                    params.polarity,
                                    SimultaneousFit, theOptions[n])+ ".txt";
            ((fcnc::bu2kstarmumu_parameters *) theParams[b].at(n))->save_param_values(txtFile);
        }
    }

    //Print signal yield in the terminal and to a tex file
    if (!Blind){
        print_sig_yields(nBins, pdf_idx, evts_cntr, f_sigs, f_sigserr);
        print_bkg_yields(nBins, pdf_idx, evts_cntr, f_sigs, f_sigserr);
    }
    print_sig_yields_tex(get_eps_label(fitReference,false, false, fitToy,
                                       params.nBins, -1,
                                       params.polarity,
                                       SimultaneousFit, opts),
                         nBins, pdf_idx, &opts, evts_cntr, f_sigs, f_sigserr);


    //saving results to root file TODO
    save_results(results_file,nBins,pdf_idx,fit_results,theParams,SimultaneousFit,&opts);
    spdlog::info("Finished fit.");

    return 0;

}