// $Id$
//====================================================================
//  AIDA Detector description implementation for LCD
//--------------------------------------------------------------------
//
//  Author     : M.Frank
//
//====================================================================

#include "DD4hep/LCDD.h"
#include "DD4hep/Plugins.h"
#include "DD4hep/Volumes.h"
#include "DD4hep/Printout.h"
#include "DDG4/Geant4Field.h"
#include "DDG4/Geant4Converter.h"
#include "DDG4/Factories.h"
#include "DDG4/Geant4SensitiveDetector.h"

// ROOT includes
#include "TROOT.h"
#include "TColor.h"
#include "TGeoNode.h"
#include "TGeoShape.h"
#include "TGeoCone.h"
#include "TGeoPcon.h"
#include "TGeoPgon.h"
#include "TGeoSphere.h"
#include "TGeoTorus.h"
#include "TGeoTube.h"
#include "TGeoTrd1.h"
#include "TGeoTrd2.h"
#include "TGeoArb8.h"
#include "TGeoMatrix.h"
#include "TGeoBoolNode.h"
#include "TGeoParaboloid.h"
#include "TGeoCompositeShape.h"
#include "TGeoShapeAssembly.h"
#include "TClass.h"
#include "TMath.h"

// Geant4 include files
#include "G4VSensitiveDetector.hh"
#include "G4VisAttributes.hh"
#include "G4ProductionCuts.hh"
#include "G4VUserRegionInformation.hh"

#include "G4Element.hh"
#include "G4SDManager.hh"
#include "G4Assembly.hh"
#include "G4Box.hh"
#include "G4Trd.hh"
#include "G4Tubs.hh"
#include "G4Cons.hh"
#include "G4Torus.hh"
#include "G4Sphere.hh"
#include "G4Polycone.hh"
#include "G4Polyhedra.hh"
#include "G4UnionSolid.hh"
#include "G4Paraboloid.hh"
#include "G4SubtractionSolid.hh"
#include "G4IntersectionSolid.hh"
#include "G4Region.hh"
#include "G4UserLimits.hh"
#include "G4LogicalVolume.hh"
#include "G4Material.hh"
#include "G4Element.hh"
#include "G4Isotope.hh"
#include "G4Transform3D.hh"
#include "G4ThreeVector.hh"
#include "G4PVPlacement.hh"
#include "G4ElectroMagneticField.hh"
#include "G4FieldManager.hh"
#include "G4ReflectionFactory.hh"

#include <iostream>
#include <iomanip>
#include <sstream>

using namespace DD4hep::Simulation;
using namespace DD4hep::Geometry;
using namespace DD4hep;
using namespace std;

#define private public
#include "G4AssemblyVolume.hh"
#undef private

struct Geant4AssemblyVolume : public G4AssemblyVolume {
  Geant4AssemblyVolume() {
  }
  size_t placeVolume(G4LogicalVolume* pPlacedVolume, G4Transform3D& transformation) {
    size_t id = fTriplets.size();
    this->AddPlacedVolume(pPlacedVolume, transformation);
    return id;
  }
  void imprint(std::vector<G4VPhysicalVolume*>& nodes, G4LogicalVolume* pMotherLV, G4Transform3D& transformation,
      G4int copyNumBase, G4bool surfCheck);

};

void Geant4AssemblyVolume::imprint(std::vector<G4VPhysicalVolume*>& nodes, G4LogicalVolume* pMotherLV,
    G4Transform3D& transformation, G4int copyNumBase, G4bool surfCheck) {
  G4AssemblyVolume* pAssembly = this;
  unsigned int numberOfDaughters;
  if (copyNumBase == 0) {
    numberOfDaughters = pMotherLV->GetNoDaughters();
  }
  else {
    numberOfDaughters = copyNumBase;
  }
  // We start from the first available index
  numberOfDaughters++;
  ImprintsCountPlus();

  std::vector < G4AssemblyTriplet > triplets = pAssembly->fTriplets;
  for (unsigned int i = 0; i < triplets.size(); i++) {
    G4Transform3D Ta(*(triplets[i].GetRotation()), triplets[i].GetTranslation());
    if (triplets[i].IsReflection()) {
      Ta = Ta * G4ReflectZ3D();
    }

    G4Transform3D Tfinal = transformation * Ta;

    if (triplets[i].GetVolume()) {
      // Generate the unique name for the next PV instance
      // The name has format:
      //
      // av_WWW_impr_XXX_YYY_ZZZ
      // where the fields mean:
      // WWW - assembly volume instance number
      // XXX - assembly volume imprint number
      // YYY - the name of a log. volume we want to make a placement of
      // ZZZ - the log. volume index inside the assembly volume
      //
      std::stringstream pvName;
      pvName << "av_" << GetAssemblyID() << "_impr_" << GetImprintsCount() << "_" << triplets[i].GetVolume()->GetName().c_str()
          << "_pv_" << i << std::ends;

      // Generate a new physical volume instance inside a mother
      // (as we allow 3D transformation use G4ReflectionFactory to
      //  take into account eventual reflection)
      //
      G4PhysicalVolumesPair pvPlaced = G4ReflectionFactory::Instance()->Place(Tfinal, pvName.str().c_str(),
          triplets[i].GetVolume(), pMotherLV, false, numberOfDaughters + i, surfCheck);

      // Register the physical volume created by us so we can delete it later
      //
      fPVStore.push_back(pvPlaced.first);
      nodes.push_back(pvPlaced.first);
      if (pvPlaced.second) {   // Supported by G4, but not by TGeo!
        fPVStore.push_back(pvPlaced.second);
        G4Exception("G4AssemblyVolume::MakeImprint(..)", "GeomVol0003", FatalException,
            "Fancy construct popping new mother from the stack!");
      }
    }
    else if (triplets[i].GetAssembly()) {
      // Place volumes in this assembly with composed transformation
      G4Exception("G4AssemblyVolume::MakeImprint(..)", "GeomVol0003", FatalException,
          "Assemblies within assembliesare not supported.");
    }
    else {
      G4Exception("G4AssemblyVolume::MakeImprint(..)", "GeomVol0003", FatalException, "Triplet has no volume and no assembly");
    }
  }
}

namespace {
  static TGeoNode* s_topPtr;
  static string indent = "";
  struct MyTransform3D : public G4Transform3D {
    MyTransform3D(double XX, double XY, double XZ, double DX, double YX, double YY, double YZ, double DY, double ZX, double ZY,
        double ZZ, double DZ)
        : G4Transform3D(XX, XY, XZ, DX, YX, YY, YZ, DY, ZX, ZY, ZZ, DZ) {
    }
  };

  void handleName(const TGeoNode* n) {
    TGeoVolume* v = n->GetVolume();
    TGeoMedium* m = v->GetMedium();
    TGeoShape* s = v->GetShape();
    string nam;
    printout(DEBUG, "G4", "TGeoNode:'%s' Vol:'%s' Shape:'%s' Medium:'%s'", n->GetName(), v->GetName(), s->GetName(),
        m->GetName());
  }

  class G4UserRegionInformation : public G4VUserRegionInformation {
  public:
    Region region;
    double threshold;
    bool storeSecondaries;
    G4UserRegionInformation()
        : threshold(0.0), storeSecondaries(false) {
    }
    virtual ~G4UserRegionInformation() {
    }
    virtual void Print() const {
      if (region.isValid())
        printout(DEBUG, "Region", "Name:%s", region.name());
    }
  };
}

/// Initializing Constructor
Geant4Converter::Geant4Converter(LCDD& lcdd)
    : Geant4Mapping(lcdd), m_checkOverlaps(true) {
  this->Geant4Mapping::init();
}

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

/// Dump element in GDML format to output stream
void* Geant4Converter::handleElement(const string& name, const TGeoElement* element) const {
  G4Element* g4e = data().g4Elements[element];
  if (!g4e) {
    g4e = G4Element::GetElement(name, false);
    if (!g4e) {
      if (element->GetNisotopes() > 1) {
        g4e = new G4Element(name, element->GetTitle(), element->GetNisotopes());
        for (int i = 0, n = element->GetNisotopes(); i < n; ++i) {
          TGeoIsotope* iso = element->GetIsotope(i);
          G4Isotope* g4iso = G4Isotope::GetIsotope(iso->GetName(), false);
          if (!g4iso) {
            g4iso = new G4Isotope(iso->GetName(), iso->GetZ(), iso->GetN(), iso->GetA());
          }
          g4e->AddIsotope(g4iso, element->GetRelativeAbundance(i));
        }
      }
      else {
        g4e = new G4Element(element->GetTitle(), name, element->Z(), element->A() * (g / mole));
      }
      stringstream str;
      str << (*g4e);
      printout(DEBUG, "Geant4Converter", "++ Created G4 %s", str.str().c_str());
    }
    data().g4Elements[element] = g4e;
  }
  return g4e;
}

/// Dump material in GDML format to output stream
void* Geant4Converter::handleMaterial(const string& name, const TGeoMedium* medium) const {
  G4Material* mat = data().g4Materials[medium];
  if (!mat) {
    mat = G4Material::GetMaterial(name, false);
    if (!mat) {
      TGeoMaterial* m = medium->GetMaterial();
      G4State state = kStateUndefined;
      double density = m->GetDensity() * (gram / cm3);
      if (density < 1e-25)
        density = 1e-25;
      switch (m->GetState()) {
      case TGeoMaterial::kMatStateSolid:
        state = kStateSolid;
        break;
      case TGeoMaterial::kMatStateLiquid:
        state = kStateLiquid;
        break;
      case TGeoMaterial::kMatStateGas:
        state = kStateGas;
        break;
      default:
      case TGeoMaterial::kMatStateUndefined:
        state = kStateUndefined;
        break;
      }
      if (m->IsMixture()) {
        double A_total = 0.0;
        TGeoMixture* mix = (TGeoMixture*) m;
        int nElements = mix->GetNelements();
        mat = new G4Material(name, density, nElements, state, m->GetTemperature(), m->GetPressure());
        for (int i = 0; i < nElements; ++i)
          A_total += (mix->GetAmixt())[i];
        for (int i = 0; i < nElements; ++i) {
          TGeoElement* e = mix->GetElement(i);
          G4Element* g4e = (G4Element*) handleElement(e->GetName(), e);
          if (!g4e) {
            printout(ERROR, "Material", "Missing component %s for material %s.", e->GetName(), mix->GetName());
          }
          mat->AddElement(g4e, (mix->GetAmixt())[i] / A_total);
        }
      }
      else {
        mat = new G4Material(name, m->GetZ(), m->GetA(), density, state, m->GetTemperature(), m->GetPressure());
      }
      stringstream str;
      str << (*mat);
      printout(DEBUG, "Geant4Converter", "++ Created G4 %s", str.str().c_str());
    }
    data().g4Materials[medium] = mat;
  }
  return mat;
}

/// Dump solid in GDML format to output stream
void* Geant4Converter::handleSolid(const string& name, const TGeoShape* shape) const {
  G4VSolid* solid = 0;
  if (shape) {
    if (0 != (solid = data().g4Solids[shape])) {
      return solid;
    }
    else if (shape->IsA() == TGeoShapeAssembly::Class()) {
      solid = (G4VSolid*) new G4Assembly();
    }
    else if (shape->IsA() == TGeoBBox::Class()) {
      const TGeoBBox* s = (const TGeoBBox*) shape;
      solid = new G4Box(name, s->GetDX() * CM_2_MM, s->GetDY() * CM_2_MM, s->GetDZ() * CM_2_MM);
    }
    else if (shape->IsA() == TGeoTube::Class()) {
      const TGeoTube* s = (const TGeoTube*) shape;
      solid = new G4Tubs(name, s->GetRmin() * CM_2_MM, s->GetRmax() * CM_2_MM, s->GetDz() * CM_2_MM, 0, 2. * M_PI);
    }
    else if (shape->IsA() == TGeoTubeSeg::Class()) {
      const TGeoTubeSeg* s = (const TGeoTubeSeg*) shape;
      solid = new G4Tubs(name, s->GetRmin() * CM_2_MM, s->GetRmax() * CM_2_MM, s->GetDz() * CM_2_MM,
          s->GetPhi1() * DEGREE_2_RAD, s->GetPhi2() * DEGREE_2_RAD);
    }
    else if (shape->IsA() == TGeoTrd1::Class()) {
      const TGeoTrd1* s = (const TGeoTrd1*) shape;
      solid = new G4Trd(name, s->GetDx1() * CM_2_MM, s->GetDx2() * CM_2_MM, s->GetDy() * CM_2_MM, s->GetDy() * CM_2_MM,
          s->GetDz() * CM_2_MM);
    }
    else if (shape->IsA() == TGeoTrd2::Class()) {
      const TGeoTrd2* s = (const TGeoTrd2*) shape;
      solid = new G4Trd(name, s->GetDx1() * CM_2_MM, s->GetDx2() * CM_2_MM, s->GetDy1() * CM_2_MM, s->GetDy2() * CM_2_MM,
          s->GetDz() * CM_2_MM);
    }
    else if (shape->IsA() == TGeoPgon::Class()) {
      const TGeoPgon* s = (const TGeoPgon*) shape;
      double phi_start = s->GetPhi1() * DEGREE_2_RAD;
      double phi_total = (s->GetDphi() + s->GetPhi1()) * DEGREE_2_RAD;
      vector<double> rmin, rmax, z;
      for (Int_t i = 0; i < s->GetNz(); ++i) {
        rmin.push_back(s->GetRmin(i) * CM_2_MM);
        rmax.push_back(s->GetRmax(i) * CM_2_MM);
        z.push_back(s->GetZ(i) * CM_2_MM);
      }
      solid = new G4Polyhedra(name, phi_start, phi_total, s->GetNedges(), s->GetNz(), &z[0], &rmin[0], &rmax[0]);
    }
    else if (shape->IsA() == TGeoPcon::Class()) {
      const TGeoPcon* s = (const TGeoPcon*) shape;
      double phi_start = s->GetPhi1() * DEGREE_2_RAD;
      double phi_total = (s->GetDphi() + s->GetPhi1()) * DEGREE_2_RAD;
      vector<double> rmin, rmax, z;
      for (Int_t i = 0; i < s->GetNz(); ++i) {
        rmin.push_back(s->GetRmin(i) * CM_2_MM);
        rmax.push_back(s->GetRmax(i) * CM_2_MM);
        z.push_back(s->GetZ(i) * CM_2_MM);
      }
      solid = new G4Polycone(name, phi_start, phi_total, s->GetNz(), &z[0], &rmin[0], &rmax[0]);
    }
    else if (shape->IsA() == TGeoConeSeg::Class()) {
      const TGeoConeSeg* s = (const TGeoConeSeg*) shape;
      solid = new G4Cons(name, s->GetRmin1() * CM_2_MM, s->GetRmax1() * CM_2_MM, s->GetRmin2() * CM_2_MM,
          s->GetRmax2() * CM_2_MM, s->GetDz() * CM_2_MM, s->GetPhi1() * DEGREE_2_RAD, s->GetPhi2() * DEGREE_2_RAD);
    }
    else if (shape->IsA() == TGeoParaboloid::Class()) {
      const TGeoParaboloid* s = (const TGeoParaboloid*) shape;
      solid = new G4Paraboloid(name, s->GetDz() * CM_2_MM, s->GetRlo() * CM_2_MM, s->GetRhi() * CM_2_MM);
    }
    else if (shape->IsA() == TGeoSphere::Class()) {
      const TGeoSphere* s = (const TGeoSphere*) shape;
      solid = new G4Sphere(name, s->GetRmin() * CM_2_MM, s->GetRmax() * CM_2_MM, s->GetPhi1() * DEGREE_2_RAD,
          s->GetPhi2() * DEGREE_2_RAD, s->GetTheta1() * DEGREE_2_RAD, s->GetTheta2() * DEGREE_2_RAD);
    }
    else if (shape->IsA() == TGeoTorus::Class()) {
      const TGeoTorus* s = (const TGeoTorus*) shape;
      solid = new G4Torus(name, s->GetRmin() * CM_2_MM, s->GetRmax() * CM_2_MM, s->GetR() * CM_2_MM,
          s->GetPhi1() * DEGREE_2_RAD, s->GetDphi() * DEGREE_2_RAD);
    }
    else if (shape->IsA() == TGeoCompositeShape::Class()) {
      const TGeoCompositeShape* s = (const TGeoCompositeShape*) shape;
      const TGeoBoolNode* boolean = s->GetBoolNode();
      TGeoBoolNode::EGeoBoolType oper = boolean->GetBooleanOperator();
      TGeoMatrix* m = boolean->GetRightMatrix();
      G4VSolid* left = (G4VSolid*) handleSolid(name + "_left", boolean->GetLeftShape());
      G4VSolid* right = (G4VSolid*) handleSolid(name + "_right", boolean->GetRightShape());
      const Double_t *t = m->GetTranslation();
      const Double_t *r = m->GetRotationMatrix();

      if (!left) {
        throw runtime_error("G4Converter: No left Geant4 Solid present for composite shape:" + name);
      }
      if (!right) {
        throw runtime_error("G4Converter: No right Geant4 Solid present for composite shape:" + name);
      }

      if (m->IsRotation()) {
        MyTransform3D transform(r[0], r[1], r[2], t[0] * CM_2_MM, r[3], r[4], r[5], t[1] * CM_2_MM, r[6], r[7], r[8],
            t[2] * CM_2_MM);
        if (oper == TGeoBoolNode::kGeoSubtraction)
          solid = new G4SubtractionSolid(name, left, right, transform);
        else if (oper == TGeoBoolNode::kGeoUnion)
          solid = new G4UnionSolid(name, left, right, transform);
        else if (oper == TGeoBoolNode::kGeoIntersection)
          solid = new G4IntersectionSolid(name, left, right, transform);
      }
      else {
        G4ThreeVector transform(t[0] * CM_2_MM, t[1] * CM_2_MM, t[2] * CM_2_MM);
        if (oper == TGeoBoolNode::kGeoSubtraction)
          solid = new G4SubtractionSolid(name, left, right, 0, transform);
        else if (oper == TGeoBoolNode::kGeoUnion)
          solid = new G4UnionSolid(name, left, right, 0, transform);
        else if (oper == TGeoBoolNode::kGeoIntersection)
          solid = new G4IntersectionSolid(name, left, right, 0, transform);
      }
    }

    if (!solid) {
      string err = "Failed to handle unknown solid shape:" + name + " of type " + string(shape->IsA()->GetName());
      throw runtime_error(err);
    }
    data().g4Solids[shape] = solid;
  }
  return solid;
}

/// Dump logical volume in GDML format to output stream
void* Geant4Converter::handleVolume(const string& name, const TGeoVolume* volume) const {
  Geant4GeometryInfo& info = data();
  G4LogicalVolume* vol = info.g4Volumes[volume];
  if (!vol) {
    const TGeoVolume* v = volume;
    Volume _v = Ref_t(v);
    string n = v->GetName();
    TGeoMedium* m = v->GetMedium();
    TGeoShape* s = v->GetShape();
    G4VSolid* solid = (G4VSolid*) handleSolid(s->GetName(), s);
    G4Material* medium = 0;
    bool assembly = s->IsA() == TGeoShapeAssembly::Class();

    SensitiveDetector det = _v.sensitiveDetector();
    G4VSensitiveDetector* sd = 0;

    if (det.isValid()) {
      sd = info.g4SensDets[det.ptr()];
      if (!sd) {
        throw runtime_error("G4Cnv::volume[" + name + "]:    + FATAL Failed to "
            "access Geant4 sensitive detector.");
      }
      sd->Activate(true);
    }
    LimitSet lim = _v.limitSet();
    G4UserLimits* user_limits = 0;
    if (lim.isValid()) {
      user_limits = info.g4Limits[lim.ptr()];
      if (!user_limits) {
        throw runtime_error("G4Cnv::volume[" + name + "]:    + FATAL Failed to "
            "access Geant4 user limits.");
      }
    }
    VisAttr vis = _v.visAttributes();
    G4VisAttributes* vis_attr = 0;
    if (vis.isValid()) {
      vis_attr = (G4VisAttributes*) handleVis(vis.name(), vis.ptr());
    }
    Region reg = _v.region();
    G4Region* region = 0;
    if (reg.isValid()) {
      region = info.g4Regions[reg.ptr()];
      if (!region) {
        throw runtime_error("G4Cnv::volume[" + name + "]:    + FATAL Failed to "
            "access Geant4 region.");
      }
    }

    printout(DEBUG, "Geant4Converter", "++ Convert Volume %-32s: %p %s/%s assembly:%s sensitive:%s", n.c_str(), v,
        s->IsA()->GetName(), v->IsA()->GetName(), (assembly ? "YES" : "NO"), (det.isValid() ? "YES" : "NO"));

    if (assembly) {
      vol = (G4LogicalVolume*) new G4AssemblyVolume();
      info.g4Volumes[v] = vol;
      return vol;
    }
    medium = (G4Material*) handleMaterial(m->GetName(), m);
    if (!solid) {
      throw runtime_error("G4Converter: No Geant4 Solid present for volume:" + n);
    }
    if (!medium) {
      throw runtime_error("G4Converter: No Geant4 material present for volume:" + n);
    }
    if (user_limits) {
      printout(DEBUG, "++ Volume     + Apply LIMITS settings:%-24s to volume %s.", lim.name(), _v.name());
    }
    vol = new G4LogicalVolume(solid, medium, n, 0, sd, user_limits);
    if (region) {
      printout(DEBUG, "Geant4Converter", "++ Volume     + Apply REGION settings: %s to volume %s.", reg.name(), _v.name());
      vol->SetRegion(region);
      region->AddRootLogicalVolume(vol);
    }
    if (vis_attr) {
      vol->SetVisAttributes(vis_attr);
    }
    if (sd) {
      printout(DEBUG, "Geant4Converter", "++ Volume:    + %s <> %s Solid:%s Mat:%s SD:%s", name.c_str(), vol->GetName().c_str(),
          solid->GetName().c_str(), medium->GetName().c_str(), sd->GetName().c_str());
    }
    info.g4Volumes[v] = vol;
    printout(DEBUG, "Geant4Converter", "++ Volume     + %s converted: %p ---> G4: %p", n.c_str(), v, vol);
  }
  return vol;
}

/// Dump logical volume in GDML format to output stream
void* Geant4Converter::collectVolume(const string& /* name */, const TGeoVolume* volume) const {
  Geant4GeometryInfo& info = data();
  const TGeoVolume* v = volume;
  Volume _v = Ref_t(v);
  Region reg = _v.region();
  LimitSet lim = _v.limitSet();
  SensitiveDetector det = _v.sensitiveDetector();

  if (lim.isValid())
    info.limits[lim.ptr()].insert(v);
  if (reg.isValid())
    info.regions[reg.ptr()].insert(v);
  if (det.isValid())
    info.sensitives[det.ptr()].insert(v);
  return (void*) v;
}

/// Dump volume placement in GDML format to output stream
void* Geant4Converter::handlePlacement(const string& name, const TGeoNode* node) const {
  static Double_t identity_rot[] = { 1., 0., 0., 0., 1., 0., 0., 0., 1. };
  Geant4GeometryInfo& info = data();
  PlacementMap::const_iterator g4it = info.g4Placements.find(node);
  G4VPhysicalVolume* g4 = (g4it == info.g4Placements.end()) ? 0 : (*g4it).second;
  if (!g4) {
    TGeoVolume* mot_vol = node->GetMotherVolume();
    TGeoVolume* vol = node->GetVolume();
    TGeoMatrix* trafo = node->GetMatrix();
    if (!trafo) {
      printout(FATAL, "Geant4Converter", "++ Attempt to handle placement without transformation:%p %s of type %s vol:%p", node,
          node->GetName(), node->IsA()->GetName(), vol);
    }
    else if (0 == vol) {
      printout(FATAL, "Geant4Converter", "++ Unknown G4 volume:%p %s of type %s vol:%s ptr:%p", node, node->GetName(),
          node->IsA()->GetName(), vol->IsA()->GetName(), vol);
    }
    else {
      int copy = node->GetNumber();
      G4LogicalVolume* g4vol = info.g4Volumes[vol];
      G4LogicalVolume* g4mot = info.g4Volumes[mot_vol];
      Geant4AssemblyVolume* ass_mot = (Geant4AssemblyVolume*) g4mot;
      Geant4AssemblyVolume* ass_dau = (Geant4AssemblyVolume*) g4vol;
      const Double_t* trans = trafo->GetTranslation();
      const Double_t* rot = trafo->IsRotation() ? trafo->GetRotationMatrix() : identity_rot;
      bool daughter_is_assembly = vol->IsA() == TGeoVolumeAssembly::Class();
      bool mother_is_assembly = mot_vol ? mot_vol->IsA() == TGeoVolumeAssembly::Class() : false;
      MyTransform3D transform(rot[0], rot[1], rot[2], trans[0] * CM_2_MM, rot[3], rot[4], rot[5], trans[1] * CM_2_MM, rot[6],
          rot[7], rot[8], trans[2] * CM_2_MM);
      CLHEP::HepRotation rotmat = transform.getRotation();

      if (mother_is_assembly) {	  // Mother is an assembly:
        printout(DEBUG, "Geant4Converter", "++ Assembly: AddPlacedVolume: %16p dau:%s "
            "Tr:x=%8.3f y=%8.3f z=%8.3f  Rot:phi=%7.3f theta=%7.3f psi=%7.3f\n", ass_mot,
            g4vol ? g4vol->GetName().c_str() : "---", transform.dx(), transform.dy(), transform.dz(), rotmat.getPhi(),
            rotmat.getTheta(), rotmat.getPsi());
        size_t id = ass_mot->placeVolume(g4vol, transform);
        info.g4AssemblyChildren[ass_mot].push_back(make_pair(id, node));
        return 0;
      }
      else if (daughter_is_assembly) {
        printout(DEBUG, "Geant4Converter", "++ Assembly: makeImprint: %16p dau:%s "
            "Tr:x=%8.3f y=%8.3f z=%8.3f  Rot:phi=%7.3f theta=%7.3f psi=%7.3f\n", ass_dau,
            g4mot ? g4mot->GetName().c_str() : "---", transform.dx(), transform.dy(), transform.dz(), rotmat.getPhi(),
            rotmat.getTheta(), rotmat.getPsi());
        std::vector<G4VPhysicalVolume*> phys_volumes;
        AssemblyChildMap::iterator i = info.g4AssemblyChildren.find(ass_dau);
        if (i == info.g4AssemblyChildren.end()) {
          printout(ERROR, "Geant4Converter", "++ Non-existing assembly [%p]", ass_dau);
        }
        const AssemblyChildren& v = (*i).second;
        ass_dau->imprint(phys_volumes, g4mot, transform, copy, m_checkOverlaps);
        if (v.size() != phys_volumes.size()) {
          printout(ERROR, "Geant4Converter", "++ Unexpected number of placements in assembly: %ld <> %ld.", v.size(),
              phys_volumes.size());
        }
        for (size_t j = 0; j < v.size(); ++j) {
          info.g4Placements[v[j].second] = phys_volumes[j];
        }
        return 0;
      }
      g4 = new G4PVPlacement(transform,   // no rotation
          g4vol,     // its logical volume
          name,      // its name
          g4mot,     // its mother (logical) volume
          false,     // no boolean operations
          copy,      // its copy number
          m_checkOverlaps);
    }
    info.g4Placements[node] = g4;
  }
  else {
    printout(ERROR, "Geant4Converter", "++ Attempt to DOUBLE-place physical volume: %s No:%d", name.c_str(), node->GetNumber());
  }
  return g4;
}

/// Convert the geometry type region into the corresponding Geant4 object(s).
void* Geant4Converter::handleRegion(const TNamed* region, const set<const TGeoVolume*>& /* volumes */) const {
  G4Region* g4 = data().g4Regions[region];
  if (!g4) {
    Region r = Ref_t(region);
    g4 = new G4Region(r.name());
    // set production cut
    G4ProductionCuts* cuts = new G4ProductionCuts();
    cuts->SetProductionCut(r.cut());
    g4->SetProductionCuts(cuts);

    // create region info with storeSecondaries flag
    G4UserRegionInformation* info = new G4UserRegionInformation();
    info->region = r;
    info->threshold = r.threshold();
    info->storeSecondaries = r.storeSecondaries();
    g4->SetUserInformation(info);

    printout(INFO, "Geant4Converter", "++ Converted region settings of:%s.", r.name());
    vector < string > &limits = r.limits();
    for (vector<string>::const_iterator i = limits.begin(); i != limits.end(); ++i) {
      const string& nam = *i;
      LimitSet ls = m_lcdd.limitSet(nam);
      if (ls.isValid()) {
        bool found = false;
        const LimitMap& lm = data().g4Limits;
        for (LimitMap::const_iterator j = lm.begin(); j != lm.end(); ++j) {
          if (nam == (*j).first->GetName()) {
            g4->SetUserLimits((*j).second);
            found = true;
            break;
          }
        }
        if (found)
          continue;
      }
      throw runtime_error("G4Region: Failed to resolve user limitset:" + *i);
    }
    data().g4Regions[region] = g4;
  }
  return g4;
}

/// Convert the geometry type LimitSet into the corresponding Geant4 object(s).
void* Geant4Converter::handleLimitSet(const TNamed* limitset, const set<const TGeoVolume*>& /* volumes */) const {
  G4UserLimits* g4 = data().g4Limits[limitset];
  if (!g4) {
    LimitSet ls = Ref_t(limitset);
    g4 = new G4UserLimits(limitset->GetName());
    const set<Limit>& limits = ls.limits();
    for (LimitSet::Object::const_iterator i = limits.begin(); i != limits.end(); ++i) {
      const Limit& l = *i;
      if (l.name == "step_length_max")
        g4->SetMaxAllowedStep(l.value);
      else if (l.name == "track_length_max")
        g4->SetMaxAllowedStep(l.value);
      else if (l.name == "time_max")
        g4->SetUserMaxTime(l.value);
      else if (l.name == "ekin_min")
        g4->SetUserMinEkine(l.value);
      else if (l.name == "range_min")
        g4->SetUserMinRange(l.value);
      else
        throw runtime_error("Unknown Geant4 user limit: " + l.toString());
    }
    data().g4Limits[limitset] = g4;
  }
  return g4;
}

/// Convert the geometry type SensitiveDetector into the corresponding Geant4 object(s).
void* Geant4Converter::handleSensitive(const TNamed* sens_det, const set<const TGeoVolume*>& /* volumes */) const {
  Geant4GeometryInfo& info = data();
  G4VSensitiveDetector* g4 = info.g4SensDets[sens_det];
  if (!g4) {
    SensitiveDetector sd = Ref_t(sens_det);
    string type = sd.type(), name = sd.name();
    g4 = PluginService::Create<G4VSensitiveDetector*>(type, name, &m_lcdd);
    if (!g4) {
      string tmp = type;
      tmp[0] = ::toupper(tmp[0]);
      type = "Geant4" + tmp;
      g4 = PluginService::Create<G4VSensitiveDetector*>(type, name, &m_lcdd);
      if (!g4) {
        PluginDebug dbg;
        g4 = ROOT::Reflex::PluginService::Create<G4VSensitiveDetector*>(type, name, &m_lcdd);
        throw runtime_error("Geant4Converter<SensitiveDetector>: FATAL Failed to "
            "create Geant4 sensitive detector factory " + name + " of type " + type + ".");
      }
    }
    g4->Activate(true);
    G4SDManager::GetSDMpointer()->AddNewDetector(g4);
    info.g4SensDets[sens_det] = g4;
  }
  return g4;
}

/// Convert the geometry visualisation attributes to the corresponding Geant4 object(s).
void* Geant4Converter::handleVis(const string& /* name */, const TNamed* vis) const {
  Geant4GeometryInfo& info = data();
  G4VisAttributes* g4 = info.g4Vis[vis];
  if (!g4) {
    float r = 0, g = 0, b = 0;
    VisAttr attr = Ref_t(vis);
    int style = attr.lineStyle();
    attr.rgb(r, g, b);
    g4 = new G4VisAttributes(attr.visible(), G4Colour(r, g, b, attr.alpha()));
    //g4->SetLineWidth(attr->GetLineWidth());
    g4->SetDaughtersInvisible(!attr.showDaughters());
    if (style == VisAttr::SOLID) {
      g4->SetLineStyle(G4VisAttributes::unbroken);
      g4->SetForceWireframe(false);
      g4->SetForceSolid(true);
    }
    else if (style == VisAttr::WIREFRAME || style == VisAttr::DASHED) {
      g4->SetLineStyle(G4VisAttributes::dashed);
      g4->SetForceSolid(false);
      g4->SetForceWireframe(true);
    }
    info.g4Vis[vis] = g4;
  }
  return g4;
}

/// Handle the geant 4 specific properties
void Geant4Converter::handleProperties(LCDD::Properties& prp) const {
  map < string, string > processors;
  static int s_idd = 9999999;
  string id;
  for (LCDD::Properties::const_iterator i = prp.begin(); i != prp.end(); ++i) {
    const string& nam = (*i).first;
    const LCDD::PropertyValues& vals = (*i).second;
    if (nam.substr(0, 6) == "geant4") {
      LCDD::PropertyValues::const_iterator id_it = vals.find("id");
      if (id_it != vals.end()) {
        id = (*id_it).second;
      }
      else {
        char txt[32];
        ::sprintf(txt, "%d", ++s_idd);
        id = txt;
      }
      processors.insert(make_pair(id, nam));
    }
  }
  for (map<string, string>::const_iterator i = processors.begin(); i != processors.end(); ++i) {
    const Geant4Converter* ptr = this;
    string nam = (*i).second;
    const LCDD::PropertyValues& vals = prp[nam];
    string type = vals.find("type")->second;
    string tag = type + "_Geant4_action";
    long result = ROOT::Reflex::PluginService::Create<long>(tag, &m_lcdd, ptr, &vals);
    if (0 == result) {
      throw runtime_error("Failed to locate plugin to interprete files of type"
          " \"" + tag + "\" - no factory:" + type);
    }
    result = *(long*) result;
    if (result != 1) {
      throw runtime_error("Failed to invoke the plugin " + tag + " of type " + type);
    }
    printout(INFO, "Geant4Converter", "+++++ Executed Successfully Geant4 setup module *%s*.", type.c_str());
  }
}

/// Convert the geometry type SensitiveDetector into the corresponding Geant4 object(s).
void* Geant4Converter::printSensitive(const TNamed* sens_det, const set<const TGeoVolume*>& /* volumes */) const {
  Geant4GeometryInfo& info = data();
  G4VSensitiveDetector* g4 = info.g4SensDets[sens_det];
  ConstVolumeSet& volset = info.sensitives[sens_det];
  SensitiveDetector sd = Ref_t(sens_det);
  stringstream str;

  printout(INFO, "Geant4Converter", "++ SensitiveDetector: %-18s %-20s Hits:%-16s", sd.name(), ("[" + sd.type() + "]").c_str(),
      sd.hitsCollection().c_str());
  str << "                    | " << "Cutoff:" << setw(6) << left << sd.energyCutoff() << setw(5) << right << volset.size()
      << " volumes ";
  if (sd.region().isValid())
    str << " Region:" << setw(12) << left << sd.region().name();
  if (sd.limits().isValid())
    str << " Limits:" << setw(12) << left << sd.limits().name();
  str << ".";
  printout(INFO, "Geant4Converter", str.str().c_str());

  for (ConstVolumeSet::iterator i = volset.begin(); i != volset.end(); ++i) {
    map<const TGeoVolume*, G4LogicalVolume*>::iterator v = info.g4Volumes.find(*i);
    G4LogicalVolume* vol = (*v).second;
    str.str("");
    str << "                                   | " << "Volume:" << setw(24) << left << vol->GetName() << " "
        << vol->GetNoDaughters() << " daughters.";
    printout(INFO, "Geant4Converter", str.str().c_str());
  }
  return g4;
}

string printSolid(G4VSolid* sol) {
  stringstream str;
  if (typeid(*sol) == typeid(G4Box)) {
    const G4Box* b = (G4Box*) sol;
    str << "++ Box: x=" << b->GetXHalfLength() << " y=" << b->GetYHalfLength() << " z=" << b->GetZHalfLength();
  }
  else if (typeid(*sol) == typeid(G4Tubs)) {
    const G4Tubs* t = (const G4Tubs*) sol;
    str << " Tubs: Ri=" << t->GetInnerRadius() << " Ra=" << t->GetOuterRadius() << " z/2=" << t->GetZHalfLength() << " Phi="
        << t->GetStartPhiAngle() << "..." << t->GetDeltaPhiAngle();
  }
  return str.str();
}

/// Print G4 placement
void* Geant4Converter::printPlacement(const string& name, const TGeoNode* node) const {
  Geant4GeometryInfo& info = data();
  G4VPhysicalVolume* g4 = info.g4Placements[node];
  G4LogicalVolume* vol = info.g4Volumes[node->GetVolume()];
  G4LogicalVolume* mot = info.g4Volumes[node->GetMotherVolume()];
  G4VSolid* sol = vol->GetSolid();
  G4ThreeVector tr = g4->GetObjectTranslation();

  G4VSensitiveDetector* sd = vol->GetSensitiveDetector();
  if (!sd)
    return g4;

  stringstream str;
  str << "G4Cnv::placement: + " << name << " No:" << node->GetNumber() << " Vol:" << vol->GetName() << " Solid:"
      << sol->GetName();
  printout(DEBUG, "G4Placement", str.str().c_str());
  str.str("");
  str << "                  |" << " Loc: x=" << tr.x() << " y=" << tr.y() << " z=" << tr.z();
  printout(DEBUG, "G4Placement", str.str().c_str());
  printout(DEBUG, "G4Placement", printSolid(sol).c_str());
  str.str("");
  str << "                  |" << " Ndau:" << vol->GetNoDaughters() << " physvols." << " Mat:" << vol->GetMaterial()->GetName()
      << " Mother:" << (char*) (mot ? mot->GetName().c_str() : "---");
  printout(DEBUG, "G4Placement", str.str().c_str());
  str.str("");
  str << "                  |" << " SD:" << (char*) (sd ? sd->GetName().c_str() : "---");
  printout(DEBUG, "G4Placement", str.str().c_str());
  return g4;
}

template <typename O, typename C, typename F> void handle(const O* o, const C& c, F pmf) {
  for (typename C::const_iterator i = c.begin(); i != c.end(); ++i) {
    (o->*pmf)((*i)->GetName(), *i);
  }
}

template <typename O, typename C, typename F> void handleMap(const O* o, const C& c, F pmf) {
  for (typename C::const_iterator i = c.begin(); i != c.end(); ++i)
    (o->*pmf)((*i).first, (*i).second);
}

template <typename O, typename C, typename F> void handleRMap(const O* o, const C& c, F pmf) {
  for (typename C::const_reverse_iterator i = c.rbegin(); i != c.rend(); ++i)
    handle(o, (*i).second, pmf);
}

/// Create geometry conversion
Geant4Converter& Geant4Converter::create(DetElement top) {
  Geant4GeometryInfo& geo = this->init();
  m_data->clear();
  collect(top, geo);
  s_topPtr = top.placement().ptr();
  m_checkOverlaps = false;

  // We do not have to handle defines etc.
  // All positions and the like are not really named.
  // Hence, start creating the G4 objects for materials, solids and log volumes.
  Material mat = m_lcdd.material("Argon");
  handleMaterial(mat.name(), mat.ptr());
  mat = m_lcdd.material("Silicon");
  handleMaterial(mat.name(), mat.ptr());

  handle(this, geo.volumes, &Geant4Converter::collectVolume);
  handle(this, geo.solids, &Geant4Converter::handleSolid);
  printout(INFO, "Geant4Converter", "++ Handled %ld solids.", geo.solids.size());
  handle(this, geo.vis, &Geant4Converter::handleVis);
  printout(INFO, "Geant4Converter", "++ Handled %ld visualization attributes.", geo.vis.size());
  handleMap(this, geo.sensitives, &Geant4Converter::handleSensitive);
  printout(INFO, "Geant4Converter", "++ Handled %ld sensitive detectors.", geo.sensitives.size());
  handleMap(this, geo.limits, &Geant4Converter::handleLimitSet);
  printout(INFO, "Geant4Converter", "++ Handled %ld limit sets.", geo.limits.size());
  handleMap(this, geo.regions, &Geant4Converter::handleRegion);
  printout(INFO, "Geant4Converter", "++ Handled %ld regions.", geo.regions.size());
  handle(this, geo.volumes, &Geant4Converter::handleVolume);
  printout(INFO, "Geant4Converter", "++ Handled %ld volumes.", geo.volumes.size());
  // Now place all this stuff appropriately
  handleRMap(this, *m_data, &Geant4Converter::handlePlacement);
  //==================== Fields
  handleProperties(m_lcdd.properties());

  //handleMap(this, geo.sensitives, &Geant4Converter::printSensitive);
  //handleRMap(this, *m_data, &Geant4Converter::printPlacement);
  geo.valid = true;
  return *this;
}