#include "DetectorSensitiveDetector.hh"

#include "HitOutputManager.hh"
#include "WireTrackingManager.hh"

#include "G4RunManager.hh"
#include "G4Step.hh"
#include "G4Track.hh"
#include "G4TouchableHistory.hh"
#include "G4VPhysicalVolume.hh"
#include "G4StepPoint.hh"

#include <cmath>
#include <string>

namespace {
std::string ClassifyDetectorType(const std::string& volumeName)
{
  if (volumeName == "PC_A") {
    return "Anode";
  }
  if (volumeName == "PC_C") {
    return "Cathode";
  }
  if (volumeName == "QQQ") {
    return "QQQ";
  }
  if (volumeName.rfind("SX3", 0) == 0) {
    return "SX3";
  }
  return volumeName;
}
}

DetectorSensitiveDetector::DetectorSensitiveDetector(const G4String& name)
: G4VSensitiveDetector(name)
{}

DetectorSensitiveDetector::~DetectorSensitiveDetector() {}

G4bool DetectorSensitiveDetector::ProcessHits(G4Step* step, G4TouchableHistory*)
{
  if (step == nullptr) {
    return false;
  }

  G4StepPoint* preStepPoint = step->GetPreStepPoint();
  if (preStepPoint == nullptr) {
    return false;
  }

  G4VPhysicalVolume* volume = preStepPoint->GetPhysicalVolume();
  if (volume == nullptr) {
    return false;
  }

  const G4double kineticEnergy = preStepPoint->GetKineticEnergy();
  const G4double energyDeposit = step->GetTotalEnergyDeposit();
  if (kineticEnergy <= 0.0 && energyDeposit <= 0.0) {
    return false;
  }

  const G4Event* currentEvent = G4RunManager::GetRunManager()->GetCurrentEvent();
  if (currentEvent == nullptr) {
    return false;
  }

  const std::string particleName = step->GetTrack()->GetParticleDefinition()->GetParticleName();
  if (!HitOutputManager::Instance().ShouldRecordParticle(particleName)) {
    return false;
  }

  const std::string volumeName = volume->GetName();
  const std::string detectorType = ClassifyDetectorType(volumeName);
  if (detectorType != "SX3" && detectorType != "QQQ") {
    return false;
  }

  WireCrossingInfo wireInfo;
  WireTrackingManager::Instance().GetInfo(currentEvent->GetEventID(), step->GetTrack()->GetTrackID(), wireInfo);
  if (wireInfo.anodeId < 0 || wireInfo.cathodeId < 0) {
    wireInfo = WireTrackingManager::Instance().InferFromTrackGeometry(step->GetTrack(), preStepPoint);
  }

  const G4ThreeVector trackDirection = step->GetTrack()->GetMomentumDirection();
  const double sinThetaZ = std::sin(trackDirection.theta());
  const double wireDeltaESinTheta = wireInfo.deltaE * sinThetaZ;

  HitOutputManager::Instance().RecordHit(currentEvent->GetEventID(),
                                         particleName,
                                         kineticEnergy,
                                         energyDeposit,
                                         wireDeltaESinTheta,
                                         detectorType,
                                         volume->GetCopyNo(),
                                         wireInfo.anodeId,
                                         wireInfo.cathodeId,
                                         wireInfo.anodeEnergy,
                                         wireInfo.cathodeEnergy,
                                         wireInfo.deltaE,
                                         volumeName,
                                         preStepPoint->GetPosition(),
                                         step->GetTrack()->GetVertexPosition());
  return true;
}
