#include "TRandom.h"      // ROOT random number generators, gRandom
#include "TFile.h"        // ROOT file I/O
#include "TTree.h"        // ROOT tree storage
#include "TH1.h"          // 1D histograms
#include "TH2.h"          // 2D histograms
#include "TStyle.h"       // ROOT plotting style controls
#include "TCanvas.h"      // ROOT canvas drawing
#include "TBenchmark.h"   // timing measurement
#include "TGraph.h"       // for energy loss interpolation
#include <cstring>
#include "TApplication.h" // ROOT app loop
#include "ClassTransfer.h" // Reaction kinematics and MC event generation
#include "ClassAnasen.h"   // ANASEN detector model classes (SX3, PW, etc.)
#include "ClassQQQ.h"      // QQQ detector model class
#include <stdio.h>
#include <stdlib.h>
#include <set>
#include "TLegend.h"
#include "TH1D.h"
#include "TObjArray.h"
#include "TBranch.h"
#include <iostream>
#include <fstream>

//======== Generate light particle based on reaction
// calculate real and reconstructed tracks and Q-value uncertainty

// Function to load energy loss table from file
TGraph* LoadELoss(const char* filename) {
  TGraph* g = new TGraph(filename, "%lg %lg");
  return g;
}

bool IsDeadAnode(int id){
    static std::set<int> dead = {}; // add dead anode IDs here, 0-23
    return dead.count(id);
}

bool IsDeadCathode(int id){
    static std::set<int> dead = {}; // add dead cathode IDs here, 0-23
    return dead.count(id);
}

bool IsDeadSX3(int id){ 
    static std::set<int> dead = {}; // add dead SX3 IDs here, 0-23 1,7,9,3
    return dead.count(id);
}

// Simulate sequential two-body decay of an unstable parent in its rest frame.
TLorentzVector SimulateSequentialDecay(const TLorentzVector &parent,
                                       int daughterA, int daughterZ,
                                       int ejectA, int ejectZ,
                                       TLorentzVector &ejectileOut){
  Isotope daughter(daughterA, daughterZ);
  Isotope ejectile(ejectA, ejectZ);

  double M = parent.M();
  double mD = daughter.Mass;
  double mE = ejectile.Mass;

  double sqM = M * M;
  double sum = mD + mE;
  double diff = mD - mE;
  double p2 = (sqM - sum*sum) * (sqM - diff*diff) / (4.0 * sqM); // two-body decay momentum squared
  if( p2 < 0 ) p2 = 0; // handle unphysical case where parent mass is less than sum of daughter and ejectile masses
  double p = TMath::Sqrt(p2); // two-body decay momentum

  double cosTheta = 2.0 * gRandom->Rndm() - 1.0; // isotropic decay in parent rest frame
  double theta = TMath::ACos(cosTheta); // polar angle of daughter in parent rest frame
  double phi = gRandom->Rndm() * TMath::TwoPi(); // azimuthal angle of daughter in parent rest frame

  TVector3 v; // momentum vector of daughter in parent rest frame
  v.SetMagThetaPhi(p, theta, phi); // daughter momentum in parent rest frame

  TLorentzVector daughterLab; // daughter 4-vector in lab frame, initialized with momentum from decay and mass of daughter
  daughterLab.SetVectM(v, mD); // set daughter 4-vector in parent rest frame, then boost to lab frame

  TLorentzVector ejectileLab; // ejectile 4-vector in lab frame, initialized with momentum opposite to daughter and mass of ejectile
  ejectileLab.SetVectM(-v, mE); //  set ejectile 4-vector in parent rest frame, then boost to lab frame

  TVector3 boost = parent.BoostVector(); // boost vector to go from parent rest frame to lab frame
  daughterLab.Boost(boost); // boost daughter to lab frame
  ejectileLab.Boost(boost); // boost ejectile to lab frame

  ejectileOut = ejectileLab; // return ejectile in lab frame
  return daughterLab;
}

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

  printf("=========================================\n");
  printf("===       ANASEN Monte Carlo          ===\n");
  printf("=========================================\n");

  // number of events can be overridden from command line
  int numEvent = 1000000;
  if( argc >= 2 ) numEvent = atoi(argv[1]);
  TransferReaction transfer;

  //To set beam energy loss, use energy loss app, and create table with target isotope, set Initial beam energy as max energy 
  transfer.SetA(27, 13, 0);            // 18Ne projectile
  TGraph* elossBeam = LoadELoss("../ELoss/HeLoss/E_vs_x_Al-27.dat"); 
  transfer.Seta(4, 2);                 // 4He target
  transfer.Setb(1, 1);                 // outgoing proton from the primary transfer
  transfer.SetB(30, 14);               // 21Na* heavy product
  const double beamA = 27;                   // mass number of 27Al beam

  bool enableSequentialDecay = false; // turning to false to disable sequential decay for now, can be set to true to enable
  const int decayDaughterA = 20;
  const int decayDaughterZ = 10;
  const int decayEjectA = 1;
  const int decayEjectZ = 1;

  // Excited state lists (projectile and heavy-product excitation states)
  std::vector<float> ExAList = {0};              // 18Ne projectile excitations in MeV
  std::vector<float> ExList = {0};             // 21Na* excitation in MeV

  // define vertex position uniform distribution ranges (mm)
  double vertexXRange[2] = {   -5, 5}; // mm - 5, 5
  double vertexYRange[2] = {   -5, 5}; // -5, 5
  double vertexZRange[2] = { -174.3, 174.3}; // -174.3, 174.3 (full length of gas volume, centered at 0)
  const double beamEntranceZ = -280; //vertexZRange[0]; // mm, assumed beam entrance into the gas


  // detector resolution / uncertainty parameters
  double sigmaSX3_W = 0; // mm, if < 0 use mid-point (no spread in SX3 horizontal dimension)
  double sigmaSX3_L = 0;  // mm, vertical spread for SX3
  double sigmaPW_A  = 0;  // normalized anode uncertainty term (0-1)
  double sigmaPW_C  = 0;  // normalized cathode uncertainty term (0-1)

  // status printout
  printf("------------ Vertex :\n");
  printf("X : %7.2f - %7.2f mm\n", vertexXRange[0], vertexXRange[1]);
  printf("Y : %7.2f - %7.2f mm\n", vertexYRange[0], vertexYRange[1]);
  printf("Z : %7.2f - %7.2f mm\n", vertexZRange[0], vertexZRange[1]);
  printf("------------ Uncertainty :\n");
  printf(" SX3 horizontal : %.1f\n", sigmaSX3_W);
  printf(" SX3   vertical : %.1f\n", sigmaSX3_L);
  printf("          Anode : %.1f mm\n", sigmaPW_A);
  printf("        Cathode : %.1f mm\n", sigmaPW_C);
  printf("        num_eve : %d \n",numEvent);

  // calculates energy/momentum/kinematics constants for transfer reaction
  transfer.CalReactionConstant();

  int nExA = ExAList.size();
  int nEx  = ExList.size();

  // optional visualization control: pass "vis" as 3rd arg
  bool enableVis = (argc >= 3 && strcmp(argv[2], "vis") == 0);
  TApplication *app = nullptr;
  if(enableVis){
    app = new TApplication("anasenVis", &argc, argv);
  }

  // storage for tracks during simulation (for visualization)
  std::vector<TVector3> visTrackVertex, visTrackDir, visTrackHitPos;
  std::vector<std::pair<int,int>> visTrackWires;  // {anodeID, cathodeID}

  // create detector representation in memory
  ANASEN * anasen = new ANASEN();       // top-level detector object
  SX3 * sx3 = anasen->GetSX3();         // silicon array part
  PW * pw = anasen->GetPW();            // proportional wire chamber part
  QQQ * qqq = anasen->GetQQQ();         // optional QQQ detector part, not used in this simulation but can be enabled for visualization

  // output file + trees
  TString saveFileName = "SimAnasen1.root";
  printf("\e[32m#################################### building Tree in %s\e[0m\n", saveFileName.Data());
  TFile * saveFile = new TFile(saveFileName, "recreate");
  TTree * tree1 = new TTree("tree1", "tree1");
  TTree * tree2 = new TTree("tree2", "tree2");
   

  // beam and CM variables saved in tree
  double KEA;
  double KEA2;
  double beamPath_cm;
  double beamEnergy;
  double beamEnergyLoss;
  tree1->Branch("beamKEA",      &KEA, "beamKEA/D");
  tree2->Branch("beamKEA",      &KEA2, "beamKEA/D");
  tree1->Branch("beamPath_cm",  &beamPath_cm, "beamPath_cm/D");
  tree2->Branch("beamPath_cm",  &beamPath_cm, "beamPath_cm/D");
  tree1->Branch("beamEnergy",   &beamEnergy, "beamEnergy/D");
  tree2->Branch("beamEnergy",   &beamEnergy, "beamEnergy/D");
  tree1->Branch("beamEnergyLoss", &beamEnergyLoss, "beamEnergyLoss/D");
  tree2->Branch("beamEnergyLoss", &beamEnergyLoss, "beamEnergyLoss/D");

  double thetaCM, phiCM;
  double thetaCM2, phiCM2;
  tree1->Branch("thetaCM", &thetaCM, "thetaCM/D");
  tree1->Branch("phiCM",     &phiCM, "phiCM/D");
  tree2->Branch("thetaCM", &thetaCM2, "thetaCM/D");
  tree2->Branch("phiCM",     &phiCM2, "phiCM/D");

  // outgoing particles in lab frame (light/heavy)
  double thetab, phib, Tb, qqqTb;
  double thetaB, phiB, TB, qqqTB;
  std::array<double, 2> T;
  tree1->Branch("thetab", &thetab, "thetab/D"); // polar angle of light particle in lab frame
  tree1->Branch("phib",     &phib, "phib/D"); // azimuthal angle of light particle in lab frame
  tree1->Branch("Tb",         &Tb, "Tb/D"); // kinetic energy of light particle at vertex (before energy loss)
  tree1->Branch("thetaB", &thetaB, "thetaB/D");
  tree1->Branch("phiB",     &phiB, "phiB/D");
  tree1->Branch("TB",         &TB, "TB/D"); // kinetic energy of heavy particle at vertex (before energy loss)
  tree1->Branch("T",          &T, "T/D");   // placeholder for true Q-value, currently set to 0 for simplicity
  tree1->Branch("qqqTb", &qqqTb, "qqqTb/D"); // kinetic energy of light particle at vertex (before energy loss) for events where the light particle hits the QQQ, currently set to 0 for simplicity
  tree1->Branch("qqqTB", &qqqTB, "qqqTB/D"); // kinetic energy of heavy particle at vertex (before energy loss) for events where the light

  double thetab2, phib2, Tb2, qqqTb2;
  double thetaB2, phiB2, TB2, qqqTB2;
  std::array<double, 2> T2;
  tree2->Branch("thetab", &thetab2, "thetab/D");
  tree2->Branch("phib",     &phib2, "phib/D");
  tree2->Branch("Tb",         &Tb2, "Tb/D");
  tree2->Branch("thetaB", &thetaB2, "thetaB/D");
  tree2->Branch("phiB",     &phiB2, "phiB/D");
  tree2->Branch("TB",         &TB2, "TB/D");
  tree2->Branch("T",          &T2, "T/D");
  tree2->Branch("qqqTb", &qqqTb2, "qqqTb/D");
  tree2->Branch("qqqTB", &qqqTB2, "qqqTB/D");

  // excitation state identifiers
  int ExAID;
  double ExA;
  tree1->Branch("ExAID", &ExAID, "ExAID/I"); // projectile excitation state ID
  tree1->Branch("ExA",     &ExA, "ExA/D"); // projectile excitation energy in MeV

  int ExAID2;
  double ExA2;
  tree2->Branch("ExAID", &ExAID2, "ExAID/I");
  tree2->Branch("ExA",     &ExA2, "ExA/D");

  int ExID;
  double Ex;
  tree1->Branch("ExID", &ExID, "ExID/I"); // target excitation state ID
  tree1->Branch("Ex",     &Ex, "Ex/D"); // target excitation energy in MeV

  int ExID2;
  double Ex2;
  tree2->Branch("ExID", &ExID2, "ExID/I");
  tree2->Branch("Ex",     &Ex2, "Ex/D");

  // true vertex position in target volume
  double vertexX, vertexY, vertexZ;
  tree1->Branch("vX",     &vertexX, "VertexX/D"); // true vertex X position in mm
  tree1->Branch("vY",     &vertexY, "VertexY/D"); // true vertex Y position in mm
  tree1->Branch("vZ",     &vertexZ, "VertexZ/D"); // true vertex Z position in mm

  double vertexX2, vertexY2, vertexZ2;
  tree2->Branch("vX",     &vertexX2, "VertexX/D");
  tree2->Branch("vY",     &vertexY2, "VertexY/D");
  tree2->Branch("vZ",     &vertexZ2, "VertexZ/D");

  // reconstructed SX3 hit position
  double sx3X, sx3Y, sx3Z;
  tree1->Branch("sx3X",     &sx3X, "sx3X/D"); // reconstructed X position from SX3 (with optional smearing) in mm
  tree1->Branch("sx3Y",     &sx3Y, "sx3Y/D"); // reconstructed Y position from SX3 (with optional smearing)
  tree1->Branch("sx3Z",     &sx3Z, "sx3Z/D"); // reconstructed Z position from SX3 (with optional smearing)

  double sx3X2, sx3Y2, sx3Z2;
  tree2->Branch("sx3X",     &sx3X2, "sx3X/D");
  tree2->Branch("sx3Y",     &sx3Y2, "sx3Y/D");
  tree2->Branch("sx3Z",     &sx3Z2, "sx3Z/D");

  double qqqX, qqqY, qqqZ;
  tree1->Branch("qqqX",     &qqqX, "qqqX/D"); // reconstructed X position from QQQ (with optional smearing) in mm
  tree1->Branch("qqqY",     &qqqY, "qqqY/D"); // reconstructed Y position from QQQ (with optional smearing)
  tree1->Branch("qqqZ",     &qqqZ, "qqqZ/D"); // reconstructed Z position from QQQ (with optional smearing)

  double qqqX2, qqqY2, qqqZ2;
  tree2->Branch("qqqX",     &qqqX2, "qqqX/D");
  tree2->Branch("qqqY",     &qqqY2, "qqqY/D");
  tree2->Branch("qqqZ",     &qqqZ2, "qqqZ/D");

  // PW nearest and next nearest wires
  int anodeID[2], cathodeID[2];
  int anodeID2[2], cathodeID2[2];
  tree1->Branch("aID", anodeID, "anodeID/I"); // anodeID[0] is nearest anode wire, anodeID[1] is next nearest anode wire
  tree1->Branch("cID", cathodeID, "cathodeID/I"); // cathodeID[0] is nearest cathode wire, cathodeID[1] is next nearest cathode wire
  tree2->Branch("aID", anodeID2, "anodeID/I");
  tree2->Branch("cID", cathodeID2, "cathodeID/I");

  // distances to nearest wires
  double anodeDist[2], cathodeDist[2];
  double anodeDist2[2], cathodeDist2[2];
  tree1->Branch("aDist",   anodeDist, "anodeDist/D");
  tree1->Branch("cDist",  cathodeDist, "cathodeDist/D");
  tree2->Branch("aDist",   anodeDist2, "anodeDist/D");
  tree2->Branch("cDist",  cathodeDist2, "cathodeDist/D");

  // SX3 channel assignment and Z fraction (depth) information
  int sx3ID, sx3Up, sx3Dn, sx3Bk, qqqID;
  double sx3ZFrac;
  int sx3ID2, sx3Up2, sx3Dn2, sx3Bk2, qqqID2;
  double sx3ZFrac2;
  tree1->Branch("sx3ID",   &sx3ID, "sx3ID/I");
  tree1->Branch("qqqID",   &qqqID, "qqqID/I");
  tree1->Branch("sx3Up",   &sx3Up, "sx3Up/I");
  tree1->Branch("sx3Dn",   &sx3Dn, "sx3Dn/I");
  tree1->Branch("sx3Bk",   &sx3Bk, "sx3Bk/I");
  tree1->Branch("sx3ZFrac", &sx3ZFrac, "sx3ZFrac/D");
  tree2->Branch("sx3ID",   &sx3ID2, "sx3ID/I");
  tree2->Branch("qqqID",   &qqqID2, "qqqID/I");
  tree2->Branch("sx3Up",   &sx3Up2, "sx3Up/I");
  tree2->Branch("sx3Dn",   &sx3Dn2, "sx3Dn/I");
  tree2->Branch("sx3Bk",   &sx3Bk2, "sx3Bk/I");
  tree2->Branch("sx3ZFrac", &sx3ZFrac2, "sx3ZFrac/D");

  // reconstructed angles from PW track fit, method 1 and 2
  double reTheta, rePhi;
  double reTheta2, rePhi2;
  tree1->Branch("reTheta", &reTheta, "reconstucted_theta/D");
  tree1->Branch("rePhi",     &rePhi, "reconstucted_phi/D");
  tree2->Branch("reTheta", &reTheta2, "reconstucted_theta/D");
  tree2->Branch("rePhi",     &rePhi2, "reconstucted_phi/D");

  double reTheta1, rePhi1;
  double reTheta12, rePhi12;
  tree1->Branch("reTheta1", &reTheta1, "reconstucted_theta1/D");
  tree1->Branch("rePhi1",     &rePhi1, "reconstucted_phi1/D");
  tree2->Branch("reTheta1", &reTheta12, "reconstucted_theta1/D");
  tree2->Branch("rePhi1",     &rePhi12, "reconstucted_phi1/D");

  // reconstructed vertex Z from PW fit
  double z0;
  double z02;
  tree1->Branch("z0",     &z0, "reconstucted_Z/D");
  tree2->Branch("z0",     &z02, "reconstucted_Z/D");

  //========timer
  TBenchmark clock;
  bool shown ;
  clock.Reset();
  clock.Start("timer");
  shown = false;

  //================================= Calculate event loop
  for( int i = 0; i < numEvent ; i++){

    // randomly sample target/projectile excitations
    ExAID = gRandom->Integer(nExA);
    ExA = ExAList[ExAID];
    transfer.SetExA(ExA);

    ExID = gRandom->Integer(nEx);
    Ex = ExList[ExID];
    transfer.SetExB(Ex);

    // recalc kinematic constants for chosen states
    transfer.CalReactionConstant();

    // vertex position in target volume
    vertexX = (vertexXRange[1]- vertexXRange[0])*gRandom->Rndm() +  vertexXRange[0];
    vertexY = (vertexYRange[1]- vertexYRange[0])*gRandom->Rndm() +  vertexYRange[0];
    vertexZ = (vertexZRange[1]- vertexZRange[0])*gRandom->Rndm() +  vertexZRange[0];

    TVector3 vertex(vertexX, vertexY, vertexZ);

    // compute beam energy at the event vertex from the gas path length
    beamPath_cm = TVector3(vertexZ - beamEntranceZ, vertexX, vertexY).Mag() * 0.1;
    if( beamPath_cm < 0 ) beamPath_cm = 0;
    beamEnergy = elossBeam->Eval(beamPath_cm); // MeV
    beamEnergyLoss = elossBeam->Eval(0.0) - beamEnergy;
    KEA = beamEnergy / beamA;
    transfer.SetIncidentEnergyAngle(KEA, 0, 0);
    transfer.CalReactionConstant();

    // isotropic CM direction
    thetaCM = TMath::ACos(2 * gRandom->Rndm() - 1) ; // polar angle in CM frame
    phiCM   = (gRandom->Rndm() - 0.5) * TMath::TwoPi();

    //==== Calculate reaction kinematics in lab frame for the primary transfer
    TLorentzVector * output = transfer.Event(thetaCM, phiCM); // returns array of outputs
    TLorentzVector Pb = output[2]; // primary proton from transfer
    TLorentzVector PB = output[3]; // excited 21Na* heavy product

    thetab = Pb.Theta() * TMath::RadToDeg();
    Tb = (Pb.E() - Pb.M()); // kinetic energy of the light proton from the primary transfer
    thetaB = PB.Theta() * TMath::RadToDeg();
    TB = (PB.E() - PB.M());
    phib = Pb.Phi() * TMath::RadToDeg();
    phiB = PB.Phi() * TMath::RadToDeg();
    T[0] = Tb;
    T[1] = TB;

    //secondary decay
    TLorentzVector decayProton;
    TLorentzVector heavy20;
    if(enableSequentialDecay){
      heavy20 = SimulateSequentialDecay(PB, decayDaughterA, decayDaughterZ,
                                       decayEjectA, decayEjectZ, decayProton);
      thetab2 = decayProton.Theta() * TMath::RadToDeg();
      phib2 = decayProton.Phi() * TMath::RadToDeg();
      Tb2 = decayProton.E() - decayProton.M();
      thetaB2 = heavy20.Theta() * TMath::RadToDeg();
      phiB2 = heavy20.Phi() * TMath::RadToDeg();
      TB2 = heavy20.E() - heavy20.M();
      T2[0] = Tb2;
      T2[1] = TB2;
    } else {
      thetab2 = TMath::QuietNaN();
      phib2  = TMath::QuietNaN();
      Tb2    = TMath::QuietNaN();
      thetaB2 = TMath::QuietNaN();
      phiB2 = TMath::QuietNaN();
      TB2 = TMath::QuietNaN();
      T2[0] = TMath::QuietNaN();
      T2[1] = TMath::QuietNaN();
    }

    delete [] output;

    // set direction vector from lab angle
    TVector3 dir(1, 0, 0);
    dir.SetTheta(thetab * TMath::DegToRad());
    dir.SetPhi(phib * TMath::DegToRad());

    // run detector response models for PW and SX3
    pw->FindWireID(vertex, dir, false);
    sx3->FindSX3Pos(vertex, dir, false);
    qqq->FindQQQPos(vertex, dir, false);

    PWHitInfo hitInfo = pw->GetHitInfo();

    anodeID[0]   = hitInfo.nearestWire.first; // nearest anode wire ID
    cathodeID[0] = hitInfo.nearestWire.second; // nearest cathode wire ID
    anodeID[1]   = hitInfo.nextNearestWire.first; // next nearest anode wire ID
    cathodeID[1] = hitInfo.nextNearestWire.second; // next nearest cathode wire ID

    anodeDist[1]   = hitInfo.nextNearestDist.first; // distance to next nearest anode wire
    cathodeDist[1] = hitInfo.nextNearestDist.second; // distance to next nearest cathode wire

    if(IsDeadAnode(anodeID[0])) continue;
    if(IsDeadCathode(cathodeID[0])) continue;

    // SX3 hit channel info and depth fraction
    sx3ID = sx3->GetID();
    qqqID = qqq->GetID();

    if(IsDeadSX3(sx3ID)) continue;

    anodeDist[0]   = hitInfo.nearestDist.first; // distance to nearest anode wire
    cathodeDist[0] = hitInfo.nearestDist.second; // distance to nearest cathode wire
    
    //start HERE
    if( sx3ID >= 0 ){
      sx3Up    = sx3->GetChUp();
      sx3Dn    = sx3->GetChDn();
      sx3Bk    = sx3->GetChBk();
      sx3ZFrac = sx3->GetZFrac();
      
      // apply intrinsic detector resolution to true SX3 hit position
      // for no smearing comment out and use GetHitPos();
      TVector3 hitPos = sx3->GetHitPosWithSigma(sigmaSX3_W, sigmaSX3_L);

      sx3X = hitPos.X();
      sx3Y = hitPos.Y();
      sx3Z = hitPos.Z();

      // store track data for visualization if enabled
      if(enableVis){
        visTrackVertex.push_back(vertex);
        visTrackDir.push_back(dir);
        visTrackHitPos.push_back(hitPos);
        visTrackWires.push_back({anodeID[0], cathodeID[0]});
      }
      // reconstruct track from PW readings + SX3 hit
      pw->CalTrack(hitPos, anodeID[0], cathodeID[0], false);
      reTheta = pw->GetTrackTheta() * TMath::RadToDeg();
      rePhi   = pw->GetTrackPhi() * TMath::RadToDeg();

      // alternative track algorithm with uncertainty parameters
      pw->CalTrack2(hitPos, hitInfo, sigmaPW_A, sigmaPW_C, false);
      reTheta1 = pw->GetTrackTheta() * TMath::RadToDeg();
      rePhi1   = pw->GetTrackPhi() * TMath::RadToDeg();

      z0 = pw->GetZ0();
      tree1->Fill();

      // fill tree2 using the secondary proton from 21Na* decay
      TVector3 dir2(1, 0, 0);
      dir2.SetTheta(thetab2 * TMath::DegToRad());
      dir2.SetPhi(phib2 * TMath::DegToRad());

      pw->FindWireID(vertex, dir2, false);
      sx3->FindSX3Pos(vertex, dir2, false);
      PWHitInfo hitInfo2 = pw->GetHitInfo();

      anodeID2[0]   = hitInfo2.nearestWire.first;
      cathodeID2[0] = hitInfo2.nearestWire.second;
      anodeID2[1]   = hitInfo2.nextNearestWire.first;
      cathodeID2[1] = hitInfo2.nextNearestWire.second;

      anodeDist2[1]   = hitInfo2.nextNearestDist.first;
      cathodeDist2[1] = hitInfo2.nextNearestDist.second;

      if(IsDeadAnode(anodeID2[0]) || IsDeadCathode(cathodeID2[0])){
        sx3ID2 = -1;
      } else {
        sx3ID2 = sx3->GetID();
      }

      if(sx3ID2 < 0 || IsDeadSX3(sx3ID2)){
        sx3ID2 = -1;
        sx3Up2 = -1;
        sx3Dn2 = -1;
        sx3Bk2 = -1;
        sx3ZFrac2 = TMath::QuietNaN();
        sx3X2 = TMath::QuietNaN();
        sx3Y2 = TMath::QuietNaN();
        sx3Z2 = TMath::QuietNaN();
        anodeDist2[0] = TMath::QuietNaN();
        cathodeDist2[0] = TMath::QuietNaN();
        reTheta2 = TMath::QuietNaN();
        rePhi2 = TMath::QuietNaN();
        reTheta12 = TMath::QuietNaN();
        rePhi12 = TMath::QuietNaN();
        z02 = TMath::QuietNaN();
      } else {
        anodeDist2[0]   = hitInfo2.nearestDist.first;
        cathodeDist2[0] = hitInfo2.nearestDist.second;
        sx3Up2    = sx3->GetChUp();
        sx3Dn2    = sx3->GetChDn();
        sx3Bk2    = sx3->GetChBk();
        sx3ZFrac2 = sx3->GetZFrac();
        TVector3 hitPos2 = sx3->GetHitPosWithSigma(sigmaSX3_W, sigmaSX3_L);
        sx3X2 = hitPos2.X();
        sx3Y2 = hitPos2.Y();
        sx3Z2 = hitPos2.Z();
        pw->CalTrack(hitPos2, anodeID2[0], cathodeID2[0], false);
        reTheta2 = pw->GetTrackTheta() * TMath::RadToDeg();
        rePhi2   = pw->GetTrackPhi() * TMath::RadToDeg();
        pw->CalTrack2(hitPos2, hitInfo2, sigmaPW_A, sigmaPW_C, false);
        reTheta12 = pw->GetTrackTheta() * TMath::RadToDeg();
        rePhi12   = pw->GetTrackPhi() * TMath::RadToDeg();
        z02       = pw->GetZ0();
      }

      KEA2 = KEA;
      thetaCM2 = thetaCM;
      phiCM2 = phiCM;
      ExAID2 = ExAID;
      ExA2 = ExA;
      ExID2 = ExID;
      Ex2 = Ex;
      vertexX2 = vertexX;
      vertexY2 = vertexY;
      vertexZ2 = vertexZ;
      qqqX = TMath::QuietNaN();
      qqqY = TMath::QuietNaN();
      qqqZ = TMath::QuietNaN();

      tree2->Fill();

    }else if (false){//(qqqID >= 0){
      // handle QQQ hit case
      sx3Up = -1;
      sx3Dn = -1;
      sx3Bk = -1;
      sx3ZFrac = TMath::QuietNaN();

      sx3X = TMath::QuietNaN();
      sx3Y = TMath::QuietNaN();
      sx3Z = TMath::QuietNaN();

      reTheta  = TMath::QuietNaN();
      rePhi    = TMath::QuietNaN();
      reTheta1 = TMath::QuietNaN();
      rePhi1   = TMath::QuietNaN();
      z0       = TMath::QuietNaN();

      qqqTb = Tb; // for simplicity, using the same kinetic energy for QQQ hit events, can be modified to simulate energy loss if desired
      qqqTB = TB;

      Tb = TMath::QuietNaN(); // mark kinetic energy as invalid for SX3 hit case
      TB = TMath::QuietNaN();

      TVector3 hitPos = qqq->GetHitPos();

      qqqX = hitPos.X();
      qqqY = hitPos.Y();
      qqqZ = hitPos.Z();

      if(enableVis){
        visTrackVertex.push_back(vertex);
        visTrackDir.push_back(dir);
        visTrackHitPos.push_back(hitPos);
        //visTrackWires.push_back({anodeID[0], cathodeID[0]});
      }

      tree1->Fill();

      TVector3 dir2(1, 0, 0);
      dir2.SetTheta(thetab2 * TMath::DegToRad());
      dir2.SetPhi(phib2 * TMath::DegToRad());

      qqq->FindQQQPos(vertex, dir2, false);

      if(qqqID2 < 0){
        qqqID2 = -1;
        qqqX2 = TMath::QuietNaN();
        qqqY2 = TMath::QuietNaN();
        qqqZ2 = TMath::QuietNaN();
        anodeDist2[0] = TMath::QuietNaN();
        cathodeDist2[0] = TMath::QuietNaN();
        reTheta2 = TMath::QuietNaN();
        rePhi2 = TMath::QuietNaN();
        reTheta12 = TMath::QuietNaN();
        rePhi12 = TMath::QuietNaN();
        z02 = TMath::QuietNaN();
        anodeID2[0]   = TMath::QuietNaN(); // no valid anode wire for QQQ hit case
        cathodeID2[0] = TMath::QuietNaN(); // no valid cathode wire for QQQ hit case
        anodeID2[1]   = TMath::QuietNaN(); // no valid next nearest anode wire for QQQ hit case
        cathodeID2[1] = TMath::QuietNaN(); // no valid next nearest cathode wire for QQQ hit case
        anodeDist2[1]   = TMath::QuietNaN();
        cathodeDist2[1] = TMath::QuietNaN();
      }

      KEA2 = KEA;
      thetaCM2 = thetaCM;
      phiCM2 = phiCM;
      ExAID2 = ExAID;
      ExA2 = ExA;
      ExID2 = ExID;
      Ex2 = Ex;
      vertexX2 = vertexX;
      vertexY2 = vertexY;
      vertexZ2 = vertexZ;

      tree2->Fill();
    }else{
      // no valid SX3 hit: mark clearly invalid
      sx3Up = -1;
      sx3Dn = -1;
      sx3Bk = -1;
      sx3ZFrac = TMath::QuietNaN();

      sx3X = TMath::QuietNaN();
      sx3Y = TMath::QuietNaN();
      sx3Z = TMath::QuietNaN();

      reTheta  = TMath::QuietNaN();
      rePhi    = TMath::QuietNaN();
      reTheta1 = TMath::QuietNaN();
      rePhi1   = TMath::QuietNaN();
      z0       = TMath::QuietNaN();
      Tb       = TMath::QuietNaN(); // mark kinetic energy as invalid for no hit case
      TB       = TMath::QuietNaN();
      // fill tree with original data (no energy loss for these events)
      //comment out tree fill for no hit case  
      //tree1->Fill();
    }

    //#################################################################### Timer
    // measure elapsed real time and print progress roughly every 10 sec
    clock.Stop("timer");
    Double_t time = clock.GetRealTime("timer");
    clock.Start("timer");

    if ( !shown ) {
      if (fmod(time, 10) < 1 ){
        printf( "%10d[%2d%%]| %8.2f sec | expect: %5.1f min \n", i, TMath::Nint((i+1)*100./numEvent), time , numEvent*time/(i+1)/60);
        shown = 1;
      }
    } else {
      if (fmod(time, 10) > 9 ){
        shown = 0;
      }
    }

  }

  // write results to ROOT file and close
  tree1->Write("", TObject::kOverwrite);
  tree2->Write("", TObject::kOverwrite);
  int count1 = tree1->GetEntries();
  int count2 = tree2->GetEntries();
  saveFile->Close();

  printf("=============== done. saved as %s. tree1 entries: %d, tree2 entries: %d\n", saveFileName.Data(), count1, count2);

  if(enableVis){ // to enable visualization, run with 3rd argument "vis", e.g. "./anasenMC 1000 vis"
    printf("Displaying geometry with %zu tracks from simulation\n", visTrackVertex.size());
    
    // Build full geometry with all wires
    anasen->DrawAnasen(0, 23, 0, 23, -1, true);
    
    // Add all stored tracks to the geometry
    TGeoManager *geom = anasen->GetGeoManager();
    TGeoVolume *worldBox = anasen->GetWorldBox();
    
    if(geom && worldBox && visTrackVertex.size() > 0){
      int trackNodeID = 500;  // start node IDs for tracks
      
      for(size_t iTrack = 0; iTrack < visTrackVertex.size(); ++iTrack){
        TVector3 vertex = visTrackVertex[iTrack];
        TVector3 dir = visTrackDir[iTrack];
        TVector3 hitPos = visTrackHitPos[iTrack];
        
        double theta = dir.Theta() * TMath::RadToDeg();
        double phi = dir.Phi() * TMath::RadToDeg();
        
        // Add a line marker at the vertex
        TGeoVolume *startMarker = geom->MakeSphere("startMarker", 0, 0, 2.0);
        startMarker->SetLineColor(kBlack);
        worldBox->AddNode(startMarker, trackNodeID, 
                         new TGeoCombiTrans(vertex.X(), vertex.Y(), vertex.Z(), 
                                          new TGeoRotation("rot", 0, 0, 0)));
        trackNodeID++;
        
        // Add track line from vertex toward hit position
        TGeoVolume *trackLine = geom->MakeTube("trackLine", 0, 0, 0.08, 150.0);
        trackLine->SetLineColor(kBlue);
        worldBox->AddNode(trackLine, trackNodeID,
                         new TGeoCombiTrans(vertex.X(), vertex.Y(), vertex.Z(),
                                          new TGeoRotation("rotTrack", phi + 90, theta, 0)));
        trackNodeID++;
        
        // Add hit position marker
        TGeoVolume *hitMarker = geom->MakeSphere("hitMarker", 0, 0, 2.0);
        hitMarker->SetLineColor(kRed);
        worldBox->AddNode(hitMarker, trackNodeID,
                         new TGeoCombiTrans(hitPos.X(), hitPos.Y(), hitPos.Z(),
                                          new TGeoRotation("rotHit", 0, 0, 0)));
        trackNodeID++;
      }
      
      // Redraw geometry with all tracks
      geom->CloseGeometry();
      geom->SetVisLevel(4);
      worldBox->Draw("ogle");
    }
    
    if(app){
      printf("Entering ROOT event loop\n");
      app->Run();
    }
  }

  delete anasen;


  return 0;

}