//
// ********************************************************************
// * 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.          *
// ********************************************************************
// g
//
/// \file G02DetectorConstruction.cc
/// \brief Implementation of the G02DetectorConstruction class

// Class G02DetectorConstruction implementation
//
// ----------------------------------------------------------------------------

#include "G02DetectorConstruction.hh"

// Geant4 includes
//
#include "G4GeometryManager.hh"
#include "G4VisAttributes.hh"
#include "globals.hh"

// Materials
//
#include "G4Material.hh"

// Geometry includes
//
#include "G4Box.hh"
#include "G4LogicalVolume.hh"
#include "G4PVParameterised.hh"
#include "G4PVPlacement.hh"
#include "G4Tubs.hh"
#include "G4VPhysicalVolume.hh"

// Reflected solids
//
#include "G4AffineTransform.hh"
#include "G4DisplacedSolid.hh"
#include "G4ReflectedSolid.hh"
#include "G4ReflectionFactory.hh"
#include "G4RotationMatrix.hh"
#include "G4Transform3D.hh"

// Assembly volumes
//
#include "G4AssemblyVolume.hh"

// Volume parameterisations
//
#include "G02ChamberParameterisation.hh"

// Messenger
//
#include "G02DetectorMessenger.hh"

// GDML parser include
//
#include "G4GDMLParser.hh"
#include "G4PhysicalConstants.hh"
#include "G4SystemOfUnits.hh"

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// Constructor
//
G02DetectorConstruction::G02DetectorConstruction()
  : G4VUserDetectorConstruction(), fAir(0), fAluminum(0), fPb(0), fXenon(0), fDetectorMessenger(0)
{
  fExpHall_x = 5. * m;

  fReadFile = "test.gdml";
  fWriteFile = "wtest.gdml";
  fStepFile = "mbb";
  fWritingChoice = 1;

  fDetectorMessenger = new G02DetectorMessenger(this);
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// Destructor
//
G02DetectorConstruction::~G02DetectorConstruction()
{
  if (fDetectorMessenger) delete fDetectorMessenger;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// Constructs geometries and materials
//
G4VPhysicalVolume* G02DetectorConstruction::Construct()
{
  // Writing or Reading of Geometry using G4GDML

  G4VPhysicalVolume* fWorldPhysVol;

  if (fWritingChoice == 0) {
    // **** LOOK HERE*** FOR READING GDML FILES
    //

    // ACTIVATING OVERLAP CHECK when read volumes are placed.
    // Can take long time in case of complex geometries
    //
    // fParser.SetOverlapCheck(true);

    fParser.Read(fReadFile);

    // READING GDML FILES OPTION: 2nd Boolean argument "Validate".
    // Flag to "false" disables check with the Schema when reading GDML file.
    // See the GDML Documentation for more information.
    //
    // fParser.Read(fReadFile,false);

    // Prints the material information
    //
    G4cout << *(G4Material::GetMaterialTable()) << G4endl;

    // Giving World Physical Volume from GDML Parser
    //
    fWorldPhysVol = fParser.GetWorldVolume();
  }
  else if (fWritingChoice == 1) {
    // **** LOOK HERE*** FOR WRITING GDML FILES
    // Detector Construction and WRITING to GDML
    //
    ListOfMaterials();
    fWorldPhysVol = ConstructDetector();

    // OPTION: TO ADD MODULE AT DEPTH LEVEL ...
    //
    // Can be a integer or a pointer to the top Physical Volume:
    //
    // G4int depth=1;
    // fParser.AddModule(depth);

    // OPTION: SETTING ADDITION OF POINTER TO NAME TO FALSE
    //
    // By default, written names in GDML consist of the given name with
    // appended the pointer reference to it, in order to make it unique.
    // Naming policy can be changed by using the following method, or
    // calling Write with additional Boolean argument to "false".
    // NOTE: you have to be sure not to have duplication of names in your
    //       Geometry Setup.
    //
    // fParser.SetAddPointerToName(false);
    //
    // or
    //
    // fParser.Write(fWriteFile, fWorldPhysVol, false);

    // OPTION: SET MAXIMUM LEVEL TO EXPORT (REDUCED TREE)...
    //
    // Can be a integer greater than zero:
    //
    // G4int maxlevel=3;
    // fParser.SetMaxExportLevel(maxlevel);

    // Writing Geometry to GDML File
    //
    fParser.Write(fWriteFile, fWorldPhysVol);

    // OPTION: SPECIFYING THE SCHEMA LOCATION
    //
    // When writing GDML file the default the Schema Location from the
    // GDML web site will be used:
    // "http://cern.ch/service-spi/app/releases/GDML/schema/gdml.xsd"
    //
    // NOTE: GDML Schema is distributed in Geant4 in the directory:
    //    $G4INSTALL/source/persistency/gdml/schema
    //
    // You can change the Schema path by adding a parameter to the Write
    // command, as follows:
    //
    // fParser.Write(fWriteFile, fWorldPhysVol, "your-path-to-schema/gdml.xsd");
  }
  else  // Demonstration how to Read STEP files using GDML
  {
    // Some printout...
    //
    ListOfMaterials();

    // Arbitrary values that should enclose any reasonable geometry
    //
    const G4double expHall_y = fExpHall_x / 50.;
    const G4double expHall_z = fExpHall_x / 50.;

    // Create the hall
    //
    G4Box* experimentalHallBox = new G4Box("ExpHallBox", fExpHall_x / 50., expHall_y, expHall_z);
    G4LogicalVolume* experimentalHallLV =
      new G4LogicalVolume(experimentalHallBox, fAir, "ExpHallLV");
    fWorldPhysVol = new G4PVPlacement(0, G4ThreeVector(0.0, 0.0, 0.0), experimentalHallLV,
                                      "ExpHallPhys", 0, false, 0);

    // G02DetectorConstruction via reading STEP File
    //
    G4LogicalVolume* LogicalVolST = fParser.ParseST(fStepFile, fAir, fAluminum);

    // Placement inside of the hall
    //
    new G4PVPlacement(0, G4ThreeVector(10.0, 0.0, 0.0), LogicalVolST, "StepPhys",
                      experimentalHallLV, false, 0);
  }

  // Set Visualization attributes to world
  //
  G4VisAttributes* BoxVisAtt = new G4VisAttributes(G4Colour(1.0, 1.0, 1.0));
  fWorldPhysVol->GetLogicalVolume()->SetVisAttributes(BoxVisAtt);

  return fWorldPhysVol;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// Utility to build and list necessary materials
//
void G02DetectorConstruction::ListOfMaterials()
{
  G4double a;  // atomic mass
  G4double z;  // atomic number
  G4double density, temperature, pressure;
  G4double fractionmass;
  G4String name, symbol;
  G4int ncomponents;

  // Elements needed for the materials

  a = 14.01 * g / mole;
  G4Element* elN = new G4Element(name = "Nitrogen", symbol = "N", z = 7., a);

  a = 16.00 * g / mole;
  G4Element* elO = new G4Element(name = "Oxygen", symbol = "O", z = 8., a);

  a = 26.98 * g / mole;
  G4Element* elAl = new G4Element(name = "Aluminum", symbol = "Al", z = 13., a);

  // Print the Element information
  //
  G4cout << *(G4Element::GetElementTable()) << G4endl;

  // Air
  //
  density = 1.29 * mg / cm3;
  fAir = new G4Material(name = "Air", density, ncomponents = 2);
  fAir->AddElement(elN, fractionmass = 0.7);
  fAir->AddElement(elO, fractionmass = 0.3);

  // Aluminum
  //
  density = 2.70 * g / cm3;
  fAluminum = new G4Material(name = "Aluminum", density, ncomponents = 1);
  fAluminum->AddElement(elAl, fractionmass = 1.0);

  // Lead
  //
  fPb = new G4Material("Lead", z = 82., a = 207.19 * g / mole, density = 11.35 * g / cm3);

  // Xenon gas
  //
  fXenon = new G4Material("XenonGas", z = 54., a = 131.29 * g / mole, density = 5.458 * mg / cm3,
                          kStateGas, temperature = 293.15 * kelvin, pressure = 1 * atmosphere);

  // Prints the material information
  //
  G4cout << *(G4Material::GetMaterialTable()) << G4endl;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// Detector Construction
//
// Detector consist from DetectorBox, Conrol Room and 4 SubDetectors
// SubDetectors1 and 2 show how to use Reflection Factory and Assembly
// SubDetectors 3 and 4 show how to use Parameterisation
//
G4VPhysicalVolume* G02DetectorConstruction::ConstructDetector()
{
  // Arbitary values that should enclose any reasonable geometry
  //
  const G4double expHall_y = fExpHall_x;
  const G4double expHall_z = fExpHall_x;

  // Create the hall
  //
  G4Box* experimentalHallBox = new G4Box("ExpHallBox", fExpHall_x, expHall_y, expHall_z);
  G4LogicalVolume* experimentalHallLV = new G4LogicalVolume(experimentalHallBox, fAir, "ExpHallLV");
  G4PVPlacement* experimentalHallPhys = new G4PVPlacement(
    0, G4ThreeVector(0.0, 0.0, 0.0), experimentalHallLV, "ExpHallPhys", 0, false, 0);

  // G02DetectorConstruction

  const G4double det_x = fExpHall_x * 0.8;
  const G4double det_y = fExpHall_x * 0.7;
  const G4double det_z = det_y;

  // Create the detector box
  //
  G4Box* detectorBox = new G4Box("detectorBox", det_x, det_y, det_z);
  G4LogicalVolume* detectorLV = new G4LogicalVolume(detectorBox, fAir, "detLV");
  // G4PVPlacement * detectorPhys =
  new G4PVPlacement(0, G4ThreeVector(0.0, 0.0, 0.0), detectorLV, "detPhys", experimentalHallLV,
                    false, 0);

  // Create the Control room box
  //
  const G4double room_x = fExpHall_x / 20.;
  const G4double room_y = room_x;
  const G4double room_z = room_x;

  G4Box* roomBox = new G4Box("roomBox", room_x, room_y, room_z);
  G4LogicalVolume* roomLV = new G4LogicalVolume(roomBox, fAir, "roomLV");
  // G4PVPlacement * roomPhys =
  new G4PVPlacement(0, G4ThreeVector(fExpHall_x - room_x - 10., 0.0, 0.0), roomLV, "roomPhys",
                    experimentalHallLV, false, 0);

  // SubDetector1
  //
  const G4double bigL = fExpHall_x / 5. + 50.;
  G4LogicalVolume* subDetectorLV1 = ConstructSubDetector1();
  // G4PVPlacement * detPhys1 =
  new G4PVPlacement(0, G4ThreeVector(bigL, 0.0, 0.0), subDetectorLV1, "PhysSubDetector1",
                    detectorLV, false, 0);

  //
  // LOOK HERE FOR REFLECTIONS
  //

  // SubDetector2
  //
  G4Translate3D translation(-bigL, 0., 0.);
  G4RotationMatrix* rotD3 = new G4RotationMatrix();
  G4Transform3D rotation = G4Rotate3D(*rotD3);
  G4ReflectX3D reflection;
  G4Transform3D transform = translation * rotation * reflection;

  // Place the reflected part using G4ReflectionFactory
  //
  G4ReflectionFactory::Instance()->Place(transform, "reflSubDetector", subDetectorLV1, detectorLV,
                                         false, 0);

  // SubDetector3
  //
  G4LogicalVolume* subDetectorLV3 = ConstructSubDetector2();
  // G4PVPlacement * detPhys3 =
  new G4PVPlacement(0, G4ThreeVector(0.0, bigL, 0.0), subDetectorLV3, "PhysSubDetectorFirst3",
                    detectorLV, false, 0);

  // SubDetector4, placement of parameterised chambers
  //
  G4LogicalVolume* subDetectorLV4 = ConstructSubDetector2();
  G4LogicalVolume* subChamberLV = ConstructParametrisationChamber();
  // G4PVPlacement * detChamb =
  new G4PVPlacement(0, G4ThreeVector(0, 0.0, 0.0), subChamberLV, "AssemblyPhys", subDetectorLV4,
                    false, 0);
  // G4PVPlacement * detPhys4 =
  new G4PVPlacement(0, G4ThreeVector(0.0, -bigL, 0.0), subDetectorLV4, "PhysSubDetectorSecond3",
                    detectorLV, false, 0);

  return experimentalHallPhys;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// SubDetector1
//
G4LogicalVolume* G02DetectorConstruction::ConstructSubDetector1()
{
  const G4double sub_x = fExpHall_x / 5.;
  const G4double sub_y = sub_x;

  // Create the hall
  //
  G4Tubs* subTub = new G4Tubs("subTub", 0., sub_x, sub_y, -90. * deg, 180 * deg);
  G4LogicalVolume* subTubLV = new G4LogicalVolume(subTub, fPb, "tubLV");
  G4LogicalVolume* AssemblyLV = ConstructAssembly();
  // G4PVPlacement * detAss =
  new G4PVPlacement(0, G4ThreeVector(sub_x / 3, 0.0, 0.0), AssemblyLV, "AssemblyPhys", subTubLV,
                    false, 0);
  return subTubLV;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// SubDetector2
//
G4LogicalVolume* G02DetectorConstruction::ConstructSubDetector2()
{
  const G4double sub_x = fExpHall_x / 10.;
  const G4double sub_y = sub_x * 2.;
  const G4double sub_z = sub_x;

  // Create the hall
  //
  G4Box* detHallBox = new G4Box("detHallBox", sub_x, sub_y, sub_z);
  G4LogicalVolume* detHallLV = new G4LogicalVolume(detHallBox, fAluminum, "detHallLV");

  return detHallLV;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// Assembly
//
G4LogicalVolume* G02DetectorConstruction::ConstructAssembly()
{
  const G4double big_x = fExpHall_x / 17;
  const G4double big_y = big_x;
  const G4double big_z = big_x;

  // Create the Box
  //
  G4Box* OuterBox = new G4Box("OuterBox", big_x, big_y, big_z);
  G4LogicalVolume* OuterBoxLV = new G4LogicalVolume(OuterBox, fAir, "OuterBoxLV");
  // G4PVPlacement * OuterBoxPhys =
  new G4PVPlacement(0, G4ThreeVector(0.0, 0.0, 0.0), OuterBoxLV, "OuterBoxPhys", 0, false, 0);

  // The aluminum object's logical volume
  //
  const G4double bigL = big_x / 2.5;
  const G4double medL = big_x / 8;
  const G4double smalL = big_x / 12;

  G4Box* BigBox = new G4Box("BBox", bigL, bigL, bigL);
  G4LogicalVolume* BigBoxLV = new G4LogicalVolume(BigBox, fAluminum, "AlBigBoxLV");
  G4Box* MedBox = new G4Box("MBox", medL, medL, medL);
  G4LogicalVolume* MedBoxLV1 = new G4LogicalVolume(MedBox, fAluminum, "AlMedBoxLV1");
  G4Box* SmallBox = new G4Box("SBox", smalL, smalL, smalL);
  G4LogicalVolume* SmallBoxLV = new G4LogicalVolume(SmallBox, fAluminum, "AlSmaBoxLV");

  const G4double bigPlace = bigL + 10.;
  const G4double medPlace = medL + 10.;
  // G4PVPlacement * BigBoxPhys =
  new G4PVPlacement(0, G4ThreeVector(bigPlace, 0.0, 0.0), BigBoxLV, "AlPhysBig", OuterBoxLV, false,
                    0);

  // Construction of Tub
  //
  G4Tubs* BigTube = new G4Tubs("BTube", 0, smalL, smalL, -pi / 2., pi);

  // Construction of Reflection of Tub
  //
  G4ReflectX3D Xreflection;
  G4Translate3D translation(-bigPlace, 0., 0.);
  G4Transform3D transform = Xreflection;

  G4ReflectedSolid* ReflBig = new G4ReflectedSolid("Refll_Big", BigTube, transform);
  G4LogicalVolume* ReflBigLV = new G4LogicalVolume(ReflBig, fXenon, "ReflBigAl");
  new G4PVPlacement(0, G4ThreeVector(0., 0.0, 0.0), ReflBigLV, "AlPhysBigTube", SmallBoxLV, false,
                    0);
  //
  // LOOK HERE FOR ASSEMBLY
  //

  // create Assembly of Boxes and Tubs
  //
  G4AssemblyVolume* assembly = new G4AssemblyVolume();
  G4RotationMatrix* rot = new G4RotationMatrix();
  G4ThreeVector posBig(-bigPlace, 0, 0);
  G4ThreeVector posBig0(bigPlace / 4, 0, 0);
  G4ThreeVector posMed(-medPlace, 0, 0);
  G4ThreeVector posMed0(medPlace, 0, 0);
  G4ThreeVector position(0., 0., 0.);

  // Add to Assembly the MediumBox1
  //
  assembly->AddPlacedVolume(MedBoxLV1, posMed0, rot);

  // Add to Assembly the Small Box
  //
  assembly->AddPlacedVolume(SmallBoxLV, posMed, rot);

  // Place the Assembly
  //
  assembly->MakeImprint(BigBoxLV, posBig0, rot, 0);

  //
  // LOOK HERE FOR ASSEMBLY with REFLECTION
  //

  G4Translate3D translation1(-bigPlace, 0., 0.);
  G4RotationMatrix* rotD3 = new G4RotationMatrix();
  G4Transform3D rotation = G4Rotate3D(*rotD3);
  G4ReflectX3D reflection;
  G4Transform3D transform1 = translation1 * rotation * reflection;

  assembly->MakeImprint(OuterBoxLV, transform1, 0, 0);

  return OuterBoxLV;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// Parameterised Chamber
//
G4LogicalVolume* G02DetectorConstruction::ConstructParametrisationChamber()
{
  const G4double chamber_x = fExpHall_x / 12.;
  const G4double chamber_y = chamber_x;
  const G4double chamber_z = chamber_x;

  // Create the hall
  //
  G4Box* paramChamberBox = new G4Box("ChamberBox", chamber_x, chamber_y, chamber_z);
  G4LogicalVolume* paramChamberLV = new G4LogicalVolume(paramChamberBox, fAir, "ChamberLV");

  // Parametrisation Chamber (taken from N02 novice example)
  //
  G4int NbOfChambers = 5;
  G4double ChamberWidth = 2 * cm;
  G4double ChamberSpacing = 8 * cm;
  G4double fTrackerLength = (NbOfChambers + 1) * ChamberSpacing;  // Full length
  G4double trackerSize = 0.5 * fTrackerLength;

  // An example of parameterised volume
  // dummy values for G4Box -- modified by parameterised volume
  //
  G4Box* solidChamber = new G4Box("chamber", 10 * cm, 10 * cm, 1 * cm);
  G4LogicalVolume* logicChamber = new G4LogicalVolume(solidChamber, fAluminum, "Chamber", 0, 0, 0);

  G4double firstPosition = -trackerSize + 0.5 * ChamberWidth;
  G4double firstLength = fTrackerLength / 10;
  G4double lastLength = fTrackerLength;

  G4VPVParameterisation* chamberParam =
    new G02ChamberParameterisation(NbOfChambers,  // NoChambers
                                   firstPosition,  // Z of center of first
                                   ChamberSpacing,  // Z spacing of centers
                                   ChamberWidth,  // Width Chamber
                                   firstLength,  // lengthInitial
                                   lastLength);  // lengthFinal
  // G4VPhysicalVolume* physiChamber =
  new G4PVParameterised("Chamber",  // their name
                        logicChamber,  // their logical volume
                        paramChamberLV,  // mother logical volume
                        kZAxis,  // Are placed along this axis
                        NbOfChambers,  // Number of chambers
                        chamberParam);  // The parametrisation
  return paramChamberLV;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// SetReadFile
//
void G02DetectorConstruction::SetReadFile(const G4String& File)
{
  fReadFile = File;
  fWritingChoice = 0;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// SetWriteFile
//
void G02DetectorConstruction::SetWriteFile(const G4String& File)
{
  fWriteFile = File;
  fWritingChoice = 1;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
//
// SetStepFile
//
void G02DetectorConstruction::SetStepFile(const G4String& File)
{
  fStepFile = File;
  fWritingChoice = 3;
}
