//
// ********************************************************************
// * 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.          *
// ********************************************************************
//
//
// $Id: G4GeometryMessenger.cc,v 1.6 2010/11/10 14:06:40 gcosmo Exp $
// GEANT4 tag $Name: geant4-09-04-ref-00 $
//
// --------------------------------------------------------------------
// GEANT 4 class source file
//
// G4GeometryMessenger
//
// Author: G.Cosmo, CERN.
//
// --------------------------------------------------------------------

#include <iomanip>
#include "G4GeometryMessenger.hh"

#include "G4TransportationManager.hh"
#include "G4GeometryManager.hh"
#include "G4VPhysicalVolume.hh"
#include "G4Navigator.hh"

#include "G4UIdirectory.hh"
#include "G4UIcommand.hh"
#include "G4UIcmdWith3VectorAndUnit.hh"
#include "G4UIcmdWith3Vector.hh"
#include "G4UIcmdWithoutParameter.hh"
#include "G4UIcmdWithABool.hh"
#include "G4UIcmdWithAnInteger.hh"
#include "G4UIcmdWithADouble.hh"
#include "G4UIcmdWithADoubleAndUnit.hh"

#include "G4GeomTestStreamLogger.hh"
#include "G4GeomTestVolume.hh"

//
// Constructor
//
G4GeometryMessenger::G4GeometryMessenger(G4TransportationManager* tman)
  : x(0,0,0), p(0,0,1), grdRes(100,100,100), cylRes(90,50,50),
    cylfZ(0.8), cylfR(0.8), newtol(false), tol(1E-4*mm),
    recLevel(0), recDepth(-1), tmanager(tman), tlogger(0), tvolume(0)
{
  geodir = new G4UIdirectory( "/geometry/" );
  geodir->SetGuidance( "Geometry control commands." );

  //
  // Geometry navigator commands
  //
  navdir = new G4UIdirectory( "/geometry/navigator/" );
  navdir->SetGuidance( "Geometry navigator control setup." );

  resCmd = new G4UIcmdWithoutParameter( "/geometry/navigator/reset", this );
  resCmd->SetGuidance( "Reset navigator and navigation history." );
  resCmd->SetGuidance( "NOTE: must be called only after kernel has been" );
  resCmd->SetGuidance( "      initialized once through the run manager!" );
  resCmd->AvailableForStates(G4State_Idle);

  verbCmd = new G4UIcmdWithAnInteger( "/geometry/navigator/verbose", this );
  verbCmd->SetGuidance( "Set run-time verbosity for the navigator." );
  verbCmd->SetGuidance(" 0 : Silent (default)");
  verbCmd->SetGuidance(" 1 : Display volume positioning and step lengths");
  verbCmd->SetGuidance(" 2 : Display step/safety info on point location");
  verbCmd->SetGuidance(" 3 : Display minimal state at -every- step");
  verbCmd->SetGuidance(" 4 : Maximum verbosity (very detailed!)");
  verbCmd->SetGuidance( "NOTE: this command has effect -only- if Geant4 has" );
  verbCmd->SetGuidance( "      been installed with the G4VERBOSE flag set!" );
  verbCmd->SetParameterName("level",true);
  verbCmd->SetDefaultValue(0);
  verbCmd->SetRange("level >=0 && level <=4");

  chkCmd = new G4UIcmdWithABool( "/geometry/navigator/check_mode", this );
  chkCmd->SetGuidance( "Set navigator in -check_mode- state." );
  chkCmd->SetGuidance( "This will cause extra checks to be applied during" );
  chkCmd->SetGuidance( "navigation. More strict and less tolerant conditions" );
  chkCmd->SetGuidance( "are applied. A run-time performance penalty may be" );
  chkCmd->SetGuidance( "observed when the -check_mode- state is activated." );
  chkCmd->SetGuidance( "NOTE: this command has effect -only- if Geant4 has" );
  chkCmd->SetGuidance( "      been installed with the G4VERBOSE flag set!" );
  chkCmd->SetParameterName("checkFlag",true);
  chkCmd->SetDefaultValue(false);
  chkCmd->AvailableForStates(G4State_Idle);

  pchkCmd = new G4UIcmdWithABool( "/geometry/navigator/push_notify", this );
  pchkCmd->SetGuidance( "Set navigator verbosity push notifications." );
  pchkCmd->SetGuidance( "This allows to disable/re-enable verbosity in" );
  pchkCmd->SetGuidance( "navigation, when tracks may get stuck and require" );
  pchkCmd->SetGuidance( "one artificial push along the direction by the" );
  pchkCmd->SetGuidance( "navigator. Notification is active by default." );
  pchkCmd->SetGuidance( "NOTE: this command has effect -only- if Geant4 has" );
  pchkCmd->SetGuidance( "      been installed with the G4VERBOSE flag set!" );
  pchkCmd->SetParameterName("pushFlag",true);
  pchkCmd->SetDefaultValue(true);
  pchkCmd->AvailableForStates(G4State_Idle);

  //
  // Geometry verification test commands
  //
  testdir = new G4UIdirectory( "/geometry/test/" );
  testdir->SetGuidance( "Geometry verification control setup." );
  testdir->SetGuidance( "Helps in detecting possible overlapping regions." );

  tolCmd = new G4UIcmdWithADoubleAndUnit( "/geometry/test/tolerance",this );
  tolCmd->SetGuidance( "Set error tolerance value." );
  tolCmd->SetGuidance( "Initial default value: 1E-4*mm." );
  tolCmd->SetParameterName( "Tolerance", true, true );
  tolCmd->SetDefaultValue( 1E-4 );
  tolCmd->SetDefaultUnit( "mm" );
  tolCmd->SetUnitCategory( "Length" );

  posCmd = new G4UIcmdWith3VectorAndUnit( "/geometry/test/position", this );
  posCmd->SetGuidance( "Set starting position for the line_test." );
  posCmd->SetParameterName( "X", "Y", "Z", true, true );
  posCmd->SetDefaultUnit( "cm" );

  dirCmd = new G4UIcmdWith3VectorAndUnit( "/geometry/test/direction", this );
  dirCmd->SetGuidance( "Set momentum direction for the line_test." );
  dirCmd->SetGuidance( "Direction needs not to be a unit vector." );
  dirCmd->SetParameterName( "Px", "Py", "Pz", true, true );
  dirCmd->SetRange( "Px != 0 || Py != 0 || Pz != 0" );

  linCmd = new G4UIcmdWithABool( "/geometry/test/line_test", this );
  linCmd->SetGuidance( "Performs test along a single specified direction/position." );
  linCmd->SetGuidance( "Use position and direction commands to change default." );
  linCmd->SetGuidance( "Initial default: position(0,0,0), direction(0,0,1)." );
  linCmd->SetGuidance( "If recursion flag is set to TRUE, the intersection checks" );
  linCmd->SetGuidance( "will be performed recursively in the geometry tree." );
  linCmd->SetParameterName("recursionFlag",true);
  linCmd->SetDefaultValue(false);
  linCmd->AvailableForStates(G4State_Idle);

  grzCmd = new G4UIcmdWith3Vector( "/geometry/test/grid_cells", this );
  grzCmd->SetGuidance( "Define resolution of grid geometry as number of cells," );
  grzCmd->SetGuidance( "specifying them for each dimension, X, Y and Z." );
  grzCmd->SetGuidance( "Will be applied to grid_test and recursive_test commands." );
  grzCmd->SetGuidance( "Initial default values: X=100, Y=100, Z=100." );
  grzCmd->SetParameterName( "X", "Y", "Z", true, true );
  grzCmd->SetDefaultValue( G4ThreeVector(100, 100, 100) );

  grdCmd = new G4UIcmdWithABool( "/geometry/test/grid_test", this );
  grdCmd->SetGuidance( "Start running the default grid test." );
  grdCmd->SetGuidance( "A grid of lines parallel to a cartesian axis is used;" );
  grdCmd->SetGuidance( "By default, only direct daughters of the mother volumes are checked." );
  grdCmd->SetGuidance( "If recursion flag is set to TRUE, the intersection checks" );
  grdCmd->SetGuidance( "will be performed recursively in the geometry tree." );
  grdCmd->SetGuidance( "NOTE: the recursion may take a very long time," );
  grdCmd->SetGuidance( "      depending on the geometry complexity !");
  grdCmd->SetParameterName("recursionFlag",true);
  grdCmd->SetDefaultValue(false);
  grdCmd->AvailableForStates(G4State_Idle);

  cyzCmd = new G4UIcmdWith3Vector( "/geometry/test/cylinder_geometry", this );
  cyzCmd->SetGuidance( "Define details of the cylinder geometry, specifying:" );
  cyzCmd->SetGuidance( "  nPhi - number of lines per Phi" );
  cyzCmd->SetGuidance( "  nZ   - number of Z points" );
  cyzCmd->SetGuidance( "  nRho - number of Rho points" );
  cyzCmd->SetGuidance( "Will be applied to the cylinder_test command." );
  cyzCmd->SetGuidance( "Initial default values: nPhi=90, nZ=50, nRho=50." );
  cyzCmd->SetParameterName( "nPhi", "nZ", "nRho", true, true );
  cyzCmd->SetDefaultValue( G4ThreeVector(90, 50, 50) );

  cfzCmd = new G4UIcmdWithADouble( "/geometry/test/cylinder_scaleZ", this );
  cfzCmd->SetGuidance( "Define the resolution of the cylinder geometry, specifying" );
  cfzCmd->SetGuidance( "the fraction scale for points along Z." );
  cfzCmd->SetGuidance( "Initial default values: fracZ=0.8" );
  cfzCmd->SetParameterName("fracZ",true);
  cfzCmd->SetDefaultValue(0.8);

  cfrCmd = new G4UIcmdWithADouble( "/geometry/test/cylinder_scaleRho", this );
  cfrCmd->SetGuidance( "Define the resolution of the cylinder geometry, specifying" );
  cfrCmd->SetGuidance( "the fraction scale for points along Rho." );
  cfrCmd->SetGuidance( "Initial default values: fracRho=0.8" );
  cfrCmd->SetParameterName("fracRho",true);
  cfrCmd->SetDefaultValue(0.8);

  cylCmd = new G4UIcmdWithABool( "/geometry/test/cylinder_test", this );
  cylCmd->SetGuidance( "Start running the cylinder test." );
  cylCmd->SetGuidance( "A set of lines in a cylindrical pattern of gradually" );
  cylCmd->SetGuidance( "increasing mesh size." );
  cylCmd->SetGuidance( "By default, only direct daughters of the mother volumes are checked." );
  cylCmd->SetGuidance( "If recursion flag is set to TRUE, the intersection checks" );
  cylCmd->SetGuidance( "will be performed recursively in the geometry tree." );
  cylCmd->SetGuidance( "NOTE: the recursion may take a very long time," );
  cylCmd->SetGuidance( "      depending on the geometry complexity !");
  cylCmd->SetParameterName("recursionFlag",true);
  cylCmd->SetDefaultValue(false);
  cylCmd->AvailableForStates(G4State_Idle);

  rcsCmd = new G4UIcmdWithAnInteger( "/geometry/test/recursion_start", this );
  rcsCmd->SetGuidance( "Set the initial level in the geometry tree for recursion." );
  rcsCmd->SetGuidance( "recursive_test will then start from the specified level." );
  rcsCmd->SetParameterName("initial_level",true);
  rcsCmd->SetDefaultValue(0);

  rcdCmd = new G4UIcmdWithAnInteger( "/geometry/test/recursion_depth", this );
  rcdCmd->SetGuidance( "Set the depth in the geometry tree for recursion." );
  rcdCmd->SetGuidance( "recursive_test will then stop after reached the specified depth." );
  rcdCmd->SetGuidance( "By default, recursion will proceed for the whole depth." );
  rcdCmd->SetParameterName("recursion_depth",true);
  rcdCmd->SetDefaultValue(-1);

  // Obsolete verification commands ...

  runCmd = new G4UIcmdWithABool( "/geometry/test/run", this );
  runCmd->SetGuidance( "Start running the default grid test." );
  runCmd->SetGuidance( "Same as the grid_test command." );
  runCmd->SetGuidance( "If recursion flag is set to TRUE, the intersection checks" );
  runCmd->SetGuidance( "will be performed recursively in the geometry tree." );
  runCmd->SetGuidance( "NOTE: the recursion may take a very long time," );
  runCmd->SetGuidance( "      depending on the geometry complexity !");
  runCmd->SetParameterName("recursionFlag",true);
  runCmd->SetDefaultValue(false);
  runCmd->AvailableForStates(G4State_Idle);

  recCmd = new G4UIcmdWithoutParameter( "/geometry/test/recursive_test", this );
  recCmd->SetGuidance( "Start running the recursive grid test." );
  recCmd->SetGuidance( "A grid of lines along a cartesian axis is recursively" );
  recCmd->SetGuidance( "to all daughters and daughters of daughters, etc." );
  recCmd->SetGuidance( "NOTE: it may take a very long time," );
  recCmd->SetGuidance( "      depending on the geometry complexity !");
  recCmd->AvailableForStates(G4State_Idle);
}

//
// Destructor
//
G4GeometryMessenger::~G4GeometryMessenger()
{
  delete linCmd; delete posCmd; delete dirCmd;
  delete grzCmd; delete grdCmd; delete recCmd; delete runCmd;
  delete rcsCmd; delete rcdCmd;
  delete cyzCmd; delete cfzCmd; delete cfrCmd; delete cylCmd;
  delete tolCmd;
  delete resCmd; delete verbCmd; delete pchkCmd; delete chkCmd;
  delete geodir; delete navdir; delete testdir;
  delete tvolume; delete tlogger;
}

//
// Init
//
void
G4GeometryMessenger::Init()
{
  // Clean old allocated loggers...
  //
  if (tlogger) delete tlogger;
  if (tvolume) delete tvolume;

  // Create a logger to send errors/output to cout
  //
  tlogger = new G4GeomTestStreamLogger(std::cout);

  // Get the world volume
  //
  G4VPhysicalVolume* world =
    tmanager->GetNavigatorForTracking()->GetWorldVolume();

  // Test the actual detector...
  //
  tvolume = new G4GeomTestVolume(world, tlogger);
}

//
// SetNewValue
//
void
G4GeometryMessenger::SetNewValue( G4UIcommand* command, G4String newValues )
{
  if (command == resCmd) {
    ResetNavigator();
  }
  else if (command == verbCmd) {
    SetVerbosity( newValues );
  }
  else if (command == chkCmd) {
    SetCheckMode( newValues );
  }
  else if (command == posCmd) {
    x = posCmd->GetNew3VectorValue( newValues );
  }
  else if (command == dirCmd) {
    p = dirCmd->GetNew3VectorValue( newValues );
    if (p.mag() < DBL_MIN) {
      G4cerr << "Please specify non-zero momentum!" << G4endl;
      p = G4ThreeVector(0,0,1);
    }
  }
  else if (command == tolCmd) {
    tol = tolCmd->GetNewDoubleValue( newValues );
    newtol = true;
  }
  else if (command == linCmd) {
    Init();
    if (linCmd->GetNewBoolValue( newValues ))
      RecursiveLineTest();
    else
      LineTest();
  }
  else if ((command == grdCmd) || (command == runCmd)){
    Init();
    if (grdCmd->GetNewBoolValue( newValues ) || runCmd->GetNewBoolValue( newValues ))
      RecursiveGridTest();
    else
      GridTest();
  }
  else if (command == grzCmd) {
    grdRes = grzCmd->GetNew3VectorValue( newValues );
    if (grdRes.mag() < DBL_MIN) {
      G4cerr << "Please specify non-zero resolution!" << G4endl;
      grdRes = G4ThreeVector(100,100,100);
    }
  }
  else if (command == cyzCmd) {
    cylRes = cyzCmd->GetNew3VectorValue( newValues );
  }
  else if (command == cfzCmd) {
    cylfZ = cfzCmd->GetNewDoubleValue( newValues );
  }
  else if (command == cfrCmd) {
    cylfR = cfrCmd->GetNewDoubleValue( newValues );
  }
  else if (command == rcsCmd) {
    recLevel = rcsCmd->GetNewIntValue( newValues );
  }
  else if (command == rcdCmd) {
    recDepth = rcdCmd->GetNewIntValue( newValues );
  }
  else if (command == recCmd) {
    Init();
    RecursiveGridTest();
  }
  else if (command == cylCmd) {
    Init();
    if (cylCmd->GetNewBoolValue( newValues ))
      RecursiveCylinderTest();
    else
      CylinderTest();
  }
}

//
// GetCurrentValue
//
G4String
G4GeometryMessenger::GetCurrentValue(G4UIcommand* command )
{
  G4String cv = "";
  if (command == posCmd) {
    cv = posCmd->ConvertToString( x, "cm" );
  }
  else if (command == tolCmd) {
    cv = tolCmd->ConvertToString( tol, "mm" );
  }
  else if (command == dirCmd) {
    cv = dirCmd->ConvertToString( p, "GeV" );
  }
  return cv;
}

//
// CheckGeometry
//
void
G4GeometryMessenger::CheckGeometry()
{
  // Verify that the geometry is closed
  //
  G4GeometryManager* geomManager = G4GeometryManager::GetInstance();
  if (!geomManager->IsGeometryClosed()) {
    geomManager->OpenGeometry();
    geomManager->CloseGeometry(true);
  }	
}

//
// ResetNavigator
//
void
G4GeometryMessenger::ResetNavigator()
{
  // Close geometry and reset optimisation if necessary
  //
  CheckGeometry();

  // Reset navigator's state
  //
  G4ThreeVector pt(0,0,0);
  G4Navigator* navigator = tmanager->GetNavigatorForTracking();
  navigator->LocateGlobalPointAndSetup(pt,0,false);
}

//
// Set navigator verbosity
//
void
G4GeometryMessenger::SetVerbosity(G4String input)
{
  G4int level = verbCmd->GetNewIntValue(input);
  G4Navigator* navigator = tmanager->GetNavigatorForTracking();
  navigator->SetVerboseLevel(level);
}

//
// Set navigator mode
//
void
G4GeometryMessenger::SetCheckMode(G4String input)
{
  G4bool mode = chkCmd->GetNewBoolValue(input);
  G4Navigator* navigator = tmanager->GetNavigatorForTracking();
  navigator->CheckMode(mode);
}

//
// Set navigator verbosity for push notifications
//
void
G4GeometryMessenger::SetPushFlag(G4String input)
{
  G4bool mode = pchkCmd->GetNewBoolValue(input);
  G4Navigator* navigator = tmanager->GetNavigatorForTracking();
  navigator->SetPushVerbosity(mode);
}

//
// LineTest
//
void
G4GeometryMessenger::LineTest()
{
  // Close geometry if necessary
  //
  CheckGeometry();

  // Verify if error tolerance has changed
  //
  if (newtol) tvolume->SetTolerance(tol);

  // Make test on single line supplied by user
  //
  tvolume->TestOneLine( x, p );

  // Print out any errors, if found
  //
  tvolume->ReportErrors();
}

//
// RecursiveLineTest
//
void
G4GeometryMessenger::RecursiveLineTest()
{
  // Close geometry if necessary
  //
  CheckGeometry();

  // Verify if error tolerance has changed
  //
  if (newtol) tvolume->SetTolerance(tol);

  // Make test on single line supplied by user recursively
  //
  tvolume->TestRecursiveLine( x, p, recLevel, recDepth );

  // Print out any errors, if found
  //
  tvolume->ReportErrors();
}

//
// GridTest
//
void
G4GeometryMessenger::GridTest()
{
  // Close geometry if necessary
  //
  CheckGeometry();

  // Verify if error tolerance has changed
  //
  if (newtol) tvolume->SetTolerance(tol);

  // Apply set of trajectories in a 3D grid pattern
  //
  tvolume->TestCartGridXYZ( G4int(grdRes.x()),
                            G4int(grdRes.y()),
                            G4int(grdRes.z()) );
  
  // Print out any errors, if found
  //
  tvolume->ReportErrors();
}

//
// RecursiveGridTest
//
void
G4GeometryMessenger::RecursiveGridTest()
{
  // Close geometry if necessary
  //
  CheckGeometry();

  // Verify if error tolerance has changed
  //
  if (newtol) tvolume->SetTolerance(tol);

  // Apply set of trajectories in a 3D grid pattern recursively
  //
  tvolume->TestRecursiveCartGrid( G4int(grdRes.x()),
                                  G4int(grdRes.y()),
                                  G4int(grdRes.z()),
                                  recLevel, recDepth );
  
  // Print out any errors, if found
  //
  tvolume->ReportErrors();
}

//
// CylinderTest
//
void
G4GeometryMessenger::CylinderTest()
{
  // Close geometry if necessary
  //
  CheckGeometry();

  // Verify if error tolerance has changed
  //
  if (newtol) tvolume->SetTolerance(tol);

  // Apply default set of lines in a cylindrical pattern of gradually
  // increasing mesh size of trajectories in a 3D grid pattern
  //
  tvolume->TestCylinder(G4int(cylRes.x()),
                        G4int(cylRes.y()),
                        G4int(cylRes.z()),
                        cylfZ, cylfR, true);
  
  // Print out any errors, if found
  //
  tvolume->ReportErrors();
}

//
// RecursiveCylinderTest
//
void
G4GeometryMessenger::RecursiveCylinderTest()
{
  // Close geometry if necessary
  //
  CheckGeometry();

  // Verify if error tolerance has changed
  //
  if (newtol) tvolume->SetTolerance(tol);

  // Apply default set of lines in a cylindrical pattern of gradually
  // increasing mesh size of trajectories in a 3D grid pattern recursively
  //
  tvolume->TestRecursiveCylinder(G4int(cylRes.x()),
                                 G4int(cylRes.y()),
                                 G4int(cylRes.z()),
                                 cylfZ, cylfR, true,
                                 recLevel, recDepth );
  
  // Print out any errors, if found
  //
  tvolume->ReportErrors();
}
