//
// ********************************************************************
// * 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.          *
// ********************************************************************
//
//
//
// ------------------------------------------------------------
// GEANT 4 class implementation
//
//      ---------------- GFlashShowerModel ----------------
//
// Authors: E.Barberio & Joanna Weng - 9.11.2004
// ------------------------------------------------------------

#include "G4Electron.hh"
#include "G4Positron.hh"
#include "G4NeutrinoE.hh"
#include "G4NeutrinoMu.hh"
#include "G4NeutrinoTau.hh"
#include "G4AntiNeutrinoE.hh"
#include "G4AntiNeutrinoMu.hh"
#include "G4AntiNeutrinoTau.hh"
#include "G4PionZero.hh"
#include "G4VProcess.hh"
#include "G4ios.hh"
#include "G4LogicalVolume.hh"
#include "geomdefs.hh"

#include "GFlashShowerModel.hh"
#include "GFlashHomoShowerParameterisation.hh"
#include "GFlashSamplingShowerParameterisation.hh"
#include "GFlashEnergySpot.hh"

GFlashShowerModel::GFlashShowerModel(G4String modelName, G4Envelope* envelope)
  : G4VFastSimulationModel(modelName, envelope), PBound(0), Parameterisation(0), HMaker(0)
{
  FlagParamType = 0;
  FlagParticleContainment = 1;
  StepInX0 = 0.1;
  EnergyStop = 0.0;
  Messenger = new GFlashShowerModelMessenger(this);
}

GFlashShowerModel::GFlashShowerModel(G4String modelName)
  : G4VFastSimulationModel(modelName), PBound(0), Parameterisation(0), HMaker(0)
{
  FlagParamType = 1;
  FlagParticleContainment = 1;
  StepInX0 = 0.1;
  EnergyStop = 0.0;
  Messenger = new GFlashShowerModelMessenger(this);
}

GFlashShowerModel::~GFlashShowerModel()
{
  delete Messenger;
}

G4bool GFlashShowerModel::IsApplicable(const G4ParticleDefinition& particleType)
{
  return &particleType == G4Electron::ElectronDefinition()
         || &particleType == G4Positron::PositronDefinition();
}

/**********************************************************************/
/* Checks whether conditions of fast parameterisation  are fullfilled */
/**********************************************************************/

G4bool GFlashShowerModel::ModelTrigger(const G4FastTrack& fastTrack)

{
  G4bool select = false;
  if (FlagParamType != 0) {
    G4double ParticleEnergy = fastTrack.GetPrimaryTrack()->GetKineticEnergy();
    G4ParticleDefinition& ParticleType = *(fastTrack.GetPrimaryTrack()->GetDefinition());
    if (ParticleEnergy > PBound->GetMinEneToParametrise(ParticleType)
        && ParticleEnergy < PBound->GetMaxEneToParametrise(ParticleType))
    {
      // check conditions depending on particle flavour
      // performance to be optimized @@@@@@@
      Parameterisation->GenerateLongitudinalProfile(ParticleEnergy);
      select = CheckParticleDefAndContainment(fastTrack);
      if (select) EnergyStop = PBound->GetEneToKill(ParticleType);
    }
  }

  return select;
}

G4bool GFlashShowerModel::CheckParticleDefAndContainment(const G4FastTrack& fastTrack)
{
  G4bool filter = false;
  G4ParticleDefinition* ParticleType = fastTrack.GetPrimaryTrack()->GetDefinition();

  if (ParticleType == G4Electron::ElectronDefinition()
      || ParticleType == G4Positron::PositronDefinition())
  {
    filter = true;
    if (FlagParticleContainment == 1) {
      filter = CheckContainment(fastTrack);
    }
  }
  return filter;
}

G4bool GFlashShowerModel::CheckContainment(const G4FastTrack& fastTrack)
{
  G4bool filter = false;
  // track informations
  G4ThreeVector DirectionShower = fastTrack.GetPrimaryTrackLocalDirection();
  G4ThreeVector InitialPositionShower = fastTrack.GetPrimaryTrackLocalPosition();

  G4ThreeVector OrthoShower, CrossShower;
  // Returns orthogonal vector
  OrthoShower = DirectionShower.orthogonal();
  // Shower in direction perpendicular to OrthoShower and DirectionShower
  CrossShower = DirectionShower.cross(OrthoShower);

  G4double R = Parameterisation->GetAveR90();
  G4double Z = Parameterisation->GetAveT90();
  G4int CosPhi[4] = {1, 0, -1, 0};
  G4int SinPhi[4] = {0, 1, 0, -1};

  G4ThreeVector Position;
  G4int NlateralInside = 0;
  // pointer to solid we're in
  G4VSolid* SolidCalo = fastTrack.GetEnvelopeSolid();
  for (int i = 0; i < 4; i++) {
    // polar coordinates
    Position = InitialPositionShower + Z * DirectionShower + R * CosPhi[i] * OrthoShower
               + R * SinPhi[i] * CrossShower;

    if (SolidCalo->Inside(Position) != kOutside) NlateralInside++;
  }

  // choose to parameterise or flag when all inetc...
  if (NlateralInside == 4) filter = true;
  // std::cout << " points =   " <<NlateralInside << std::endl;
  return filter;
}

void GFlashShowerModel::DoIt(const G4FastTrack& fastTrack, G4FastStep& fastStep)
{
  // parametrise electrons
  if (fastTrack.GetPrimaryTrack()->GetDefinition() == G4Electron::ElectronDefinition()
      || fastTrack.GetPrimaryTrack()->GetDefinition() == G4Positron::PositronDefinition())
    ElectronDoIt(fastTrack, fastStep);
}

void GFlashShowerModel::ElectronDoIt(const G4FastTrack& fastTrack, G4FastStep& fastStep)
{
  // std::cout<<"--- ElectronDoit --- "<<std::endl;

  fastStep.KillPrimaryTrack();
  fastStep.ProposePrimaryTrackPathLength(0.0);
  fastStep.ProposeTotalEnergyDeposited(fastTrack.GetPrimaryTrack()->GetKineticEnergy());

  //-----------------------------
  // Get track parameters
  //-----------------------------
  // E,vect{p} and t,vec(x)
  G4double Energy = fastTrack.GetPrimaryTrack()->GetKineticEnergy();

  // axis of the shower, in global reference frame:
  G4ThreeVector DirectionShower = fastTrack.GetPrimaryTrack()->GetMomentumDirection();
  G4ThreeVector OrthoShower, CrossShower;
  OrthoShower = DirectionShower.orthogonal();
  CrossShower = DirectionShower.cross(OrthoShower);

  //--------------------------------
  /// Generate longitudinal profile
  //--------------------------------
  Parameterisation->GenerateLongitudinalProfile(Energy);
  // performance iteration @@@@@@@

  /// Initialisation of long. loop variables
  G4VSolid* SolidCalo = fastTrack.GetEnvelopeSolid();
  G4ThreeVector pos = fastTrack.GetPrimaryTrackLocalPosition();
  G4ThreeVector dir = fastTrack.GetPrimaryTrackLocalDirection();
  G4double Bound = SolidCalo->DistanceToOut(pos, dir);

  G4double Dz = 0.00;
  G4double ZEndStep = 0.00;

  G4double EnergyNow = Energy;
  G4double EneIntegral = 0.00;
  G4double LastEneIntegral = 0.00;
  G4double DEne = 0.00;

  G4double NspIntegral = 0.00;
  G4double LastNspIntegral = 0.00;
  G4double DNsp = 0.00;

  // starting point of the shower:
  G4ThreeVector PositionShower = fastTrack.GetPrimaryTrack()->GetPosition();
  G4ThreeVector NewPositionShower = PositionShower;
  G4double StepLenght = 0.00;

  //--------------------------
  /// Begin Longitudinal Loop
  //-------------------------

  do {
    // determine step size=min(1Xo,next boundary)
    G4double stepLength = StepInX0 * Parameterisation->GetX0();
    if (Bound < stepLength) {
      Dz = Bound;
      Bound = 0.00;
    }
    else {
      Dz = stepLength;
      Bound = Bound - Dz;
    }
    ZEndStep = ZEndStep + Dz;

    // Determine Energy Release in Step
    if (EnergyNow > EnergyStop) {
      LastEneIntegral = EneIntegral;
      EneIntegral = Parameterisation->IntegrateEneLongitudinal(ZEndStep);
      DEne = std::min(EnergyNow, (EneIntegral - LastEneIntegral) * Energy);
      LastNspIntegral = NspIntegral;
      NspIntegral = Parameterisation->IntegrateNspLongitudinal(ZEndStep);
      DNsp =
        std::max(1., std::floor((NspIntegral - LastNspIntegral) * Parameterisation->GetNspot()));
    }
    // end of the shower
    else {
      DEne = EnergyNow;
      DNsp = std::max(1., std::floor((1. - NspIntegral) * Parameterisation->GetNspot()));
    }
    EnergyNow = EnergyNow - DEne;

    // Apply sampling fluctuation - only in sampling calorimeters
    //
    GFlashSamplingShowerParameterisation* sp =
      dynamic_cast<GFlashSamplingShowerParameterisation*>(Parameterisation);
    if (sp) {
      G4double DEneSampling = sp->ApplySampling(DEne, Energy);
      DEne = DEneSampling;
    }

    // move particle in the middle of the step
    StepLenght = StepLenght + Dz / 2.00;
    NewPositionShower = NewPositionShower + StepLenght * DirectionShower;
    StepLenght = Dz / 2.00;

    // generate spots & hits:
    for (G4int i = 0; i < DNsp; ++i) {
      GFlashEnergySpot Spot;

      // Spot energy: the same for all spots
      Spot.SetEnergy(DEne / DNsp);
      G4double PhiSpot = Parameterisation->GeneratePhi();  // phi of spot
      G4double RSpot = Parameterisation  // radius of spot
                         ->GenerateRadius(i, Energy, ZEndStep - Dz / 2.);

      // check reference-> may be need to introduce rot matrix @@@
      // Position: equally spaced in z

      G4ThreeVector SpotPosition =
        NewPositionShower + Dz / DNsp * DirectionShower * (i + 1 / 2. - DNsp / 2.)
        + RSpot * std::cos(PhiSpot) * OrthoShower + RSpot * std::sin(PhiSpot) * CrossShower;
      Spot.SetPosition(SpotPosition);

      // Generate Hits of this spot
      HMaker->make(&Spot, &fastTrack);
    }
  } while (EnergyNow > 0.0 && Bound > 0.0);

  //---------------
  /// End Loop
  //---------------
}

/*

void
GFlashShowerModel::GammaDoIt(const G4FastTrack& fastTrack,
                                   G4FastStep&  fastStep)
{

  if( fastTrack.GetPrimaryTrack()->GetKineticEnergy() > EnergyStop )
    return;

  //deposita in uno spot unico l'energia
  //con andamento exp decrescente.

  // Kill the particle to be parametrised
  fastStep.KillPrimaryTrack();
  fastStep.SetPrimaryTrackPathLength(0.0);
  fastStep.SetTotalEnergyDeposited(fastTrack.GetPrimaryTrack()
                                   ->GetKineticEnergy());
  // other settings????
  feSpotList.clear();

  //-----------------------------
  // Get track parameters
  //-----------------------------

  // E,vect{p} and t,vec(x)
  G4double Energy =
    fastTrack.GetPrimaryTrack()->GetKineticEnergy();
  // axis of the shower, in global reference frame:
  G4ThreeVector DirectionShower =
    fastTrack.GetPrimaryTrack()->GetMomentumDirection();
  // starting point of the shower:
  G4ThreeVector PositionShower =
    fastTrack.GetPrimaryTrack()->GetPosition();

  //G4double DEneSampling = Parameterisation->ApplySampling(Energy,Energy);
  //if(DEneSampling <= 0.00) DEneSampling=Energy;

  if(Energy > 0.0)
  {
    G4double dist = Parameterisation->GenerateExponential(Energy);

    GFlashEnergySpot Spot;
    Spot.SetEnergy( Energy );
    G4ThreeVector SpotPosition = PositionShower + dist*DirectionShower;
    Spot.SetPosition(SpotPosition);

    // Record the Spot:
    feSpotList.push_back(Spot);

    //Generate Hits of this spot
    HMaker->make(Spot);
  }
}

*/
