//
// ********************************************************************
// * License and Disclaimer                                           *
// *                                                                  *
// * The  Geant4 software  is  copyright of the Copyright Holders  of *
// * the Geant4 Collaboration.  It is provided  under  the terms  and *
// * conditions of the Geant4 Software License,  included in the file *
// * LICENSE and available at  http://cern.ch/geant4/license .  These *
// * include a list of copyright holders.                             *
// *                                                                  *
// * Neither the authors of this software system, nor their employing *
// * institutes,nor the agencies providing financial support for this *
// * work  make  any representation or  warranty, express or implied, *
// * regarding  this  software system or assume any liability for its *
// * use.  Please see the license in the file  LICENSE  and URL above *
// * for the full disclaimer and the limitation of liability.         *
// *                                                                  *
// * This  code  implementation is the result of  the  scientific and *
// * technical work of the GEANT4 collaboration.                      *
// * By using,  copying,  modifying or  distributing the software (or *
// * any work based  on the software)  you  agree  to acknowledge its *
// * use  in  resulting  scientific  publications,  and indicate your *
// * acceptance of all terms of the Geant4 Software license.          *
// ********************************************************************
//
/// \file RMC01AnalysisManager.cc
/// \brief Implementation of the RMC01AnalysisManager class

//      Class Name:        RMC01AnalysisManager
//        Author:               L. Desorgher
//         Organisation:         SpaceIT GmbH
//        Contract:        ESA contract 21435/08/NL/AT
//         Customer:             ESA/ESTEC
//////////////////////////////////////////////////////////////

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

#include "RMC01AnalysisManager.hh"
#include "G4AdjointSimManager.hh"
#include "G4SDManager.hh"
#include "RMC01SD.hh"
#include "G4THitsCollection.hh"
#include "G4Electron.hh"
#include "G4Proton.hh"
#include "G4Gamma.hh"
#include "G4Timer.hh"
#include "G4RunManager.hh"
#include "G4MTRunManager.hh"
#include "G4PhysicalConstants.hh"
#include "G4SystemOfUnits.hh"
#include "RMC01AnalysisManagerMessenger.hh"
#include "G4AccumulableManager.hh"
#include "G4Threading.hh"
#include "G4PhysicsOrderedFreeVector.hh"
#include "G4Event.hh"
#include "globals.hh"
#include "G4SPSEneDistribution.hh"
#include "RMC01PrimaryGeneratorAction.hh"

#include "G4AutoLock.hh"

namespace
{
  G4Mutex fMergeMutex = G4MUTEX_INITIALIZER;
  G4double fGlobalEdep = 0.;
  G4double fGlobalEdep2 = 0.;
  std::fstream fConvergenceFileOutput;
  G4Timer fTimer;
  G4double elapsed_time=0.;
  G4int nb_global_evt_processed=0;
}

RMC01AnalysisManager *RMC01AnalysisManager::GetInstance()
{
  static G4ThreadLocal RMC01AnalysisManager theInstance;
  return &theInstance;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

RMC01AnalysisManager::RMC01AnalysisManager()
 :fAccumulated_edep("Accumulated_edep", 0.),
  fAccumulated_edep2("Accumulated_edep2", 0.), fMean_edep(0.),
  fError_mean_edep(0.), fRelative_error(0.),
  fPrecision_to_reach(0.),fStop_run_if_precision_reached(true),
  fNb_evt_modulo_for_convergence_test(5000),
  fEdep_rmatrix_vs_electron_prim_energy(0),
  fElectron_current_rmatrix_vs_electron_prim_energy(0),
  fGamma_current_rmatrix_vs_electron_prim_energy(0),
  fEdep_rmatrix_vs_gamma_prim_energy(0),
  fElectron_current_rmatrix_vs_gamma_prim_energy(0),
  fGamma_current_rmatrix_vs_gamma_prim_energy(0),
  fEdep_rmatrix_vs_proton_prim_energy(0),
  fElectron_current_rmatrix_vs_proton_prim_energy(0),
  fProton_current_rmatrix_vs_proton_prim_energy(0),
  fGamma_current_rmatrix_vs_proton_prim_energy(0),
  fFactoryOn(false),
  fPrimSpectrumType(EXPO),
  fAlpha_or_E0(.5*MeV),fAmplitude_prim_spectrum (1.),
  fEmin_prim_spectrum(1.*keV),fEmax_prim_spectrum (20.*MeV),
  fAdjoint_sim_mode(true),fNb_evt_per_adj_evt(2)
{ 
  
  fMsg = new RMC01AnalysisManagerMessenger(this);

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

  //---------------------------------
  //Primary particle ID for normalisation of adjoint results
  //---------------------------------
  
  fPrimPDG_ID = G4Electron::Electron()->GetPDGEncoding();
  
  fFileName[0] = "sim";

  G4AccumulableManager *accumulableManager = G4AccumulableManager::Instance();
  accumulableManager->Register(fAccumulated_edep);
  accumulableManager->Register(fAccumulated_edep2);
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

RMC01AnalysisManager::~RMC01AnalysisManager() 
{;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void RMC01AnalysisManager::BeginOfRun(const G4Run* aRun)
{
  fIsEndOfRun =false;
  G4AccumulableManager *accumulableManager = G4AccumulableManager::Instance();
  accumulableManager->Reset();
  fRelative_error=1.;
  fMean_edep=0.;
  fError_mean_edep=0.;

  fAdjoint_sim_mode =G4AdjointSimManager::GetInstance()->GetAdjointSimMode();

  if (fAdjoint_sim_mode)
  {
    fNb_evt_per_adj_evt=aRun->GetNumberOfEventToBeProcessed()/
                       G4AdjointSimManager::GetInstance()->GetNbEvtOfLastRun();
    if (G4Threading::IsMasterThread()
      || !G4Threading::IsMultithreadedApplication())
    {
      fConvergenceFileOutput.open("ConvergenceOfAdjointSimulationResults.txt",
                                                                std::ios::out);
      fConvergenceFileOutput << "Normalised Edep[MeV]\terror[MeV]"
                             << "\tcomputing_time[s]\tnb_adjoint_event"
                             << std::endl;
      fConvergenceFileOutput.setf(std::ios::scientific);
      fConvergenceFileOutput.precision(6);
    }
  }
  else
  {
    if (G4Threading::IsMasterThread()
      || !G4Threading::IsMultithreadedApplication())
    {
      fConvergenceFileOutput.open("ConvergenceOfForwardSimulationResults.txt",
                                                               std::ios::out);
      fConvergenceFileOutput
        << "Edep per event [MeV]\terror[MeV]\tcomputing_time[s]\tnb_event"
        <<std::endl;
      fConvergenceFileOutput.setf(std::ios::scientific);
      fConvergenceFileOutput.precision(6);
    }
  }
  Book();
  if (G4Threading::IsMasterThread()
     || !G4Threading::IsMultithreadedApplication())
  {
    fGlobalEdep = 0.;
    fGlobalEdep2 = 0.;
    nb_global_evt_processed=0;
    fTimer.Start();
    elapsed_time=0.;
  }
}
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void RMC01AnalysisManager::EndOfRun(const G4Run* aRun)
{
  G4int nb_evt=aRun->GetNumberOfEvent();
  G4double factor =1./ nb_evt;
  G4AccumulableManager *accumulableManager = G4AccumulableManager::Instance();
  accumulableManager->Merge();
  if (G4Threading::IsMasterThread()
    || !G4Threading::IsMultithreadedApplication())
  {
    fTimer.Stop();
    elapsed_time+=fTimer.GetRealElapsed();
  }

  fIsEndOfRun =true;
  ComputeMeanEdepAndError(fMean_edep,fError_mean_edep,nb_evt);

  if (!fAdjoint_sim_mode)
  {
    if (G4Threading::IsMasterThread()
      || !G4Threading::IsMultithreadedApplication())
    {
      G4cout << "Results of forward simulation!" << std::endl;
      G4cout << "edep per event [MeV] = " << fMean_edep << std::endl;
      G4cout << "precision[MeV] = " << fError_mean_edep << std::endl;
    }
  }
  else
  {
    if (G4Threading::IsMasterThread())
    {
      G4cout << "Results of reverse/adjoint simulation!" << std::endl;
      G4cout << "normalised edep [MeV] = " << fMean_edep << std::endl;
      G4cout << "precision[MeV] = " << fError_mean_edep << std::endl;
    }
    factor = 1.*G4AdjointSimManager::GetInstance()->GetNbEvtOfLastRun()
           * fNb_evt_per_adj_evt/aRun->GetNumberOfEvent();
    nb_evt=nb_evt/fNb_evt_per_adj_evt;
  }
  Save(factor);
  if (G4Threading::IsMasterThread()
     || !G4Threading::IsMultithreadedApplication())
  {
    fConvergenceFileOutput << fMean_edep << '\t' << fError_mean_edep
                           << '\t' << elapsed_time << '\t' << nb_evt
                           << std::endl;
    fConvergenceFileOutput.close();
  }
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void RMC01AnalysisManager::BeginOfEvent(const G4Event* )
{
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void RMC01AnalysisManager::EndOfEvent(const G4Event* anEvent)
{  
  if (fAdjoint_sim_mode) EndOfEventForAdjointSimulation(anEvent);
  else EndOfEventForForwardSimulation(anEvent);

  //Test convergence. The error is already computed
  //--------------------------------------
  //Nb event is the nb event already performed by the Thread
  G4int nb_event = G4RunManager::GetRunManager()
                 ->GetCurrentRun()->GetNumberOfEvent();
  nb_event = anEvent->GetEventID()+1;

  if (fAdjoint_sim_mode)
  {
    G4double  n_adj_evt= nb_event/fNb_evt_per_adj_evt;
    if (n_adj_evt*fNb_evt_per_adj_evt == nb_event)
    {
      nb_event =static_cast<G4int>(n_adj_evt);
    }        
    else nb_event=0;
  }

  if (nb_event>100 && fStop_run_if_precision_reached
     && fPrecision_to_reach >fRelative_error)
  {
    G4cout << fPrecision_to_reach*100. << "%  Precision reached!" << std::endl;
    G4RunManager::GetRunManager()->AbortRun(true);
  }
   
  if (nb_event>0 && nb_event % fNb_evt_modulo_for_convergence_test == 0)
  {
    G4AutoLock lock(&fMergeMutex);
    fTimer.Stop();
    elapsed_time+=fTimer.GetRealElapsed();
    fTimer.Start();
    fConvergenceFileOutput << fMean_edep << '\t' << fError_mean_edep
                           << '\t' << elapsed_time << '\t' << nb_event
                           << std::endl;
  }
}   

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void  RMC01AnalysisManager::EndOfEventForForwardSimulation(
                                                     const G4Event* anEvent)
{  
   
   G4SDManager* SDman = G4SDManager::GetSDMpointer();
   G4HCofThisEvent* HCE = anEvent->GetHCofThisEvent();
   RMC01DoubleWithWeightHitsCollection* edepCollection =
         (RMC01DoubleWithWeightHitsCollection*)
              (HCE->GetHC(0));

   RMC01DoubleWithWeightHitsCollection* electronCurrentCollection =
             (RMC01DoubleWithWeightHitsCollection*)
                (HCE->GetHC(SDman->GetCollectionID("current_electron")));

   RMC01DoubleWithWeightHitsCollection* protonCurrentCollection =
             (RMC01DoubleWithWeightHitsCollection*)
                   (HCE->GetHC(SDman->GetCollectionID("current_proton")));

   RMC01DoubleWithWeightHitsCollection* gammaCurrentCollection =
             (RMC01DoubleWithWeightHitsCollection*)
                     (HCE->GetHC(SDman->GetCollectionID("current_gamma")));
   
   // Total energy deposited in Event
   // -------------------------------
   G4double totEdep=0; 
   size_t i;
   if(edepCollection)
     for (i=0;i<edepCollection->entries();i++)
       totEdep+=(*edepCollection)[i]->GetValue()
               *(*edepCollection)[i]->GetWeight();
   {
     G4AutoLock lock(&fMergeMutex);
     nb_global_evt_processed +=1;
   }
   if (totEdep>0.)
   {
     fAccumulated_edep +=totEdep ;
     fAccumulated_edep2 +=totEdep*totEdep ;

     G4AutoLock lock(&fMergeMutex);
     fGlobalEdep += totEdep ;
     fGlobalEdep2 += totEdep*totEdep ;
     G4PrimaryParticle* thePrimary= anEvent->GetPrimaryVertex()->GetPrimary();
     G4double E0= thePrimary->GetG4code()->GetPDGMass();
     G4double P=thePrimary->GetMomentum().mag();
     G4double prim_ekin =std::sqrt(E0*E0+P*P)-E0;
     fEdep_vs_prim_ekin->fill(prim_ekin,totEdep);
   } 
   ComputeMeanEdepAndError(fMean_edep,fError_mean_edep,nb_global_evt_processed);
   if (fError_mean_edep>0) fRelative_error= fError_mean_edep/fMean_edep;
                   
   // Particle current on sensitive cylinder
   // -------------------------------------

   if(electronCurrentCollection)
     for (i=0;i<electronCurrentCollection->entries();i++)
     {
       G4double ekin =(*electronCurrentCollection)[i]->GetValue();
       G4double weight=(*electronCurrentCollection)[i]->GetWeight();
       fElectron_current->fill(ekin,weight);
     }
   
   if(protonCurrentCollection)
     for (i=0;i<protonCurrentCollection->entries();i++)
     {
       G4double ekin =(*protonCurrentCollection)[i]->GetValue();
       G4double weight=(*protonCurrentCollection)[i]->GetWeight();
       fProton_current->fill(ekin,weight);
     }        
   
   if(gammaCurrentCollection)
     for (i=0;i<gammaCurrentCollection->entries();i++)
     {
       G4double ekin =(*gammaCurrentCollection)[i]->GetValue();
       G4double weight=(*gammaCurrentCollection)[i]->GetWeight();
       fGamma_current->fill(ekin,weight);
     }
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void  RMC01AnalysisManager::EndOfEventForAdjointSimulation(
                                                      const G4Event* anEvent)
{  
  //Output from Sensitive volume computed during the forward tracking phase
  //-----------------------------------------------------------------------
  G4SDManager* SDman = G4SDManager::GetSDMpointer();
  G4HCofThisEvent* HCE = anEvent->GetHCofThisEvent();
  RMC01DoubleWithWeightHitsCollection* edepCollection =
                  (RMC01DoubleWithWeightHitsCollection*)(
                    HCE->GetHC(SDman->GetCollectionID("edep")));

  RMC01DoubleWithWeightHitsCollection* electronCurrentCollection =
                  (RMC01DoubleWithWeightHitsCollection*)(
                 HCE->GetHC(SDman->GetCollectionID("current_electron")));

  RMC01DoubleWithWeightHitsCollection* protonCurrentCollection =
                  (RMC01DoubleWithWeightHitsCollection*)(
                 HCE->GetHC(SDman->GetCollectionID("current_proton")));

  RMC01DoubleWithWeightHitsCollection* gammaCurrentCollection =
                  (RMC01DoubleWithWeightHitsCollection*)(
                 HCE->GetHC(SDman->GetCollectionID("current_gamma")));
  
  // Computation of total energy deposited in fwd tracking phase
  // -------------------------------
  G4double totEdep=0;
  size_t i;
  if(edepCollection)
    for (i=0;i<edepCollection->entries();i++)
      totEdep+=(*edepCollection)[i]->GetValue()
              *(*edepCollection)[i]->GetWeight();

  // Output from adjoint tracking phase
  //----------------------------------------------------------------------------
  
  G4AdjointSimManager* theAdjointSimManager =
                   G4AdjointSimManager::GetInstance();

  size_t nb_adj_track =
       theAdjointSimManager->GetNbOfAdointTracksReachingTheExternalSurface();
  G4double total_normalised_weight = 0.;

  // We need to loop over the adjoint tracks that have reached the external
  // surface.
  for (size_t j=0;j<nb_adj_track;j++)
  {
    G4int pdg_nb =theAdjointSimManager
         ->GetFwdParticlePDGEncodingAtEndOfLastAdjointTrack(j);
    G4double prim_ekin=theAdjointSimManager
                              ->GetEkinAtEndOfLastAdjointTrack(j);
    G4double adj_weight=theAdjointSimManager
                             ->GetWeightAtEndOfLastAdjointTrack(j);
 
    //Factor of normalisation to user defined prim spectrum (power law or exp)
    //------------------------------------------------------------------------
    G4double normalised_weight = 0.;
    if (pdg_nb== fPrimPDG_ID && prim_ekin>= fEmin_prim_spectrum
                                         && prim_ekin<= fEmax_prim_spectrum)
      normalised_weight =
                adj_weight*PrimDiffAndDirFluxForAdjointSim(prim_ekin);
    total_normalised_weight += normalised_weight;

    // Answer matrices
    // ---------------
    G4AnaH1* edep_rmatrix =0;
    G4AnaH2* electron_current_rmatrix =0;
    G4AnaH2* gamma_current_rmatrix =0;
    G4AnaH2* proton_current_rmatrix =0;

    if (pdg_nb == G4Electron::Electron()->GetPDGEncoding())  //e- matrices
    {
      edep_rmatrix = fEdep_rmatrix_vs_electron_prim_energy;
      electron_current_rmatrix =
                          fElectron_current_rmatrix_vs_electron_prim_energy;
      gamma_current_rmatrix = fGamma_current_rmatrix_vs_electron_prim_energy;
    }
    else if (pdg_nb == G4Gamma::Gamma()->GetPDGEncoding())
    {
      //gammma answer matrices
      edep_rmatrix = fEdep_rmatrix_vs_gamma_prim_energy;
      electron_current_rmatrix = fElectron_current_rmatrix_vs_gamma_prim_energy;
      gamma_current_rmatrix = fGamma_current_rmatrix_vs_gamma_prim_energy;
    }
    else if (pdg_nb == G4Proton::Proton()->GetPDGEncoding()){
      //proton answer matrices
      edep_rmatrix = fEdep_rmatrix_vs_proton_prim_energy;
      electron_current_rmatrix =
                         fElectron_current_rmatrix_vs_proton_prim_energy;
      gamma_current_rmatrix = fGamma_current_rmatrix_vs_proton_prim_energy;
      proton_current_rmatrix = fProton_current_rmatrix_vs_proton_prim_energy;
    }
    // Register histo edep vs prim ekin
    //----------------------------------
    if (normalised_weight>0) fEdep_vs_prim_ekin
                        ->fill(prim_ekin,totEdep*normalised_weight);
    // Registering answer matrix
    //---------------------------
    edep_rmatrix->fill(prim_ekin,totEdep*adj_weight/cm2);
    
    //Registering of current of particles on the sensitive volume
    //------------------------------------------------------------
   
    if(electronCurrentCollection)
      for (i=0;i<electronCurrentCollection->entries();i++)
      {
        G4double ekin =(*electronCurrentCollection)[i]->GetValue();
        G4double weight=(*electronCurrentCollection)[i]->GetWeight();
        fElectron_current->fill(ekin,weight*normalised_weight);
        if(electron_current_rmatrix)
          electron_current_rmatrix->fill(prim_ekin,ekin,weight*adj_weight/cm2);
      }
    if(protonCurrentCollection)
      for (i=0;i<protonCurrentCollection->entries();i++)
      {
        G4double ekin =(*protonCurrentCollection)[i]->GetValue();
        G4double weight=(*protonCurrentCollection)[i]->GetWeight();
        fProton_current->fill(ekin,weight*normalised_weight);
        if(proton_current_rmatrix)
          proton_current_rmatrix->fill(prim_ekin,ekin,weight*adj_weight/cm2);
      }
    if(gammaCurrentCollection)
      for (i=0;i<gammaCurrentCollection->entries();i++)
      {
        G4double ekin =(*gammaCurrentCollection)[i]->GetValue();
        G4double weight=(*gammaCurrentCollection)[i]->GetWeight();
        fGamma_current->fill(ekin,weight*normalised_weight);
        if(gamma_current_rmatrix)
          gamma_current_rmatrix->fill(prim_ekin,ekin,weight*adj_weight/cm2);
      }
  }

  // Registering of total energy deposited in Event
  // -------------------------------
  {
    G4AutoLock lock(&fMergeMutex);
    nb_global_evt_processed +=1;
  }
  G4bool new_mean_computed=false;
  if (totEdep>0.)
  {
    if (total_normalised_weight>0.)
    {
      G4double edep=totEdep* total_normalised_weight;

      // Check if the edep is not wrongly too high
      // -----------------------------------------
      G4double new_mean , new_error;
      fAccumulated_edep +=edep;
      fAccumulated_edep2 +=edep*edep;


      G4AutoLock lock(&fMergeMutex);
      fGlobalEdep += edep;
      fGlobalEdep2 += edep*edep;

      ComputeMeanEdepAndError(new_mean,new_error,nb_global_evt_processed);
      G4double new_relative_error = 1.;
      if ( new_error >0)
        new_relative_error = new_error/ new_mean;
      fMean_edep = new_mean;
      fError_mean_edep = new_error;
      fRelative_error =new_relative_error;
      new_mean_computed=true;
    }
    if (!new_mean_computed)
    {
      G4AutoLock lock(&fMergeMutex);
      ComputeMeanEdepAndError(fMean_edep,fError_mean_edep,
                              nb_global_evt_processed);
      if (fError_mean_edep>0)
        fRelative_error= fError_mean_edep/fMean_edep;
    }
  }
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

G4double RMC01AnalysisManager::
PrimDiffAndDirFluxForAdjointSim(G4double prim_energy)
{ 
  G4double flux=fAmplitude_prim_spectrum;
  if (fPrimSpectrumType ==EXPO)
    flux*=std::exp(-prim_energy/fAlpha_or_E0);
  else
    flux*=std::pow(prim_energy, -fAlpha_or_E0);
  return flux;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
/*
void  RMC01AnalysisManager::WriteHisto(G4AnaH1* anHisto,
            G4double scaling_factor, G4String fileName, G4String header_lines)
{ std::fstream FileOutput(fileName, std::ios::out);
  FileOutput<<header_lines;
  FileOutput.setf(std::ios::scientific);
  FileOutput.precision(6);

  for (G4int i =0;i<G4int(anHisto->axis().bins());i++) {
        FileOutput<<anHisto->axis().bin_lower_edge(i)
              <<'\t'<<anHisto->axis().bin_upper_edge(i)
              <<'\t'<<anHisto->bin_height(i)*scaling_factor
              <<'\t'<<anHisto->bin_error(i)*scaling_factor<<std::endl;
  }
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void  RMC01AnalysisManager::WriteHisto(G4AnaH2* anHisto,
            G4double scaling_factor, G4String fileName, G4String header_lines)
{ std::fstream FileOutput(fileName, std::ios::out);
  FileOutput<<header_lines;
  
  FileOutput.setf(std::ios::scientific);
  FileOutput.precision(6);

  for (G4int i =0;i<G4int(anHisto->axis_x().bins());i++) {
    for (G4int j =0;j<G4int(anHisto->axis_y().bins());j++) {
       FileOutput<<anHisto->axis_x().bin_lower_edge(i)
                     <<'\t'<<anHisto->axis_x().bin_upper_edge(i)
                   <<'\t'<<anHisto->axis_y().bin_lower_edge(i)
                   <<'\t'<<anHisto->axis_y().bin_upper_edge(i)
                   <<'\t'<<anHisto->bin_height(i,j)*scaling_factor
                <<'\t'<<anHisto->bin_error(i,j)*scaling_factor
                                                                  <<std::endl;
        }
  }
}
*/
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void RMC01AnalysisManager::
ComputeMeanEdepAndError(G4double& mean,G4double& error,
                        G4int nb_of_global_evt_processed)
{  
  G4double nb_event=G4double(nb_of_global_evt_processed);
  G4double factor=1.;
  G4double factor1=1.;

  if (fAdjoint_sim_mode)
  {
     nb_event /= fNb_evt_per_adj_evt;
     G4double nb_evt_sim = G4double(G4AdjointSimManager::GetInstance()
                         ->GetNbEvtOfLastRun());
     factor = nb_evt_sim;
     factor1 = factor*nb_evt_sim;
  }

  // VI: error computation now is based on number of entries and not 
  //     number of events
  // LD: This is wrong! With the use of fNentry the results were no longer
  //     correctly normalised. The mean and the error should be computed
  //     with nb_event. The old computation has been reset.

  if (nb_event>1.)
  {
    mean = fGlobalEdep*factor/nb_event;
    G4double mean_x2 = fGlobalEdep2*factor1/nb_event;
    if (fIsEndOfRun)
    {
      mean = fAccumulated_edep.GetValue()*factor/nb_event;
      mean_x2 = fAccumulated_edep2.GetValue()*factor1/nb_event;
    }
    G4double n_eff= nb_event;
    G4double var_weighted = (mean_x2 - mean*mean)*n_eff/(n_eff-1.);
    error = std::sqrt(std::max(var_weighted, 0.)/n_eff);
  }
  else
  {
    mean=0;
    error=0;
  }
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void RMC01AnalysisManager::SetPrimaryExpSpectrumForAdjointSim(
                     const G4String& particle_name, G4double omni_fluence,
                     G4double E0, G4double Emin, G4double Emax)
{
  fPrimSpectrumType = EXPO;
  if (particle_name == "e-" )
    fPrimPDG_ID = G4Electron::Electron()->GetPDGEncoding();
  else if (particle_name == "gamma")
    fPrimPDG_ID = G4Gamma::Gamma()->GetPDGEncoding();
  else if (particle_name == "proton")
    fPrimPDG_ID = G4Proton::Proton()->GetPDGEncoding();
  else
  {
    G4cout << "The particle that you did select is not in the candidate "
           << "list for primary [e-, gamma, proton]!" << G4endl;
    return;
  }        
  fAlpha_or_E0 = E0 ;
  fAmplitude_prim_spectrum = omni_fluence/E0
                           / (std::exp(-Emin/E0)-std::exp(-Emax/E0))/4./pi;
  fEmin_prim_spectrum = Emin ;
  fEmax_prim_spectrum = Emax;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void RMC01AnalysisManager::SetPrimaryPowerLawSpectrumForAdjointSim(
                           const G4String& particle_name, G4double omni_fluence,
                                 G4double alpha, G4double Emin,G4double Emax)
{
  fPrimSpectrumType=POWER;
  if (particle_name == "e-" )
    fPrimPDG_ID = G4Electron::Electron()->GetPDGEncoding();
  else if (particle_name == "gamma")
    fPrimPDG_ID = G4Gamma::Gamma()->GetPDGEncoding();
  else if (particle_name == "proton")
    fPrimPDG_ID = G4Proton::Proton()->GetPDGEncoding();
  else
  {
    G4cout << "The particle that you did select is not in the candidate"
           << " list for primary [e-, gamma, proton]!"<<G4endl;
    return;
  }        

  if (alpha ==1.)
  {
    fAmplitude_prim_spectrum = omni_fluence/std::log(Emax/Emin)/4./pi;
  }
  else
  {
    G4double p=1.-alpha;
    fAmplitude_prim_spectrum = omni_fluence/p
                             / (std::pow(Emax,p)-std::pow(Emin,p))/4./pi;
  }

  fAlpha_or_E0 = alpha;
  fEmin_prim_spectrum = Emin;
  fEmax_prim_spectrum = Emax;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void RMC01AnalysisManager::Book()
{
  //----------------------
  //Creation of histograms
  //----------------------

  //Energy binning of the histograms : 60 log bins over [1keV-1GeV]

  G4double emin=1.*keV;
  G4double emax=1.*GeV;

  //file_name
  fFileName[0]="forward_sim";
  if (fAdjoint_sim_mode) fFileName[0]="adjoint_sim";

  //Histo manager
  G4AnalysisManager* theHistoManager = G4AnalysisManager::Instance();
  theHistoManager->SetDefaultFileType("root");
  G4String extension = theHistoManager->GetFileType();
  fFileName[1] = fFileName[0] + "." + extension;
  theHistoManager->SetFirstHistoId(1);

  G4bool fileOpen = theHistoManager->OpenFile(fFileName[0]);
  if (!fileOpen)
  {
    G4cout << "\n---> RMC01AnalysisManager::Book(): cannot open "
           << fFileName[1]
           << G4endl;
    return;
  }

  // Create directories
  //   theHistoManager->SetHistoDirectoryName("histo");

  // Histograms for :
  //        1)the forward simulation results
  //        2)the Reverse MC  simulation results normalised to a user spectrum
  //------------------------------------------------------------------------

  G4int idHisto =
      theHistoManager->CreateH1(G4String("Edep_vs_prim_ekin"),
      G4String("edep vs e- primary energy"),60,emin,emax,
      "none","none",G4String("log"));
  fEdep_vs_prim_ekin = theHistoManager->GetH1(idHisto);

  idHisto = theHistoManager->CreateH1(G4String("elecron_current"),
        G4String("electron"),60,emin,emax,
        "none","none",G4String("log"));

  fElectron_current  =  theHistoManager->GetH1(idHisto);

  idHisto= theHistoManager->CreateH1(G4String("proton_current"),
        G4String("proton"),60,emin,emax,
        "none","none",G4String("log"));
  fProton_current=theHistoManager->GetH1(idHisto);

  idHisto= theHistoManager->CreateH1(G4String("gamma_current"),
          G4String("gamma"),60,emin,emax,
          "none","none",G4String("log"));
  fGamma_current=theHistoManager->GetH1(idHisto);

  // Response matrices for the adjoint simulation only
  //-----------------------------------------------
  if (fAdjoint_sim_mode)
  {
  // Response matrices for external isotropic e- source
  //--------------------------------------------------

    idHisto =
      theHistoManager->CreateH1(G4String("Edep_rmatrix_vs_electron_prim_energy"),
        G4String("electron RM vs e- primary energy"),60,emin,emax,
        "none","none",G4String("log"));
     fEdep_rmatrix_vs_electron_prim_energy = theHistoManager->GetH1(idHisto);

    idHisto =
      theHistoManager->
         CreateH2(G4String("Electron_current_rmatrix_vs_electron_prim_energy"),
         G4String("electron current  RM vs e- primary energy"),
         60,emin,emax,60,emin,emax,
         "none","none","none","none",G4String("log"),G4String("log"));

    fElectron_current_rmatrix_vs_electron_prim_energy =
                                             theHistoManager->GetH2(idHisto);

    idHisto =
      theHistoManager->
           CreateH2(G4String("Gamma_current_rmatrix_vs_electron_prim_energy"),
           G4String("gamma current  RM vs e- primary energy"),
           60,emin,emax,60,emin,emax,
           "none","none","none","none",G4String("log"),G4String("log"));

    fGamma_current_rmatrix_vs_electron_prim_energy =
                                           theHistoManager->GetH2(idHisto);

    //Response matrices for external isotropic gamma source

    idHisto =
      theHistoManager->CreateH1(G4String("Edep_rmatrix_vs_gamma_prim_energy"),
         G4String("electron RM vs gamma primary energy"),60,emin,emax,
        "none","none",G4String("log"));
    fEdep_rmatrix_vs_gamma_prim_energy = theHistoManager->GetH1(idHisto);

    idHisto =
      theHistoManager->
          CreateH2(G4String("Electron_current_rmatrix_vs_gamma_prim_energy"),
          G4String("electron current  RM vs gamma primary energy"),
          60,emin,emax,60,emin,emax,
          "none","none","none","none",G4String("log"),G4String("log"));

    fElectron_current_rmatrix_vs_gamma_prim_energy =
                                               theHistoManager->GetH2(idHisto);

    idHisto =
      theHistoManager->
          CreateH2(G4String("Gamma_current_rmatrix_vs_gamma_prim_energy"),
          G4String("gamma current  RM vs gamma primary energy"),
          60,emin,emax,60,emin,emax,
          "none","none","none","none",G4String("log"),G4String("log"));

    fGamma_current_rmatrix_vs_gamma_prim_energy= theHistoManager->GetH2(idHisto);

    // Response matrices for external isotropic proton source

    idHisto =
      theHistoManager->CreateH1(G4String("Edep_rmatrix_vs_proton_prim_energy"),
         G4String("electron RM vs proton primary energy"),60,emin,emax,
         "none","none",G4String("log"));
    fEdep_rmatrix_vs_proton_prim_energy = theHistoManager->GetH1(idHisto);

    idHisto =
      theHistoManager->
         CreateH2(G4String("Electron_current_rmatrix_vs_proton_prim_energy"),
         G4String("electron current  RM vs proton primary energy"),
         60,emin,emax,60,emin,emax,
         "none","none","none","none",G4String("log"),G4String("log"));

    fElectron_current_rmatrix_vs_proton_prim_energy =
                                                theHistoManager->GetH2(idHisto);

    idHisto =
      theHistoManager->
         CreateH2(G4String("Gamma_current_rmatrix_vs_proton_prim_energy"),
         G4String("gamma current  RM vs proton primary energy"),
         60,emin,emax,60,emin,emax,
         "none","none","none","none",G4String("log"),G4String("log"));

    fGamma_current_rmatrix_vs_proton_prim_energy =
                                              theHistoManager->GetH2(idHisto);

    idHisto =
      theHistoManager->
         CreateH2(G4String("Proton_current_rmatrix_vs_proton_prim_energy"),
         G4String("proton current  RM vs proton primary energy"),
         60,emin,emax,60,emin,emax,
         "none","none","none","none",G4String("log"),G4String("log"));

    fProton_current_rmatrix_vs_proton_prim_energy =
                                                theHistoManager->GetH2(idHisto);
  }
  fFactoryOn = true;
  G4cout << "\n----> Histogram Tree is opened in " << fFileName[1] << G4endl;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void RMC01AnalysisManager::Save(G4double scaling_factor)
{
  if (fFactoryOn)
  {
    G4AnalysisManager* theHistoManager = G4AnalysisManager::Instance();

    //scaling of results
    //-----------------
    for (int ind=1; ind<=theHistoManager->GetNofH1s();ind++)
    {
       theHistoManager->SetH1Ascii(ind,true);
       theHistoManager->ScaleH1(ind,scaling_factor);
    }
    for (int ind=1; ind<=theHistoManager->GetNofH2s();ind++)
       theHistoManager->ScaleH2(ind,scaling_factor);

    theHistoManager->Write();
    theHistoManager->CloseFile();
    G4cout << "\n----> Histogram Tree is saved in " << fFileName[1] << G4endl;

    fFactoryOn = false;
  }
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void RMC01AnalysisManager::
SetUserDefinedSpectrumPointForAdjointSim(const  G4String& particle_name,
                                         G4double omni_fluence,
                                         G4bool is_arbitrary_point_wise)
{
  fPrimSpectrumType = USER;
  if (particle_name == "e-" )
    fPrimPDG_ID = G4Electron::Electron()->GetPDGEncoding();
  else if (particle_name == "gamma")
    fPrimPDG_ID = G4Gamma::Gamma()->GetPDGEncoding();
  else if (particle_name == "proton")
    fPrimPDG_ID = G4Proton::Proton()->GetPDGEncoding();
  else
  {
    G4cout << "The particle that you did select is not in the candidate "
           << "list for primary [e-, gamma, proton]!" << G4endl;
    return;
  }
  G4cout << "Define user spectrum" << G4endl;
  G4cout << dynamic_cast<const RMC01PrimaryGeneratorAction*>
            (G4RunManager::GetRunManager()->GetUserPrimaryGeneratorAction())
         << G4endl;

  G4cout << G4MTRunManager::GetMasterRunManager()
            ->GetUserPrimaryGeneratorAction() << G4endl;
  G4SPSEneDistribution* theSPSEneDistribution =
    dynamic_cast<const RMC01PrimaryGeneratorAction*>
    (G4RunManager::GetRunManager()->GetUserPrimaryGeneratorAction())
     ->GetParticleGun()->GetCurrentSource()->GetEneDist();
  G4PhysicsOrderedFreeVector* aVec = nullptr;

  f_energy_vec.clear();
  f_flux_vec.clear();

  if (!is_arbitrary_point_wise)
  {
    aVec = new G4PhysicsOrderedFreeVector(theSPSEneDistribution
                                          ->GetUserDefinedEnergyHisto());
    fEmin_prim_spectrum = aVec->GetMinEnergy();
    fEmax_prim_spectrum = aVec->GetMaxEnergy();
    for (size_t i=0;i<aVec->GetVectorLength();i++)
    {
      f_energy_vec.push_back(aVec->GetLowEdgeEnergy(i));
      f_flux_vec.push_back((*aVec)[i]);
    }
    f_flux_vec[0]=0.;
  }
  else
  {
    G4PhysicsOrderedFreeVector aVec1= theSPSEneDistribution->GetArbEnergyHisto();

    // Interpolation spline method does not  works properly
    // Therfore we use the linear one. We will use G4DataInterpolation

    size_t n = aVec1.GetVectorLength();
    G4double* evec=new G4double[n-1];
    G4double* fvec=new G4double[n-1];
    for (size_t i=0;i<n-1;i++)
    {
      evec[i]=aVec1.GetLowEdgeEnergy(i);
      fvec[i]=aVec1[i];
    }

    f_flux_vec.push_back(0);
    G4double  flux1=aVec1[0];
    G4double E1 =aVec1.GetLowEdgeEnergy(0);
    f_energy_vec.push_back(E1);
    fEmin_prim_spectrum = E1;

    for (size_t i=1;i<n-1;i++)
    {
      G4double flux2=aVec1[i];
      G4double E2 = aVec1.GetLowEdgeEnergy(i);
      size_t lastidx=0;

      if ((E2/E1)>1.05)  //rebin this interval
      {
        G4double E =E1*1.05;
        G4double flux=0.;
        G4double logE1,logE2,logf1,logf2,d;
        logE1=std::log(E1);
        logE2=std::log(E2);
        logf1=std::log(flux1);
        logf2=std::log(flux2);
        d=(logf2-logf1)/(logE2-logE1);

        while(E<E2)
        {
          flux=aVec1.Value(E,lastidx);
          f_energy_vec.push_back(E);
          flux=std::exp(logf1+d*(std::log(E)-logE1));
          f_flux_vec.push_back((E-E1)*(flux1+flux)/2.);
          E1=E;
          flux1=flux;
          E=E1*1.05;
        }
      }

      f_energy_vec.push_back(E2);
      f_flux_vec.push_back((E2-E1)*(flux1+flux2)/2.);
      flux1=flux2;
      E1=E2;
      fEmax_prim_spectrum = E2;
    }
  }

  G4double integral_flux =0.;
  for (size_t i=0;i<f_flux_vec.size();i++)
    integral_flux += f_flux_vec[i];
  for (size_t i=1;i<f_flux_vec.size();i++)
  {
    G4double dE=f_energy_vec[i]-f_energy_vec[i-1];
    f_flux_vec[i] = omni_fluence*f_flux_vec[i]/integral_flux/4/pi/dE;
  }
}
