int gammaJetHistogram(const TString configFile, const TString inputFile, const TString outputFile, const int nJobs, const int jobNum) {
    TH1::SetDefaultSumw2();

    std::cout << "running gammaJetHistogram()" << std::endl;
    std::cout << "configFile  = " << configFile.Data() << std::endl;
    std::cout << "inputFile   = " << inputFile.Data() << std::endl;
    std::cout << "outputFile  = " << outputFile.Data() << std::endl;

    InputConfiguration configInput = InputConfigurationParser::Parse(configFile.Data());
    CutConfiguration configCuts = CutConfigurationParser::Parse(configFile.Data());

    // input configuration
    if (!configInput.isValid) {
        std::cout << "Invalid input configuration" << std::endl;
        return 1;
    } else if (!configCuts.isValid) {
        std::cout << "Invalid cut configuration" << std::endl;
        return 1;
    }

    // input configuration
    // verbose about input configuration
    std::cout << "Input Configuration :" << std::endl;

    const int collision = configInput.proc[INPUT::kHISTOGRAM].i[INPUT::k_collisionType];
    const char* collisionName = getCollisionTypeName((COLL::TYPE)collision).c_str();
    std::cout << "collision = " << collisionName << std::endl;

    const bool isMC = collisionIsMC((COLL::TYPE)collision);
    const bool isHI = collisionIsHI((COLL::TYPE)collision);

    // observable bins
    std::vector<float> bins_pt[2];          // array of vectors for eta bins, each array element is a vector.
    std::vector<int>   bins_hiBin[2];       // array of vectors for hiBin bins, each array element is a vector.

    bins_pt[0] = ConfigurationParser::ParseListFloat(configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kPHOTON].s[CUTS::PHO::k_bins_pt_gt]);
    bins_pt[1] = ConfigurationParser::ParseListFloat(configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kPHOTON].s[CUTS::PHO::k_bins_pt_lt]);
    bins_hiBin[0] = ConfigurationParser::ParseListInteger(configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kEVENT].s[CUTS::EVT::k_bins_hiBin_gt]);
    bins_hiBin[1] = ConfigurationParser::ParseListInteger(configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kEVENT].s[CUTS::EVT::k_bins_hiBin_lt]);

    const int nSmearBins = configCuts.proc[CUTS::kSKIM].obj[CUTS::kJET].i[CUTS::JET::k_nSmearBins];

    // event cuts/weights
    // photon cuts
    const std::string trigger = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kPHOTON].s[CUTS::PHO::k_trigger_gammaJet].c_str();
    const float cut_phoHoverE = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kPHOTON].f[CUTS::PHO::k_phoHoverE];
    const float cut_phoSigmaIEtaIEta = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kPHOTON].f[CUTS::PHO::k_phoSigmaIEtaIEta];
    const float cut_sumIso = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kPHOTON].f[CUTS::PHO::k_sumIso];

    // jet cuts
    const std::string jetCollection = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kJET].s[CUTS::JET::k_jetCollection];
    const float cut_jetpt = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kJET].f[CUTS::JET::k_pt];
    const float cut_jeteta = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kJET].f[CUTS::JET::k_eta];
    const int   cut_jetID = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kJET].i[CUTS::JET::k_jetID];

    // gammaJet cuts
    const float cut_awayRange = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kGAMMAJET].f[CUTS::GJT::k_awayRange] * TMath::Pi();
    const float cut_dR = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kGAMMAJET].f[CUTS::GJT::k_dR];

    // process cuts
    const bool doPhotonEnergyScaleSystematics = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kPHOTON].i[CUTS::PHO::k_doPhotonEnergyScaleSystematics];
    const bool doElectronRejection = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kPHOTON].i[CUTS::PHO::k_doElectronRejection];
    const bool doPhotonIsolationSys = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kPHOTON].i[CUTS::PHO::k_doPhotonIsolationSys];
    const bool useCorrectedSumIso = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kPHOTON].i[CUTS::PHO::k_useCorrectedSumIso];

    const float smearingResJet = configCuts.proc[CUTS::kSKIM].obj[CUTS::kJET].f[CUTS::JET::k_smearingRes];
    const bool doSmearingRes = (smearingResJet > 0);
    const int sysUncFactor = 100;
    TRandom3 rand(12345);
    
    const int dphi_check = configCuts.proc[CUTS::kHISTOGRAM].obj[CUTS::kEVENT].i[CUTS::EVT::k_dphi_check];

    const int nBins_pt = bins_pt[0].size();         // assume <myvector>[0] and <myvector>[1] have the same size.
    const int nBins_hiBin = bins_hiBin[0].size();     // assume <myvector>[0] and <myvector>[1] have the same size.
    // verbose about cut configuration
    std::cout << "Cut Configuration :" << std::endl;
    std::cout << "nBins_pt = " << nBins_pt << std::endl;
    for (int i=0; i<nBins_pt; ++i)
        std::cout << Form("bins_pt[%d] = [%.1f, %.1f)", i, bins_pt[0][i], bins_pt[1][i]) << std::endl;
    std::cout << "nBins_hiBin = " << nBins_hiBin << std::endl;
    for (int i=0; i<nBins_hiBin; ++i)
        std::cout << Form("bins_hiBin[%d] = [%d, %d)", i, bins_hiBin[0][i], bins_hiBin[1][i]) << std::endl;

    std::cout << "trigger    = " << trigger.c_str() << std::endl;

    std::cout << "cut_phoHoverE             = " << cut_phoHoverE << std::endl;
    std::cout << "cut_phoSigmaIEtaIEta      = " << cut_phoSigmaIEtaIEta << std::endl;
    std::cout << "cut_sumIso                = " << cut_sumIso << std::endl;
    std::cout << "useCorrectedSumIso        = " << useCorrectedSumIso << std::endl;

    std::cout << "jetCollection             = " << jetCollection.c_str() << std::endl;
    std::cout << "cut_jetpt                 = " << cut_jetpt << std::endl;
    std::cout << "cut_jeteta                = " << cut_jeteta << std::endl;
    std::cout << "cut_jetID                 = " << cut_jetID << std::endl;

    std::cout << "cut_awayRange             = " << cut_awayRange << std::endl;
    std::cout << "cut_dR                    = " << cut_dR << std::endl;

    std::cout << "doPhotonEnergyScaleSystematics = " << doPhotonEnergyScaleSystematics << std::endl;
    std::cout << "doSmearingRes = " << doSmearingRes << std::endl;
    std::cout << "smearingResJet = " << smearingResJet << std::endl;

    /// Input Bookkeeping block ///
    TFile* input = TFile::Open(inputFile);

    TTree* tHlt = (TTree*)input->Get("hltTree");
    tHlt->SetBranchStatus("*", 0);
    tHlt->SetBranchStatus("HLT_HISinglePhoton40_Eta1p5_v1", 1);
    tHlt->SetBranchStatus("HLT_HISinglePhoton40_Eta1p5_v2", 1);
    tHlt->SetBranchStatus("HLT_HISinglePhoton50_Eta3p1_v1", 1);
    tHlt->SetBranchStatus("HLT_HISinglePhoton50_Eta3p1_v2", 1);
    tHlt->SetBranchStatus("HLT_HISinglePhoton40_Eta1p5ForPPRef_v1", 1);

    TTree* tPho = (TTree*)input->Get("EventTree");    // photons
    tPho->SetBranchStatus("*", 0);
    tPho->SetBranchStatus("phoEt", 1);
    tPho->SetBranchStatus("phoEtCorrected", 1);
    tPho->SetBranchStatus("phoEtCorrected_sys", 1);
    tPho->SetBranchStatus("phoEta", 1);
    tPho->SetBranchStatus("phoPhi", 1);
    tPho->SetBranchStatus("phoSigmaIEtaIEta_2012", 1);
    tPho->SetBranchStatus("pho_ecalClusterIsoR4", 1);
    tPho->SetBranchStatus("pho_hcalRechitIsoR4", 1);
    tPho->SetBranchStatus("pho_trackIsoR4PtCut20", 1);
    tPho->SetBranchStatus("pho_sumIsoCorrected", 1);
    tPho->SetBranchStatus("phoHoverE", 1);
    tPho->SetBranchStatus("phoE3x3", 1);
    tPho->SetBranchStatus("phoE5x5", 1);
    tPho->SetBranchStatus("phoE1x5", 1);
    tPho->SetBranchStatus("phoE2x5", 1);
    if (doElectronRejection) {
        tPho->SetBranchStatus("nEle", 1);
        tPho->SetBranchStatus("elePt", 1);
        tPho->SetBranchStatus("eleEta", 1);
        tPho->SetBranchStatus("elePhi", 1);
        tPho->SetBranchStatus("eleEoverP", 1);
    }
    if (isMC && doPhotonIsolationSys) {
        tPho->SetBranchStatus("nMC", 1);
        tPho->SetBranchStatus("mcPID", 1);
        tPho->SetBranchStatus("mcEta", 1);
        tPho->SetBranchStatus("mcPhi", 1);
        tPho->SetBranchStatus("mcCalIsoDR03", 1);
        tPho->SetBranchStatus("mcCalIsoDR04", 1);
        tPho->SetBranchStatus("pho_genMatchedIndex", 1);
    }

    TTree* tJet = (TTree*)input->Get(jetCollection.c_str());
    if (!tJet) {
        std::cout << "following jet collection is not found in the input file : " << jetCollection.c_str() << std::endl;
        std::cout << "exiting" << std::endl;
        return 1;
    }

    tJet->SetBranchStatus("*", 0);
    tJet->SetBranchStatus("nref", 1);
    tJet->SetBranchStatus("jtpt", 1);
    tJet->SetBranchStatus("jtpt_smeared", 1);
    // tJet->SetBranchStatus("jteta", 1);

    TTree* tHiEvt = (TTree*)input->Get("HiEvt");       // HiEvt tree will be placed in PP forest as well.

    ggHiNtuplizer pho;
    pho.setupTreeForReading(tPho);

    Jets jet;
    jet.setupTreeForReading(tJet);

    hiEvt evt;
    evt.setupTreeForReading(tHiEvt);

    Int_t HLT_HISinglePhoton40_Eta1p5_v1, HLT_HISinglePhoton40_Eta1p5_v2;
    Int_t HLT_HISinglePhoton50_Eta3p1_v1, HLT_HISinglePhoton50_Eta3p1_v2;
    Int_t HLT_HISinglePhoton40_Eta1p5ForPPRef_v1;
    tHlt->SetBranchAddress("HLT_HISinglePhoton40_Eta1p5_v1", &HLT_HISinglePhoton40_Eta1p5_v1);
    tHlt->SetBranchAddress("HLT_HISinglePhoton40_Eta1p5_v2", &HLT_HISinglePhoton40_Eta1p5_v2);
    tHlt->SetBranchAddress("HLT_HISinglePhoton50_Eta3p1_v1", &HLT_HISinglePhoton50_Eta3p1_v1);
    tHlt->SetBranchAddress("HLT_HISinglePhoton50_Eta3p1_v2", &HLT_HISinglePhoton50_Eta3p1_v2);
    tHlt->SetBranchAddress("HLT_HISinglePhoton40_Eta1p5ForPPRef_v1", &HLT_HISinglePhoton40_Eta1p5ForPPRef_v1);

    TTree* gammaJetTree[nSmearBins+1];
    gammaJetTree[0] = (TTree*)input->Get(Form("gamma_%s", jetCollection.c_str()));
    for (int i=0; i<nSmearBins; i++)
        gammaJetTree[i+1] = (TTree*)input->Get(Form("gamma_%s_smearBin%i", jetCollection.c_str(), i));

    GammaJet gammaJet[nSmearBins+1];
    for (int i=0; i<nSmearBins+1; i++)
        gammaJet[i].setupGammaJetTree(gammaJetTree[i]);

    TTree* jetTreeMB = 0;
    TTree* gammaJetTreeMB = 0;
    // check the existence of HI specific trees in "gammaJetSkim.root" file
    bool hasJetsMB = false;
    bool hasGammaJetMB = false;
    if (isHI) {
        input->GetObject(Form("%sMB", jetCollection.c_str()), jetTreeMB);
        input->GetObject(Form("gamma_%sMB", jetCollection.c_str()), gammaJetTreeMB);

        if (jetTreeMB)
            hasJetsMB = true;
        if (gammaJetTreeMB)
            hasGammaJetMB = true;
    }

    Jets jetMB;
    GammaJet gammaJetMB;
    if (hasJetsMB && hasGammaJetMB) {
        jetTreeMB->SetBranchStatus("*", 0);
        jetTreeMB->SetBranchStatus("jtpt", 1);
        jetTreeMB->SetBranchStatus("nref", 1);
        // jetTreeMB->SetBranchStatus("jteta", 1);

        jetMB.setupTreeForReading(jetTreeMB);
        gammaJetMB.setupGammaJetTree(gammaJetTreeMB);
    }

    // smearing set up block
    // ONLY used for JER systematic
    // WARNING make super sure this matches the values in gammaJetSkim.C !!
    // otherwise JER systematic will be off
    jetCorrector resolutionJetSmear[6];

    // smear 0-10 %
    std::vector<double> CSN_HI_cent0010 = {0.06, 1.23, 8.38};
    std::vector<double> CSN_phi_HI_cent0010 = {-3.18781/10000000, 0.125911, 2.23898};
    // smear 10-30 %
    std::vector<double> CSN_HI_cent1030 = {0.06, 1.23, 5.88};
    std::vector<double> CSN_phi_HI_cent1030 = {1.14344/100000, 0.179847, 1.56128};
    // smear 30-50 %
    std::vector<double> CSN_HI_cent3050 = {0.06, 1.23, 3.24};
    std::vector<double> CSN_phi_HI_cent3050 = {0.0145775, 0.1222, 1.21751};
    // smear 50-100 %
    std::vector<double> CSN_HI_cent50100 = {0.06, 1.23, 0};
    std::vector<double> CSN_phi_HI_cent50100 = {-0.0073078, 0.168879, 0.798885};

    // 0-30 and 30-100 are necessary only for smeared pp JER computation
    // smear 0-30 %
    std::vector<double> CSN_HI_cent0030 = {0.06, 1.23, 7.38};
    std::vector<double> CSN_phi_HI_cent0030 = {-1.303/1000000, 0.1651, 1.864};
    // smear 30-100 %
    std::vector<double> CSN_HI_cent30100 = {0.06, 1.23, 2.1};
    std::vector<double> CSN_phi_HI_cent30100 = {-2.013/100000000, 0.1646, 1.04};


    for (int i=0; i<6; ++i) {
        resolutionJetSmear[i].rand = rand;

        switch (i) {
        case 0: //0-10
            resolutionJetSmear[i].CSN_HI = CSN_HI_cent0010;
            resolutionJetSmear[i].CSN_phi_HI = CSN_phi_HI_cent0010;
            break;
        case 1: //10-30
            resolutionJetSmear[i].CSN_HI = CSN_HI_cent1030;
            resolutionJetSmear[i].CSN_phi_HI = CSN_phi_HI_cent1030;
            break;
        case 2: //30-50
            resolutionJetSmear[i].CSN_HI = CSN_HI_cent3050;
            resolutionJetSmear[i].CSN_phi_HI = CSN_phi_HI_cent3050;
            break;
        case 3: //50-100
            resolutionJetSmear[i].CSN_HI = CSN_HI_cent50100;
            resolutionJetSmear[i].CSN_phi_HI = CSN_phi_HI_cent50100;
            break;
        case 4: // 0-30, for smeared pp only
            resolutionJetSmear[i].CSN_HI = CSN_HI_cent0030;
            resolutionJetSmear[i].CSN_phi_HI = CSN_phi_HI_cent0030;
            break;
        case 5: // 30-100, for smeared pp only
            resolutionJetSmear[i].CSN_HI = CSN_HI_cent30100;
            resolutionJetSmear[i].CSN_phi_HI = CSN_phi_HI_cent30100;
            break;
        }
    }

    /// End Input Bookkeeping block //

    TFile* output = TFile::Open(outputFile, "RECREATE");
    // histograms will be put under a directory whose name is the type of the collision
    if (!output->GetKey(collisionName))
        output->mkdir(collisionName, Form("input file is %s", inputFile.Data()));
    output->cd(collisionName);
    std::cout << "histograms will be put under directory : " << collisionName << std::endl;

    TTree *configTree = setupConfigurationTreeForWriting(configCuts);

    int nCorrHist = correlationHistNames.size();
    correlationHist corrHists[nCorrHist][nBins_pt][nBins_hiBin];

    // prepare histogram names for xjg, abs(dphi) and jet pt
    for (int iHist=0; iHist<nCorrHist; ++iHist) {
        for (int i=0; i<nBins_pt; ++i) {
            for (int j=0; j<nBins_hiBin; ++j) {
                corrHists[iHist][i][j].name = Form("%s_ptBin%d_hiBin%d", correlationHistNames[iHist].c_str(), i, j);

                for (int iCorr = 0; iCorr < CORR::kN_CORRFNC; ++iCorr) {
                    for (int jCorr = 0; jCorr < CORR::kN_CORRFNC; ++jCorr) {
                        std::string subHistName = Form("%s_ptBin%d_hiBin%d_%s_%s", correlationHistNames[iHist].c_str(), i, j,
                                                       CORR::CORR_PHO_LABELS[iCorr].c_str(), CORR::CORR_JET_LABELS[jCorr].c_str());
                        corrHists[iHist][i][j].h1D_name[iCorr][jCorr] = subHistName.c_str();
                        corrHists[iHist][i][j].h1D[iCorr][jCorr] = new TH1D(Form("h1D_%s", subHistName.c_str()), "",
                                nBinsx[iHist], xlow[iHist], xup[iHist]);

                        corrHists[iHist][i][j].h1D_titleX[iCorr][jCorr] = correlationHistTitleX[iHist].c_str();
                        corrHists[iHist][i][j].h1D_titleY_final_norm[iCorr][jCorr] = correlationHistTitleY_final_normalized[iHist].c_str();
                    }
                }
            }
        }
    }

    // histograms to store the number of photon events, not photon-Jet event
    // those histograms have a single bin whose content is the number of photon events
    // they are just a tool to store number.
    TH1D* h_nPho[nBins_pt][nBins_hiBin][2];
    for (int i=0; i<nBins_pt; ++i) {
        for (int j=0; j<nBins_hiBin; ++j) {
            std::string histNamePhoRAW = Form("h_nPho_ptBin%d_hiBin%d_%s", i, j, CORR::CORR_PHO_LABELS[CORR::kRAW].c_str());
            h_nPho[i][j][CORR::kRAW] = new TH1D(histNamePhoRAW.c_str(), "", 1, 0, 1);

            std::string histNamePhoBKG = Form("h_nPho_ptBin%d_hiBin%d_%s", i, j, CORR::CORR_PHO_LABELS[CORR::kBKG].c_str());
            h_nPho[i][j][CORR::kBKG] = new TH1D(histNamePhoBKG.c_str(), "", 1, 0, 1);
        }
    }

    // selection for jet regions
    // jet from bkg region are already separated from raw region.
    // no additional selection for jets. just use different trees.
    std::cout << "####################" << std::endl;
    std::cout << "gammaJetTree->GetEntries() = " << gammaJetTree[0]->GetEntries() << std::endl;
    if (trigger.compare("") != 0 && !isMC)
        std::cout << "gammaJetTree->GetEntries(trigger==1) = " << tHlt->GetEntries(Form("%s == 1", trigger.c_str())) << std::endl;
    else
        std::cout << "gammaJetTree->GetEntries(trigger==1) is skipped because either no trigger is specified or the data is coming from MC." << std::endl;
    std::cout << "####################" << std::endl;

    long long nentries = gammaJetTree[0]->GetEntries();
    long long firstEntry = 0;
    long long lastEntry = nentries;
    std::cout << "Total Entries: " << nentries << std::endl;

    if (nJobs != -1) {
        if (jobNum >= nJobs) {
            std::cout << "jobNum > nJobs, invalid configuration, aborting" << std::endl;
            return 1;
        }

        firstEntry = floor(nentries/nJobs)*jobNum;
        lastEntry = floor(nentries/nJobs)*(jobNum+1);
        if (jobNum == nJobs-1)
            lastEntry = nentries;

        std::cout << "For this job " << jobNum << std::endl;
        std::cout << "First Entry: " << firstEntry << std::endl;
        std::cout << "Final Entry: " << lastEntry << std::endl;
    }

    for (long long jentry = firstEntry; jentry < lastEntry; jentry++) {
        if (jentry % 2000 == 0 && nJobs == -1)
            printf("current entry = %lli out of %lli : %3f%%\n", jentry, nentries, (double)jentry/nentries*100);

        tHlt->GetEntry(jentry);
        // event selection
        if (!isMC && !HLT_HISinglePhoton40_Eta1p5_v1)
            continue;
        if (isHI && isMC && !HLT_HISinglePhoton40_Eta1p5_v2)
            continue;
        if (!isHI && isMC && !HLT_HISinglePhoton40_Eta1p5ForPPRef_v1)
            continue;

        tPho->GetEntry(jentry);
        gammaJetTree[0]->GetEntry(jentry);

        // eta cut moved to skim step
        // if (TMath::Abs((*pho.phoEta)[gammaJet[0].phoIdx]) > 1.44) continue;

        // noise cut
        if (((*pho.phoE3x3)[gammaJet[0].phoIdx]/(*pho.phoE5x5)[gammaJet[0].phoIdx] > 2./3.-0.03 &&
             (*pho.phoE3x3)[gammaJet[0].phoIdx]/(*pho.phoE5x5)[gammaJet[0].phoIdx] < 2./3.+0.03) &&
            ((*pho.phoE1x5)[gammaJet[0].phoIdx]/(*pho.phoE5x5)[gammaJet[0].phoIdx] > 1./3.-0.03 &&
             (*pho.phoE1x5)[gammaJet[0].phoIdx]/(*pho.phoE5x5)[gammaJet[0].phoIdx] < 1./3.+0.03) &&
            ((*pho.phoE2x5)[gammaJet[0].phoIdx]/(*pho.phoE5x5)[gammaJet[0].phoIdx] > 2./3.-0.03 &&
             (*pho.phoE2x5)[gammaJet[0].phoIdx]/(*pho.phoE5x5)[gammaJet[0].phoIdx] < 2./3.+0.03)) continue;

        // isolation cut
        if (useCorrectedSumIso) {
            if ((*pho.pho_sumIsoCorrected)[gammaJet[0].phoIdx] > cut_sumIso) {
                continue;
            }
        } else if (((*pho.pho_ecalClusterIsoR4)[gammaJet[0].phoIdx] +
                    (*pho.pho_hcalRechitIsoR4)[gammaJet[0].phoIdx] +
                    (*pho.pho_trackIsoR4PtCut20)[gammaJet[0].phoIdx]) > cut_sumIso) {
            continue;
        }

        if ((*pho.phoHoverE)[gammaJet[0].phoIdx] > 0.1)
            continue;

        bool isSignalPho = ((*pho.phoSigmaIEtaIEta_2012)[gammaJet[0].phoIdx] < cut_phoSigmaIEtaIEta);
        bool isBkgPho = ((*pho.phoSigmaIEtaIEta_2012)[gammaJet[0].phoIdx] > 0.011 &&
                         (*pho.phoSigmaIEtaIEta_2012)[gammaJet[0].phoIdx] < 0.017);

        if (!(isSignalPho || isBkgPho))
            continue;

        int phoType = isSignalPho ? CORR::kRAW : CORR::kBKG;

        // reco electron rejection part
        bool isEle = false;
        if (doElectronRejection) {
            float eleEpTemp = 100.0;
            for (int ie=0; ie<pho.nEle; ++ie) {
                if ((*pho.elePt)[ie] < 10)
                    continue;
                if (abs((*pho.eleEta)[ie] - (*pho.phoEta)[gammaJet[0].phoIdx]) > 0.03) // deta
                    continue;
                if (abs(getDPHI((*pho.elePhi)[ie], (*pho.phoPhi)[gammaJet[0].phoIdx])) > 0.03) // dphi
                    continue;
                if (eleEpTemp < pho.eleEoverP->at(ie))
                    continue;

                isEle = true;
                break;
            }
        }
        if (isEle)
            continue;

        // photon isolation systematic part (gen matching & genIso condition)
        if (isMC && doPhotonIsolationSys) {
            if (!(((*pho.pho_genMatchedIndex)[gammaJet[0].phoIdx]!=-1) && (pho.mcCalIsoDR04->at((*pho.pho_genMatchedIndex)[gammaJet[0].phoIdx])<5.0))) {
                // std::cout << "failedGenIso : mcCalIso = " << (pho.mcCalIsoDR04->at((*pho.pho_genMatchedIndex)[gammaJet[0].phoIdx]))<< std::endl;
                continue;
            }
        }

        tJet->GetEntry(jentry);
        tHiEvt->GetEntry(jentry);

        float weight = isMC ? evt.weight : 1;
        float phoEt = doPhotonEnergyScaleSystematics ? (*pho.phoEtCorrected_sys)[gammaJet[0].phoIdx] : (*pho.phoEtCorrected)[gammaJet[0].phoIdx];

        // handle nEntriesPho separate from jet loop
        for (int i=0; i<nBins_pt; ++i) {
            if (phoEt <  bins_pt[0][i] || phoEt >= bins_pt[1][i])
                continue;
            for (int j=0; j<nBins_hiBin; ++j) {
                if (isHI && (evt.hiBin <  bins_hiBin[0][j] || evt.hiBin >= bins_hiBin[1][j]))
                    continue;
                for (int iHist = 0; iHist < nCorrHist; iHist++)
                    corrHists[iHist][i][j].nEntriesPho[phoType][CORR::kRAW] += weight;
            }
        }

        if(doSmearingRes)
            weight /= sysUncFactor;

        for (int j=0; j<nBins_hiBin; ++j) {
            int smearBin = 0;
            if (isHI) {
                if (evt.hiBin <  bins_hiBin[0][j] || evt.hiBin >= bins_hiBin[1][j])
                    continue;
            } else {
                if (j) {
                    // change smearBin to select the smeared jtpt and jtphi corresponding to the centrality bin
                    // 0 is unsmeared jtpt
                    smearBin = j;
                }

                if (smearBin)
                    gammaJetTree[smearBin]->GetEntry(jentry);
            }

            for (int ijet = 0; ijet < jet.nref; ijet++) {
                int multiplyJets = 1;
                if(doSmearingRes)
                    multiplyJets = sysUncFactor;
                for(int iResSmear = 0; iResSmear < multiplyJets; iResSmear++){
                    float jetpt = (*jet.jtpt_smeared)[smearBin][ijet];
                    float smearFactor = 1;
                    if(doSmearingRes) {
                        //smearFactor = rand.Gaus(1,smearingResJet);
                        float SF = 1 + smearingResJet;
                        int resolutionBin = 0;
                        if(isHI) {
                            resolutionBin = getResolutionBin(evt.hiBin);
                        } else {
                            resolutionBin = getResolutionBinPP(smearBin);
                        }
                        float initialResolution = resolutionJetSmear[resolutionBin].getResolutionHI(jetpt);
                        smearFactor = rand.Gaus(1, SF * initialResolution * sqrt(SF*SF - 1));
                        jetpt *= smearFactor;
                    }
                    // jet cuts
                    // jteta cut moved to skim
                    // if (TMath::Abs(jet.jteta[ijet]) > cut_jeteta) continue;
                    if ((*gammaJet[0].jetID)[ijet] < cut_jetID)
                        continue;
                    if (jetpt < cut_jetpt)
                        continue;
                    if ((*gammaJet[smearBin].dR)[ijet] < cut_dR)
                        continue;

                    for (int i=0; i<nBins_pt; ++i) {
                        if (phoEt <  bins_pt[0][i] || phoEt >= bins_pt[1][i])
                            continue;

                        // fill histograms
                        // dphi = 1
                        corrHists[1][i][j].h1D[phoType][CORR::kRAW]->Fill(TMath::Abs((*gammaJet[smearBin].dphi)[ijet]), weight);
                        corrHists[1][i][j].nEntries[phoType][CORR::kRAW] += weight;
                        
                        //apply dphi cuts now
                        if (TMath::Abs((*gammaJet[smearBin].dphi)[ijet]) <= cut_awayRange)
                            continue;
                        // xjg = 0
                        // jtpt = 2
                        float xjg = doPhotonEnergyScaleSystematics ? (*gammaJet[smearBin].xjgCorrected_sys)[ijet] : (*gammaJet[smearBin].xjgCorrected)[ijet];
                        if(doSmearingRes){
                            xjg *= smearFactor;
                        }
                        corrHists[0][i][j].h1D[phoType][CORR::kRAW]->Fill(xjg, weight);
                        corrHists[0][i][j].nEntries[phoType][CORR::kRAW] += weight;
                        corrHists[2][i][j].h1D[phoType][CORR::kRAW]->Fill(jetpt, weight);
                        corrHists[2][i][j].nEntries[phoType][CORR::kRAW] += weight;
                    }
                }
            }
        }

        if (dphi_check && isHI) {
            for (int ijet = 0; ijet < jet.nref; ijet++) {
                for (int j=0; j<nBins_hiBin; ++j) {
                    if (evt.hiBin <  bins_hiBin[0][j] || evt.hiBin >= bins_hiBin[1][j])
                        continue;
                    if (jet.jtpt[ijet] < cut_jetpt)
                        continue;
                    if ((*gammaJet[0].dR)[ijet] < cut_dR)
                        continue;

                    for (int i=0; i<nBins_pt; ++i) {
                        if ((*pho.phoEtCorrected)[gammaJet[0].phoIdx] <  bins_pt[0][i] ||
                            (*pho.phoEtCorrected)[gammaJet[0].phoIdx] >= bins_pt[1][i]) continue;

                        float lower_sideband = 1;
                        float upper_sideband = TMath::Pi()/2;
                        float sideband_width = upper_sideband - lower_sideband;

                        // select dphi sideband region
                        if (TMath::Abs((*gammaJet[0].dphi)[ijet]) > upper_sideband || TMath::Abs((*gammaJet[0].dphi)[ijet]) < lower_sideband)
                            continue;

                        corrHists[1][i][j].h1D[phoType][CORR::kBKG]->Fill((TMath::Abs((*gammaJet[0].dphi)[ijet])-lower_sideband)*(TMath::Pi()/sideband_width), weight*40*(TMath::Pi()/sideband_width));
                        corrHists[1][i][j].nEntries[phoType][CORR::kBKG] += weight*40*(TMath::Pi()/sideband_width);

                        corrHists[0][i][j].h1D[phoType][CORR::kBKG]->Fill((*gammaJet[0].xjgCorrected)[ijet], weight*40*(TMath::Pi()/8/sideband_width));
                        corrHists[0][i][j].nEntries[phoType][CORR::kBKG] += weight*40*(TMath::Pi()/8/sideband_width);
                        corrHists[2][i][j].h1D[phoType][CORR::kBKG]->Fill(jet.jtpt[ijet], weight*40*(TMath::Pi()/8/sideband_width));
                        corrHists[2][i][j].nEntries[phoType][CORR::kBKG] += weight*40*(TMath::Pi()/8/sideband_width);
                    }
                }
            }
        } else {
            if (hasJetsMB && hasGammaJetMB) {
                jetTreeMB->GetEntry(jentry);
                gammaJetTreeMB->GetEntry(jentry);

                for (int ijet = 0; ijet < jetMB.nref; ijet++) {
                    int multiplyJets = 1;
                    if(doSmearingRes)
                        multiplyJets = sysUncFactor;
                    for(int iResSmear = 0; iResSmear < multiplyJets; iResSmear++){
                        float jetpt = jetMB.jtpt[ijet];
                        float smearFactor = 1;
                        if(doSmearingRes) {
                            //smearFactor = rand.Gaus(1,smearingResJet);
                            float SF = 1 + smearingResJet;
                            int resolutionBin = 0;
                            resolutionBin = getResolutionBin(evt.hiBin);
                            float initialResolution = resolutionJetSmear[resolutionBin].getResolutionHI(jetpt);
                            smearFactor = rand.Gaus(1, SF * initialResolution * sqrt(SF*SF - 1));
                            jetpt *= smearFactor;
                        }

                        // jet cuts
                        if ((*gammaJetMB.dR)[ijet] < cut_dR)
                            continue;
                        // jteta cut moved to skim
                        // if (TMath::Abs(jetMB.jteta[ijet]) > cut_jeteta) continue;
                        if (jetpt < cut_jetpt)
                            continue;
                        if ((*gammaJetMB.jetID)[ijet] < cut_jetID)
                            continue;

                        for (int i=0; i<nBins_pt; ++i) {
                            if ((*pho.phoEtCorrected)[gammaJetMB.phoIdx] <  bins_pt[0][i] ||
                                (*pho.phoEtCorrected)[gammaJetMB.phoIdx] >= bins_pt[1][i]) continue;
                            for (int j=0; j<nBins_hiBin; ++j) {
                                if (evt.hiBin <  bins_hiBin[0][j] || evt.hiBin >= bins_hiBin[1][j])
                                    continue;

                                // fill histograms
                                // dphi = 1
                                corrHists[1][i][j].h1D[phoType][CORR::kBKG]->Fill(TMath::Abs((*gammaJetMB.dphi)[ijet]), weight);
                                corrHists[1][i][j].nEntries[phoType][CORR::kBKG] += weight;
                                // apply dphi cuts now
                                if (TMath::Abs((*gammaJetMB.dphi)[ijet]) <= cut_awayRange)
                                    continue;
                                // xjg = 0
                                // jtpt = 2
                                float xjg = doPhotonEnergyScaleSystematics ? (*gammaJetMB.xjgCorrected_sys)[ijet] : (*gammaJetMB.xjgCorrected)[ijet];
                                if(doSmearingRes){
                                    xjg *= smearFactor;
                                }
                                corrHists[0][i][j].h1D[phoType][CORR::kBKG]->Fill(xjg, weight);
                                corrHists[0][i][j].nEntries[phoType][CORR::kBKG] += weight;
                                corrHists[2][i][j].h1D[phoType][CORR::kBKG]->Fill(jetpt, weight);
                                corrHists[2][i][j].nEntries[phoType][CORR::kBKG] += weight;
                            }
                        }
                    }
                }
            }
        }
    }

    /// Histogram arithmetic (no reading I/O)
    for (int iHist = 0; iHist < nCorrHist; iHist++) {
        for (int i=0; i<nBins_pt; ++i) {
            for (int j=0; j<nBins_hiBin; ++j) {
                corrHists[iHist][i][j].nEntriesPho[CORR::kRAW][CORR::kBKG] = corrHists[iHist][i][j].nEntriesPho[CORR::kRAW][CORR::kRAW];
                corrHists[iHist][i][j].nEntriesPho[CORR::kBKG][CORR::kBKG] = corrHists[iHist][i][j].nEntriesPho[CORR::kBKG][CORR::kRAW];

                if (h_nPho[i][j][CORR::kRAW]->GetBinContent(1) == 0) {
                    h_nPho[i][j][CORR::kRAW]->SetBinContent(1, corrHists[iHist][i][j].nEntriesPho[CORR::kRAW][CORR::kRAW]);
                    h_nPho[i][j][CORR::kRAW]->Write("", TObject::kOverwrite);
                }

                if (h_nPho[i][j][CORR::kBKG]->GetBinContent(1) == 0) {
                    h_nPho[i][j][CORR::kBKG]->SetBinContent(1, corrHists[iHist][i][j].nEntriesPho[CORR::kBKG][CORR::kRAW]);
                    h_nPho[i][j][CORR::kBKG]->Write("", TObject::kOverwrite);
                }

                std::cout << "nEntries[CORR::kRAW][CORR::kRAW] = " << corrHists[iHist][i][j].nEntries[CORR::kRAW][CORR::kRAW] << std::endl;
                std::cout << "nEntries[CORR::kBKG][CORR::kRAW] = " << corrHists[iHist][i][j].nEntries[CORR::kBKG][CORR::kRAW] << std::endl;
                std::cout << "nEntriesPho[CORR::kRAW][CORR::kRAW] = " << corrHists[iHist][i][j].nEntriesPho[CORR::kRAW][CORR::kRAW] << std::endl;
                std::cout << "nEntriesPho[CORR::kBKG][CORR::kRAW] = " << corrHists[iHist][i][j].nEntriesPho[CORR::kBKG][CORR::kRAW] << std::endl;

                std::string histoTitle;
                if (bins_pt[1][i] == 9999 && bins_hiBin[0][j] <= 0 && bins_hiBin[1][j] >= 200)
                    histoTitle = Form("%s , p^{#gamma}_{T} > %.0f GeV/c", collisionName , bins_pt[0][i]);
                else if (bins_hiBin[0][j] <= 0 && bins_hiBin[1][j] >= 200)
                    histoTitle = Form("%s , %.0f < p^{#gamma}_{T} < %.0f GeV/c", collisionName , bins_pt[0][i], bins_pt[1][i]);
                else if (bins_pt[1][i] == 9999)
                    histoTitle = Form("%s , p^{#gamma}_{T} > %.0f GeV/c, %d-%d %% ", collisionName , bins_pt[0][i], bins_hiBin[0][j]/2, bins_hiBin[1][j]/2);
                else
                    histoTitle = Form("%s , %.0f < p^{#gamma}_{T} < %.0f GeV/c, %d-%d %% ", collisionName , bins_pt[0][i], bins_pt[1][i], bins_hiBin[0][j]/2, bins_hiBin[1][j]/2);

                // histograms for RAW and BKG regions
                for (int iCorr = 0; iCorr < CORR::kN_CORRFNC-1; ++iCorr) {
                    for (int jCorr = 0; jCorr < CORR::kN_CORRFNC-1; ++jCorr) {
                        if (jCorr == CORR::kBKG && !isHI) continue;      // no jet background for non-HI

                        std::string titleX = corrHists[iHist][i][j].h1D_titleX[iCorr][jCorr].c_str();
                        corrHists[iHist][i][j].h1D[iCorr][jCorr]->SetTitle(Form("%s;%s;%s", histoTitle.c_str(), titleX.c_str(), "Entries"));
                        corrHists[iHist][i][j].h1D[iCorr][jCorr]->SetMarkerStyle(kFullCircle);
                        corrHists[iHist][i][j].h1D[iCorr][jCorr]->SetMarkerColor(kBlack);

                        // histogram name excluding the "h1D" prefix
                        std::string tmpH1D_name = corrHists[iHist][i][j].h1D_name[iCorr][jCorr].c_str();
                        std::string tmpHistName = corrHists[iHist][i][j].h1D[iCorr][jCorr]->GetName();

                        corrHists[iHist][i][j].h1D[iCorr][jCorr]->Write("", TObject::kOverwrite);
                    }
                }
            }
        }
    }

    configTree->Write("", TObject::kOverwrite);

    output->Write("", TObject::kOverwrite);
    input->Close();
    output->Close();

    return 0;
}
void photonRaaSkim(const TString configFile, const TString inputFile, const TString outputFile, COLL::TYPE colli)
{
    std::cout<<"running photonRaaSkim()"<<std::endl;
    std::cout<<"configFile  = "<< configFile.Data() <<std::endl;
    std::cout<<"inputFile   = "<< inputFile.Data() <<std::endl;
    std::cout<<"outputFile  = "<< outputFile.Data() <<std::endl;

    InputConfiguration configInput = InputConfigurationParser::Parse(configFile.Data());
    CutConfiguration configCuts = CutConfigurationParser::Parse(configFile.Data());

    // input configuration
/*    int collisionType;
    if (configInput.isValid) {
        collisionType = configInput.proc[INPUT::kSKIM].i[INPUT::k_collisionType];
    }
    else {
        collisionType = COLL::kPP;
    }
*/
    // verbose about input configuration
    int collisionType = colli;
    std::cout<<"Input Configuration :"<<std::endl;
    std::cout << "collisionType = " << collisionType << std::endl;
    const char* collisionName =  getCollisionTypeName((COLL::TYPE)collisionType).c_str();
    std::cout << "collision = " << collisionName << std::endl;

    // cut configuration
    float cut_vz;
    int cut_pcollisionEventSelection;
    int cut_pPAprimaryVertexFilter;
    int cut_pBeamScrapingFilter;

    float cutPhoEt;
    float cutPhoEta;

    if (configCuts.isValid) {
        cut_vz = configCuts.proc[CUTS::kSKIM].obj[CUTS::kEVENT].f[CUTS::EVT::k_vz];
        cut_pcollisionEventSelection = configCuts.proc[CUTS::kSKIM].obj[CUTS::kEVENT].i[CUTS::EVT::k_pcollisionEventSelection];
        cut_pPAprimaryVertexFilter = configCuts.proc[CUTS::kSKIM].obj[CUTS::kEVENT].i[CUTS::EVT::k_pPAprimaryVertexFilter];
        cut_pBeamScrapingFilter = configCuts.proc[CUTS::kSKIM].obj[CUTS::kEVENT].i[CUTS::EVT::k_pBeamScrapingFilter];

        cutPhoEt = configCuts.proc[CUTS::kSKIM].obj[CUTS::kPHOTON].f[CUTS::PHO::k_et];
        cutPhoEta = configCuts.proc[CUTS::kSKIM].obj[CUTS::kPHOTON].f[CUTS::PHO::k_eta];

    }
    else {
        cut_vz = 15;
        cut_pcollisionEventSelection = 1;
        cut_pPAprimaryVertexFilter = 1;
        cut_pBeamScrapingFilter = 1;

        cutPhoEt = 15;
        cutPhoEta = 1.44;
    }

    bool isMC = collisionIsMC((COLL::TYPE)collisionType);
    bool isHI = collisionIsHI((COLL::TYPE)collisionType);
    bool isPP = collisionIsPP((COLL::TYPE)collisionType);

    // verbose about cut configuration
    std::cout<<"Cut Configuration :"<<std::endl;
    std::cout<<"cut_vz = "<< cut_vz <<std::endl;
    if (isHI) {
        std::cout<<"cut_pcollisionEventSelection = "<< cut_pcollisionEventSelection <<std::endl;
    }
    else {   // PP
        std::cout<<"cut_pPAprimaryVertexFilter = "<< cut_pPAprimaryVertexFilter <<std::endl;
        std::cout<<"cut_pBeamScrapingFilter = "<< cut_pBeamScrapingFilter <<std::endl;
    }

    std::cout<<"cutPhoEt  = "<<cutPhoEt<<std::endl;
    std::cout<<"cutPhoEta = "<<cutPhoEta<<std::endl;

    std::vector<std::string> inputFiles = InputConfigurationParser::ParseFiles(inputFile.Data());
    std::cout<<"input ROOT files : num = "<<inputFiles.size()<< std::endl;
    std::cout<<"#####"<< std::endl;
    for (std::vector<std::string>::iterator it = inputFiles.begin() ; it != inputFiles.end(); ++it) {
        std::cout<<(*it).c_str()<< std::endl;
    }
    std::cout<<"##### END #####"<< std::endl;

    TChain* treeHLT   = new TChain("hltanalysis/HltTree");
    TChain* treeggHiNtuplizer  = new TChain("ggHiNtuplizer/EventTree");
    TChain* treeHiEvt = new TChain("hiEvtAnalyzer/HiTree");
    TChain* treeSkim  = new TChain("skimanalysis/HltTree");
    TChain* treeHiForestInfo = new TChain("HiForest/HiForestInfo");
    TChain* treeGen=0;
    if(isMC) treeGen  = new TChain("HiGenParticleAna/hi");

    // pthatWeight Calculation block! 
    int nPthat = 5;
    float pthatCut[nPthat+1];
    const char* lowestPthatFileName="";
    int nfiles = 0;
    for (std::vector<std::string>::iterator it = inputFiles.begin() ; it != inputFiles.end(); ++it) {
        treeHLT->Add((*it).c_str());
        treeggHiNtuplizer->Add((*it).c_str());
        treeHiEvt->Add((*it).c_str());
        treeSkim->Add((*it).c_str());
        treeHiForestInfo->Add((*it).c_str());
        if(isMC) treeGen->Add((*it).c_str());
        if(isMC && (nfiles==0)) { 
            lowestPthatFileName = (*it).c_str(); 
            TString str(lowestPthatFileName);
            cout << "lowestPthatFileName = " << lowestPthatFileName << endl;
            if(str.Contains("15")) {
                float temp[] = {15,30,50,80,120,9999};          
                for(int j=0;j<nPthat+1;j++){
                    pthatCut[j] = temp[j];
                }
            } else if(str.Contains("30")) {
                float temp[] = {30,50,80,120,170,9999};          
                for(int j=0;j<nPthat+1;j++){
                    pthatCut[j] = temp[j];
                }
            }
        }
        nfiles++;
    }

    float tmpWeight[nPthat];
    if(isMC) {
        for(int j=0; j<nPthat ; j++){
            tmpWeight[j] = xSecCal(lowestPthatFileName,treeHiEvt, pthatCut[j], pthatCut[j+1]);
            cout << collisionName << ", pthatWeight of " << pthatCut[j] << " to " << pthatCut[j+1] << " = " << tmpWeight[j] << endl;
        }
    }
/*
    HiForestInfoController hfic(treeHiForestInfo);
    std::cout<<"### HiForestInfo Tree ###"<< std::endl;
    hfic.printHiForestInfo();
    std::cout<<"###"<< std::endl;
*/
    treeHLT->SetBranchStatus("*",0);     // disable all branches
    treeHLT->SetBranchStatus("HLT_HI*SinglePhoton*Eta*",1);     // enable photon branches
    treeHLT->SetBranchStatus("HLT_HI*DoublePhoton*Eta*",1);     // enable photon branches

    float vz;
    Int_t hiBin;
    UInt_t run, lumis;
    ULong64_t event;
    float pthat, pthatWeight;
    treeHiEvt->SetBranchAddress("vz",&vz);
    treeHiEvt->SetBranchAddress("hiBin",&hiBin);
    treeHiEvt->SetBranchAddress("run", &run);
    treeHiEvt->SetBranchAddress("evt", &event);
    treeHiEvt->SetBranchAddress("lumi", &lumis);
    if(isMC) {
        //treeHiEvt->Branch("pthatWeight", &pthatWeight, "pthatWeight/F");
        treeHiEvt->SetBranchAddress("pthat", &pthat);
    }   

    // specify explicitly which branches to store, do not use wildcard
    treeSkim->SetBranchStatus("*",0);

    Int_t pcollisionEventSelection;  // this filter is used for HI.
    if (isHI) {
        treeSkim->SetBranchStatus("pcollisionEventSelection",1);
        if (treeSkim->GetBranch("pcollisionEventSelection")) {
            treeSkim->SetBranchAddress("pcollisionEventSelection",&pcollisionEventSelection);
        }
        else {   // overwrite to default
            pcollisionEventSelection = 1;
            std::cout<<"could not get branch : pcollisionEventSelection"<<std::endl;
            std::cout<<"set to default value : pcollisionEventSelection = "<<pcollisionEventSelection<<std::endl;
        }
    }
    else {
        pcollisionEventSelection = 0;    // default value if the collision is not HI, will not be used anyway.
    }
    Int_t pPAprimaryVertexFilter;    // this filter is used for PP.
    if (isPP) {
        treeSkim->SetBranchStatus("pPAprimaryVertexFilter",1);
        if (treeSkim->GetBranch("pPAprimaryVertexFilter")) {
            treeSkim->SetBranchAddress("pPAprimaryVertexFilter",&pPAprimaryVertexFilter);
        }
        else {   // overwrite to default
            pPAprimaryVertexFilter = 1;
            std::cout<<"could not get branch : pPAprimaryVertexFilter"<<std::endl;
            std::cout<<"set to default value : pPAprimaryVertexFilter = "<<pPAprimaryVertexFilter<<std::endl;
        }
    }
    else {
        pPAprimaryVertexFilter = 0;      // default value if the collision is not PP, will not be used anyway.
    }
    Int_t pBeamScrapingFilter;   // this filter is used for PP.
    if (isPP) {
        treeSkim->SetBranchStatus("pBeamScrapingFilter",1);
        if (treeSkim->GetBranch("pBeamScrapingFilter")) {
            treeSkim->SetBranchAddress("pBeamScrapingFilter",&pBeamScrapingFilter);
        }
        else {   // overwrite to default
            pBeamScrapingFilter = 1;
            std::cout<<"could not get branch : pBeamScrapingFilter"<<std::endl;
            std::cout<<"set to default value : pBeamScrapingFilter = "<<pBeamScrapingFilter<<std::endl;
        }
    }
    else {
        pBeamScrapingFilter = 0;     // default value if the collision is not PP, will not be used anyway.
    }

    // objects for z-jet correlations
    ggHiNtuplizer ggHi;
    ggHi.setupTreeForReading(treeggHiNtuplizer);    // treeggHiNtuplizer is input
    treeggHiNtuplizer->SetBranchStatus("*",0);
    treeggHiNtuplizer->SetBranchStatus("run",1);
    treeggHiNtuplizer->SetBranchStatus("event",1);
    treeggHiNtuplizer->SetBranchStatus("lumis",1);
    treeggHiNtuplizer->SetBranchStatus("nPho",1);
    treeggHiNtuplizer->SetBranchStatus("pho*",1);
    treeggHiNtuplizer->SetBranchStatus("pf*",1);
    //treeggHiNtuplizer->SetBranchStatus("tower*",1);
    if(isMC) treeggHiNtuplizer->SetBranchStatus("mc*",1);



    TFile* output = TFile::Open(outputFile,"RECREATE");
    TTree* configTree = setupConfigurationTreeForWriting(configCuts);

    // output tree variables
    TTree *outputTreeHLT=0, *outputTreeggHiNtuplizer=0, 
          *outputTreeHiEvt=0, *outputTreeSkim=0, *outputTreeHiForestInfo=0, *outputTreeGen=0;

    // output tree variables
    outputTreeHLT    = treeHLT->CloneTree(0);
    outputTreeggHiNtuplizer = treeggHiNtuplizer->CloneTree(0);
    outputTreeHiEvt = treeHiEvt->CloneTree(0);
    outputTreeSkim   = treeSkim->CloneTree(0);
    outputTreeHiForestInfo = treeHiForestInfo->CloneTree(0);
    if(isMC) outputTreeGen = treeGen ->CloneTree(0);

    outputTreeSkim->SetName("skimTree");
    outputTreeHLT->SetMaxTreeSize(MAXTREESIZE);
    outputTreeggHiNtuplizer->SetMaxTreeSize(MAXTREESIZE);
    outputTreeHiEvt->SetMaxTreeSize(MAXTREESIZE);
    outputTreeSkim->SetMaxTreeSize(MAXTREESIZE);
    outputTreeHiForestInfo->SetMaxTreeSize(MAXTREESIZE);
    if(isMC) outputTreeGen->SetMaxTreeSize(MAXTREESIZE);

    if(isMC) outputTreeHiEvt->Branch("pthatWeight", &pthatWeight, "pthatWeight/F");
  

    /////// Event Matching for DATA ///////
    EventMatcher* em = new EventMatcher();
    Long64_t duplicateEntries = 0;
    Long64_t entriesPassedEventSelection =0;
    Long64_t entriesAnalyzed =0;
    Long64_t entriesSpikeRejected=0;
    Long64_t entries = treeggHiNtuplizer->GetEntries();
    std::cout << "entries = " << entries << std::endl;
    std::cout<< "Loop : ggHiNtuplizer/EventTree" <<std::endl;
    //for (Long64_t j_entry=10000; j_entry<10100; ++j_entry)
    for (Long64_t j_entry=0; j_entry<entries; ++j_entry)
    {
        if (j_entry % 2000 == 0)  {
            std::cout << "current entry = " <<j_entry<<" out of "<<entries<<" : "<<std::setprecision(2)<<(double)j_entry/entries*100<<" %"<<std::endl;
        }

        treeHLT->GetEntry(j_entry);
        treeggHiNtuplizer->GetEntry(j_entry);
        treeSkim->GetEntry(j_entry);
        treeHiEvt->GetEntry(j_entry);
        treeHiForestInfo->GetEntry(j_entry);
        if(isMC) treeGen->GetEntry(j_entry);

        bool eventAdded = em->addEvent(run,lumis,event,j_entry);
        //std::cout << run << " " << lumis << " " << event << " " << j_entry << std::endl;
        if(!eventAdded) // this event is duplicate, skip this one.
        {
            duplicateEntries++;
            continue;
        }

        if(isMC) {
            if((pthat>=pthatCut[0]) && (pthat<pthatCut[1])) pthatWeight = tmpWeight[0];
            else if((pthat>=pthatCut[1]) && (pthat<pthatCut[2])) pthatWeight = tmpWeight[1];
            else if((pthat>=pthatCut[2]) && (pthat<pthatCut[3])) pthatWeight = tmpWeight[2];
            else if((pthat>=pthatCut[3]) && (pthat<pthatCut[4])) pthatWeight = tmpWeight[3];
            else if((pthat>=pthatCut[4]) && (pthat<pthatCut[5])) pthatWeight = tmpWeight[4];
            else continue;
        }
        // event selection
        if (!(TMath::Abs(vz) < cut_vz))  continue;
        if (isHI) {
            if ((pcollisionEventSelection < cut_pcollisionEventSelection))  continue;
        }
        else {
            if (pPAprimaryVertexFilter < cut_pPAprimaryVertexFilter || pBeamScrapingFilter < cut_pBeamScrapingFilter)  continue;
        }
        entriesPassedEventSelection++;

        // photon block
        // find leading photon
        int phoIdx = -1;     // index of the leading photon
        double maxPhoEt = -1;
        for(int i=0; i<ggHi.nPho; ++i)
        {
            bool failedEtCut  = (ggHi.phoEt->at(i) < cutPhoEt) ;
            bool failedEtaCut = (TMath::Abs(ggHi.phoEta->at(i)) > cutPhoEta) ;
            bool failedSpikeRejection;
            bool failedHotSpotRejection;
            //if (isHI) {
            failedSpikeRejection =( (ggHi.phoEta->at(i)<1.44) && 
                    (ggHi.phoSigmaIEtaIEta->at(i) < 0.002 ||
                     ggHi.pho_swissCrx->at(i)     > 0.9   ||
                     TMath::Abs(ggHi.pho_seedTime->at(i)) > 3) );
            // }
            // else {
            //     failedSpikeRejection = (ggHi.phoSigmaIEtaIEta->at(i) < 0.002);
            // }

            failedHotSpotRejection = (
                (ggHi.phoE3x3->at(i)/ggHi.phoE5x5->at(i) > 2./3.-0.03 && ggHi.phoE3x3->at(i)/ggHi.phoE5x5->at(i) < 2./3.+0.03) &&
                (ggHi.phoE1x5->at(i)/ggHi.phoE5x5->at(i) > 1./3.-0.03 && ggHi.phoE1x5->at(i)/ggHi.phoE5x5->at(i) < 1./3.+0.03) &&
                (ggHi.phoE2x5->at(i)/ggHi.phoE5x5->at(i) > 2./3.-0.03 && ggHi.phoE2x5->at(i)/ggHi.phoE5x5->at(i) < 2./3.+0.03) );
            
            bool failedHoverE = (ggHi.phoHoverE->at(i) > 0.2);      // <0.1 cut is applied after corrections
            //               bool failedEnergyRatio = ((float)ggHi.phoSCRawE->at(i)/ggHi.phoE->at(i) < 0.5);

            if (failedEtCut)          continue;
            if (failedEtaCut)         continue;
            if (failedSpikeRejection) continue;
            if (failedHotSpotRejection) {entriesSpikeRejected++; continue;}
            if (failedHoverE)         continue;
            //               if (failedEnergyRatio)    continue;    // actually applied after corrections

            if (ggHi.phoEt->at(i) > maxPhoEt)
            {
                maxPhoEt = ggHi.phoEt->at(i);
                phoIdx = i;
            }
        }
        if (phoIdx == -1) continue;
        entriesAnalyzed++;

        outputTreeHLT->Fill();
        outputTreeggHiNtuplizer->Fill();
        outputTreeHiEvt->Fill();
        outputTreeSkim->Fill();
        outputTreeHiForestInfo->Fill();
        if(isMC) outputTreeGen->Fill();
    }// event loop closed here

    std::cout<<  "Loop ENDED : ggHiNtuplizer/EventTree" <<std::endl;
    std::cout << "entries            = " << entries << std::endl;
    std::cout << "duplicateEntries   = " << duplicateEntries << std::endl;
    std::cout << "entriesPassedEventSelection   = " << entriesPassedEventSelection << std::endl;
    std::cout << "entriesAnalyzed               = " << entriesAnalyzed << std::endl;
    std::cout << "entriesSpikeRejected          = " << entriesSpikeRejected << std::endl;
    std::cout << "outputTreeHLT->GetEntries()   = " << outputTreeHLT->GetEntries() << std::endl;
    std::cout << "outputTreeggHiNtuplizer->GetEntries()   = " << outputTreeggHiNtuplizer->GetEntries() << std::endl;
    std::cout << "outputTreeSkim->GetEntries()  = " << outputTreeSkim->GetEntries() << std::endl;
    std::cout << "outputTreeHiEvt->GetEntries() = " << outputTreeHiEvt->GetEntries() << std::endl;
    std::cout << "outputTreeHiForestInfo->GetEntries() = " << outputTreeHiForestInfo->GetEntries() << std::endl;
    if(isMC) std::cout << "outputTreeGen->GetEntries() = " << outputTreeGen->GetEntries() << std::endl;


    output->cd();
    configTree->Write("",TObject::kOverwrite);
    output->Write("",TObject::kOverwrite);
    output->Close();
}
void dimuonSkim(const TString configFile, const TString inputFile, const TString outputFile)
{
       std::cout<<"running dimuonSkim()"   <<std::endl;
       std::cout<<"configFile  = "<< configFile.Data() <<std::endl;
       std::cout<<"inputFile   = "<< inputFile.Data() <<std::endl;
       std::cout<<"outputFile  = "<< outputFile.Data() <<std::endl;

       InputConfiguration configInput = InputConfigurationParser::Parse(configFile.Data());
       CutConfiguration configCuts = CutConfigurationParser::Parse(configFile.Data());

       if (!configInput.isValid) {
           std::cout << "Input configuration is invalid." << std::endl;
           std::cout << "exiting" << std::endl;
           return;
       }
       if (!configCuts.isValid) {
           std::cout << "Cut configuration is invalid." << std::endl;
           std::cout << "exiting" << std::endl;
           return;
       }

       // input configuration
       int collisionType = configInput.proc[INPUT::kSKIM].i[INPUT::k_collisionType];
       std::string treePath = configInput.proc[INPUT::kSKIM].s[INPUT::k_treePath];

       // set default values
       if (treePath.size() == 0)  treePath = "ggHiNtuplizer/EventTree";

       // verbose about input configuration
       std::cout<<"Input Configuration :"<<std::endl;
       std::cout << "collisionType = " << collisionType << std::endl;
       const char* collisionName =  getCollisionTypeName((COLL::TYPE)collisionType).c_str();
       std::cout << "collision = " << collisionName << std::endl;
       std::cout << "treePath = " << treePath.c_str() << std::endl;

       // cut configuration
       float cut_vz = configCuts.proc[CUTS::kSKIM].obj[CUTS::kEVENT].f[CUTS::EVT::k_vz];
       int cut_pcollisionEventSelection = configCuts.proc[CUTS::kSKIM].obj[CUTS::kEVENT].i[CUTS::EVT::k_pcollisionEventSelection];
       int cut_pPAprimaryVertexFilter = configCuts.proc[CUTS::kSKIM].obj[CUTS::kEVENT].i[CUTS::EVT::k_pPAprimaryVertexFilter];
       int cut_pBeamScrapingFilter = configCuts.proc[CUTS::kSKIM].obj[CUTS::kEVENT].i[CUTS::EVT::k_pBeamScrapingFilter];

       int cut_nMu = configCuts.proc[CUTS::kSKIM].obj[CUTS::kMUON].i[CUTS::MUO::k_nMu];

       // bool isMC = collisionIsMC((COLL::TYPE)collisionType);
       bool isHI = collisionIsHI((COLL::TYPE)collisionType);
       bool isPP = collisionIsPP((COLL::TYPE)collisionType);

       // verbose about cut configuration
       std::cout<<"Cut Configuration :"<<std::endl;
       std::cout<<"cut_vz = "<< cut_vz <<std::endl;
       if (isHI) {
           std::cout<<"cut_pcollisionEventSelection = "<< cut_pcollisionEventSelection <<std::endl;
       }
       else {   // PP
           std::cout<<"cut_pPAprimaryVertexFilter = "<< cut_pPAprimaryVertexFilter <<std::endl;
           std::cout<<"cut_pBeamScrapingFilter = "<< cut_pBeamScrapingFilter <<std::endl;
       }

       std::cout<<"cut_nMu = "<<cut_nMu<<std::endl;

       std::vector<std::string> inputFiles = InputConfigurationParser::ParseFiles(inputFile.Data());

       std::cout<<"input ROOT files : num = "<<inputFiles.size()<< std::endl;
       std::cout<<"#####"<< std::endl;
       for (std::vector<std::string>::iterator it = inputFiles.begin() ; it != inputFiles.end(); ++it) {
           std::cout<<(*it).c_str()<< std::endl;
       }
       std::cout<<"##### END #####"<< std::endl;

       TChain* treeHLT   = new TChain("hltanalysis/HltTree");
       TChain* treeggHiNtuplizer  = new TChain("ggHiNtuplizer/EventTree");
       TChain* treeHiEvt = new TChain("hiEvtAnalyzer/HiTree");
       TChain* treeSkim  = new TChain("skimanalysis/HltTree");
       TChain* treeHiForestInfo = new TChain("HiForest/HiForestInfo");

       for (std::vector<std::string>::iterator it = inputFiles.begin() ; it != inputFiles.end(); ++it) {
          treeHLT->Add((*it).c_str());
          treeggHiNtuplizer->Add((*it).c_str());
          treeHiEvt->Add((*it).c_str());
          treeSkim->Add((*it).c_str());
          treeHiForestInfo->Add((*it).c_str());
       }

       HiForestInfoController hfic(treeHiForestInfo);
       std::cout<<"### HiForestInfo Tree ###"<< std::endl;
       hfic.printHiForestInfo();
       std::cout<<"###"<< std::endl;

       treeHLT->SetBranchStatus("*",0);     // disable all branches
       treeHLT->SetBranchStatus("HLT_HI*SinglePhoton*Eta*",1);     // enable photon branches
       treeHLT->SetBranchStatus("HLT_HI*DoublePhoton*Eta*",1);     // enable photon branches
       treeHLT->SetBranchStatus("*DoubleMu*",1);                      // enable muon branches
       treeHLT->SetBranchStatus("HLT_HIL1Mu*",1);                     // enable muon branches
       treeHLT->SetBranchStatus("HLT_HIL2Mu*",1);                     // enable muon branches
       treeHLT->SetBranchStatus("HLT_HIL3Mu*",1);                     // enable muon branches
       
       // specify explicitly which branches to store, do not use wildcard
       treeHiEvt->SetBranchStatus("*",1);

       // specify explicitly which branches to store, do not use wildcard
       treeSkim->SetBranchStatus("*",0);

       Int_t pcollisionEventSelection;  // this filter is used for HI.
       if (isHI) {
           treeSkim->SetBranchStatus("pcollisionEventSelection",1);
           if (treeSkim->GetBranch("pcollisionEventSelection")) {
               treeSkim->SetBranchAddress("pcollisionEventSelection",&pcollisionEventSelection);
           }
           else {   // overwrite to default
               pcollisionEventSelection = 1;
               std::cout<<"could not get branch : pcollisionEventSelection"<<std::endl;
               std::cout<<"set to default value : pcollisionEventSelection = "<<pcollisionEventSelection<<std::endl;
           }
       }
       else {
           pcollisionEventSelection = 0;    // default value if the collision is not HI, will not be used anyway.
       }
       Int_t pPAprimaryVertexFilter;    // this filter is used for PP.
       if (isPP) {
           treeSkim->SetBranchStatus("pPAprimaryVertexFilter",1);
           if (treeSkim->GetBranch("pPAprimaryVertexFilter")) {
               treeSkim->SetBranchAddress("pPAprimaryVertexFilter",&pPAprimaryVertexFilter);
           }
           else {   // overwrite to default
               pPAprimaryVertexFilter = 1;
               std::cout<<"could not get branch : pPAprimaryVertexFilter"<<std::endl;
               std::cout<<"set to default value : pPAprimaryVertexFilter = "<<pPAprimaryVertexFilter<<std::endl;
           }
       }
       else {
           pPAprimaryVertexFilter = 0;      // default value if the collision is not PP, will not be used anyway.
       }
       Int_t pBeamScrapingFilter;   // this filter is used for PP.
       if (isPP) {
           treeSkim->SetBranchStatus("pBeamScrapingFilter",1);
           if (treeSkim->GetBranch("pBeamScrapingFilter")) {
               treeSkim->SetBranchAddress("pBeamScrapingFilter",&pBeamScrapingFilter);
           }
           else {   // overwrite to default
               pBeamScrapingFilter = 1;
               std::cout<<"could not get branch : pBeamScrapingFilter"<<std::endl;
               std::cout<<"set to default value : pBeamScrapingFilter = "<<pBeamScrapingFilter<<std::endl;
           }
       }
       else {
           pBeamScrapingFilter = 0;     // default value if the collision is not PP, will not be used anyway.
       }

       ggHiNtuplizer ggHi;
       ggHi.setupTreeForReading(treeggHiNtuplizer);
       
       hiEvt hiEvt;
       hiEvt.setupTreeForReading(treeHiEvt);

       TFile* output = new TFile(outputFile,"RECREATE");
       TTree *configTree = setupConfigurationTreeForWriting(configCuts);

       // output tree variables
       TTree *outputTreeHLT           = treeHLT->CloneTree(0);
       outputTreeHLT->SetName("hltTree");
       outputTreeHLT->SetTitle("subbranches of hltanalysis/HltTree");
       TTree *outputTreeggHiNtuplizer = treeggHiNtuplizer->CloneTree(0);
       TTree *outputTreeHiEvt = treeHiEvt->CloneTree(0);
       outputTreeHiEvt->SetName("HiEvt");
       outputTreeHiEvt->SetTitle("subbranches of hiEvtAnalyzer/HiTree");
       TTree* outputTreeSkim  = treeSkim->CloneTree(0);
       outputTreeSkim->SetName("skim");
       outputTreeSkim->SetTitle("subbranches of skimanalysis/HltTree");
       TTree* outputTreeHiForestInfo = treeHiForestInfo->CloneTree(0);
       outputTreeHiForestInfo->SetName("HiForestInfo");
       outputTreeHiForestInfo->SetTitle("first entry of HiForest/HiForestInfo");
       
       outputTreeHLT->SetMaxTreeSize(MAXTREESIZE);
       outputTreeggHiNtuplizer->SetMaxTreeSize(MAXTREESIZE);
       outputTreeHiEvt->SetMaxTreeSize(MAXTREESIZE);
       outputTreeHiForestInfo->SetMaxTreeSize(MAXTREESIZE);

       // write HiForestInfo
       treeHiForestInfo->GetEntry(0);
       outputTreeHiForestInfo->Fill();

       TTree *diMuonTree = new TTree("dimuon","muon pairs");
       diMuonTree->SetMaxTreeSize(MAXTREESIZE);

       dimuon diMu;
       diMu.branchDiMuonTree(diMuonTree);

       EventMatcher* em = new EventMatcher();
       Long64_t duplicateEntries = 0;

       Long64_t entries = treeggHiNtuplizer->GetEntries();
       Long64_t entriesPassedEventSelection = 0;
       Long64_t entriesAnalyzed = 0;
       std::cout << "entries = " << entries << std::endl;
       std::cout<< "Loop : " << treePath.c_str() <<std::endl;
       for (Long64_t j_entry=0; j_entry<entries; ++j_entry)
       {
           if (j_entry % 20000 == 0)  {
             std::cout << "current entry = " <<j_entry<<" out of "<<entries<<" : "<<std::setprecision(2)<<(double)j_entry/entries*100<<" %"<<std::endl;
           }

           treeHLT->GetEntry(j_entry);
           treeggHiNtuplizer->GetEntry(j_entry);
           treeHiEvt->GetEntry(j_entry);
           treeSkim->GetEntry(j_entry);

           bool eventAdded = em->addEvent(ggHi.run,ggHi.lumis,ggHi.event,j_entry);
           if(!eventAdded) // this event is duplicate, skip this one.
           {
               duplicateEntries++;
               continue;
           }

           // event selection
           if (!(TMath::Abs(hiEvt.vz) < cut_vz))  continue;
           if (isHI) {
               if ((pcollisionEventSelection < cut_pcollisionEventSelection))  continue;
           }
           else {
               if (pPAprimaryVertexFilter < cut_pPAprimaryVertexFilter || pBeamScrapingFilter < cut_pBeamScrapingFilter)  continue;
           }
           entriesPassedEventSelection++;

           // skip if there are no muon pairs to study
           if(ggHi.nMu < cut_nMu)  continue;
           entriesAnalyzed++;

           diMu.makeDiMuonPairs(ggHi);

           outputTreeHLT->Fill();
           outputTreeggHiNtuplizer->Fill();
           outputTreeHiEvt->Fill();
           outputTreeSkim->Fill();
           diMuonTree->Fill();
       }
       std::cout<< "Loop ENDED : " << treePath.c_str() <<std::endl;
       std::cout << "entries            = " << entries << std::endl;
       std::cout << "duplicateEntries   = " << duplicateEntries << std::endl;
       std::cout << "entriesPassedEventSelection   = " << entriesPassedEventSelection << std::endl;
       std::cout << "entriesAnalyzed    = " << entriesAnalyzed << std::endl;
       std::cout << "outputTreeHLT->GetEntries()           = " << outputTreeHLT->GetEntries() << std::endl;
       std::cout << "outputTreeggHiNtuplizer->GetEntries() = " << outputTreeggHiNtuplizer->GetEntries() << std::endl;
       std::cout << "outputTreeHiEvt->GetEntries() = " << outputTreeHiEvt->GetEntries() << std::endl;
       std::cout << "outputTreeSkim->GetEntries()  = " << outputTreeSkim->GetEntries() << std::endl;
       std::cout << "diMuonTree->GetEntries()          = " << diMuonTree->GetEntries() << std::endl;

       // overwrite existing trees
       outputTreeHLT->Write("", TObject::kOverwrite);
       outputTreeggHiNtuplizer->Write("", TObject::kOverwrite);
       outputTreeHiEvt->Write("", TObject::kOverwrite);
       diMuonTree->Write("", TObject::kOverwrite);

       configTree->Write("", TObject::kOverwrite);

       output->Write("", TObject::kOverwrite);
       output->Close();

       std::cout<<"dimuonSkim() - END"   <<std::endl;
}
void quickPhotonPurity_yj_multiTreeUtil(const TString configFile, const TString inputData, const TString inputMC, const TString outputName, const TString coll="pbpb")
{
  TH1::SetDefaultSumw2();
  CutConfiguration config = CutConfigurationParser::Parse(configFile.Data());
  TTree *configTree = setupConfigurationTreeForWriting(config);
   const char* photreeSt="";
   const char* hitreeSt="";
  if(coll=="pbpb") {
      photreeSt="EventTree";
      hitreeSt="HiTree";
  } else {
      photreeSt="ggHiNtuplizer/EventTree";
      hitreeSt="hiEvtAnalyzer/HiTree";
  } 

  TFile *dataFile = TFile::Open(pbpbDatafname);
  //TFile *dataFile = TFile::Open(inputData);
  TTree *dataTree = (TTree*)dataFile->Get(photreeSt);
  TTree *dataEvtTree = (TTree*)dataFile->Get(hitreeSt);
//  TTree *dataTree = (TTree*)dataFile->Get("ggHiNtuplizer/EventTree");
//  TTree *dataEvtTree = (TTree*)dataFile->Get("hiEvtAnalyzer/HiTree");
  //TTree *dataTree = (TTree*)dataFile->Get("photonSkimTree");

  dataTree->AddFriend(dataEvtTree);
  TFile *mcFile = TFile::Open(pbpbMCfname);
  //TFile *mcFile = TFile::Open(inputMC);
  TTree *mcTree = (TTree*)mcFile->Get("ggHiNtuplizer/EventTree");
  TTree *mcEvtTree = (TTree*)mcFile->Get("hiEvtAnalyzer/HiTree");
  //TTree *mcTree = (TTree*)mcFile->Get("photonSkimTree");
    mcTree->AddFriend(mcEvtTree);
  TFile *outFile = new TFile(outputName,"RECREATE");

  const TCut sidebandIsolation = "((pho_ecalClusterIsoR4 + pho_hcalRechitIsoR4 + pho_trackIsoR4PtCut20)>10) && ((pho_ecalClusterIsoR4 + pho_hcalRechitIsoR4 + pho_trackIsoR4PtCut20)<20) && phoHoverE<0.1";
  const TCut mcIsolation = "(pho_genMatchedIndex!= -1) && mcCalIsoDR04[pho_genMatchedIndex]<5 && abs(mcPID[pho_genMatchedIndex])<=22";
    cout << "JJ" << endl;
  //TCanvas *cPurity[nPTBINS];
  //TCanvas *cPurity = new TCanvas("c1","c1",337*nPTBINS,300*nCENTBINS/**2*/);
  TCanvas *cPurity = new TCanvas("c1","c1",400*nPTBINS,400*nCENTBINS);
  //cPurity->Divide(nPTBINS,2*nCENTBINS,0,0);
  //cPurity->Divide(nPTBINS,nCENTBINS,0,0);
  makeMultiPanelCanvas(cPurity, nPTBINS, nCENTBINS, 0.0, 0.0 , 0.2, 0.15, 0.005);
  cout << "nPTBINS = " << nPTBINS << ", nCENTBINS = " << nCENTBINS << ", nETABINS = " << nETABINS << endl;
  for(Int_t i = 0; i < nPTBINS; ++i) {
    cout << "i : " << i << endl;
    //cPurity[i] = new TCanvas(Form("c1_%d",i),"",1920,1000);
    //cPurity[i]->Divide(nETABINS,2,0,0);
    for(Int_t j = 0; j < nCENTBINS; ++j) {
    cout << "j : " << j << endl;
      for(Int_t k = 0; k< nETABINS; ++k) {
    cout << "k : " << k << endl;
	TString ptCut = Form("(phoEt >= %f) && (phoEt < %f)",
			     PTBINS[i], PTBINS[i+1]);
	TString centCut = Form("((hiBin) >= %i) && ((hiBin) < %i)",
			     CENTBINS[j], CENTBINS[j+1]);
	TString etaCut = Form("(phoEta >= %f) && (phoEta < %f)",
			      ETABINS[k], ETABINS[k+1]);

	//TString pPbflipetaCut = Form("(eta*((run>211257)*-1+(run<211257)) >=%f) && (eta*((run>211257)*-1+(run<211257)) <%f)",
	//			     ETABINS[k], ETABINS[k+1]);

	TCut dataCandidateCut = sampleIsolation && etaCut && ptCut && centCut;
	TCut sidebandCut =  sidebandIsolation && etaCut && ptCut && centCut;
	TCut mcSignalCut = dataCandidateCut && mcIsolation;

	// if(nETABINS != 1)
	// {
	//   dataCandidateCut = sampleIsolation && pPbflipetaCut && ptCut && centCut;
	//   sidebandCut =  sidebandIsolation && pPbflipetaCut && ptCut && centCut;
	//   mcSignalCut =  sampleIsolation && etaCut && ptCut && centCut && mcIsolation;
	// }

	PhotonPurity fitr = getPurity(config, dataTree, mcTree,
				      dataCandidateCut, sidebandCut,
				      mcSignalCut);

	//cPurity[i*nCENTBINS+j] = new TCanvas(Form("cpurity%d",i*nCENTBINS+j),
	// 					 "",500,500);
    cout << "centBin = " << centCut << ", ptBin : " << ptCut << ",,,, canvas # : "<<2*(k+j)*nPTBINS+i+1 << endl;
    cout << "k = " << k << ", j = " << j << ", i = " << i << endl;
    //cPurity->cd(2*(k+j)*nPTBINS+i+1);
	cPurity->cd((k+j)*nPTBINS+i+1);
	//cPurity[i]->cd(k+1);

	TH1F *hSigPdf = fitr.sigPdf;
	TH1F *hBckPdf = fitr.bckPdf;
	TH1D *hData1  = fitr.data;
	hSigPdf->Add(hBckPdf);

	TString name = "mcfit_total_ptbin";
	name += i;

	// outFile->cd();
	// hSigPdf->SetName(name);
	// hSigPdf->Write();


	// TH1D *err = (TH1D*)hSigPdf->Clone("error");
	// TH1D *tempErr[4];
	// err->Reset();
	// for(int s = 0; s < 4; s++)
	// {
	//   if(s == 0)
	//     tempErr[s] = (TH1D*)TFile::Open("photonPurity_sys_loose.root")->Get(name);
	//   else if(s ==1)
	//     tempErr[s] = (TH1D*)TFile::Open("photonPurity_sys_tight.root")->Get(name);
	//   else if(s ==2)
	//     tempErr[s] = (TH1D*)TFile::Open("photonPurity_sys_sigshift.root")->Get(name);
	//   else if(s ==3)
	//     tempErr[s] = (TH1D*)TFile::Open("photonPurity_sys_bkgshift.root")->Get(name);
	//   tempErr[s]->Divide(hSigPdf);
	//   for (Int_t l=1; l<=tempErr[s]->GetNbinsX();l++)
	//   {
	//     tempErr[s]->SetBinContent(l, TMath::Abs(tempErr[s]->GetBinContent(l))-1);
	//   }
	// }
	// for (Int_t l=1; l<=err->GetNbinsX();l++)
	// {
	//   Double_t errVal = TMath::Sqrt(tempErr[0]->GetBinContent(l)*tempErr[0]->GetBinContent(l) +
	// 				tempErr[1]->GetBinContent(l)*tempErr[1]->GetBinContent(l) +
	// 				tempErr[2]->GetBinContent(l)*tempErr[2]->GetBinContent(l) +
	// 				tempErr[3]->GetBinContent(l)*tempErr[3]->GetBinContent(l)
	//     );
	//   err->SetBinContent(l, errVal);
	// }

	// plot stacked histos
	handsomeTH1(hSigPdf);
	mcStyle(hSigPdf);
	sbStyle(hBckPdf);
	cleverRange(hSigPdf,1.5);
	hSigPdf->SetAxisRange(0.001,0.024,"X");
	hSigPdf->SetNdivisions(505);
	hSigPdf->GetYaxis()->SetTitleOffset(1.75);
	hSigPdf->SetYTitle("Entries");
	hSigPdf->SetXTitle("#sigma_{#eta #eta}");

	hSigPdf->DrawCopy("hist");
	//drawSys(hSigPdf, err, kRed, -1, 0.001);
	hBckPdf->DrawCopy("same hist");
	hData1->DrawCopy("same");

	Float_t xpos = 0.44;
	if(2*(k+j)*nPTBINS+i+1 == 1)
	  xpos = 0.54;

	TLegend *t3=new TLegend(xpos, 0.45, 0.92, 0.71);
	t3->AddEntry(hData1,LABEL,"pl");
	t3->AddEntry(hSigPdf,"Signal","lf");
	t3->AddEntry(hBckPdf,"Background","lf");
	t3->SetFillColor(0);
	t3->SetBorderSize(0);
	t3->SetFillStyle(0);
	t3->SetTextFont(43);
	t3->SetTextSize(20);
	//if(i == 0)
	// TH1D *dummyHist = new TH1D("dummyHist","",10,0,10);
	// dummyHist->Fill(1);
	// dummyHist->SetFillColor(kRed);
	// dummyHist->SetLineColor(kRed);
	// dummyHist->SetFillStyle(1001);
	// t3->AddEntry(dummyHist,"MC Sys. Error","f");
	// if(i == 0)
	//   t3->Draw();

	if(i == 3)
	{
	  drawText("CMS Preliminary", xpos, 0.68,1,20);
	  drawText("PbPb #sqrt{s}_{_{NN}}=5.02 TeV", xpos, 0.60,1,20);
	  drawText("#intL = 404 #ub^{-1}", xpos, 0.50,1,20);
	}



	//drawText("|#eta_{#gamma}| < 1.479",0.5680963,0.9);
	//drawText(Form("%f shift",fitr.sigMeanShift),0.57,0.82);
	//drawText("Background Correction",0.57,0.82);
	//drawText("bkg Tighter",0.57,0.82);
	//if(nPTBINS != 1)
	drawText(Form("%.0f GeV < p_{T}^{#gamma} < %.0f GeV",
		      PTBINS[i], PTBINS[i+1]),
		 xpos, 0.90,1,20);
	// if(/*nCENTBINS != 1 && */i ==0)
	drawText(Form("%.0f - %.0f%c",
		      CENTBINS[j]/2., CENTBINS[j+1]/2.,'%'),
		 xpos, 0.82,1,20);
	// if(nETABINS != 1)
	//   drawText(Form("%.3f < #eta_{#gamma} < %.3f",
	// 		ETABINS[k], ETABINS[k+1]),
	// 	   xpos, 0.82,1,20);
	drawText(Form("Purity (#sigma_{#eta#eta} < 0.01) : %.2f", (Float_t)fitr.purity),
		 xpos, 0.76,1,20);
	drawText(Form("#chi^{2}/ndf : %.2f", (Float_t)fitr.chisq),
		 xpos, 0.45,1,20);


	// //plot ratio
	// cPurity->cd((2*(j+k)+1)*nPTBINS+i+1);
	// //cPurity[i]->cd(nETABINS + k+ 1);
	// TH1D* ratio = (TH1D*)hData1->Clone("ratio");
	// ratio->Divide(hData1, hSigPdf, 1, 1);
	// ratio->SetMinimum(0);
	// ratio->SetMaximum(3);
	// ratio->SetXTitle("#sigma_{#eta #eta}");
	// ratio->GetXaxis()->CenterTitle();
	// ratio->SetYTitle("Data/Fit");
	// ratio->GetYaxis()->CenterTitle();
	// ratio->DrawCopy("E");
	// TLine *line = new TLine(0,1,maxSIGMA,1);
	// line->SetLineStyle(2);
	// line->Draw("same");

	// TString savename = Form("purity_pA_barrel_pt%.0f_hf%.0f_plot",
	// 			PTBINS[i], CENTBINS[j]);
	// cPurity[i*nCENTBINS+j]->SaveAs(savename+".C");
	// cPurity[i*nCENTBINS+j]->SaveAs(savename+".pdf");
	// cPurity[i*nCENTBINS+j]->SaveAs(savename+".png");

      }
    }
    //cPurity[i]->SaveAs(Form("pPb_purity_etadep_wshift_ptbin%.0f.png",PTBINS[i]));
    //cPurity[i]->SaveAs(Form("pPb_purity_etadep_noshift_inclusive.png"));
  }
  outFile->cd();
  configTree->Write();
  cPurity->Write();
  outFile->Close();
  //cPurity->SaveAs(SAVENAME+".C");
  //cPurity->SaveAs(SAVENAME+".png");
  //cPurity->SaveAs(SAVENAME+".pdf");
}
void minBiasJetSkim(const TString configFile, const TString inputFile, const TString outputFile)
{
       std::cout<<"running minBiasJetSkim()"<<std::endl;
       std::cout<<"configFile  = "<< configFile.Data() <<std::endl;
       std::cout<<"inputFile   = "<< inputFile.Data() <<std::endl;
       std::cout<<"outputFile  = "<< outputFile.Data() <<std::endl;

       InputConfiguration configInput = InputConfigurationParser::Parse(configFile.Data());
       CutConfiguration configCuts = CutConfigurationParser::Parse(configFile.Data());

       if (!configInput.isValid) {
           std::cout << "Input configuration is invalid." << std::endl;
           std::cout << "exiting" << std::endl;
           return;
       }
       if (!configCuts.isValid) {
           std::cout << "Cut configuration is invalid." << std::endl;
           std::cout << "exiting" << std::endl;
           return;
       }

       // cut configuration
       float cut_vz = configCuts.proc[CUTS::kSKIM].obj[CUTS::kEVENT].f[CUTS::EVT::k_vz];
       int cut_pcollisionEventSelection = configCuts.proc[CUTS::kSKIM].obj[CUTS::kEVENT].i[CUTS::EVT::k_pcollisionEventSelection];

       std::vector<std::string> jetCollections = ConfigurationParser::ParseList(configCuts.proc[CUTS::kSKIM].obj[CUTS::kJET].s[CUTS::JET::k_jetCollection]);
       int nMaxEvents_minBiasMixing = configCuts.proc[CUTS::kSKIM].obj[CUTS::kGAMMAJET].i[CUTS::GJT::k_nMaxEvents_minBiasMixing];
       int nCentralityBins = configCuts.proc[CUTS::kSKIM].obj[CUTS::kGAMMAJET].i[CUTS::GJT::k_nCentralityBins];
       int nVertexBins = configCuts.proc[CUTS::kSKIM].obj[CUTS::kGAMMAJET].i[CUTS::GJT::k_nVertexBins];
       int nEventPlaneBins = configCuts.proc[CUTS::kSKIM].obj[CUTS::kGAMMAJET].i[CUTS::GJT::k_nEventPlaneBins];

       int nJetCollections = jetCollections.size();

       // verbose about cut configuration
       std::cout<<"Cut Configuration :"<<std::endl;
       std::cout<<"cut_vz = "<< cut_vz <<std::endl;
       std::cout<<"cut_pcollisionEventSelection = "<< cut_pcollisionEventSelection <<std::endl;

       std::cout<<"nJetCollections = "<< nJetCollections <<std::endl;
       for (int i=0; i<nJetCollections; ++i) {
           std::cout << Form("jetCollections[%d] = %s", i, jetCollections.at(i).c_str()) << std::endl;
       }
       std::cout<<"nMaxEvents_minBiasMixing = "<< nMaxEvents_minBiasMixing <<std::endl;
       std::cout<<"nCentralityBins          = "<< nCentralityBins <<std::endl;
       std::cout<<"nVertexBins              = "<< nVertexBins <<std::endl;
       std::cout<<"nEventPlaneBins          = "<< nEventPlaneBins <<std::endl;

       std::vector<std::string> inputFiles = InputConfigurationParser::ParseFiles(inputFile.Data());

       std::cout<<"input ROOT files : num = "<<inputFiles.size()<< std::endl;
       std::cout<<"#####"<< std::endl;
       for (std::vector<std::string>::iterator it = inputFiles.begin() ; it != inputFiles.end(); ++it) {
           std::cout<<(*it).c_str()<< std::endl;
       }
       std::cout<<"##### END #####"<< std::endl;

       TChain* treeHLT   = new TChain("hltanalysis/HltTree");
       TChain* treeEvent = new TChain("ggHiNtuplizer/EventTree");
       TChain* treeJets[nJetCollections];
       for (int i=0; i<nJetCollections; ++i) {
           treeJets[i] = new TChain(Form("%s/t", jetCollections.at(i).c_str()));
       }
       TChain* treeHiEvt = new TChain("hiEvtAnalyzer/HiTree");
       TChain* treeSkim  = new TChain("skimanalysis/HltTree");
       TChain* treeHiForestInfo = new TChain("HiForest/HiForestInfo");

       for (std::vector<std::string>::iterator it = inputFiles.begin() ; it != inputFiles.end(); ++it) {
          treeHLT->Add((*it).c_str());
          treeEvent->Add((*it).c_str());
          for (int i=0; i<nJetCollections; ++i) {
              treeJets[i]->Add((*it).c_str());
          }
          treeHiEvt->Add((*it).c_str());
          treeSkim->Add((*it).c_str());
          if (it == inputFiles.begin())  treeHiForestInfo->Add((*it).c_str());
       }

       HiForestInfoController hfic(treeHiForestInfo);
       std::cout<<"### HiForestInfo Tree ###"<< std::endl;
       hfic.printHiForestInfo();
       std::cout<<"###"<< std::endl;

       treeHLT->SetBranchStatus("*",0);     // disable all branches

       treeEvent->SetBranchStatus("*",0);
       treeEvent->SetBranchStatus("run",1);
       treeEvent->SetBranchStatus("event",1);
       treeEvent->SetBranchStatus("lumis",1);

       for (int i=0; i<nJetCollections; ++i) {
           treeJets[i]->SetBranchStatus("*",0);        // disable all branches
           treeJets[i]->SetBranchStatus("nref",1);     // enable jet branches
           treeJets[i]->SetBranchStatus("rawpt",1);    // enable jet branches
           treeJets[i]->SetBranchStatus("jtpt",1);     // enable jet branches
           treeJets[i]->SetBranchStatus("jteta",1);    // enable jet branches
           treeJets[i]->SetBranchStatus("jtphi",1);    // enable jet branches
           treeJets[i]->SetBranchStatus("jty",1);      // enable jet branches
           treeJets[i]->SetBranchStatus("jtpu",1);     // enable jet branches
           treeJets[i]->SetBranchStatus("jtm",1);      // enable jet branches
           treeJets[i]->SetBranchStatus("track*",1);
           treeJets[i]->SetBranchStatus("charged*",1);
           treeJets[i]->SetBranchStatus("photon*",1);
           treeJets[i]->SetBranchStatus("neutral*",1);
           treeJets[i]->SetBranchStatus("eMax*",1);
           treeJets[i]->SetBranchStatus("eSum*",1);
           treeJets[i]->SetBranchStatus("eN*",1);
           treeJets[i]->SetBranchStatus("muMax*",1);
           treeJets[i]->SetBranchStatus("muSum*",1);
           treeJets[i]->SetBranchStatus("muN*",1);
       }

       // specify explicitly which branches to store, do not use wildcard
       treeHiEvt->SetBranchStatus("*",0);
       treeHiEvt->SetBranchStatus("run",1);
       treeHiEvt->SetBranchStatus("evt",1);
       treeHiEvt->SetBranchStatus("lumi",1);
       treeHiEvt->SetBranchStatus("vz",1);
       treeHiEvt->SetBranchStatus("hiBin",1);
       treeHiEvt->SetBranchStatus("hiHF",1);
       treeHiEvt->SetBranchStatus("hiHFplus",1);
       treeHiEvt->SetBranchStatus("hiHFminus",1);
       treeHiEvt->SetBranchStatus("hiHFplusEta4",1);
       treeHiEvt->SetBranchStatus("hiHFminusEta4",1);
      //  treeHiEvt->SetBranchStatus("hiNevtPlane",1);

       float vz;
       Int_t hiBin;
       Float_t         hiEvtPlanes[29];   //[hiNevtPlane]
       treeHiEvt->SetBranchAddress("hiEvtPlanes",&hiEvtPlanes);

       treeHiEvt->SetBranchAddress("vz",&vz);
       treeHiEvt->SetBranchAddress("hiBin",&hiBin);

       // specify explicitly which branches to store, do not use wildcard
       treeSkim->SetBranchStatus("*",0);
       treeSkim->SetBranchStatus("ana_step",1);
       treeSkim->SetBranchStatus("pcollisionEventSelection",1);
       treeSkim->SetBranchStatus("pHBHENoiseFilterResultProducer",1);
       treeSkim->SetBranchStatus("HBHENoiseFilterResultRun1",1);
       treeSkim->SetBranchStatus("HBHENoiseFilterResultRun2Loose",1);
       treeSkim->SetBranchStatus("HBHENoiseFilterResultRun2Tight",1);
       treeSkim->SetBranchStatus("HBHENoiseFilterResult",1);
       treeSkim->SetBranchStatus("HBHEIsoNoiseFilterResult",1);

       Int_t pcollisionEventSelection;

       if (treeSkim->GetBranch("pcollisionEventSelection")) {
           treeSkim->SetBranchAddress("pcollisionEventSelection",&pcollisionEventSelection);
       }
       else {   // overwrite to default
           pcollisionEventSelection = 1;
           std::cout<<"could not get branch : pcollisionEventSelection"<<std::endl;
           std::cout<<"set to default value : pcollisionEventSelection = "<<pcollisionEventSelection<<std::endl;
       }

       // event information
       UInt_t run, lumis;
       ULong64_t event;
       treeEvent->SetBranchAddress("run", &run);
       treeEvent->SetBranchAddress("event", &event);
       treeEvent->SetBranchAddress("lumis", &lumis);

       TFile* output = new TFile(outputFile,"RECREATE");

       TTree *configTree = setupConfigurationTreeForWriting(configCuts);

       int centBinWidth = 200/nCentralityBins;  // number of "hiBin"s that a centrality bin covers
       int vertexBinWidth = 30/nVertexBins;     // number of "vz"s    that a vertex     bin covers
       float eventPlaneBinWidth = TMath::Pi()/nEventPlaneBins;     // number of "vz"s    that a vertex     bin covers
                                                // accepted vz range is -15 to 15.

       TTree *outputTreesHLT[nCentralityBins][nVertexBins][nEventPlaneBins];
       TTree *outputTreesEvent[nCentralityBins][nVertexBins][nEventPlaneBins];
       TTree *outputTreesJet[nCentralityBins][nVertexBins][nEventPlaneBins][nJetCollections];
       TTree *outputTreesHiEvt[nCentralityBins][nVertexBins][nEventPlaneBins];
       TTree *outputTreesSkim[nCentralityBins][nVertexBins][nEventPlaneBins];

       Long64_t nEntriesFilled[nCentralityBins][nVertexBins][nEventPlaneBins];    // number of events read for a centrality bin
       for(int i=0; i<nCentralityBins; ++i)
       {
           for(int j=0; j<nVertexBins; ++j){
             for(int l=0; l<nEventPlaneBins; ++l) {

               nEntriesFilled[i][j][l] = 0;

               outputTreesHLT[i][j][l] = treeHLT->CloneTree(0);
               outputTreesHLT[i][j][l]->SetName(Form("hltTree_centBin%d_vzBin%d_evPlaneBin%d", i, j, l));
               outputTreesHLT[i][j][l]->SetTitle(Form("subbranches of hltanalysis/HltTree for centrality bin %d vz bin %d evPlane Bin %d", i, j, l));
               outputTreesHLT[i][j][l]->SetMaxTreeSize(MAXTREESIZE);

               outputTreesEvent[i][j][l] = treeEvent->CloneTree(0);
               outputTreesEvent[i][j][l]->SetName(Form("EventTree_centBin%d_vzBin%d_evPlaneBin%d", i, j, l));
               outputTreesEvent[i][j][l]->SetTitle(Form("subbranches of ggHiNtuplizer/EventTree for centrality bin %d vz bin %d evPlane Bin %d", i, j, l));
               outputTreesEvent[i][j][l]->SetMaxTreeSize(MAXTREESIZE);

               for(int k=0; k<nJetCollections; ++k){
                   std::string jetCollectionName = jetCollections.at(k).c_str();
                   outputTreesJet[i][j][l][k] = treeJets[k]->CloneTree(0);
                   outputTreesJet[i][j][l][k]->SetName(Form("%s_centBin%d_vzBin%d_evPlaneBin%d", jetCollectionName.c_str(), i, j, l));
                   outputTreesJet[i][j][l][k]->SetTitle(Form("subbranches of %s/t for centrality bin %d vz bin %d evPlane Bin %d", jetCollectionName.c_str(), i, j, l));
                   outputTreesJet[i][j][l][k]->SetMaxTreeSize(MAXTREESIZE);
               }

               outputTreesHiEvt[i][j][l] = treeHiEvt->CloneTree(0);
               outputTreesHiEvt[i][j][l]->SetName(Form("HiEvt_centBin%d_vzBin%d_evPlaneBin%d", i, j, l));
               outputTreesHiEvt[i][j][l]->SetTitle(Form("subbranches of hiEvtAnalyzer/HiTree for centrality bin %d vz bin %d evPlane Bin %d", i, j, l));
               outputTreesHiEvt[i][j][l]->SetMaxTreeSize(MAXTREESIZE);

               outputTreesSkim[i][j][l] = treeSkim->CloneTree(0);
               outputTreesSkim[i][j][l]->SetName(Form("skim_centBin%d_vzBin%d_evPlaneBin%d", i, j, l));
               outputTreesSkim[i][j][l]->SetTitle(Form("subbranches of skimanalysis/HltTree for centrality bin %d vz bin %d evPlane Bin %d", i, j, l));
               outputTreesSkim[i][j][l]->SetMaxTreeSize(MAXTREESIZE);
             }
           }
       }
//       TTree* outputTreeHiForestInfo = treeHiForestInfo->CloneTree(0);
//       outputTreeHiForestInfo->SetName("HiForestInfo");
//       outputTreeHiForestInfo->SetTitle("first entry of HiForest/HiForestInfo");
//
//       outputTreeHiForestInfo->SetMaxTreeSize(MAXTREESIZE);
//
//       // write HiForestInfo
//       treeHiForestInfo->GetEntry(0);
//       outputTreeHiForestInfo->Fill();

       EventMatcher* em = new EventMatcher();
       Long64_t duplicateEntries = 0;

       Long64_t entries = treeEvent->GetEntries();
       Long64_t entriesAnalyzed = 0;
       std::cout << "entries         = " << entries << std::endl;
       std::cout<< "Loop : ggHiNtuplizer/EventTree" <<std::endl;
       for (Long64_t j_entry=0; j_entry<entries; ++j_entry)
       {
           if (j_entry % 20000 == 0)  {
             std::cout << "current entry = " <<j_entry<<" out of "<<entries<<" : "<<std::setprecision(2)<<(double)j_entry/entries*100<<" %"<<std::endl;
           }

           treeHLT->GetEntry(j_entry);
           treeEvent->GetEntry(j_entry);
           for (int i = 0; i<nJetCollections; ++i) {
               treeJets[i]->GetEntry(j_entry);
           }
           treeHiEvt->GetEntry(j_entry);
           treeSkim->GetEntry(j_entry);

           bool eventAdded = em->addEvent(run,lumis,event,j_entry);
           if(!eventAdded) // this event is duplicate, skip this one.
           {
               duplicateEntries++;
               continue;
           }

           // event selection
           if(!(TMath::Abs(vz) < cut_vz)) continue;
           if(pcollisionEventSelection < cut_pcollisionEventSelection) continue;
           entriesAnalyzed++;

           int centBin = hiBin / centBinWidth;
           int vzBin   = (vz+15) / vertexBinWidth;
           int evplaneBin = (hiEvtPlanes[8]+(TMath::Pi()/2.)) / eventPlaneBinWidth;
          //  std::cout<<evplaneBin<< " bin " << eventPlaneBinWidth << " value " << hiEvtPlanes[8]+(TMath::Pi()/2.) << std::endl;
           if (nEntriesFilled[centBin][vzBin][evplaneBin] > nMaxEvents_minBiasMixing) continue;

           outputTreesHLT[centBin][vzBin][evplaneBin]->Fill();
           outputTreesEvent[centBin][vzBin][evplaneBin]->Fill();
           for (int i = 0; i<nJetCollections; ++i) {
               outputTreesJet[centBin][vzBin][evplaneBin][i]->Fill();
           }
           outputTreesHiEvt[centBin][vzBin][evplaneBin]->Fill();
           outputTreesSkim[centBin][vzBin][evplaneBin]->Fill();

           nEntriesFilled[centBin][vzBin][evplaneBin]++;
       }
       std::cout<<  "Loop ENDED : ggHiNtuplizer/EventTree" <<std::endl;
       std::cout << "entries            = " << entries << std::endl;
       std::cout << "duplicateEntries   = " << duplicateEntries << std::endl;
       std::cout << "entriesAnalyzed    = " << entriesAnalyzed << std::endl;

       for(int i=0; i<nCentralityBins; ++i){
           for(int j=0; j<nVertexBins; ++j){
             for(int l=0; l<nEventPlaneBins; ++l){
                 std::cout<<Form("outputTreesJet[%d][%d][%d]->GetEntries() = %lld", i, j, l, outputTreesEvent[i][j][l]->GetEntries())<<std::endl;
                 if (outputTreesEvent[i][j][l]->GetEntries() < 100) {
                     std::cout<< "Be careful : less than 100 events were put into centBin = "<<i<<" , vertexBin = "<<j<<" , EventPlaneBin = "<<l<<std::endl;
                 }
             }
          }
       }

       configTree->Write("",TObject::kOverwrite);

       output->Write("",TObject::kOverwrite);
       output->Close();

       std::cout<<"minBiasJetSkim() - END"<<std::endl;
}