//==========================================================================
//  AIDA Detector description implementation 
//--------------------------------------------------------------------------
// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN)
// All rights reserved.
//
// For the licensing terms see $DD4hepINSTALL/LICENSE.
// For the list of contributors see $DD4hepINSTALL/doc/CREDITS.
//
// Author     : M.Frank
//
//==========================================================================

// Framework include files
#include "DDG4/Geant4SensitiveDetector.h"
#include "DDG4/Geant4Converter.h"
#include "DDG4/Geant4Hits.h"
#include "DD4hep/Segmentations.h"
#include "DD4hep/Printout.h"
#include "DD4hep/Detector.h"

// Geant4 include files
#include "G4Step.hh"
#include "G4PVPlacement.hh"

// ROOT include files
#include "TGeoNode.h"

#define DEBUG 0

// C/C++ include files
#include <iostream>
#include <stdexcept>

using namespace std;
using namespace dd4hep;
using namespace dd4hep::sim;

/// Constructor. The detector element is identified by the name
Geant4SensitiveDetector::Geant4SensitiveDetector(const string& nam, Detector& description)
  : G4VSensitiveDetector(nam), m_detDesc(description), m_detector(), m_sensitive(), m_readout(), m_hce(0) {
  m_sensitive = description.sensitiveDetector(nam);
  m_detector = description.detector(nam);
  m_readout = m_sensitive.readout();
}

/// Standard destructor
Geant4SensitiveDetector::~Geant4SensitiveDetector() {
}

/// Initialize the sensitive detector for the usage of a single hit collection
bool Geant4SensitiveDetector::defineCollection(const string& coll_name) {
  if (coll_name.empty()) {
    throw runtime_error("Geant4SensitiveDetector: No collection defined for "+name()+" of type "+string(m_sensitive.type()));
  }
  collectionName.insert(coll_name);
  return true;
}

/// Access HitCollection container names
const string& Geant4SensitiveDetector::hitCollectionName(int which) const {
  size_t w = which;
  if (w >= collectionName.size()) {
    throw runtime_error("The collection name index for subdetector " + name() + " is out of range!");
  }
  return collectionName[which];
}

/// Create single hits collection
Geant4SensitiveDetector::HitCollection* Geant4SensitiveDetector::createCollection(const string& coll_name) const {
  return new G4THitsCollection<Geant4Hit>(GetName(), coll_name);
}
namespace dd4hep {
  namespace sim {
    template <> Geant4CalorimeterHit*
    Geant4SensitiveDetector::find<Geant4CalorimeterHit>(const HitCollection* c, const HitCompare<Geant4CalorimeterHit>& cmp) {
      typedef vector<Geant4CalorimeterHit*> _V;
      const _V* v = (const _V*) c->GetVector();
      for (_V::const_iterator i = v->begin(); i != v->end(); ++i)
        if (cmp(*i))
          return *i;
      return 0;
    }
  }
}

/// Method invoked at the begining of each event.
void Geant4SensitiveDetector::Initialize(G4HCofThisEvent* HCE) {
  int count = 0;
  m_hce = HCE;
  for (G4CollectionNameVector::const_iterator i = collectionName.begin(); i != collectionName.end(); ++i, ++count) {
    G4VHitsCollection* c = createCollection(*i);
    m_hce->AddHitsCollection(GetCollectionID(count), c);
  }
}

/// Method invoked at the end of each event.
void Geant4SensitiveDetector::EndOfEvent(G4HCofThisEvent* /* HCE */) {
  m_hce = 0;
  // Eventuall print event summary
}

/// Method for generating hit(s) using the information of G4Step object.
G4bool Geant4SensitiveDetector::ProcessHits(G4Step* step, G4TouchableHistory* hist) {
  return process(step, hist);
}

/// Method for generating hit(s) using the information of G4Step object.
G4bool Geant4SensitiveDetector::process(G4Step* step, G4TouchableHistory* hist) {
  double ene_cut = m_sensitive.energyCutoff();
  if (step->GetTotalEnergyDeposit() > ene_cut) {
    if (!Geant4Hit::isGeantino(step->GetTrack())) {
#if DEBUG
      dumpStep(step, hist);
#endif
      return buildHits(step, hist);
    }
  }
#if DEBUG
  std::cout << " *** too small energy deposit : " << step->GetTotalEnergyDeposit() 
            << " < " << ene_cut << "    at " << step->GetPreStepPoint()->GetPosition() << std::endl;
#endif
  return false;
}

/// Retrieve the hits collection associated with this detector by its collection identifier
Geant4SensitiveDetector::HitCollection* Geant4SensitiveDetector::collectionByID(int id) {
  HitCollection* hc = (HitCollection*) m_hce->GetHC(id);
  if (hc)
    return hc;
  throw runtime_error("The collection index for subdetector " + name() + " is wrong!");
}

/// Retrieve the hits collection associated with this detector by its serial number
Geant4SensitiveDetector::HitCollection* Geant4SensitiveDetector::collection(int which) {
  size_t w = which;
  if (w < collectionName.size()) {
    HitCollection* hc = (HitCollection*) m_hce->GetHC(GetCollectionID(which));
    if (hc)
      return hc;
    throw runtime_error("The collection index for subdetector " + name() + " is wrong!");
  }
  throw runtime_error("The collection name index for subdetector " + name() + " is out of range!");
}

/// Method is invoked if the event abortion is occured.
void Geant4SensitiveDetector::clear() {
}

/// Dump Step information (careful: very verbose)
void Geant4SensitiveDetector::dumpStep(G4Step* st, G4TouchableHistory* /* history */) {
  Geant4StepHandler step(st);
  Geant4Mapping&    cnv = Geant4Mapping::instance();
  Position          pos1 = step.prePos();
  Position          pos2 = step.postPos();
  Momentum          mom = step.postMom();

  printout(INFO, "G4Step", "  Track:%08ld Pos:(%8f %8f %8f) -> (%f %f %f)  Mom:%7.0f %7.0f %7.0f", 
           long(step.track), pos1.X(),
           pos1.Y(), pos1.Z(), pos2.X(), pos2.Y(), pos2.Z(), mom.X(), mom.Y(), mom.Z());
  printout(INFO, "G4Step", "                pre-Vol: %s  Status:%s", 
           step.preVolume()->GetName().c_str(), step.preStepStatus());
  printout(INFO, "G4Step", "                post-Vol:%s  Status:%s", 
           step.postVolume()->GetName().c_str(),
           step.postStepStatus());

  const G4VPhysicalVolume* pv = step.volume(step.post);

  typedef Geant4GeometryMaps::PlacementMap Places;
  const Places& places = cnv.data().g4Placements;

  for (const auto& i : places )  {
    const G4VPhysicalVolume* pl = i.second;
    const G4VPhysicalVolume* qv = pl;
    if (qv == pv) {
      const TGeoNode* tpv = i.first.ptr();
      printf("           Found TGeoNode:%s!\n", tpv->GetName());
    }
  }
}

long long Geant4SensitiveDetector::getVolumeID(G4Step* aStep) {
  Geant4StepHandler step(aStep);
  Geant4VolumeManager volMgr = Geant4Mapping::instance().volumeManager();
  VolumeID id = volMgr.volumeID(step.preTouchable());

#if 0 //  additional checks ...
  const G4VPhysicalVolume* g4v = step.volume( step.pre );

  if ( id == Geant4VolumeManager::InvalidPath ) {
    ::printf("                 -->  Severe ERROR: Invalid placement path: touchable corrupted?\n");
  }
  else if ( id == Geant4VolumeManager::Insensitive ) {
    ::printf("                 -->  WARNING: Only sensitive volumes may be decoded. %s\n" , g4v->GetName().c_str() );
  }
  else if ( id == Geant4VolumeManager::NonExisting ) {
    ::printf("                 -->  WARNING: non existing placement path.\n");
  }
  else {

    std::stringstream str;
    Geant4VolumeManager::VolIDDescriptor dsc;
    Geant4VolumeManager::VolIDFields& fields = dsc.second;
    volMgr.volumeDescriptor( step.preTouchable(), dsc );
    for(Geant4VolumeManager::VolIDFields::iterator i=fields.begin(); i!=fields.end();++i) {
      str << (*i).first->name() << "=" << (*i).second << " ";
    }
    ::printf("                 -->  CellID: %X [%X] -> %s\n",id,dsc.first,str.str().c_str());
  }
#endif
  return id;
}


long long Geant4SensitiveDetector::getCellID(G4Step* s) {
  StepHandler h(s);
  Geant4VolumeManager volMgr = Geant4Mapping::instance().volumeManager();
  VolumeID            volID  = volMgr.volumeID(h.preTouchable());
  Segmentation        seg    = m_readout.segmentation();
  if ( seg.isValid() )  {
    G4ThreeVector global = 0.5 * ( h.prePosG4()+h.postPosG4());
    G4ThreeVector local  = h.preTouchable()->GetHistory()->GetTopTransform().TransformPoint(global);
    Position loc(local.x()*MM_2_CM, local.y()*MM_2_CM, local.z()*MM_2_CM);
    Position glob(global.x()*MM_2_CM, global.y()*MM_2_CM, global.z()*MM_2_CM);
    VolumeID cID = seg.cellID(loc,glob,volID);
    return cID;
  }
  return volID;
}