//
// ********************************************************************
// * 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 Run.cc
/// \brief Implementation of the Run class

#include "Run.hh"

#include "EmAcceptance.hh"
#include "PrimaryGeneratorAction.hh"

#include "G4Run.hh"
#include "G4SystemOfUnits.hh"
#include "G4UnitsTable.hh"

#include <iomanip>

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

Run::Run(DetectorConstruction* det, PrimaryGeneratorAction* kin) : fDet(det), fKin(kin)
{
  Reset();
}

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

void Run::Reset()
{
  f_nLbin = fDet->GetnLtot();
  f_dEdL.resize(f_nLbin);
  fSumELongit.resize(f_nLbin);
  fSumELongitCumul.resize(f_nLbin);
  fSumE2Longit.resize(f_nLbin);
  fSumE2LongitCumul.resize(f_nLbin);

  f_nRbin = fDet->GetnRtot();
  f_dEdR.resize(f_nRbin);
  fSumERadial.resize(f_nRbin);
  fSumERadialCumul.resize(f_nRbin);
  fSumE2Radial.resize(f_nRbin);
  fSumE2RadialCumul.resize(f_nRbin);

  fChargedStep = 0.0;
  fNeutralStep = 0.0;

  fVerbose = 0;

  // initialize arrays of cumulative energy deposition
  //
  for (G4int i = 0; i < f_nLbin; ++i) {
    fSumELongit[i] = fSumE2Longit[i] = fSumELongitCumul[i] = fSumE2LongitCumul[i] = 0.;
  }
  for (G4int j = 0; j < f_nRbin; ++j) {
    fSumERadial[j] = fSumE2Radial[j] = fSumERadialCumul[j] = fSumE2RadialCumul[j] = 0.;
  }
  // initialize track length
  fSumChargTrLength = fSum2ChargTrLength = fSumNeutrTrLength = fSum2NeutrTrLength = 0.;
}

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

void Run::InitializePerEvent()
{
  // initialize arrays of energy deposit per bin
  for (G4int i = 0; i < f_nLbin; ++i) {
    f_dEdL[i] = 0.;
  }

  for (G4int j = 0; j < f_nRbin; ++j) {
    f_dEdR[j] = 0.;
  }

  // initialize tracklength
  fChargTrLength = fNeutrTrLength = 0.;
}

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

void Run::FillPerEvent()
{
  // accumulate statistic
  //
  G4double dLCumul = 0.;
  for (G4int i = 0; i < f_nLbin; ++i) {
    fSumELongit[i] += f_dEdL[i];
    fSumE2Longit[i] += f_dEdL[i] * f_dEdL[i];
    dLCumul += f_dEdL[i];
    fSumELongitCumul[i] += dLCumul;
    fSumE2LongitCumul[i] += dLCumul * dLCumul;
  }

  G4double dRCumul = 0.;
  for (G4int j = 0; j < f_nRbin; j++) {
    fSumERadial[j] += f_dEdR[j];
    fSumE2Radial[j] += f_dEdR[j] * f_dEdR[j];
    dRCumul += f_dEdR[j];
    fSumERadialCumul[j] += dRCumul;
    fSumE2RadialCumul[j] += dRCumul * dRCumul;
  }

  fSumChargTrLength += fChargTrLength;
  fSum2ChargTrLength += fChargTrLength * fChargTrLength;
  fSumNeutrTrLength += fNeutrTrLength;
  fSum2NeutrTrLength += fNeutrTrLength * fNeutrTrLength;

  // fill histograms
  //

  G4double Ekin = fKin->GetParticleGun()->GetParticleEnergy();
  G4double mass = fKin->GetParticleGun()->GetParticleDefinition()->GetPDGMass();
  G4double radl = fDet->GetMaterial()->GetRadlen();

  G4AnalysisManager* analysisManager = G4AnalysisManager::Instance();
  analysisManager->FillH1(1, 100. * dLCumul / (Ekin + mass));
  analysisManager->FillH1(2, fChargTrLength / radl);
  analysisManager->FillH1(3, fNeutrTrLength / radl);

  // profiles
  G4double norm = 100. / (Ekin + mass);
  G4double dLradl = fDet->GetdLradl();
  for (G4int i = 0; i < f_nLbin; ++i) {
    G4double bin = (i + 0.5) * dLradl;
    analysisManager->FillP1(0, bin, norm * f_dEdL[i] / dLradl);
  }
  G4double dRradl = fDet->GetdRradl();
  for (G4int j = 0; j < f_nRbin; ++j) {
    G4double bin = (j + 0.5) * dRradl;
    analysisManager->FillP1(1, bin, norm * f_dEdR[j] / dRradl);
  }
}

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

void Run::Merge(const G4Run* run)
{
  const Run* localRun = static_cast<const Run*>(run);

  fChargedStep += localRun->fChargedStep;
  fNeutralStep += localRun->fNeutralStep;

  for (G4int i = 0; i < f_nLbin; ++i) {
    fSumELongit[i] += localRun->fSumELongit[i];
    fSumE2Longit[i] += localRun->fSumE2Longit[i];
    fSumELongitCumul[i] += localRun->fSumELongitCumul[i];
    fSumE2LongitCumul[i] += localRun->fSumE2LongitCumul[i];
  }

  for (G4int j = 0; j < f_nRbin; ++j) {
    fSumERadial[j] += localRun->fSumERadial[j];
    fSumE2Radial[j] += localRun->fSumE2Radial[j];
    fSumERadialCumul[j] += localRun->fSumERadialCumul[j];
    fSumE2RadialCumul[j] += localRun->fSumE2RadialCumul[j];
  }

  fSumChargTrLength += localRun->fSumChargTrLength;
  fSum2ChargTrLength += localRun->fSum2ChargTrLength;
  fSumNeutrTrLength += localRun->fSumNeutrTrLength;
  fSum2NeutrTrLength += localRun->fSum2NeutrTrLength;

  G4Run::Merge(run);
}

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

void Run::EndOfRun(G4double edep, G4double rms, G4double& limit)
{
  G4int NbOfEvents = GetNumberOfEvent();

  G4double kinEnergy = fKin->GetParticleGun()->GetParticleEnergy();
  assert(NbOfEvents * kinEnergy > 0);

  fChargedStep /= G4double(NbOfEvents);
  fNeutralStep /= G4double(NbOfEvents);

  G4double mass = fKin->GetParticleGun()->GetParticleDefinition()->GetPDGMass();
  G4double norme = 100. / (NbOfEvents * (kinEnergy + mass));

  // longitudinal
  //
  G4double dLradl = fDet->GetdLradl();

  MyVector MeanELongit(f_nLbin), rmsELongit(f_nLbin);
  MyVector MeanELongitCumul(f_nLbin), rmsELongitCumul(f_nLbin);

  G4AnalysisManager* analysisManager = G4AnalysisManager::Instance();

  G4int i;
  for (i = 0; i < f_nLbin; ++i) {
    MeanELongit[i] = norme * fSumELongit[i];
    rmsELongit[i] =
      norme * std::sqrt(std::abs(NbOfEvents * fSumE2Longit[i] - fSumELongit[i] * fSumELongit[i]));

    MeanELongitCumul[i] = norme * fSumELongitCumul[i];
    rmsELongitCumul[i] = norme
                         * std::sqrt(std::abs(NbOfEvents * fSumE2LongitCumul[i]
                                              - fSumELongitCumul[i] * fSumELongitCumul[i]));
    G4double bin = (i + 0.5) * dLradl;
    analysisManager->FillH1(4, bin, MeanELongit[i] / dLradl);
    analysisManager->FillH1(5, bin, rmsELongit[i] / dLradl);
    bin = (i + 1) * dLradl;
    analysisManager->FillH1(6, bin, MeanELongitCumul[i]);
    analysisManager->FillH1(7, bin, rmsELongitCumul[i]);
  }

  // radial
  //
  G4double dRradl = fDet->GetdRradl();

  MyVector MeanERadial(f_nRbin), rmsERadial(f_nRbin);
  MyVector MeanERadialCumul(f_nRbin), rmsERadialCumul(f_nRbin);

  for (i = 0; i < f_nRbin; ++i) {
    MeanERadial[i] = norme * fSumERadial[i];
    rmsERadial[i] =
      norme * std::sqrt(std::abs(NbOfEvents * fSumE2Radial[i] - fSumERadial[i] * fSumERadial[i]));

    MeanERadialCumul[i] = norme * fSumERadialCumul[i];
    rmsERadialCumul[i] = norme
                         * std::sqrt(std::abs(NbOfEvents * fSumE2RadialCumul[i]
                                              - fSumERadialCumul[i] * fSumERadialCumul[i]));

    G4double bin = (i + 0.5) * dRradl;
    analysisManager->FillH1(8, bin, MeanERadial[i] / dRradl);
    analysisManager->FillH1(9, bin, rmsERadial[i] / dRradl);
    bin = (i + 1) * dRradl;
    analysisManager->FillH1(10, bin, MeanERadialCumul[i]);
    analysisManager->FillH1(11, bin, rmsERadialCumul[i]);
  }

  // find Moliere confinement
  //
  const G4double EMoliere = 90.;
  G4double iMoliere = 0.;
  if ((MeanERadialCumul[0] <= EMoliere) && (MeanERadialCumul[f_nRbin - 1] >= EMoliere)) {
    G4int imin = 0;
    while ((imin < f_nRbin - 1) && (MeanERadialCumul[imin] < EMoliere)) {
      ++imin;
    }

    G4double del = MeanERadialCumul[imin + 1] - MeanERadialCumul[imin];
    G4double ratio = (del > 0.0) ? (EMoliere - MeanERadialCumul[imin]) / del : 0.0;
    iMoliere = 1. + imin + ratio;
  }

  // track length
  //
  norme = 1. / (NbOfEvents * (fDet->GetMaterial()->GetRadlen()));
  G4double MeanChargTrLength = norme * fSumChargTrLength;
  G4double rmsChargTrLength =
    norme
    * std::sqrt(std::abs(NbOfEvents * fSum2ChargTrLength - fSumChargTrLength * fSumChargTrLength));

  G4double MeanNeutrTrLength = norme * fSumNeutrTrLength;
  G4double rmsNeutrTrLength =
    norme
    * std::sqrt(std::abs(NbOfEvents * fSum2NeutrTrLength - fSumNeutrTrLength * fSumNeutrTrLength));

  // print
  std::ios::fmtflags mode = G4cout.flags();
  G4cout.setf(std::ios::fixed, std::ios::floatfield);
  G4int prec = G4cout.precision(2);

  if (fVerbose) {
    G4cout << "                 LOGITUDINAL PROFILE                   "
           << "      CUMULATIVE LOGITUDINAL PROFILE" << G4endl << G4endl;

    G4cout << "        bin   "
           << "           Mean         rms         "
           << "        bin "
           << "           Mean      rms \n"
           << G4endl;

    for (i = 0; i < f_nLbin; ++i) {
      G4double inf = i * dLradl, sup = inf + dLradl;

      G4cout << std::setw(8) << inf << "->" << std::setw(5) << sup << " radl: " << std::setw(7)
             << MeanELongit[i] << "%  " << std::setw(9) << rmsELongit[i] << "%       "
             << "      0->" << std::setw(5) << sup << " radl: " << std::setw(7)
             << MeanELongitCumul[i] << "%  " << std::setw(7) << rmsELongitCumul[i] << "% "
             << G4endl;
    }

    G4cout << G4endl << G4endl << G4endl;

    G4cout << "                  RADIAL PROFILE                   "
           << "      CUMULATIVE  RADIAL PROFILE" << G4endl << G4endl;

    G4cout << "        bin   "
           << "           Mean         rms         "
           << "        bin "
           << "           Mean      rms \n"
           << G4endl;

    for (i = 0; i < f_nRbin; ++i) {
      G4double inf = i * dRradl, sup = inf + dRradl;

      G4cout << std::setw(8) << inf << "->" << std::setw(5) << sup << " radl: " << std::setw(7)
             << MeanERadial[i] << "%  " << std::setw(9) << rmsERadial[i] << "%       "
             << "      0->" << std::setw(5) << sup << " radl: " << std::setw(7)
             << MeanERadialCumul[i] << "%  " << std::setw(7) << rmsERadialCumul[i] << "% "
             << G4endl;
    }
  }

  G4cout << "\n ===== SUMMARY ===== \n" << G4endl;

  G4cout << " Total number of events:        " << NbOfEvents << "\n"
         << " Mean number of charged steps:  " << fChargedStep << G4endl;
  G4cout << " Mean number of neutral steps:  " << fNeutralStep << "\n" << G4endl;

  G4cout << " energy deposit : " << std::setw(7) << MeanELongitCumul[f_nLbin - 1] << " % E0 +- "
         << std::setw(7) << rmsELongitCumul[f_nLbin - 1] << " % E0" << G4endl;
  G4cout << " charged traklen: " << std::setw(7) << MeanChargTrLength << " radl +- " << std::setw(7)
         << rmsChargTrLength << " radl" << G4endl;
  G4cout << " neutral traklen: " << std::setw(7) << MeanNeutrTrLength << " radl +- " << std::setw(7)
         << rmsNeutrTrLength << " radl" << G4endl;

  if (iMoliere > 0.) {
    G4double RMoliere1 = iMoliere * fDet->GetdRradl();
    G4double RMoliere2 = iMoliere * fDet->GetdRlength();
    G4cout << "\n " << EMoliere << " % confinement: radius = " << RMoliere1 << " radl  ("
           << G4BestUnit(RMoliere2, "Length") << ")"
           << "\n"
           << G4endl;
  }

  G4cout.setf(mode, std::ios::floatfield);
  G4cout.precision(prec);

  // Acceptance

  G4int nLbin = fDet->GetnLtot();
  if (limit < DBL_MAX) {
    EmAcceptance acc;
    acc.BeginOfAcceptance("Total Energy in Absorber", NbOfEvents);
    G4double e = MeanELongitCumul[nLbin - 1] / 100.;
    G4double r = rmsELongitCumul[nLbin - 1] / 100.;
    acc.EmAcceptanceGauss("Edep", NbOfEvents, e, edep, rms, limit);
    acc.EmAcceptanceGauss("Erms", NbOfEvents, r, rms, rms, 2.0 * limit);
    acc.EndOfAcceptance();
  }
  limit = DBL_MAX;
}

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