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

#include "DetectorConstruction.hh"

#include "DetectorMessenger.hh"

#include "G4GeometryManager.hh"
#include "G4LogicalVolume.hh"
#include "G4LogicalVolumeStore.hh"
#include "G4Material.hh"
#include "G4NistManager.hh"
#include "G4PVPlacement.hh"
#include "G4PhysicalConstants.hh"
#include "G4PhysicalVolumeStore.hh"
#include "G4ReflectionFactory.hh"
#include "G4RotationMatrix.hh"
#include "G4SolidStore.hh"
#include "G4SystemOfUnits.hh"
#include "G4Transform3D.hh"
#include "G4Trd.hh"
#include "G4Tubs.hh"

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

DetectorConstruction::DetectorConstruction()
{
  fMessenger = new DetectorMessenger(this);
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

DetectorConstruction::~DetectorConstruction()
{
  delete fMessenger;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

G4VPhysicalVolume* DetectorConstruction::Construct()
{
  // Materials
  G4NistManager* nist = G4NistManager::Instance();
  G4Material* material = nist->FindOrBuildMaterial("G4_AIR");

  // Clean old geometry, if any
  //
  G4GeometryManager::GetInstance()->OpenGeometry();
  G4PhysicalVolumeStore::GetInstance()->Clean();
  G4LogicalVolumeStore::GetInstance()->Clean();
  G4SolidStore::GetInstance()->Clean();
  G4ReflectionFactory::Instance()->Clean();

  // World
  //
  G4double rmin = 0.;
  G4double rmax = 5 * cm;
  G4double hz = 5 * cm;
  G4double phiMin = 0.;
  G4double deltaPhi = 360 * degree;

  auto solidWorld = new G4Tubs("World",  // name
                               rmin, rmax, hz, phiMin, deltaPhi);  // size

  fWorldVolume = new G4LogicalVolume(solidWorld,  // solid
                                     material,  // material
                                     "World");  // name

  G4VPhysicalVolume* physiWorld = new G4PVPlacement(nullptr,  // no rotation
                                                    G4ThreeVector(),  // at (0,0,0)
                                                    fWorldVolume,  // logical volume
                                                    "World",  // name
                                                    nullptr,  // mother volume
                                                    false,  // no boolean operation
                                                    0);  // copy number

  // Trd volume
  //
  G4double dX1 = 1 * cm;
  G4double dX2 = 1 * cm;
  G4double dY1 = 1 * cm;
  G4double dY2 = 2 * cm;
  G4double dZ = 3 * cm;

  auto solidTrd = new G4Trd("trd",  // name
                            dX1 / 2, dX2 / 2, dY1 / 2, dY2 / 2, dZ / 2);  // size

  fTrdVolume = new G4LogicalVolume(solidTrd,  // solid
                                   material,  // material
                                   "trd");  // name

  // Place Volume1 and Volume2 according to selected methods
  //
  switch (fMethod) {
    case kWithDirectMatrix:
      PlaceWithDirectMatrix();
      break;
    case kWithInverseMatrix:
      PlaceWithInverseMatrix();
      break;
    case kWithAxialRotations:
      PlaceWithAxialRotations();
      break;
    case kWithEulerAngles:
      PlaceWithEulerAngles();
      break;
    case kWithReflections:
      PlaceWithReflections();
      break;
    default:;
      ;
  }

  // Return the root volume
  //
  return physiWorld;
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void DetectorConstruction::PlaceWithDirectMatrix()
{
  G4double og = 3 * cm;

  // 1st position
  //
  G4double phi = 30 * deg;
  // u, v, w are the daughter axes, projected on the mother frame
  G4ThreeVector u = G4ThreeVector(0, 0, -1);
  G4ThreeVector v = G4ThreeVector(-std::sin(phi), std::cos(phi), 0.);
  G4ThreeVector w = G4ThreeVector(std::cos(phi), std::sin(phi), 0.);
  G4RotationMatrix rotm1 = G4RotationMatrix(u, v, w);
  G4cout << "\n --> phi = " << phi / deg << " deg;  direct rotation matrix : ";
  rotm1.print(G4cout);
  G4ThreeVector position1 = og * w;
  G4Transform3D transform1 = G4Transform3D(rotm1, position1);

  new G4PVPlacement(transform1,  // position, rotation
                    fTrdVolume,  // logical volume
                    "Trd",  // name
                    fWorldVolume,  // mother volume
                    false,  // no boolean operation
                    1);  // copy number

  // 2nd position
  //
  phi = phi + 90 * deg;
  v = G4ThreeVector(-std::sin(phi), std::cos(phi), 0.);
  w = G4ThreeVector(std::cos(phi), std::sin(phi), 0.);
  G4RotationMatrix rotm2 = G4RotationMatrix(u, v, w);
  G4ThreeVector position2 = og * w;
  G4Transform3D transform2 = G4Transform3D(rotm2, position2);
  new G4PVPlacement(transform2,  // position, rotation
                    fTrdVolume,  // logical volume
                    "Trd",  // name
                    fWorldVolume,  // mother volume
                    false,  // no boolean operation
                    2);  // copy number
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void DetectorConstruction::PlaceWithInverseMatrix()
{
  G4double og = 3 * cm;

  // 1st position
  //
  G4double phi = 30 * deg;
  // u, v, w are the daughter axes, projected on the mother frame
  G4ThreeVector u = G4ThreeVector(0, 0, -1);
  G4ThreeVector v = G4ThreeVector(-std::sin(phi), std::cos(phi), 0.);
  G4ThreeVector w = G4ThreeVector(std::cos(phi), std::sin(phi), 0.);
  G4RotationMatrix rotm1 = G4RotationMatrix(u, v, w);
  auto rotm1Inv = new G4RotationMatrix(rotm1.inverse());
  G4cout << "\n --> phi = " << phi / deg << " deg;  inverse rotation matrix : ";
  rotm1Inv->print(G4cout);
  G4ThreeVector position1 = og * w;

  new G4PVPlacement(rotm1Inv, position1,
                    fTrdVolume,  // logical volume
                    "Trd",  // name
                    fWorldVolume,  // mother volume
                    false,  // no boolean operation
                    1);  // copy number

  // 2nd position
  //
  phi = phi + 90 * deg;
  v = G4ThreeVector(-std::sin(phi), std::cos(phi), 0.);
  w = G4ThreeVector(std::cos(phi), std::sin(phi), 0.);
  G4RotationMatrix rotm2 = G4RotationMatrix(u, v, w);
  auto rotm2Inv = new G4RotationMatrix(rotm2.inverse());
  G4ThreeVector position2 = og * w;

  new G4PVPlacement(rotm2Inv,  // rotation
                    position2,  // position
                    fTrdVolume,  // logical volume
                    "Trd",  // name
                    fWorldVolume,  // mother volume
                    false,  // no boolean operation
                    2);  // copy number
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void DetectorConstruction::PlaceWithAxialRotations()
{
  G4double og = 3 * cm;

  // 1st position (with first G4PVPlacement constructor)
  //
  G4double phi = 30 * deg, theta = 90 * deg;
  G4ThreeVector rotAxis = G4ThreeVector(std::sin(theta - pi / 2), 0., std::cos(theta - pi / 2));
  G4RotationMatrix rotm1 = G4RotationMatrix();
  rotm1.rotateY(theta);
  rotm1.rotate(phi, rotAxis);
  G4cout << "\n --> direct rotation matrix : "
         << " theta = " << theta / deg << " deg;"
         << " phi  = " << phi / deg << " deg;";
  rotm1.print(G4cout);
  G4ThreeVector w =
    G4ThreeVector(std::sin(theta) * std::cos(phi), std::sin(phi), std::cos(theta) * std::cos(phi));
  G4ThreeVector position1 = og * w;
  G4Transform3D transform1(rotm1, position1);

  new G4PVPlacement(transform1,  // rotation,position
                    fTrdVolume,  // logical volume
                    "Trd",  // name
                    fWorldVolume,  // mother volume
                    false,  // no boolean operation
                    1);  // copy number

  // 2nd position (with second G4PVPlacement constructor)
  //
  phi = phi + 90 * deg;
  // rotm2Inv could be calculated with rotm2.inverse()
  // but also by the following :
  auto rotm2Inv = new G4RotationMatrix();
  rotm2Inv->rotate(-phi, rotAxis);
  rotm2Inv->rotateY(-theta);
  w =
    G4ThreeVector(std::sin(theta) * std::cos(phi), std::sin(phi), std::cos(theta) * std::cos(phi));
  G4ThreeVector position2 = og * w;

  new G4PVPlacement(rotm2Inv,  // rotation
                    position2,  // position
                    fTrdVolume,  // logical volume
                    "Trd",  // name
                    fWorldVolume,  // mother volume
                    false,  // no boolean operation
                    2);  // copy number
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void DetectorConstruction::PlaceWithEulerAngles()
{
  // definitions : mother frame = {x,y,z} ; daughter frame = {u,v,w}
  //  n = node line = intercept of xy and uv planes
  //  phi_euler   = (x,n) : precession
  //  theta_euler = (z,w) : nutation
  //  psi_euler   = (n,u) : proper rotation

  G4double og = 3 * cm;

  // 1st position (with first G4PVPlacement constructor)
  //
  G4double phi = 30 * deg;
  G4double phi_euler = phi + pi / 2;
  G4double theta_euler = 90 * deg;
  G4double psi_euler = -90 * deg;
  // attention : clhep Euler constructor build inverse matrix !
  G4RotationMatrix rotm1Inv = G4RotationMatrix(phi_euler, theta_euler, psi_euler);
  G4RotationMatrix rotm1 = rotm1Inv.inverse();
  // remark : could be built as rotm1 = G4RotationMatrix(-psi, -theta, -phi)
  G4cout << "\n --> phi = " << phi / deg << " deg;  direct rotation matrix : ";
  rotm1.print(G4cout);
  G4ThreeVector w = G4ThreeVector(std::cos(phi), std::sin(phi), 0.);
  G4ThreeVector position1 = og * w;
  G4Transform3D transform1 = G4Transform3D(rotm1, position1);

  new G4PVPlacement(transform1,  // position, rotation
                    fTrdVolume,  // logical volume
                    "Trd",  // name
                    fWorldVolume,  // mother volume
                    false,  // no boolean operation
                    1);  // copy number

  // 2nd position (with second G4PVPlacement constructor)
  //
  phi = phi + 90 * deg;

  phi_euler = phi + pi / 2;
  auto rotm2Inv = new G4RotationMatrix(phi_euler, theta_euler, psi_euler);
  w = G4ThreeVector(std::cos(phi), std::sin(phi), 0.);
  G4ThreeVector position2 = og * w;

  new G4PVPlacement(rotm2Inv,  // rotation
                    position2,  // position
                    fTrdVolume,  // logical volume
                    "Trd",  // name
                    fWorldVolume,  // mother volume
                    false,  // no boolean operation
                    2);  // copy number
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

void DetectorConstruction::PlaceWithReflections()
{
  /// Placement with reflections.
  /// In order to better show the reflection symmetry we do not apply
  /// the rotation along Y axis.

  G4double og = 3 * cm;

  // Place first two positionz in z = + 3cm
  //

  // 1st position
  G4double phi = 30 * deg;
  G4RotationMatrix rotm1;
  // rotm1.rotateY(90*deg);
  rotm1.rotateZ(phi);
  G4ThreeVector uz = G4ThreeVector(std::cos(phi), std::sin(phi), 0);
  G4ThreeVector position = og * uz;
  G4Transform3D transform1(rotm1, position);
  G4Transform3D translateZ = HepGeom::Translate3D(0, 0, 3. * cm);

  new G4PVPlacement(translateZ * transform1,  // rotation,position
                    fTrdVolume,  // logical volume
                    "Trd",  // name
                    fWorldVolume,  // mother volume
                    false,  // no boolean operation
                    1);  // copy number

  // 2nd position
  phi = phi + pi / 2;
  G4RotationMatrix rotm2;
  // rotm2.rotateY(90*deg);
  rotm2.rotateZ(phi);
  uz = G4ThreeVector(std::cos(phi), std::sin(phi), 0.);
  position = og * uz;
  G4Transform3D transform2 = G4Transform3D(rotm2, position);

  new G4PVPlacement(translateZ * transform2,  // rotation, position
                    fTrdVolume,  // logical volume
                    "Trd",  // name
                    fWorldVolume,  // mother volume
                    false,  // no boolean operation
                    2);  // copy number

  // Place next two positionz in z = - 3cm with reflection
  //

  // 3rd position
  translateZ = HepGeom::Translate3D(0, 0, -3. * cm);
  G4Transform3D reflect3D = HepGeom::ReflectZ3D();

  G4ReflectionFactory::Instance()->Place(translateZ * transform1 * reflect3D,  // rotation,position
                                         "Trd",  // name
                                         fTrdVolume,  // logical volume
                                         fWorldVolume,  // mother volume
                                         false,  // no boolean operation
                                         3);  // copy number

  // 4rd position
  G4ReflectionFactory::Instance()->Place(translateZ * transform2 * reflect3D,  // rotation,position
                                         "Trd",  // name
                                         fTrdVolume,  // logical volume
                                         fWorldVolume,  // mother volume
                                         false,  // no boolean operation
                                         4);  // copy number
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......

#include "G4RunManager.hh"

void DetectorConstruction::SetMethod(EMethod method)
{
  fMethod = method;
  G4RunManager::GetRunManager()->DefineWorldVolume(Construct());
}

//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
