//
// ********************************************************************
// * 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: G4RunManagerKernel.cc,v 1.48 2010/06/11 09:02:55 gcosmo Exp $
// GEANT4 tag $Name: geant4-09-04-beta-cand-01 $
//
//

#include "G4RunManagerKernel.hh"

#include <vector>

#include "G4StateManager.hh"
#include "G4ApplicationState.hh"
#include "G4ExceptionHandler.hh"
#include "G4PrimaryTransformer.hh"
#include "G4GeometryManager.hh"
#include "G4TransportationManager.hh"
#include "G4VPhysicalVolume.hh"
#include "G4LogicalVolume.hh"
#include "G4VUserPhysicsList.hh"
#include "G4ParticleTable.hh"
#include "G4Region.hh"
#include "G4RegionStore.hh"
#include "G4ProductionCuts.hh"
#include "G4ProductionCutsTable.hh"
#include "G4SDManager.hh"
#include "G4UImanager.hh"
#include "G4VVisManager.hh"
#include "G4UnitsTable.hh"
#include "G4Version.hh"
#include "G4ios.hh"

#ifdef G4FPE_DEBUG
  #include "G4FPEDetection.hh"
#endif

G4RunManagerKernel* G4RunManagerKernel::fRunManagerKernel = 0;

G4RunManagerKernel* G4RunManagerKernel::GetRunManagerKernel()
{ return fRunManagerKernel; }

G4RunManagerKernel::G4RunManagerKernel()
:physicsList(0),currentWorld(0),
 geometryInitialized(false),physicsInitialized(false),
 geometryNeedsToBeClosed(true),geometryToBeOptimized(true),
 physicsNeedsToBeReBuilt(true),verboseLevel(0),
 numberOfParallelWorld(0)
{
#ifdef G4FPE_DEBUG
  InvalidOperationDetection();
#endif

  defaultExceptionHandler = new G4ExceptionHandler();
  if(fRunManagerKernel)
  {
    G4Exception("G4RunManagerKernel::G4RunManagerKernel()","MoreThanOneRunManager",
                FatalException,"More than one G4RunManagerKernel is constructed.");
  }
  fRunManagerKernel = this;

  G4ParticleTable* particleTable = G4ParticleTable::GetParticleTable();
  if(particleTable->entries()>0)
  {
    // No particle should be registered beforehand
    G4cerr<<"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"<<G4endl;
    G4cerr<<" G4RunManagerKernel fatal exception"<<G4endl;
    G4cerr<<"  -- Following particles have already been registered"<<G4endl;
    G4cerr<<"     before G4RunManagerKernel is instantiated."<<G4endl;
    for(int i=0;i<particleTable->entries();i++)
    { G4cerr<<"     "<<particleTable->GetParticle(i)->GetParticleName()<<G4endl; }
    G4cerr<<"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"<<G4endl;
    G4Exception("G4RunManagerKernel::G4RunManagerKernel()","StaticParticleDefinition",
       FatalException,"Particles have already been instantiated before G4RunManagerKernel.");
  }
  
  // construction of Geant4 kernel classes
  eventManager = new G4EventManager();
  defaultRegion = new G4Region("DefaultRegionForTheWorld"); // deleted by store
  defaultRegion->SetProductionCuts(
    G4ProductionCutsTable::GetProductionCutsTable()->GetDefaultProductionCuts());

  // Following line is tentatively moved from SetPhysics method
  // Commented out for introduction of non-static particle definition // G4ParticleTable::GetParticleTable()->SetReadiness();
  // set the initial application state
  G4StateManager::GetStateManager()->SetNewState(G4State_PreInit);

  // version banner
  G4String vs = G4Version;
  vs = vs.substr(1,vs.size()-2);
  versionString = " Geant4 version ";
  versionString += vs;
  versionString += "   ";
  versionString += G4Date;
  G4cout << G4endl
    << "*************************************************************" << G4endl
    << versionString << G4endl
    << "                      Copyright : Geant4 Collaboration" << G4endl
    << "                      Reference : NIM A 506 (2003), 250-303" << G4endl
    << "                            WWW : http://cern.ch/geant4" << G4endl
    << "*************************************************************" << G4endl
    << G4endl;
}

G4RunManagerKernel::~G4RunManagerKernel()
{
  // set the application state to the quite state
  if(verboseLevel>0) G4cout << "G4 kernel has come to Quit state." << G4endl;
  G4StateManager* pStateManager = G4StateManager::GetStateManager();
  pStateManager->SetNewState(G4State_Quit);

  // open geometry for deletion
  G4GeometryManager::GetInstance()->OpenGeometry();

  // deletion of Geant4 kernel classes
  G4SDManager* fSDM = G4SDManager::GetSDMpointerIfExist();
  if(fSDM)
  {
    delete fSDM;
    if(verboseLevel>1) G4cout << "G4SDManager deleted." << G4endl;
  }
  delete eventManager;
  if(verboseLevel>1) G4cout << "EventManager deleted." << G4endl;
  G4UImanager* pUImanager = G4UImanager::GetUIpointer();
  {
    if(pUImanager) delete pUImanager;
    if(verboseLevel>1) G4cout << "UImanager deleted." << G4endl;
  }
  G4UnitDefinition::ClearUnitsTable();
  if(verboseLevel>1) G4cout << "Units table cleared." << G4endl;
  delete pStateManager; 
  if(verboseLevel>1) G4cout << "StateManager deleted." << G4endl;
  delete defaultExceptionHandler;
  if(verboseLevel>1) G4cout << "RunManagerKernel is deleted." << G4endl;
  fRunManagerKernel = 0;
}

void G4RunManagerKernel::DefineWorldVolume(G4VPhysicalVolume* worldVol,
                                     G4bool topologyIsChanged)
{
  G4StateManager*    stateManager = G4StateManager::GetStateManager();
  G4ApplicationState currentState = stateManager->GetCurrentState();
  if(!(currentState==G4State_Idle||currentState==G4State_PreInit))
  { 
    G4Exception("G4RunManagerKernel::DefineWorldVolume",
                "DefineWorldVolumeAtIncorrectState",
                JustWarning,
                "Geant4 kernel is not PreInit or Idle state : Method ignored.");
    if(verboseLevel>1) G4cerr << "Current application state is " 
      << stateManager->GetStateString(currentState) << G4endl;
    return;
  }

  // The world volume MUST NOT have a region defined by the user
  if(worldVol->GetLogicalVolume()->GetRegion())
  {
    if(worldVol->GetLogicalVolume()->GetRegion()!=defaultRegion)
    {
      G4cerr << "The world volume has a user-defined region <"
           << worldVol->GetLogicalVolume()->GetRegion()->GetName()
           << ">." << G4endl;
      G4Exception("G4RunManager::DefineWorldVolume",
                "RUN:WorldHasUserDefinedRegion",
                FatalException,
                "World would have a default region assigned by RunManagerKernel.");
    }
  }

  // Remove old world logical volume from the default region, if exist
  if(defaultRegion->GetNumberOfRootVolumes())
  {
    if(defaultRegion->GetNumberOfRootVolumes()>size_t(1))
    {
      G4Exception("G4RunManager::DefineWorldVolume",
                "DefaultRegionHasMoreThanOneVolume",
                FatalException,
                "Default world region should have a unique logical volume.");
    }
    std::vector<G4LogicalVolume*>::iterator lvItr
     = defaultRegion->GetRootLogicalVolumeIterator();
    defaultRegion->RemoveRootLogicalVolume(*lvItr,false);
    if(verboseLevel>1) G4cout 
     << "Obsolete world logical volume is removed from the default region." << G4endl;
  }

  // Accept the world volume
  currentWorld = worldVol; 

  // Set the default region to the world
  G4LogicalVolume* worldLog = currentWorld->GetLogicalVolume();
  worldLog->SetRegion(defaultRegion);
  defaultRegion->AddRootLogicalVolume(worldLog);
  if(verboseLevel>1) G4cout << worldLog->GetName()
   << " is registered to the default region." << G4endl;

  // Set the world volume, notify the Navigator and reset its state
  G4TransportationManager::GetTransportationManager()
      ->SetWorldForTracking(currentWorld);
  if(topologyIsChanged) geometryNeedsToBeClosed = true;
  
  // Notify the VisManager as well
  G4VVisManager* pVVisManager = G4VVisManager::GetConcreteInstance();
  if(pVVisManager) pVVisManager->GeometryHasChanged();

  geometryInitialized = true;
  if(physicsInitialized && currentState!=G4State_Idle)
  { stateManager->SetNewState(G4State_Idle); }
} 
  
void G4RunManagerKernel::SetPhysics(G4VUserPhysicsList* uPhys)
{
  physicsList = uPhys;
  G4ParticleTable::GetParticleTable()->SetReadiness();
  physicsList->ConstructParticle();
  if(verboseLevel>2) G4ParticleTable::GetParticleTable()->DumpTable();
  if(verboseLevel>1)
  {
    G4cout << "List of instantiated particles ============================================" << G4endl;
    G4int nPtcl = G4ParticleTable::GetParticleTable()->entries();
    for(G4int i=0;i<nPtcl;i++)
    {
      G4ParticleDefinition* pd = G4ParticleTable::GetParticleTable()->GetParticle(i);
      G4cout << pd->GetParticleName() << " ";
      if(i%10==9) G4cout << G4endl;
    }
    G4cout << G4endl;
  }
}
  
void G4RunManagerKernel::InitializePhysics()
{
  G4StateManager*    stateManager = G4StateManager::GetStateManager();
  G4ApplicationState currentState = stateManager->GetCurrentState();
  if(!(currentState==G4State_Idle||currentState==G4State_PreInit))
  { 
    G4Exception("G4RunManagerKernel::InitializePhysics",
                "InitializePhysicsAtIncorrectState",
                JustWarning,
                "Geant4 kernel is not PreInit or Idle state : Method ignored.");
    return;
  }

  if(!physicsList)
  {
    G4Exception("G4RunManagerKernel::InitializePhysics",
                "PhysicsListIsNotDefined",
                FatalException,
                "G4VUserPhysicsList is not defined");
  }

  //if(verboseLevel>1) G4cout << "physicsList->ConstructParticle() start." << G4endl;
  //physicsList->ConstructParticle();

  if(verboseLevel>1) G4cout << "physicsList->Construct() start." << G4endl;
  if(numberOfParallelWorld>0) physicsList->UseCoupledTransportation();
  physicsList->Construct();

  if(verboseLevel>1) G4cout << "physicsList->CheckParticleList() start." << G4endl;
  physicsList->CheckParticleList();
  if(verboseLevel>1) G4cout << "physicsList->setCut() start." << G4endl;
  physicsList->SetCuts();
  CheckRegions();
  physicsInitialized = true;
  if(geometryInitialized && currentState!=G4State_Idle)
  { stateManager->SetNewState(G4State_Idle); }
}

G4bool G4RunManagerKernel::RunInitialization()
{
  G4StateManager*    stateManager = G4StateManager::GetStateManager();
  G4ApplicationState currentState = stateManager->GetCurrentState();

  if(!geometryInitialized) 
  { 
    G4Exception("G4RunManagerKernel::RunInitialization",
                "GeometryHasNotYetInitialized",
                JustWarning,
                "Geometry has not yet initialized : method ignored.");
    return false;
  }
  
  if(!physicsInitialized) 
  { 
    G4Exception("G4RunManagerKernel::RunInitialization",
                "PhysicsHasNotYetInitialized",
                JustWarning,
                "Physics has not yet initialized : method ignored.");
    return false;
  }

  if( currentState != G4State_Idle )
  { 
    G4Exception("G4RunManagerKernel::RunInitialization",
                "RunInitializationAtIncorrectState",
                JustWarning,
                "Geant4 kernel not in Idle state : method ignored.");
    return false;
  }

  //if(numberOfParallelWorld>0)
  //{ // Confirm G4CoupledTransportation is used 
  //  if(!ConfirmCoupledTransportation())
  //  { G4Exception("G4CoupledTransportation must be used for parallel world."); }
  //}
    
  UpdateRegion();
  BuildPhysicsTables();

  if(geometryNeedsToBeClosed) ResetNavigator();
 
  GetPrimaryTransformer()->CheckUnknown();

  stateManager->SetNewState(G4State_GeomClosed);
  return true;
}

void G4RunManagerKernel::RunTermination()
{ G4StateManager::GetStateManager()->SetNewState(G4State_Idle); }

void G4RunManagerKernel::ResetNavigator()
{
  // We have to tweak the navigator's state in case a geometry has been
  // modified between runs. By the following calls we ensure that navigator's
  // state is reset properly. It is required the geometry to be closed
  // and previous optimisations to be cleared.
  
  G4GeometryManager* geomManager = G4GeometryManager::GetInstance();
  if(verboseLevel>1) G4cout << "Start closing geometry." << G4endl;
  geomManager->OpenGeometry();
  geomManager->CloseGeometry(geometryToBeOptimized, verboseLevel>1);
 
  // Reseting Navigator has been moved to G4Eventmanager, so that resetting
  // is now done for every event.  
  // G4ThreeVector center(0,0,0);
  // G4Navigator* navigator =
  //     G4TransportationManager::GetTransportationManager()->GetNavigatorForTracking();
  // navigator->LocateGlobalPointAndSetup(center,0,false);

  geometryNeedsToBeClosed = false;
}

void G4RunManagerKernel::UpdateRegion()
{
  G4StateManager*    stateManager = G4StateManager::GetStateManager();
  G4ApplicationState currentState = stateManager->GetCurrentState();
  if( currentState != G4State_Idle )
  { 
    G4Exception("G4RunManagerKernel::UpdateRegion",
                "RegionUpdateAtIncorrectState",
                JustWarning,
                "Geant4 kernel not in Idle state : method ignored.");
    return;
  }

  CheckRegions();
  G4RegionStore::GetInstance()->UpdateMaterialList(currentWorld);
  G4ProductionCutsTable::GetProductionCutsTable()->UpdateCoupleTable(currentWorld);
}

void G4RunManagerKernel::BuildPhysicsTables()
{ 
  if(G4ProductionCutsTable::GetProductionCutsTable()->IsModified()
  || physicsNeedsToBeReBuilt)
  {
    physicsList->BuildPhysicsTable();
    G4ProductionCutsTable::GetProductionCutsTable()->PhysicsTableUpdated();
    physicsNeedsToBeReBuilt = false;
  }

  if(verboseLevel>1) DumpRegion();
  if(verboseLevel>0) physicsList->DumpCutValuesTable();
  physicsList->DumpCutValuesTableIfRequested();
}

void G4RunManagerKernel::CheckRegions()
{
  G4TransportationManager* transM = G4TransportationManager::GetTransportationManager();
  size_t nWorlds = transM->GetNoWorlds();
  std::vector<G4VPhysicalVolume*>::iterator wItr;
  for(size_t i=0;i<G4RegionStore::GetInstance()->size();i++)
  { 
    G4Region* region = (*(G4RegionStore::GetInstance()))[i];

    //Let each region have a pointer to the world volume where it belongs to.
    //G4Region::SetWorld() checks if the region belongs to the given world and set it
    //only if it does. Thus, here we go through all the registered world volumes.
    wItr = transM->GetWorldsIterator();
    for(size_t iw=0;iw<nWorlds;iw++)
    {
      region->SetWorld(*wItr);
      wItr++;
    }

    //Now check for the regions which belongs to the tracking world
    if(region->GetWorldPhysical()!=currentWorld) continue;

    G4ProductionCuts* cuts = region->GetProductionCuts();
    if(!cuts)
    {
      G4cerr << "Warning : Region <" << region->GetName()
             << "> does not have specific production cuts," << G4endl
             << "even though it appears in the current tracking world." << G4endl;
      G4cerr << "Default cuts are used for this region." << G4endl;
      region->SetProductionCuts(
          G4ProductionCutsTable::GetProductionCutsTable()->GetDefaultProductionCuts());
    }
  }
}

void G4RunManagerKernel::DumpRegion(const G4String& rname) const
{
  G4Region* region = G4RegionStore::GetInstance()->GetRegion(rname);
  if(region) DumpRegion(region);
}

void G4RunManagerKernel::DumpRegion(G4Region* region) const
{
  if(!region)
  {
    for(size_t i=0;i<G4RegionStore::GetInstance()->size();i++)
    { DumpRegion((*(G4RegionStore::GetInstance()))[i]); }
  }
  else
  {
    G4cout << G4endl;
    G4cout << "Region <" << region->GetName() << ">";
    if(region->GetWorldPhysical())
    {
      G4cout << " -- appears in <" 
           << region->GetWorldPhysical()->GetName() << "> world volume";
    }
    else
    { G4cout << " -- is not associated to any world."; }
    G4cout << G4endl;

    G4cout << " Root logical volume(s) : ";
    size_t nRootLV = region->GetNumberOfRootVolumes();
    std::vector<G4LogicalVolume*>::iterator lvItr = region->GetRootLogicalVolumeIterator();
    for(size_t j=0;j<nRootLV;j++)
    { G4cout << (*lvItr)->GetName() << " "; lvItr++; }
    G4cout << G4endl;

    G4cout << " Pointers : G4VUserRegionInformation[" << region->GetUserInformation() 
           << "], G4UserLimits[" << region->GetUserLimits() 
           << "], G4FastSimulationManager[" << region->GetFastSimulationManager()
           << "], G4UserSteppingAction[" << region->GetRegionalSteppingAction() << "]" << G4endl;
    
    if(region->GetWorldPhysical()!=currentWorld)
    {
      G4cout << G4endl;
      return;
    }
    G4cout << " Materials : ";
    std::vector<G4Material*>::const_iterator mItr = region->GetMaterialIterator();
    size_t nMaterial = region->GetNumberOfMaterials();
    for(size_t iMate=0;iMate<nMaterial;iMate++)
    {
      G4cout << (*mItr)->GetName() << " ";
      mItr++;
    }
    G4cout << G4endl;
    G4ProductionCuts* cuts = region->GetProductionCuts();
    if(!cuts)
    {
      G4cerr << "Warning : Region <" << region->GetName()
             << "> does not have specific production cuts." << G4endl;
      G4cerr << "Default cuts are used for this region." << G4endl;
      region->SetProductionCuts(
          G4ProductionCutsTable::GetProductionCutsTable()->GetDefaultProductionCuts());
    }
    G4cout << " Production cuts : "
           << "  gamma " << G4BestUnit(cuts->GetProductionCut("gamma"),"Length")
           << "     e- " << G4BestUnit(cuts->GetProductionCut("e-"),"Length")
           << "     e+ " << G4BestUnit(cuts->GetProductionCut("e+"),"Length")
           << " proton " << G4BestUnit(cuts->GetProductionCut("proton"),"Length")
           << G4endl;
  }
}

#include "G4ParticleTable.hh"
#include "G4ParticleDefinition.hh"
#include "G4ProcessManager.hh"
#include "G4ProcessVector.hh"
#include "G4VProcess.hh"
G4bool G4RunManagerKernel::ConfirmCoupledTransportation()
{
  G4ParticleTable* theParticleTable = G4ParticleTable::GetParticleTable();
  G4ParticleTable::G4PTblDicIterator* theParticleIterator = theParticleTable->GetIterator();
  theParticleIterator->reset();
  if((*theParticleIterator)())
  {
    G4ParticleDefinition* pd = theParticleIterator->value();
    G4ProcessManager* pm = pd->GetProcessManager();
    G4ProcessVector* pv = pm->GetAlongStepProcessVector(typeDoIt);
    G4VProcess* p = (*pv)[0];
    return ( (p->GetProcessName()) == "CoupledTransportation" );
  }
  return false;
}

