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

#include "F04DetectorConstruction.hh"

#include "F04DetectorMessenger.hh"
#include "F04FocusSolenoid.hh"
#include "F04GlobalField.hh"
#include "F04Materials.hh"
#include "F04SimpleSolenoid.hh"

#include "G4AutoDelete.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 "G4RotationMatrix.hh"
#include "G4RunManager.hh"
#include "G4SolidStore.hh"
#include "G4StateManager.hh"
#include "G4SystemOfUnits.hh"
#include "G4Tubs.hh"
#include "globals.hh"

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

F04DetectorConstruction::F04DetectorConstruction()
{
  SetTargetAngle(170);
  SetCaptureMgntB1(2.5 * tesla);
  SetCaptureMgntB2(5.0 * tesla);
  SetTransferMgntB(5.0 * tesla);

  fDegraderPos = -fTransferMgntLength / 2. + fDegraderThickness / 2.;

  fDetectorMessenger = new F04DetectorMessenger(this);
}

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

F04DetectorConstruction::~F04DetectorConstruction()
{
  delete fDetectorMessenger;
}

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

G4VPhysicalVolume* F04DetectorConstruction::Construct()
{
  if (fPhysiWorld) {
    G4GeometryManager::GetInstance()->OpenGeometry();
    G4PhysicalVolumeStore::GetInstance()->Clean();
    G4LogicalVolumeStore::GetInstance()->Clean();
    G4SolidStore::GetInstance()->Clean();
  }

  fMaterials = F04Materials::GetInstance();

  DefineMaterials();

  return ConstructDetector();
}

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

void F04DetectorConstruction::DefineMaterials()
{
  // define materials for the experiment

  fVacuum = fMaterials->GetMaterial("G4_Galactic");

  fWorldMaterial = fMaterials->GetMaterial("G4_AIR");
  fDegraderMaterial = fMaterials->GetMaterial("G4_Pb");
  fTargetMaterial = fMaterials->GetMaterial("G4_W");
}

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

G4VPhysicalVolume* F04DetectorConstruction::ConstructDetector()
{
  fSolidWorld = new G4Tubs("World", 0., GetWorldSizeR(), GetWorldSizeZ() / 2., 0., twopi);

  fLogicWorld = new G4LogicalVolume(fSolidWorld, GetWorldMaterial(), "World");

  fPhysiWorld =
    new G4PVPlacement(nullptr, G4ThreeVector(), "World", fLogicWorld, nullptr, false, 0);

  // Capture Magnet

  fSolidCaptureMgnt =
    new G4Tubs("CaptureMgnt", 0., GetCaptureMgntRadius(), GetCaptureMgntLength() / 2., 0., twopi);

  fLogicCaptureMgnt = new G4LogicalVolume(fSolidCaptureMgnt, fVacuum, "CaptureMgnt");

  fCaptureMgntCenter = G4ThreeVector();

  fPhysiCaptureMgnt = new G4PVPlacement(nullptr, fCaptureMgntCenter, "CaptureMgnt",
                                        fLogicCaptureMgnt, fPhysiWorld, false, 0);

  // Transfer Magnet

  fSolidTransferMgnt = new G4Tubs("TransferMgnt", 0., GetTransferMgntRadius(),
                                  GetTransferMgntLength() / 2., 0., twopi);

  fLogicTransferMgnt = new G4LogicalVolume(fSolidTransferMgnt, fVacuum, "TransferMgnt");

  G4double z = GetCaptureMgntLength() / 2. + GetTransferMgntLength() / 2. + GetTransferMgntPos();
  G4double x = GetTransferMgntPos() / 2.;

  fTransferMgntCenter = G4ThreeVector(x, 0., z);

  auto g4rot = new G4RotationMatrix();
  *g4rot = StringToRotationMatrix("Y30,X10");
  *g4rot = g4rot->inverse();
  if (*g4rot == G4RotationMatrix()) g4rot = nullptr;

  fPhysiTransferMgnt = new G4PVPlacement(g4rot, fTransferMgntCenter, "TransferMgnt",
                                         fLogicTransferMgnt, fPhysiWorld, false, 0);

  // Test Plane

  auto solidTestPlane = new G4Tubs("TestPlane", 0., GetTransferMgntRadius(), 1. * mm, 0., twopi);

  auto logicTestPlane = new G4LogicalVolume(solidTestPlane, fVacuum, "TestPlane");

  z = GetTransferMgntLength() / 2. - 1. * mm;

  G4ThreeVector testPlaneCenter = G4ThreeVector(0., 0., z);

  new G4PVPlacement(nullptr, testPlaneCenter, "TestPlane", logicTestPlane, fPhysiTransferMgnt,
                    false, 0);

  // Target

  if (GetTargetThickness() > 0.) {
    fSolidTarget =
      new G4Tubs("Target", 0., GetTargetRadius(), GetTargetThickness() / 2., 0., twopi);

    fLogicTarget = new G4LogicalVolume(fSolidTarget, GetTargetMaterial(), "Target");

    G4int i = GetTargetAngle();

    G4String angle = std::to_string(i);
    G4StrUtil::strip(angle);
    angle = "Y" + angle;

    g4rot = new G4RotationMatrix();
    *g4rot = StringToRotationMatrix(angle);
    *g4rot = g4rot->inverse();
    if (*g4rot == G4RotationMatrix()) g4rot = nullptr;

    G4ThreeVector targetCenter(0., 0., GetTargetPos());

    fPhysiTarget =
      new G4PVPlacement(g4rot, targetCenter, "Target", fLogicTarget, fPhysiCaptureMgnt, false, 0);
  }

  // Degrader

  if (GetDegraderThickness() > 0.) {
    fSolidDegrader =
      new G4Tubs("Degrader", 0., GetDegraderRadius(), GetDegraderThickness() / 2., 0., twopi);

    fLogicDegrader = new G4LogicalVolume(fSolidDegrader, GetDegraderMaterial(), "Degrader");

    G4ThreeVector degraderCenter = G4ThreeVector(0., 0., GetDegraderPos());

    fPhysiDegrader = new G4PVPlacement(nullptr, degraderCenter, "Degrader", fLogicDegrader,
                                       fPhysiTransferMgnt, false, 0);
  }

  return fPhysiWorld;
}

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

void F04DetectorConstruction::SetWorldMaterial(const G4String materialChoice)
{
  G4Material* pttoMaterial = G4NistManager::Instance()->FindOrBuildMaterial(materialChoice);

  if (pttoMaterial != fWorldMaterial) {
    if (pttoMaterial) {
      fWorldMaterial = pttoMaterial;
      G4RunManager::GetRunManager()->PhysicsHasBeenModified();
    }
    else {
      G4cout << "\n--> WARNING from SetWorldMaterial : " << materialChoice << " not found"
             << G4endl;
    }
  }
}

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

void F04DetectorConstruction::SetTargetMaterial(const G4String materialChoice)
{
  G4Material* pttoMaterial = G4NistManager::Instance()->FindOrBuildMaterial(materialChoice);

  if (pttoMaterial != fTargetMaterial) {
    if (pttoMaterial) {
      fTargetMaterial = pttoMaterial;
      G4RunManager::GetRunManager()->PhysicsHasBeenModified();
    }
    else {
      G4cout << "\n-->  WARNING from SetTargetMaterial : " << materialChoice << " not found"
             << G4endl;
    }
  }
}

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

void F04DetectorConstruction::SetDegraderMaterial(const G4String materialChoice)

{
  G4Material* pttoMaterial = G4NistManager::Instance()->FindOrBuildMaterial(materialChoice);

  if (pttoMaterial != fDegraderMaterial) {
    if (pttoMaterial) {
      fDegraderMaterial = pttoMaterial;
      G4RunManager::GetRunManager()->PhysicsHasBeenModified();
    }
    else {
      G4cout << "\n--> WARNING from SetDegraderMaterial : " << materialChoice << " not found"
             << G4endl;
    }
  }
}

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

void F04DetectorConstruction::SetWorldSizeZ(G4double val)
{
  fWorldSizeZ = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetWorldSizeR(G4double val)
{
  fWorldSizeR = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetCaptureMgntRadius(G4double val)
{
  fCaptureMgntRadius = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetCaptureMgntLength(G4double val)
{
  fCaptureMgntLength = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetCaptureMgntB1(G4double val)
{
  fCaptureMgntB1 = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetCaptureMgntB2(G4double val)
{
  fCaptureMgntB2 = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetTransferMgntRadius(G4double val)
{
  fTransferMgntRadius = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetTransferMgntLength(G4double val)
{
  fTransferMgntLength = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetTransferMgntB(G4double val)
{
  fTransferMgntB = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetTransferMgntPos(G4double val)
{
  fTransferMgntPos = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetTargetRadius(G4double val)
{
  fTargetRadius = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetTargetThickness(G4double val)
{
  fTargetThickness = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetTargetPos(G4double val)
{
  fTargetPos = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetTargetAngle(G4int val)
{
  fTargetAngle = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetDegraderRadius(G4double val)
{
  fDegraderRadius = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetDegraderThickness(G4double val)
{
  fDegraderThickness = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::SetDegraderPos(G4double val)
{
  fDegraderPos = val;
  if (G4StateManager::GetStateManager()->GetCurrentState() != G4State_PreInit)
    G4RunManager::GetRunManager()->GeometryHasBeenModified();
}

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

void F04DetectorConstruction::ConstructSDandField()
{
  if (!fFieldSetUp.Get()) {
    F04GlobalField* field = F04GlobalField::GetObject(this);
    G4AutoDelete::Register(field);  // Kernel will delete the F04GlobalField
    fFieldSetUp.Put(field);
  }
}

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

G4RotationMatrix F04DetectorConstruction::StringToRotationMatrix(G4String rotation)
{
  // We apply successive rotations OF THE OBJECT around the FIXED
  // axes of the parent's local coordinates; rotations are applied
  // left-to-right (rotation="r1,r2,r3" => r1 then r2 then r3).

  G4RotationMatrix rot;

  unsigned int place = 0;

  while (place < rotation.size()) {
    G4double angle;
    char* p(nullptr);
    G4String current = rotation.substr(place + 1);
    angle = strtod(current.c_str(), &p) * deg;

    if (!p || (*p != ',' && *p != '\0')) {
      G4cerr << "Invalid rotation specification: " << rotation.c_str() << G4endl;

      return rot;
    }

    G4RotationMatrix thisRotation;

    switch (rotation.substr(place, 1).c_str()[0]) {
      case 'X':
      case 'x':
        thisRotation = G4RotationMatrix(CLHEP::HepRotationX(angle));
        break;
      case 'Y':
      case 'y':
        thisRotation = G4RotationMatrix(CLHEP::HepRotationY(angle));
        break;
      case 'Z':
      case 'z':
        thisRotation = G4RotationMatrix(CLHEP::HepRotationZ(angle));
        break;
      default:
        G4cerr << " Invalid rotation specification: " << rotation << G4endl;
        return rot;
    }

    rot = thisRotation * rot;
    place = rotation.find(',', place);
    if (place > rotation.size()) break;
    ++place;
  }

  return rot;
}
