#include "ClassHelio.h"
#include "TROOT.h"
#include "TBenchmark.h"
#include "TLorentzVector.h"
#include "TMath.h"
#include "TFile.h"
#include "TF1.h"
#include "TTree.h"
#include "TRandom.h"
#include "TClonesArray.h"
#include <vector>
#include <fstream>

//----------- usage 
// $root sim.C+ | tee output.txt
// this will same the massage to output.txt

void knockout(){

   //================================================= User Setting
   //---- reaction
   int AA = 23, ZA = 9;
   int Aa = 1,  Za = 1;
   int A2 = 1,  Z2 = 1;
   
   bool isNormalKinematics = false;
   bool isOverRideExNegative = true;
   double maxkb = 200.;
   
   //---- beam
   double KEAmean = 100; // MeV/u
   const int nKEA = 1; 
   double KEAList[nKEA] = {300}; 
   
   double KEAsigma = 0; //KEAmean*0.001; // MeV/u , assume Guassian
   double thetaMean = 0.; // mrad 
   double thetaSigma = 0.; // mrad , assume Guassian due to small angle
   
   int numEvent = 100000;
   
   //---- HELIOS detector geometry
   string heliosDetGeoFile = "";//"detectorGeo_upstream.txt";
   double BField = 4.0; // if not detector, must set B-field, else, this value is not used.
   double BFieldTheta = 0.; // direction of B-field
   double eSigma = 0.0001 ; // detector energy sigma MeV
   double zSigma = 0.1 ; // detector position sigma mm
   
   //---- excitation of Beam 
   int nExA = 1;
   double ExAList[nExA];
   ExAList[0] = 0.000; // MeV
   //ExAList[1] = 1.567;
    
   //---- Separation energy
   string separationFile = "separation_energies.txt";
   
   //---- save root file name
   TString saveFileName = "knockout.root";
   
   //---- Auxiliary setting
   bool isTargetScattering = false;
   bool isDecay = false;
   bool isReDo = false; // redo calculation until detected. 
   
   //---- target
   double density = 0.913; // 0.913 g/cm3
   double targetThickness = 2./2. * 2.2e-4; // 2.2 um = 201 ug/cm2
   string stoppingPowerForA = "208Pb_in_CD2.txt"; // generated by SRIM
   string stoppingPowerForb = "1H_in_CD2.txt";
   string stoppingPowerForB = "209Pb_in_CD2.txt";
   
   //=============================================================
   //=============================================================
   //=============================================================
   //===== Set Reaction
   Knockout reaction;
   int AB = AA-A2, ZB = ZA-Z2;
   int A1 = Aa   , Z1 = Za;
   reaction.SetA(AA,ZA);
   reaction.Seta(Aa,Za);
   reaction.Set2(A2,Z2);
   reaction.SetIncidentEnergyAngle(KEAmean, 0, 0);
   reaction.OverRideExNegative(isOverRideExNegative);
   
   printf("===================================================\n");
   printf("=========== %s ===========\n", reaction.GetReactionName().Data());
   printf("=========== KE: %9.4f +- %5.4f MeV/u, dp/p = %5.2f %% \n", KEAmean, KEAsigma, KEAsigma/KEAmean * 50.);
   printf("======== theta: %9.4f +- %5.4f MeV/u \n", thetaMean, thetaSigma);
   printf("===================================================\n");

   
   //======== Set HELIOS
   printf("############################################## HELIOS configuration\n");   
   HELIOS helios1; // for particle-1
   HELIOS helios2; // for particle-2
   helios1.SetMagneticFieldDirection(BFieldTheta);
   helios2.SetMagneticFieldDirection(BFieldTheta);
   bool sethelios1 = helios1.SetDetectorGeometry(heliosDetGeoFile);
   bool sethelios2 = helios2.SetDetectorGeometry(heliosDetGeoFile);
   if( sethelios1 && sethelios2 ) {
		int mDet = helios1.GetNumberOfDetectorsInSamePos();
		printf("========== energy resol.: %f MeV\n", eSigma);
		printf("=========== pos-Z resol.: %f mm \n", zSigma); 
	}else{
		helios1.SetMagneticField(BField);
		helios2.SetMagneticField(BField);
		printf("======== B-field : %5.2f T, Theta: %5.2f deg\n", BField, BFieldTheta);
	}
	
	//==== Target scattering, only energy loss
   if(isTargetScattering) printf("############################################## Target Scattering\n");
   TargetScattering msA;
   TargetScattering msB;
   TargetScattering msb;

   if(isTargetScattering) printf("======== Target : (thickness : %6.2f um) x (density : %6.2f g/cm3) = %6.2f ug/cm2\n", 
                        targetThickness * 1e+4, 
                        density, 
                        targetThickness * density * 1e+6);  
            
   if( isTargetScattering ){
      msA.LoadStoppingPower(stoppingPowerForA);
      msb.LoadStoppingPower(stoppingPowerForb);
      msB.LoadStoppingPower(stoppingPowerForB);
   }
   
   //======= Decay of particle-B
   Decay decay;
   decay.SetMotherDaugther(AB, ZB, AB-1,ZB); //neutron decay
	
	
   //======= loading excitation energy
   printf("############################################## excitation energies\n");
   vector<double> SpList;
   printf("----- loading separation energies.");
   ifstream file;
   file.open(separationFile.c_str());
   string isotopeName;
   if( file.is_open() ){
      string line;
      int i = 0;
      while( file >> line){
         //printf("%d, %s \n", i,  line.c_str());
         if( line.substr(0,2) == "//" ) continue;
         if( i == 0 ) isotopeName = line; 
         if ( i >= 1 ){
            SpList.push_back(atof(line.c_str()));
         }
         i = i + 1;
      }
      file.close();
      printf("... done.\n");
      printf("========== %s\n", isotopeName.c_str());
      int n = SpList.size();
      for(int i = 0; i < n ; i++){
         if( isDecay ) {
            TLorentzVector temp(0,0,0,0);
            int decayID = decay.CalDecay(temp, SpList[i], 0);
            if( decayID == 1) {
               printf("%d, Sp: %6.2f MeV --> Decay. \n", i, SpList[i]);
            }else{
               printf("%d, Sp: %6.2f MeV\n", i, SpList[i]);
            }
         }else{
            printf("%d, Sp: %6.2f MeV \n", i, SpList[i]);
         }
      }
   }else{
       printf("... fail\n");
       return;
   }

   //====================== build tree
   TFile * saveFile = new TFile(saveFileName, "recreate");
   TTree * tree = new TTree("tree", "tree");
   
   double thetaNN, phiNN;
   double theta1, phi1, T1;
   double theta2, phi2, T2;
   double thetaB, TB;
   double Sp, kb, thetab, phib;
   int SpID;

   double ExA;
   int ExAID;
   double KEA, KEAscattered, theta, phi;
   
   double mB,mb;

   tree->Branch("theta1", &theta1, "theta1/D");
   tree->Branch("phi1", &phi1, "phi1/D");
   tree->Branch("T1", &T1, "T1/D");
   tree->Branch("theta2", &theta2, "theta2/D");
   tree->Branch("phi2", &phi2, "phi2/D");
   tree->Branch("T2", &T2, "T2/D");
   tree->Branch("thetaB", &thetaB, "thetaB/D");
   tree->Branch("TB", &TB, "TB/D");
   tree->Branch("thetaNN", &thetaNN, "thetaNN/D");
   tree->Branch("phiNN", &phiNN, "phiNN/D");
   tree->Branch("Sp", &Sp, "Sp/D");
   tree->Branch("kb", &kb, "kb/D");
   tree->Branch("thetab", &thetab, "thetab/D");
   tree->Branch("phib", &phib, "phib/D");
   
   tree->Branch("SpID", &SpID, "SpID/I");
   
   tree->Branch("ExAID", &ExAID, "ExAID/I");
   tree->Branch("KEA", &KEA, "KEA/D");
   if(isTargetScattering) tree->Branch("KEAscattered", &KEAscattered, "KEAscattered/D");
   tree->Branch("theta", &theta, "theta/D");
   tree->Branch("phi", &phi, "phi/D");

   tree->Branch("mB", &mB, "mB/D");
   tree->Branch("mb", &mb, "mb/D");
   
   tree->Branch("Bfield", &BField, "Bfield/D");
   
   TClonesArray * arr = new TClonesArray("TLorentzVector");
   tree->Branch("fV", arr, 256000);
   arr->BypassStreamer();
   
   TClonesArray * arrN = new TClonesArray("TLorentzVector");
   tree->Branch("fVN", arrN, 256000);
   arrN->BypassStreamer();
   
   TLorentzVector* fourVector = NULL;

   // the output of Helios.CalHit
   double e1, z1, t1, rho1, x1h, y1h, r1h;
   double e2, z2, t2, rho2, x2h, y2h, r2h;
	tree->Branch("e1", &e1, "e1/D");
   tree->Branch("z1", &z1, "z1/D");
   tree->Branch("t1", &t1, "t1/D");
   tree->Branch("x1h", &x1h, "x1h/D"); //at 200 mm downstream
   tree->Branch("y1h", &y1h, "y1h/D");
   tree->Branch("r1h", &r1h, "r1h/D");
   tree->Branch("rho1", &rho1, "rho1/D");
   
   tree->Branch("e2", &e2, "e2/D");
   tree->Branch("z2", &z2, "z2/D");
   tree->Branch("t2", &t2, "t2/D");
   tree->Branch("x2h", &x2h, "x2h/D"); //at 200 mm downstream
   tree->Branch("y2h", &y2h, "y2h/D");
   tree->Branch("r2h", &r2h, "r2h/D");
   tree->Branch("rho2", &rho2, "rho2/D");
   
   double Sp2;
   tree->Branch("Sp2", &Sp2, "Sp2/D");
   
   //different coordinate
   double a0, a1, a2; // in k1-k2 coordinate
   tree->Branch("a0", &a0, "a0/D");
   tree->Branch("a1", &a1, "a1/D");
   tree->Branch("a2", &a2, "a2/D");
   
   double b; // in THREEDEE coordinate
   tree->Branch("b", &b, "b/D");
   
   //========timer
   TBenchmark clock;
   bool shown ;   
   clock.Reset();
   clock.Start("timer");
   shown = false;
   printf("############################################## generating %d events \n", numEvent);
   
   //====================================================== calculate 
   int count = 0;
   for( int i = 0; i < numEvent; i++){
      bool redoFlag = true;
      if( !isReDo ) redoFlag = false;
      do{
      
         //==== Set Ex of A
         ExAID = gRandom->Integer(nExA);
         ExA = ExAList[ExAID];
         reaction.SetExA(ExA);
         
         //==== Set Sp of B
         SpID = gRandom->Integer(SpList.size());
         Sp = SpList[SpID]; 
         
         //==== Set incident beam
         if( KEAsigma == 0 ){
            KEA = KEAmean;
         }else{
            KEA = gRandom->Gaus(KEAmean, KEAsigma);
         }
         if( thetaSigma == 0 ){
            theta = thetaMean;
         }else{
            theta = gRandom->Gaus(thetaMean, thetaSigma);
         }
         
         int rKEA = gRandom->Integer(nKEA);
         KEA = KEAList[rKEA];
         
         /*
         KEA = 300*gRandom->Rndm();
         BField = 4*gRandom->Rndm();
			helios1.SetMagneticField(BField);
			helios2.SetMagneticField(BField);
			*/
         
         reaction.SetIncidentEnergyAngle(KEA, theta, 0.);
         
         
         /*
         //For target scattering
         reaction.CalIncidentChannel(isNormalKinematics); // but only need is PA
         TLorentzVector PA = reaction.GetPA();            
         
         double depth = 0;
         if( isTargetScattering ){
            //==== Target scattering, only energy loss
            depth = targetThickness * gRandom->Rndm();
            msA.SetTarget(density, depth); 
            TLorentzVector PAnew = msA.Scattering(PA);
            KEAscattered = msA.GetKE()/AA;
            reaction.SetIncidentEnergyAngle(KEAscattered, theta, phi);
         }*/
         
         //==== Calculate reaction
         thetaNN = TMath::ACos(2 * gRandom->Rndm() - 1) ; 
         phiNN = TMath::TwoPi() * gRandom->Rndm(); 
         
         kb = maxkb * gRandom->Rndm();
         thetab = TMath::ACos(2 * gRandom->Rndm() - 1);
         phib = TMath::TwoPi() * gRandom->Rndm();

         reaction.SetBSpk(Sp, kb, thetab, phib);
         reaction.CalReactionConstant(isNormalKinematics);
         reaction.Event(thetaNN, phiNN);

         TLorentzVector PA = reaction.GetPA();
         TLorentzVector Pa = reaction.GetPa();
               
         TLorentzVector P1 = reaction.GetP1();
         TLorentzVector P2 = reaction.GetP2();
         TLorentzVector Pb = reaction.GetPb();
         TLorentzVector PB = reaction.GetPB();
         
         /*
         //==== Calculate energy loss of scattered and recoil in target
         if( isTargetScattering ){
            if( Pb.Theta() < TMath::PiOver2() ){
               msb.SetTarget(density, targetThickness - depth);
            }else{
               msb.SetTarget(density, depth);
            }
            Pb = msb.Scattering(Pb);
            TbLoss = msb.GetKELoss();
            msB.SetTarget(density, targetThickness - depth);
            PB = msB.Scattering(PB);
         }else{
            TbLoss = 0;
         }
         
         //======= Decay of particle-B
         if( isDecay){
            int decayID = decay.CalDecay(PB, Sp, 0); // decay to ground state
            if( decayID == 1 ){
               PB = decay.GetDaugther_D();
               decayTheta = decay.GetAngleChange();
            }else{
               decayTheta = TMath::QuietNaN();
            }
         }
         */
                  
         //################################### tree branches
         //===== reaction
         theta1 = P1.Theta() * TMath::RadToDeg();
         theta2 = P2.Theta() * TMath::RadToDeg();
         thetaB = PB.Theta() * TMath::RadToDeg();
      
         T1 = P1.E() - P1.M();
         T2 = P2.E() - P2.M();
         TB = PB.E() - PB.M();
         
         phi1 = P1.Phi() * TMath::RadToDeg();
         phi2 = P2.Phi() * TMath::RadToDeg();
         
         //--------- diff coordinate
         b = TMath::ASin( TMath::Sin(P2.Theta()) * TMath::Sin(P1.Phi() - P2.Phi() )) * TMath::RadToDeg();
         
         TVector3 kA = (PA.Vect()).Unit();
         TVector3 k1 = (P1.Vect()).Unit();
         TVector3 k2 = (P2.Vect()).Unit();
         TVector3 n = (k1.Cross(k2)).Unit();
         TVector3 j = (kA - (kA.Dot(n))*n).Unit();
         
         a0 = TMath::ASin(n.Dot(kA)) * TMath::RadToDeg();
         a1 = TMath::ACos(k1.Dot(j)) * TMath::RadToDeg();
         a2 = TMath::ACos(k2.Dot(j)) * TMath::RadToDeg();
         
         //----------- mass
         mB = PB.M();
         mb = Pb.M();
         
         TVector3 bA = PA.BoostVector();
            
         for(int i = 0; i < 6 ; i++){
            TLorentzVector temp;
            double xyzt[4];
            switch(i){
               case 0: temp = PA; break;
               case 1: temp = Pa; break;
               case 2: temp = P1; break;
               case 3: temp = P2; break;
               case 4: temp = PB; break;
               case 5: temp = Pb; break;
            }
            
            temp.GetXYZT(xyzt);
            
            fourVector = (TLorentzVector*) arr->ConstructedAt(i);
            fourVector->SetXYZT(xyzt[0], xyzt[1], xyzt[2], xyzt[3]);
            
            //into normal kinematic
            temp.Boost(-bA);  
            temp.GetXYZT(xyzt);
            
            fourVector = (TLorentzVector*) arrN->ConstructedAt(i);
            fourVector->SetXYZT(xyzt[0], xyzt[1], xyzt[2], xyzt[3]);
            
         }
         
         
         //==== Helios
         int hit1 = helios1.CalHit(P1, Z1, PB, ZB);
         int hit2 = helios2.CalHit(P2, Z2, PB, ZB);
         
         double recoilZ = 1000;
         
         e1 = helios1.GetEnergy() + gRandom->Gaus(0, eSigma);
         z1 = helios1.GetZ() ; 
         t1 = helios1.GetTime();
         rho1 = helios1.GetRho();
         x1h = helios1.GetXPos(recoilZ);
         y1h = helios1.GetYPos(recoilZ);
         r1h = helios1.GetR(recoilZ);
         
         e2 = helios2.GetEnergy() + gRandom->Gaus(0, eSigma);
         z2 = helios2.GetZ() ; 
         t2 = helios2.GetTime();
         rho2 = helios2.GetRho();
         x2h = helios2.GetXPos(recoilZ);
         y2h = helios2.GetYPos(recoilZ);
         r2h = helios2.GetR(recoilZ);
         
         double gammaA = PA.Gamma();
         double betaA = PA.Beta();
         
         Sp2 = (1-gammaA)* 938.272 - gammaA*(e1+e2) + betaA*gammaA*(P1.Pz() + P2.Pz()) ;
         
         //printf("%f, %f | %f, %f \n", e1, z1, e2, z2);
         
         //change thetaNN into deg
         thetaNN = thetaNN * TMath::RadToDeg();
         
         if( hit1 == 1) {
            count ++;
         }
         
         if( isReDo ){
            if( hit1 == 1) {
               redoFlag = false;
            }else{
               redoFlag = true;
               //printf("%d, %2d, thetaNN : %f, theta : %f, z0: %f \n", i, hit, thetaNN * TMath::RadToDeg(), thetab, helios.GetZ0());
            }
         }else{
            redoFlag = false;
         }
         
         
      }while( redoFlag );
      tree->Fill();
      
      //#################################################################### Timer  
      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;
         }
      }
   }
   
   saveFile->Write();
   saveFile->Close();
   
   printf("=============== done. saved as %s. count(hit==1) : %d\n", saveFileName.Data(), count);
   
   gROOT->ProcessLine(".q");
   
   /**/

}
