// main161.cc is a part of the PYTHIA event generator.
// Copyright (C) 2026 Torbjorn Sjostrand.
// PYTHIA is licenced under the GNU GPL v2 or later, see COPYING for details.
// Please respect the MCnet Guidelines, see GUIDELINES for details.

// Authors: Stefan Prestel

// Contact: Christian T. Preuss <christian.preuss@uni-goettingen.de>

// Keywords: merging; leading order; jet finding; fastjet; kT

// This program illustrates how to do CKKW-L merging, see the Matrix Element
// Merging page in the online manual. An example command is
//     ./main161 -c main161.cmnd -i w+_production_lhc_0.lhe -o histout161.dat
// where main161.cmnd supplies the commands, w+_production_lhc_0.lhe
// provides the input LHE events, and histout161.dat is the output
// file. This example requires FastJet.

#include "Pythia8/Pythia.h"
#include "Pythia8Plugins/InputParser.h"

using namespace Pythia8;

// Functions for histogramming
#include "fastjet/PseudoJet.hh"
#include "fastjet/ClusterSequence.hh"
#include "fastjet/CDFMidPointPlugin.hh"
#include "fastjet/CDFJetCluPlugin.hh"

//==========================================================================

// Find the Durham kT separation of the clustering from
// nJetMin --> nJetMin-1 jets in te input event

double pTfirstJet( const Event& event, int nJetMin, double Rparam) {

  double yPartonMax = 4.;

  // Fastjet analysis - select algorithm and parameters
  fastjet::Strategy               strategy = fastjet::Best;
  fastjet::RecombinationScheme    recombScheme = fastjet::E_scheme;
  fastjet::JetDefinition         *jetDef = NULL;
  // For hadronic collision, use hadronic Durham kT measure
  if(event[3].colType() != 0 || event[4].colType() != 0)
    jetDef = new fastjet::JetDefinition(fastjet::kt_algorithm, Rparam,
                                      recombScheme, strategy);
  // For e+e- collision, use e+e- Durham kT measure
  else
    jetDef = new fastjet::JetDefinition(fastjet::ee_kt_algorithm,
                                      recombScheme, strategy);
  // Fastjet input
  std::vector <fastjet::PseudoJet> fjInputs;
  // Reset Fastjet input
  fjInputs.resize(0);

  // Loop over event record to decide what to pass to FastJet
  for (int i = 0; i < event.size(); ++i) {
    // (Final state && coloured+photons) only!
    if ( !event[i].isFinal()
      || event[i].isLepton()
      || event[i].id() == 23
      || abs(event[i].id()) == 24
      || abs(event[i].y()) > yPartonMax)
      continue;

    // Store as input to Fastjet
    fjInputs.push_back( fastjet::PseudoJet (event[i].px(),
            event[i].py(), event[i].pz(),event[i].e() ) );
  }

  // Do nothing for empty input
  if (int(fjInputs.size()) == 0) {
    delete jetDef;
    return 0.0;
  }

  // Run Fastjet algorithm
  fastjet::ClusterSequence clustSeq(fjInputs, *jetDef);
  // Extract kT of first clustering
  double pTFirst = sqrt(clustSeq.exclusive_dmerge_max(nJetMin-1));

  delete jetDef;
  // Return kT
  return pTFirst;

}

//==========================================================================

// Class for user interaction with the merging

class MyMergingHooks : public MergingHooks {

private:

public:

  // Default constructor
  MyMergingHooks();
  // Destructor
  ~MyMergingHooks();

  // Functional definition of the merging scale
  virtual double tmsDefinition( const Event& event);

  // Helper function for tms definition
  double myKTdurham(const Particle& RadAfterBranch,
           const Particle& EmtAfterBranch, int Type, double D );

};

//--------------------------------------------------------------------------

// Constructor
MyMergingHooks::MyMergingHooks() {}

// Destructor
MyMergingHooks::~MyMergingHooks() {}

//--------------------------------------------------------------------------

// Definition of the merging scale

double MyMergingHooks::tmsDefinition( const Event& event){

  // Cut only on QCD partons!
  // Count particle types
  int nFinalColoured = 0;
  int nFinalNow =0;
  for( int i=0; i < event.size(); ++i) {
    if(event[i].isFinal()){
      if(event[i].id() != 23 && abs(event[i].id()) != 24)
        nFinalNow++;
      if( event[i].colType() != 0)
        nFinalColoured++;
    }
  }

  // Use MergingHooks in-built functions to get information on the hard process
  int nLeptons = nHardOutLeptons();
  int nQuarks  = nHardOutPartons();
  int nResNow  = nResInCurrent();

  // Check if photons, electrons etc. have been produced. If so, do not veto
  if(nFinalNow - ( (nLeptons+nQuarks)/2 - nResNow)*2 != nFinalColoured){
    // Sometimes, Pythia detaches the decay products even though no
    // resonance was put into the LHE file, to catch this, add another
    // if statement
    if(nFinalNow != nFinalColoured) return 0.;
  }

  // Check that one parton has been produced. If not (e.g. in MPI), do not veto
  int nMPI = infoPtr->nMPI();
  if(nMPI > 1) return 0.;

  // Declare kT algorithm parameters
  double Dparam = 0.4;
  int kTtype = -1;
  // Declare final parton vector
  vector <int> FinalPartPos;
  FinalPartPos.clear();
  // Search event record for final state partons
  for (int i=0; i < event.size(); ++i)
    if(event[i].isFinal() && event[i].colType() != 0)
      FinalPartPos.push_back(i);

  // Find minimal Durham kT in event, using own function: Check
  // definition of separation
  int type = (event[3].colType() == 0 && event[4].colType() == 0) ? 1 : kTtype;
  // Find minimal kT
  double ktmin = event[0].e();
  for(int i=0; i < int(FinalPartPos.size()); ++i){
    double kt12  = ktmin;
    // Compute separation to the beam axis for hadronic collisions
    if(type == -1 || type == -2) {
      double temp = event[FinalPartPos[i]].pT();
      kt12 = min(kt12, temp);
    }
    // Compute separation to other final state jets
    for(int j=i+1; j < int(FinalPartPos.size()); ++j) {
      double temp = kTdurham( event[FinalPartPos[i]], event[FinalPartPos[j]],
                      type, Dparam);
      kt12 = min(kt12, temp);
    }
    // Keep the minimal Durham separation
    ktmin = min(ktmin,kt12);
  }

  // Return minimal Durham kT
  return ktmin;

}

//--------------------------------------------------------------------------

// Function to compute durham y separation from Particle input

double MyMergingHooks::myKTdurham(const Particle& RadAfterBranch,
  const Particle& EmtAfterBranch, int Type, double D ){

  // Declare return variable
  double ktdur;
  // Save 4-momenta of final state particles
  Vec4 jet1 = RadAfterBranch.p();
  Vec4 jet2 = EmtAfterBranch.p();

  if( Type == 1) {
    // Get angle between jets for e+e- collisions, make sure that
    // -1 <= cos(theta) <= 1
    double costh;
    if (jet1.pAbs()*jet2.pAbs() <=0.) costh = 1.;
    else {
      costh = costheta(jet1,jet2);
    }
    // Calculate kt durham separation between jets for e+e- collisions
    ktdur = 2.0*min( pow(jet1.e(),2) , (pow(jet2.e(),2)) )*(1.0 - costh);
  } else if( Type == -1 ){
    // Get delta_eta and cosh(Delta_eta) for hadronic collisions
    double eta1 = 0.5*log( (jet1.e() + jet1.pz()) / (jet1.e() - jet1.pz()) );
    double eta2 = 0.5*log( (jet2.e() + jet2.pz()) / (jet2.e() - jet2.pz()) );
    // Get delta_phi and cos(Delta_phi) for hadronic collisions
    double pt1 = sqrt( pow(jet1.px(),2) + pow(jet1.py(),2) );
    double pt2 = sqrt( pow(jet2.px(),2) + pow(jet2.py(),2) );
    double cosdPhi = ( jet1.px()*jet2.px() + jet1.py()*jet2.py() ) / (pt1*pt2);
    double dPhi = acos( cosdPhi );
    // Calculate kT durham like fastjet
     ktdur = min( pow(pt1,2),pow(pt2,2) )
           * ( pow(eta1-eta2,2) + pow(dPhi,2) ) / pow(D,2);
  } else if( Type == -2 ){
    // Get delta_eta and cosh(Delta_eta) for hadronic collisions
    double eta1 = 0.5*log( (jet1.e() + jet1.pz()) / (jet1.e() - jet1.pz()) );
    double eta2 = 0.5*log( (jet2.e() + jet2.pz()) / (jet2.e() - jet2.pz()) );
     double coshdEta = cosh( eta1 - eta2 );
    // Get delta_phi and cos(Delta_phi) for hadronic collisions
    double pt1 = sqrt( pow(jet1.px(),2) + pow(jet1.py(),2) );
    double pt2 = sqrt( pow(jet2.px(),2) + pow(jet2.py(),2) );
    double cosdPhi = ( jet1.px()*jet2.px() + jet1.py()*jet2.py() ) / (pt1*pt2);
    // Calculate kT durham separation "SHERPA-like"
     ktdur = 2.0*min( pow(pt1,2),pow(pt2,2) )
           * ( coshdEta - cosdPhi ) / pow(D,2);
  } else {
    ktdur = 0.0;
  }
  // Return kT
  return sqrt(ktdur);
}

//==========================================================================

// Example main programm to illustrate merging

int main( int argc, char* argv[] ){

  // Set up command line options.
  InputParser ip("Illustrates how to do CKKW-L merging.",
    {"./main161 -c main161.cmnd -i w+_production_lhc_0.lhe "
        "-o histout161.dat"});
  ip.require("c", "Use this user-written command file.", {"-cmnd"});
  ip.require("o", "Path for output histogram files.", {"-out"});
  ip.require("i", "Full name of the input LHE file (with path).", {"-in"});

  // Initialize the parser and exit if necessary.
  InputParser::Status status = ip.init(argc, argv);
  if (status != InputParser::Valid) return status;

  // Grab the options.
  string cFile = ip.get<string>("c");
  string iPath = ip.get<string>("i");
  string oPath = ip.get<string>("o");

  // Create Pythia.
  Pythia pythia;
  pythia.readFile(cFile);

  // Number of events
  int nEvent = pythia.mode("Main:numberOfEvents");

  // Construct user inut for merging
  MergingHooksPtr myMergingHooks = make_shared<MyMergingHooks>();
  pythia.setMergingHooksPtr( myMergingHooks );

  // For ISR regularisation off
  pythia.settings.forceParm("SpaceShower:pT0Ref",0.);

  // Declare histograms
  Hist histPTFirst("pT of first jet",100,0.,100.);
  Hist histPTSecond("pT of second jet",100,0.,100.);

  // Read in ME configurations
  pythia.readString("Beams:frameType = 4");
  pythia.readString("Beams:LHEF = " + iPath);

  // If Pythia fails to initialize, exit with error.
  if (!pythia.init()) return 1;

  // Start generation loop
  for( int iEvent=0; iEvent<nEvent; ++iEvent ){

    // Generate next event
    if( ! pythia.next()) continue;

    // Get CKKWL weight of current event
    double evtweight = pythia.info.weight();
    double weight    = pythia.info.mergingWeight();
    weight      *= evtweight;

    // Fill bins with CKKWL weight
    double pTfirst = pTfirstJet(pythia.event,1, 0.4);
    double pTsecnd = pTfirstJet(pythia.event,2, 0.4);
    histPTFirst.fill( pTfirst, weight);
    histPTSecond.fill( pTsecnd, weight);

    if(iEvent%1000 == 0) cout << iEvent << endl;

  } // end loop over events to generate

  // print cross section, errors
  pythia.stat();

  // Normalise histograms
  double norm = 1.
              * pythia.info.sigmaGen()
              * 1./ double(nEvent);

  histPTFirst           *= norm;
  histPTSecond          *= norm;

  // Get the number of jets in the LHE file from the file name
  string jetsInLHEF = iPath.substr(iPath.size()-5, iPath.size());
  jetsInLHEF = jetsInLHEF.substr(0, jetsInLHEF.size()-4);

  // Write histograms to dat file. Use "jetsInLHEF" to label the files
  // Once all the samples up to the maximal desired jet multiplicity from the
  // matrix element are run, add all histograms to produce a
  // matrix-element-merged prediction

  ofstream write;
  stringstream suffix;
  suffix << jetsInLHEF << "_wv.dat";

  // Write histograms to file
  write.open( (char*)(oPath + "PTjet1_" + suffix.str()).c_str());
  histPTFirst.table(write);
  write.close();

  write.open( (char*)(oPath + "PTjet2_" + suffix.str()).c_str());
  histPTSecond.table(write);
  write.close();

  return 0;

  // Done
}
