#include "BeamReactionSteppingAction.hh"

#include "BeamConfig.hh"

#include "ReactionGenerator.hh"
#include "WireTrackingManager.hh"

#include "G4Step.hh"
#include "G4Track.hh"
#include "G4DynamicParticle.hh"
#include "G4EventManager.hh"
#include "G4StackManager.hh"
#include "G4ParticleDefinition.hh"
#include "G4Proton.hh"
#include "G4Alpha.hh"
#include "G4SystemOfUnits.hh"
#include "G4RandomTools.hh"

#include <cmath>

BeamReactionSteppingAction::BeamReactionSteppingAction()
: G4UserSteppingAction(),
  fReaction(new ReactionGenerator())
{}

BeamReactionSteppingAction::~BeamReactionSteppingAction()
{
  delete fReaction;
}

void BeamReactionSteppingAction::UserSteppingAction(const G4Step* step)
{
  WireTrackingManager::Instance().UpdateForStep(step);

  const G4Track* track = step->GetTrack();
  if (track->GetTrackStatus() != fAlive) {
    return;
  }

  const G4ParticleDefinition* particle = track->GetDefinition();
  if (particle->GetParticleType() != "nucleus") {
    return;
  }

  if (particle->GetAtomicNumber() != BeamConfig::kAtomicNumber ||
      particle->GetAtomicMass() != BeamConfig::kAtomicMass) {
    return;
  }

  const auto* prePoint = step->GetPreStepPoint();
  if (!prePoint || !prePoint->GetPhysicalVolume()) {
    return;
  }

  if (prePoint->GetPhysicalVolume()->GetName() != BeamConfig::kTargetVolumeName) {
    return;
  }

  const G4double stepLength = step->GetStepLength();
  if (stepLength <= 0.0) {
    return;
  }

  const auto* material = prePoint->GetMaterial();
  if (!material) {
    return;
  }

  const G4double atomDensity = material->GetTotNbOfAtomsPerVolume();
  const G4double sigma = fReaction->GetEffectiveCrossSection();
  const G4double probability = 1.0 - std::exp(-atomDensity * sigma * stepLength);

  if (G4UniformRand() > probability) {
    return;
  }

  ReactionOutput output = fReaction->SampleEvent(track->GetKineticEnergy(), track->GetMomentumDirection());
  if (output.kineticEnergy <= 0.0) {
    return;
  }

  const G4ThreeVector momentum(output.px, output.py, output.pz);
  G4ThreeVector direction = momentum;
  if (direction.mag2() > 0.0) {
    direction = direction.unit();
  } else {
    direction = G4ThreeVector(0.0, 0.0, 1.0);
  }

  G4ParticleDefinition* product = nullptr;
  if (output.channel == ReactionChannel::Proton) {
    product = G4Proton::ProtonDefinition();
  } else {
    product = G4Alpha::AlphaDefinition();
  }

  auto* dynamicParticle = new G4DynamicParticle(product, direction, output.kineticEnergy);
  auto* secondaryTrack = new G4Track(dynamicParticle, track->GetGlobalTime(), prePoint->GetPosition());
  secondaryTrack->SetParentID(track->GetTrackID());
  secondaryTrack->SetTouchableHandle(track->GetTouchableHandle());

  G4EventManager::GetEventManager()->GetStackManager()->PushOneTrack(secondaryTrack, nullptr);

  G4Track* mutableTrack = const_cast<G4Track*>(track);
  mutableTrack->SetTrackStatus(fStopAndKill);
}
