//
// ********************************************************************
// * 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.          *
// ********************************************************************
//
// G4BiasingProcessInterface
// --------------------------------------------------------------------

#include "G4BiasingProcessInterface.hh"
#include "G4VBiasingOperator.hh"
#include "G4VBiasingOperation.hh"
#include "G4ParticleChangeForOccurenceBiasing.hh"
#include "G4ParticleChangeForNothing.hh"
#include "G4VBiasingInteractionLaw.hh"
#include "G4InteractionLawPhysical.hh"
#include "G4ProcessManager.hh"
#include "G4BiasingAppliedCase.hh"
#include "G4ParallelGeometriesLimiterProcess.hh"

G4Cache<G4bool> G4BiasingProcessInterface::fResetInteractionLaws;// = true;
G4Cache<G4bool> G4BiasingProcessInterface::fCommonStart;//          = true;
G4Cache<G4bool> G4BiasingProcessInterface::fCommonEnd;//            = true;
G4Cache<G4bool> G4BiasingProcessInterface::fDoCommonConfigure;

G4BiasingProcessInterface::G4BiasingProcessInterface(const G4String& name)
  : G4VProcess( name ),
    fResetWrappedProcessInteractionLength( true )
{
  for (G4int i = 0 ; i < 8 ; i++)  fFirstLastFlags[i] = false;
  fResetInteractionLaws.Put( true );
  fCommonStart         .Put( true );
  fCommonEnd           .Put( true );
  fDoCommonConfigure   .Put( true );
}


G4BiasingProcessInterface::
G4BiasingProcessInterface(G4VProcess* wrappedProcess,
                          G4bool wrappedIsAtRest, 
                          G4bool wrappedIsAlongStep,
                          G4bool wrappedIsPostStep,
                          G4String useThisName)
  : G4VProcess(useThisName != ""
                 ? useThisName
                 : "biasWrapper("+wrappedProcess->GetProcessName()+")",
               wrappedProcess->GetProcessType()),
    fWrappedProcess( wrappedProcess ),
    fIsPhysicsBasedBiasing( true ),
    fWrappedProcessIsAtRest( wrappedIsAtRest ),
    fWrappedProcessIsAlong( wrappedIsAlongStep ),
    fWrappedProcessIsPost( wrappedIsPostStep )
{
  for (G4int i = 0 ; i < 8 ; ++i)
    fFirstLastFlags[i] = false;
  fResetInteractionLaws.Put( true );
  fCommonStart.Put(true);
  fCommonEnd.Put(true);
  fDoCommonConfigure.Put(true);
  
  SetProcessSubType(fWrappedProcess->GetProcessSubType());

  // -- create physical interaction law:
  fPhysicalInteractionLaw = new G4InteractionLawPhysical("PhysicalInteractionLawFor("+GetProcessName()+")");
  // -- instantiate particle change wrapper for occurrence biaising:
  fOccurenceBiasingParticleChange = new G4ParticleChangeForOccurenceBiasing("biasingPCfor"+GetProcessName());
  // -- instantiate a "do nothing" particle change:
  fDummyParticleChange = new G4ParticleChangeForNothing();
}

G4BiasingProcessInterface::~G4BiasingProcessInterface()
{
  delete fPhysicalInteractionLaw;
  delete fOccurenceBiasingParticleChange;
  delete fDummyParticleChange;
}

const G4BiasingProcessSharedData*
G4BiasingProcessInterface::GetSharedData( const G4ProcessManager* mgr )
{
  const auto & itr = G4BiasingProcessSharedData::fSharedDataMap.Find( mgr );
  if ( itr !=  G4BiasingProcessSharedData::fSharedDataMap.End( ) )
  {
    return (*itr).second;
  }
  else return nullptr;
}


void G4BiasingProcessInterface::StartTracking(G4Track* track)
{
  fCurrentTrack = track;
  if ( fIsPhysicsBasedBiasing ) fWrappedProcess->StartTracking(fCurrentTrack);
  fOccurenceBiasingOperation          = nullptr;
  fPreviousOccurenceBiasingOperation  = nullptr;
  fFinalStateBiasingOperation         = nullptr;
  fPreviousFinalStateBiasingOperation = nullptr;
  fNonPhysicsBiasingOperation         = nullptr;
  fPreviousNonPhysicsBiasingOperation = nullptr;
  fBiasingInteractionLaw              = nullptr;
  fPreviousBiasingInteractionLaw      = nullptr;
  
  fPreviousStepSize = -1.0;
  
  fResetWrappedProcessInteractionLength = false;
  
  if ( fCommonStart.Get() )
  {
    fCommonStart.Put( false );// = false;
    fCommonEnd.Put( true  );// = true;
      
    fSharedData->fCurrentBiasingOperator = nullptr;
    fSharedData->fPreviousBiasingOperator = nullptr;

    // -- Add a  "fSharedData->nStarting" here and outside bracket "fSharedData->nStarting++" and " if (fSharedData->nStarting) == fSharedData->(vector interface length)"
    // -- call to the loop "StartTracking" of operators"
      
    for (std::size_t optr=0 ; optr<(G4VBiasingOperator::GetBiasingOperators()).size(); ++optr)
    {
      (G4VBiasingOperator::GetBiasingOperators())[optr]->StartTracking( fCurrentTrack );
    }
  }
}

void G4BiasingProcessInterface::EndTracking()
{
  if ( fIsPhysicsBasedBiasing )
    fWrappedProcess->EndTracking();
  if ( fSharedData->fCurrentBiasingOperator)
    (fSharedData->fCurrentBiasingOperator)->ExitingBiasing(fCurrentTrack, this); 
  fBiasingInteractionLaw = nullptr;

  // -- Inform operators of end of tracking:
  if ( fCommonEnd.Get() )
  {
    fCommonEnd  .Put( false );// = false;
    fCommonStart.Put( true );// = true;
      
    for ( std::size_t optr=0; optr<(G4VBiasingOperator::GetBiasingOperators()).size(); ++optr)
    {
      (G4VBiasingOperator::GetBiasingOperators())[optr]->EndTracking( );
    }
    // -- for above loop, do as in StartTracking.
  }
}

G4double G4BiasingProcessInterface::
PostStepGetPhysicalInteractionLength( const G4Track& track,
                                      G4double previousStepSize,
                                      G4ForceCondition* condition )
{
  // ---------------------------------------------------------------------------------------------------
  // -- The "biasing process master" takes care of updating the biasing operator, and for all biasing
  // -- processes it invokes the PostStepGPIL of physical wrapped processes (anticipate stepping manager
  // -- call ! ) to make all cross-sections updated with current step, and hence available before the
  // -- first call to the biasing operator.
  // ---------------------------------------------------------------------------------------------------
  if ( fIamFirstGPIL )
  {
    // -- Update previous biasing operator, and assume the operator stays the same by
    // -- default and that it is not left at the beginning of this step. These
    // -- assumptions might be wrong if there is a volume change (in paralllel or
    // -- mass geometries) in what case the flags will be updated.
    fSharedData->fPreviousBiasingOperator = fSharedData->fCurrentBiasingOperator;
    fSharedData->fIsNewOperator           = false;
    fSharedData->fLeavingPreviousOperator = false;
    // -- If new volume, either in mass or parallel geometries, get possible new biasing operator:
    // -------------------------------------------------------------------------------------------
    // -- Get biasing operator in parallel geometries:
    G4bool firstStepInParallelVolume = false;
    if ( fSharedData->fParallelGeometriesLimiterProcess )
    {
      G4VBiasingOperator* newParallelOperator( nullptr );
      G4bool firstStep = ( track.GetCurrentStepNumber() == 1 );
      std::size_t iParallel = 0;
      for ( auto wasLimiting : fSharedData->fParallelGeometriesLimiterProcess->GetWasLimiting() )
      {
        if ( firstStep || wasLimiting )
        {
          firstStepInParallelVolume = true;
          auto tmpParallelOperator = G4VBiasingOperator::
             GetBiasingOperator((fSharedData->fParallelGeometriesLimiterProcess
             ->GetCurrentVolumes()[iParallel])
             ->GetLogicalVolume());
          if ( newParallelOperator )
          {
            if ( tmpParallelOperator )
            {
              G4ExceptionDescription ed;
              ed << " Several biasing operators are defined at the same place\n"
                 << " in parallel geometries ! Found:\n";
              ed << "    - `" << newParallelOperator->GetName() << "' and \n";
              ed << "    - `" << tmpParallelOperator->GetName() << "'.\n";
              ed << " Keeping `" << newParallelOperator->GetName()
                 << "'. Behavior not guaranteed ! Please consider having only one operator at a place."
                 << G4endl;
              G4Exception(" G4BiasingProcessInterface::PostStepGetPhysicalInteractionLength(...)",
                          "BIAS.GEN.30", JustWarning, ed);
            }
          }
          else newParallelOperator = tmpParallelOperator;
        }
        ++iParallel;
      }
      fSharedData->fParallelGeometryOperator = newParallelOperator;
    } // -- end of " if ( fSharedData->fParallelGeometriesLimiterProcess )"

    // -- Get biasing operator in mass geometry:
    // -- [§§ Note : bug with this first step ? Does not work if previous step was concurrently limited with geometry. Might make use of safety at last point ?]
    G4bool  firstStepInVolume = ( (track.GetStep()->GetPreStepPoint()->GetStepStatus() == fGeomBoundary)
                               || (track.GetCurrentStepNumber() == 1) );
    //      fSharedData->fIsNewOperator           = false;
    //      fSharedData->fLeavingPreviousOperator = false;
    if ( firstStepInVolume )
    {
      G4VBiasingOperator* newOperator = G4VBiasingOperator::
        GetBiasingOperator( track.GetVolume()->GetLogicalVolume() );
      fSharedData->fMassGeometryOperator = newOperator;
      if ( ( newOperator != nullptr ) && ( fSharedData->fParallelGeometryOperator != nullptr ) )
      {
        G4ExceptionDescription ed;
        ed << " Biasing operators are defined at the same place in mass and parallel geometries ! Found:\n";
        ed << "    - `" << fSharedData->fParallelGeometryOperator->GetName() << "' in parallel geometry and \n";
        ed << "    - `" << newOperator->GetName() << "' in mass geometry.\n";
        ed << " Keeping `" << fSharedData->fParallelGeometryOperator->GetName() << "'. Behavior not guaranteed ! Please consider having only one operator at a place. " << G4endl;
        G4Exception(" G4BiasingProcessInterface::PostStepGetPhysicalInteractionLength(...)",
                    "BIAS.GEN.31", JustWarning, ed);
      }
    }

    // -- conclude the operator selection, giving priority to parallel geometry (as told in exception message BIAS.GEN.30):
    if ( firstStepInVolume || firstStepInParallelVolume )
    {
      G4VBiasingOperator* newOperator = fSharedData->fParallelGeometryOperator;
      if ( newOperator == nullptr )
        newOperator = fSharedData->fMassGeometryOperator;
	  
      fSharedData->fCurrentBiasingOperator = newOperator ;

      if ( newOperator != fSharedData->fPreviousBiasingOperator )
      {
        fSharedData->fLeavingPreviousOperator = ( fSharedData->fPreviousBiasingOperator != nullptr ) ;
        fSharedData->fIsNewOperator = ( newOperator != nullptr );
      }
    }

    // -- calls to wrapped process PostStepGPIL's:
    // -------------------------------------------
    // -- Each physics wrapper process has its
    // --   fWrappedProcessPostStepGPIL      ,
    // --   fWrappedProcessForceCondition    ,
    // --   fWrappedProcessInteractionLength
    // -- updated.
    if ( fSharedData->fCurrentBiasingOperator != nullptr )
    {
      for (std::size_t i=0; i<(fSharedData->fPhysicsBiasingProcessInterfaces).size(); ++i)
      {
        (fSharedData->fPhysicsBiasingProcessInterfaces)[i]->InvokeWrappedProcessPostStepGPIL( track, previousStepSize, condition );
      }
    }
  } // -- end of "if ( fIamFirstGPIL )"

  // -- Remember previous operator and proposed operations, if any, and reset:
  // -------------------------------------------------------------------------
  // -- remember only in case some biasing might be called
  if ( ( fSharedData->fPreviousBiasingOperator != nullptr ) ||
       ( fSharedData->fCurrentBiasingOperator  != nullptr )    )
  {
    fPreviousOccurenceBiasingOperation  = fOccurenceBiasingOperation;
    fPreviousFinalStateBiasingOperation = fFinalStateBiasingOperation;
    fPreviousNonPhysicsBiasingOperation = fNonPhysicsBiasingOperation;
    fPreviousBiasingInteractionLaw      = fBiasingInteractionLaw;
    // -- reset:
    fOccurenceBiasingOperation          = nullptr;
    fFinalStateBiasingOperation         = nullptr;
    fNonPhysicsBiasingOperation         = nullptr;
    fBiasingInteractionLaw              = nullptr;
    // -- Physics PostStep and AlongStep GPIL
    // fWrappedProcessPostStepGPIL      : updated by InvokeWrappedProcessPostStepGPIL(...) above
    fBiasingPostStepGPIL                = DBL_MAX;
    // fWrappedProcessInteractionLength : updated by InvokeWrappedProcessPostStepGPIL(...) above; inverse of analog cross-section.
    // fWrappedProcessForceCondition    : updated by InvokeWrappedProcessPostStepGPIL(...) above
    fBiasingForceCondition              = NotForced;
    fWrappedProcessAlongStepGPIL        = DBL_MAX;
    fBiasingAlongStepGPIL               = DBL_MAX;
    fWrappedProcessGPILSelection        = NotCandidateForSelection;
    fBiasingGPILSelection               = NotCandidateForSelection;
    // -- for helper:
    fPreviousStepSize                   = previousStepSize;
  }

  // -- previous step size value; it is switched to zero if resetting a wrapped process:
  // -- (same trick used than in InvokedWrappedProcessPostStepGPIL )
  G4double usedPreviousStepSize = previousStepSize;

  // ----------------------------------------------
  // -- If leaving a biasing operator, let it know:
  // ----------------------------------------------
  if ( fSharedData->fLeavingPreviousOperator )
  {
    (fSharedData->fPreviousBiasingOperator)->ExitingBiasing( &track, this ); 
    // -- if no further biasing operator, reset process behavior to standard tracking:
    if ( fSharedData->fCurrentBiasingOperator == nullptr )
    {
      ResetForUnbiasedTracking();
      if ( fIsPhysicsBasedBiasing )
      {
        // -- if the physics process has been under occurrence biasing, reset it:
        if ( fResetWrappedProcessInteractionLength )
        {
          fResetWrappedProcessInteractionLength = false;
          fWrappedProcess->ResetNumberOfInteractionLengthLeft();
          // -- We set "previous step size" as 0.0, to let the process believe this is first step:
          usedPreviousStepSize = 0.0;
        }
      }
    }
  }

  // --------------------------------------------------------------
  // -- no operator : analog tracking if physics-based, or nothing:
  // --------------------------------------------------------------
  if ( fSharedData->fCurrentBiasingOperator == nullptr )
  {
    // -- take note of the "usedPreviousStepSize" value:
    if ( fIsPhysicsBasedBiasing )
    {
      return fWrappedProcess->PostStepGetPhysicalInteractionLength(track, usedPreviousStepSize, condition);
    }
    else
    {
      *condition = NotForced;
      return DBL_MAX;
    }
  }

  // --------------------------------------------------
  // -- A biasing operator exists. Proceed with
  // -- treating non-physics and physics biasing cases:
  //---------------------------------------------------
  
  // -- non-physics-based biasing case:
  // ----------------------------------
  if ( !fIsPhysicsBasedBiasing )
  {  
    fNonPhysicsBiasingOperation = (fSharedData->fCurrentBiasingOperator)->GetProposedNonPhysicsBiasingOperation( &track, this );
    if ( fNonPhysicsBiasingOperation == nullptr )
    {
      *condition = NotForced;
      return DBL_MAX;
    }
    return fNonPhysicsBiasingOperation->DistanceToApplyOperation(&track, previousStepSize, condition);
  }

  // -- Physics based biasing case:
  // ------------------------------
  // -- Ask for possible GPIL biasing operation:
  fOccurenceBiasingOperation = (fSharedData->fCurrentBiasingOperator)->GetProposedOccurenceBiasingOperation( &track, this );

  // -- no operation for occurrence biasing, analog GPIL returns the wrapped process GPIL and condition values
  if ( fOccurenceBiasingOperation == nullptr )
  {
    *condition = fWrappedProcessForceCondition;
    return fWrappedProcessPostStepGPIL;
  }

  // -- A valid GPIL biasing operation has been proposed:
  // -- 0) remember wrapped process will need to be reset on biasing exit, if particle survives:
  fResetWrappedProcessInteractionLength = true;
  // -- 1) update process interaction length for reference analog interaction law ( fWrappedProcessInteractionLength updated/collected above):
  fPhysicalInteractionLaw->SetPhysicalCrossSection( 1.0 / fWrappedProcessInteractionLength );
  // -- 2) Collect biasing interaction law:
  // --    The interaction law pointer is collected as a const pointer to the interaction law object.
  // --    This interaction law will be kept under control of the biasing operation, which is the only
  // --    entity that will change the state of the biasing interaction law.
  // --    The force condition for biasing is asked at the same time, passing the analog one as default:
  fBiasingForceCondition = fWrappedProcessForceCondition;
  fBiasingInteractionLaw = fOccurenceBiasingOperation->ProvideOccurenceBiasingInteractionLaw( this, fBiasingForceCondition );
  // -- 3) Ask operation to sample the biasing interaction law:
  fBiasingPostStepGPIL = fBiasingInteractionLaw->GetSampledInteractionLength();

  // -- finish
  *condition = fBiasingForceCondition;
  return fBiasingPostStepGPIL;
}

G4VParticleChange* G4BiasingProcessInterface::PostStepDoIt(const G4Track& track,
                                                           const G4Step& step)
{
  // ---------------------------------------
  // -- case outside of volume with biasing:
  // ---------------------------------------
  if ( fSharedData->fCurrentBiasingOperator == nullptr )
    return fWrappedProcess->PostStepDoIt(track, step);
  
  // ----------------------------
  // -- non-physics biasing case:
  // ----------------------------
  if ( !fIsPhysicsBasedBiasing )
  {
    G4VParticleChange* particleChange = fNonPhysicsBiasingOperation->GenerateBiasingFinalState( &track, &step );
    (fSharedData->fCurrentBiasingOperator)->ReportOperationApplied( this, BAC_NonPhysics, fNonPhysicsBiasingOperation, particleChange );
    return particleChange;
  }

  // -- physics biasing case:
  // ------------------------
  // -- It proceeds with the following logic:
  // -- 1) Obtain the final state
  // --    This final state may be analog or biased.
  // --    The biased final state is obtained through a biasing operator
  // --    returned by the operator.
  // -- 2) The biased final state may be asked to be "force as it is"
  // --    in what case the particle change is returned as is to the
  // --    stepping.
  // --    In all other cases (analog final state or biased final but
  // --    not forced) the final state weight may be modified by the
  // --    occurrence biasing, if such an occurrence biasing is at play.
  
  // -- Get final state, biased or analog:
  G4VParticleChange* finalStateParticleChange;
  G4BiasingAppliedCase BAC;
  fFinalStateBiasingOperation = (fSharedData->fCurrentBiasingOperator)->GetProposedFinalStateBiasingOperation( &track, this );
  // -- Flag below is to force the biased generated particle change to be returned "as is" to the stepping, disregarding there
  // -- was or not a occurrence biasing that would apply. Weight relevance under full responsibility of the biasing operation.
  G4bool forceBiasedFinalState = false;
  if ( fFinalStateBiasingOperation != nullptr )
  {
    finalStateParticleChange = fFinalStateBiasingOperation->ApplyFinalStateBiasing( this, &track, &step, forceBiasedFinalState );
    BAC = BAC_FinalState;
  }
  else
  {
    finalStateParticleChange = fWrappedProcess->PostStepDoIt(track, step);
    BAC =  BAC_None ;
  }
  
  // -- if no occurrence biasing operation, we're done:
  if ( fOccurenceBiasingOperation == nullptr )
  {
    (fSharedData->fCurrentBiasingOperator)->ReportOperationApplied( this, BAC, fFinalStateBiasingOperation, finalStateParticleChange );
    return finalStateParticleChange;
  }
  
  // -- if biased final state has been asked to be forced, we're done:
  if ( forceBiasedFinalState )
  {
    (fSharedData->fCurrentBiasingOperator)->ReportOperationApplied( this, BAC, fFinalStateBiasingOperation, finalStateParticleChange );
    return finalStateParticleChange;
  }

  // -- If occurrence biasing, applies the occurrence biasing weight correction on top of final state (biased or not):
  G4double weightForInteraction = 1.0;
  if ( !fBiasingInteractionLaw->IsSingular() )
  {
    weightForInteraction = fPhysicalInteractionLaw->ComputeEffectiveCrossSectionAt(step.GetStepLength())
                         / fBiasingInteractionLaw ->ComputeEffectiveCrossSectionAt(step.GetStepLength());
  }
  else
  {
    // -- at this point effective XS can only be infinite, if not, there is a logic problem
    if ( !fBiasingInteractionLaw->IsEffectiveCrossSectionInfinite() )
    {
      G4ExceptionDescription ed;
      ed << "Internal inconsistency in cross-section handling. Please report !" << G4endl;
      G4Exception(" G4BiasingProcessInterface::PostStepDoIt(...)",
                  "BIAS.GEN.02", JustWarning, ed);
      // -- if XS is infinite, weight is zero (and will stay zero), but we'll do differently.
      // -- Should foresee in addition something to remember that in case of singular
      // -- distribution, weight can only be partly calculated
    }
  }
  
  if ( weightForInteraction <= 0. )
  {
    G4ExceptionDescription ed;
    ed << " Negative interaction weight : w_I = "
       <<  weightForInteraction << " XS_I(phys) = "
       << fBiasingInteractionLaw ->ComputeEffectiveCrossSectionAt(step.GetStepLength())
       <<" XS_I(bias) = "
       << fPhysicalInteractionLaw->ComputeEffectiveCrossSectionAt(step.GetStepLength())
       << " step length = " << step.GetStepLength()
       << " Interaction law = `" << fBiasingInteractionLaw << "'"
       << G4endl;
    G4Exception(" G4BiasingProcessInterface::PostStepDoIt(...)",
                "BIAS.GEN.03", JustWarning, ed);
  }
  
  (fSharedData->fCurrentBiasingOperator)
     ->ReportOperationApplied( this, BAC, fOccurenceBiasingOperation,
                               weightForInteraction,
                               fFinalStateBiasingOperation,
                               finalStateParticleChange );

  fOccurenceBiasingParticleChange->SetOccurenceWeightForInteraction( weightForInteraction );
  fOccurenceBiasingParticleChange->SetSecondaryWeightByProcess( true );
  fOccurenceBiasingParticleChange->SetWrappedParticleChange( finalStateParticleChange );
  fOccurenceBiasingParticleChange->ProposeTrackStatus( finalStateParticleChange->GetTrackStatus() );
  fOccurenceBiasingParticleChange->StealSecondaries(); // -- this also makes weightForInteraction applied to secondaries stolen

  // -- finish:
  return fOccurenceBiasingParticleChange;
}

// -- AlongStep methods:
G4double G4BiasingProcessInterface::
AlongStepGetPhysicalInteractionLength(const G4Track& track,
                                            G4double previousStepSize,
                                            G4double currentMinimumStep,
                                            G4double& proposedSafety, 
                                            G4GPILSelection* selection)
{
  // -- for helper methods:
  fCurrentMinimumStep = currentMinimumStep;
  fProposedSafety = proposedSafety;

  // -- initialization default case:
  fWrappedProcessAlongStepGPIL = DBL_MAX;
  *selection                   = NotCandidateForSelection;
  // ---------------------------------------
  // -- case outside of volume with biasing:
  // ---------------------------------------
  if ( fSharedData->fCurrentBiasingOperator == nullptr )
  {
    if ( fWrappedProcessIsAlong )
      fWrappedProcessAlongStepGPIL = fWrappedProcess
        ->AlongStepGetPhysicalInteractionLength(track, previousStepSize,
                                                currentMinimumStep,
                                                proposedSafety, selection);
    return fWrappedProcessAlongStepGPIL;
  }
  
  // --------------------------------------------------------------------
  // -- non-physics based biasing: no along operation expected (for now):
  // --------------------------------------------------------------------
  if ( !fIsPhysicsBasedBiasing ) return fWrappedProcessAlongStepGPIL;
  
  // ----------------------
  // -- physics-based case:
  // ----------------------
  if ( fOccurenceBiasingOperation == nullptr )
  {
    if ( fWrappedProcessIsAlong )
      fWrappedProcessAlongStepGPIL = fWrappedProcess
        ->AlongStepGetPhysicalInteractionLength(track, previousStepSize,
                                                currentMinimumStep,
                                                proposedSafety, selection);
    return fWrappedProcessAlongStepGPIL;
  }

  // -----------------------------------------------------------
  // -- From here we have an valid occurrence biasing operation:
  // -----------------------------------------------------------
  // -- Give operation opportunity to shorten step proposed by physics process:
  fBiasingAlongStepGPIL = fOccurenceBiasingOperation->ProposeAlongStepLimit( this );
  G4double minimumStep  = fBiasingAlongStepGPIL < currentMinimumStep
                        ? fBiasingAlongStepGPIL : currentMinimumStep;
  // -- wrapped process is called with minimum step ( <= currentMinimumStep passed ) : an along process can not
  // -- have its operation stretched over what it expects:
  if ( fWrappedProcessIsAlong )
  {
    fWrappedProcessAlongStepGPIL = fWrappedProcess
      ->AlongStepGetPhysicalInteractionLength(track, previousStepSize,
                                              minimumStep,
                                              proposedSafety, selection);
    fWrappedProcessGPILSelection = *selection;
    fBiasingGPILSelection = fOccurenceBiasingOperation
      ->ProposeGPILSelection( fWrappedProcessGPILSelection );
  }
  else
  {
    fBiasingGPILSelection = fOccurenceBiasingOperation
      ->ProposeGPILSelection( NotCandidateForSelection );
    fWrappedProcessAlongStepGPIL = fBiasingAlongStepGPIL;
  }
  
  *selection = fBiasingGPILSelection;

  return fWrappedProcessAlongStepGPIL;
}

G4VParticleChange*
G4BiasingProcessInterface::AlongStepDoIt(const G4Track& track,
                                         const G4Step& step)
{
  // ---------------------------------------
  // -- case outside of volume with biasing:
  // ---------------------------------------
  if ( fSharedData->fCurrentBiasingOperator == nullptr )
  {
    if ( fWrappedProcessIsAlong )
    {
      return fWrappedProcess->AlongStepDoIt(track, step);
    }
    else
    {
      fDummyParticleChange->Initialize( track );
      return fDummyParticleChange;
    }
  }
  
  // -----------------------------------
  // -- case inside volume with biasing:
  // -----------------------------------
  if ( fWrappedProcessIsAlong )
  {
    fOccurenceBiasingParticleChange
      ->SetWrappedParticleChange(fWrappedProcess->AlongStepDoIt(track, step));
  }
  else  
  {
    fOccurenceBiasingParticleChange->SetWrappedParticleChange ( nullptr );
    fOccurenceBiasingParticleChange->ProposeTrackStatus( track.GetTrackStatus() );
  }
  G4double weightForNonInteraction (1.0);
  if ( fBiasingInteractionLaw != nullptr ) 
  {
    weightForNonInteraction =
      fPhysicalInteractionLaw->ComputeNonInteractionProbabilityAt(step.GetStepLength()) /
      fBiasingInteractionLaw ->ComputeNonInteractionProbabilityAt(step.GetStepLength());
      
    fOccurenceBiasingOperation->AlongMoveBy( this, &step, weightForNonInteraction );

    if ( weightForNonInteraction <= 0. )
    {
      G4ExceptionDescription ed;
      ed << " Negative non interaction weight : w_NI = " << weightForNonInteraction <<
            " p_NI(phys) = " <<  fPhysicalInteractionLaw->ComputeNonInteractionProbabilityAt(step.GetStepLength()) <<
            " p_NI(bias) = " <<  fBiasingInteractionLaw ->ComputeNonInteractionProbabilityAt(step.GetStepLength()) <<
            " step length = "  <<  step.GetStepLength() <<
            " biasing interaction law = `" << fBiasingInteractionLaw->GetName() << "'" << G4endl;
      G4Exception(" G4BiasingProcessInterface::AlongStepDoIt(...)",
                  "BIAS.GEN.04", JustWarning, ed);
    }
  }
  
  fOccurenceBiasingParticleChange
    ->SetOccurenceWeightForNonInteraction( weightForNonInteraction );
  
  return fOccurenceBiasingParticleChange;
}

// -- AtRest methods
G4double G4BiasingProcessInterface::
AtRestGetPhysicalInteractionLength(const G4Track& track,
                                   G4ForceCondition* condition)
{
  return fWrappedProcess->AtRestGetPhysicalInteractionLength(track, condition);
}

G4VParticleChange* G4BiasingProcessInterface::AtRestDoIt(const G4Track& track,
                                                         const G4Step& step)
{
  return fWrappedProcess->AtRestDoIt(track, step);
}

G4bool G4BiasingProcessInterface::IsApplicable(const G4ParticleDefinition& pd)
{
  if ( fWrappedProcess != nullptr )
    return fWrappedProcess->IsApplicable(pd);
  else
    return true;
}

void  G4BiasingProcessInterface::SetMasterProcess(G4VProcess* masterP)
{
  // -- Master for this process:
  G4VProcess::SetMasterProcess(masterP);
  // -- Master for wrapped process:
  if ( fWrappedProcess != nullptr )
  {
    const G4BiasingProcessInterface* thisWrapperMaster
      = (const G4BiasingProcessInterface *)GetMasterProcess();
    // -- paranoia check: (?)
    G4VProcess* wrappedMaster = nullptr;
    wrappedMaster = thisWrapperMaster->GetWrappedProcess();
    fWrappedProcess->SetMasterProcess( wrappedMaster );
  }
}

void G4BiasingProcessInterface::
BuildPhysicsTable(const G4ParticleDefinition& pd)
{
  // -- Sequential mode : called second (after PreparePhysicsTable(..))
  // -- MT mode         : called second (after PreparePhysicsTable(..)) by master thread.
  // --                   Corresponding process instance not used then by tracking.
  // -- PreparePhysicsTable(...) has been called first for all processes,
  // -- so the first/last flags and G4BiasingProcessInterface vector of processes have
  // -- been properly setup, fIamFirstGPIL is valid.
  if ( fWrappedProcess != nullptr )
  {
    fWrappedProcess->BuildPhysicsTable(pd);
  }

  if ( fIamFirstGPIL )
  {
    // -- Re-order vector of processes to match that of the GPIL
    // -- (made for fIamFirstGPIL, but important is to have it made once):
    ReorderBiasingVectorAsGPIL();
    // -- Let operators to configure themselves for the master thread or for sequential mode.
    // -- Intended here is in particular the registration to physics model catalog.
    // -- The fDoCommonConfigure is to ensure that this Configure is made by only one process (othewise each first process makes the call):
    if ( fDoCommonConfigure.Get() )
    {
      for ( std::size_t optr=0; optr<(G4VBiasingOperator::GetBiasingOperators()).size(); ++optr)
      {
        (G4VBiasingOperator::GetBiasingOperators())[optr]->Configure( );
      }
      fDoCommonConfigure.Put(false);
    }
  }
}

void G4BiasingProcessInterface::
PreparePhysicsTable(const G4ParticleDefinition& pd)
{
  // -- Sequential mode : called first (before BuildPhysicsTable(..))
  // -- MT mode         : called first (before BuildPhysicsTable(..)) by master thread.
  // --                   Corresponding process instance not used then by tracking.
  // -- Let process finding its first/last position in the process manager:
  SetUpFirstLastFlags();
  if ( fWrappedProcess != nullptr )
  {
    fWrappedProcess->PreparePhysicsTable(pd);
  }
}

G4bool  G4BiasingProcessInterface::
StorePhysicsTable(const G4ParticleDefinition* pd, const G4String& s, G4bool f)
{
  if ( fWrappedProcess != nullptr )
    return fWrappedProcess->StorePhysicsTable(pd, s, f);
  else
    return false;
}

G4bool G4BiasingProcessInterface::
RetrievePhysicsTable(const G4ParticleDefinition* pd, const G4String& s, G4bool f)
{
  if ( fWrappedProcess != nullptr )
    return fWrappedProcess->RetrievePhysicsTable(pd, s, f);
  else
    return false;
}

void G4BiasingProcessInterface::SetProcessManager(const G4ProcessManager* mgr)
{ 
  if ( fWrappedProcess != nullptr )
    fWrappedProcess->SetProcessManager(mgr);
  else
    G4VProcess::SetProcessManager(mgr);

  // -- initialize fSharedData pointer:
  if (G4BiasingProcessSharedData::fSharedDataMap.Find(mgr)
   == G4BiasingProcessSharedData::fSharedDataMap.End() )
  {
    fSharedData = new G4BiasingProcessSharedData( mgr );
    G4BiasingProcessSharedData::fSharedDataMap[mgr] = fSharedData;
  }
  else
  {
    fSharedData = G4BiasingProcessSharedData::fSharedDataMap[mgr] ;
  }
  // -- augment list of co-operating processes:
  fSharedData->fBiasingProcessInterfaces.push_back( this );
  fSharedData->fPublicBiasingProcessInterfaces.push_back( this );
  if ( fIsPhysicsBasedBiasing ) 
  {
    fSharedData->fPhysicsBiasingProcessInterfaces.push_back( this );
    fSharedData-> fPublicPhysicsBiasingProcessInterfaces.push_back( this );
  }
  else
  {
    fSharedData->fNonPhysicsBiasingProcessInterfaces.push_back( this );
    fSharedData->fPublicNonPhysicsBiasingProcessInterfaces.push_back( this );
  }
  // -- remember process manager:
  fProcessManager = mgr;
}

const G4ProcessManager* G4BiasingProcessInterface::GetProcessManager()
{
  if ( fWrappedProcess != nullptr )
    return fWrappedProcess->GetProcessManager();
  else
    return G4VProcess::GetProcessManager();
}

void G4BiasingProcessInterface::
BuildWorkerPhysicsTable(const G4ParticleDefinition& pd)
{
  // -- Sequential mode : not called
  // -- MT mode         : called after PrepareWorkerPhysicsTable(..)
  // -- PrepareWorkerPhysicsTable(...) has been called first for all processes,
  // -- so the first/last flags and G4BiasingProcessInterface vector of processes have
  // -- been properly setup, fIamFirstGPIL is valid.
  if ( fWrappedProcess != nullptr )
  {
    fWrappedProcess->BuildWorkerPhysicsTable(pd);
  }

  if ( fIamFirstGPIL )
  {
    // -- Re-order vector of processes to match that of the GPIL
    // -- (made for fIamFirstGPIL, but important is to have it made once):
    ReorderBiasingVectorAsGPIL();
    // -- Let operators to configure themselves for the worker thread, if needed.
    // -- Registration to physics model catalog **IS NOT** to be made here, but in Configure().
    // -- The fDoCommonConfigure is to ensure that this Configure is made by only one process (othewise each first process makes the call):
    if ( fDoCommonConfigure.Get() )
    {
      for ( std::size_t optr=0 ; optr<(G4VBiasingOperator::GetBiasingOperators()).size(); ++optr)
      {
        (G4VBiasingOperator::GetBiasingOperators())[optr]->ConfigureForWorker( );
      }
      fDoCommonConfigure.Put(false);
    }
  }
}

void G4BiasingProcessInterface::
PrepareWorkerPhysicsTable(const G4ParticleDefinition& pd)
{
  // -- Sequential mode : not called
  // -- MT mode         : called first, before BuildWorkerPhysicsTable(..)
  // -- Let process finding its first/last position in the process manager:
  SetUpFirstLastFlags();

  if ( fWrappedProcess != nullptr )
  {
    fWrappedProcess->PrepareWorkerPhysicsTable(pd);
  }
}

void G4BiasingProcessInterface::ResetNumberOfInteractionLengthLeft()
{
  if ( fWrappedProcess != nullptr )
    fWrappedProcess->ResetNumberOfInteractionLengthLeft();
}

G4bool G4BiasingProcessInterface::
GetIsFirstPostStepGPILInterface( G4bool physOnly ) const
{
  G4int iPhys = ( physOnly ) ? 1 : 0;
  return fFirstLastFlags[IdxFirstLast( 1, 1, iPhys)];
}

G4bool  G4BiasingProcessInterface::
GetIsLastPostStepGPILInterface( G4bool physOnly ) const
{
  G4int iPhys = ( physOnly ) ? 1 : 0;
  return fFirstLastFlags[IdxFirstLast( 0, 1, iPhys)];
}

G4bool G4BiasingProcessInterface::
GetIsFirstPostStepDoItInterface( G4bool physOnly ) const
{
  G4int iPhys = ( physOnly ) ? 1 : 0;
  return fFirstLastFlags[IdxFirstLast( 1, 0, iPhys)];
}

G4bool  G4BiasingProcessInterface::
GetIsLastPostStepDoItInterface( G4bool physOnly ) const
{
  G4int iPhys = ( physOnly ) ? 1 : 0;
  return fFirstLastFlags[IdxFirstLast( 0, 0, iPhys)];
}

G4bool G4BiasingProcessInterface::
IsFirstPostStepGPILInterface(G4bool physOnly) const
{
  G4bool isFirst = true;
  const G4ProcessVector* pv = fProcessManager->GetPostStepProcessVector(typeGPIL);
  G4int thisIdx(-1);
  for ( auto i = 0; i < (G4int)pv->size(); ++i )
  {
    if ( (*pv)(i) == this ) { thisIdx = i; break; }
  }
  if ( thisIdx < 0 ) return false; // -- to ignore pure along processes
  for ( std::size_t i=0; i<(fSharedData->fBiasingProcessInterfaces).size(); ++i )
  {
    if ( (fSharedData->fBiasingProcessInterfaces)[i]->fIsPhysicsBasedBiasing || !physOnly )
    {
      G4int thatIdx(-1);
      for (auto j = 0; j < (G4int)pv->size(); ++j )
      {
        if ( (*pv)(j) == (fSharedData->fBiasingProcessInterfaces)[i] )
        {
          thatIdx = j; break;
        }
      }
      if ( thatIdx >= 0 ) // -- to ignore pure along processes
      {
        if ( thisIdx >  thatIdx )
        {
          isFirst = false;
          break;
        }
      }
    }
  }
  return isFirst;
}

G4bool G4BiasingProcessInterface::
IsLastPostStepGPILInterface(G4bool physOnly) const
{
  G4bool isLast = true;
  const G4ProcessVector* pv = fProcessManager->GetPostStepProcessVector(typeGPIL);
  G4int thisIdx(-1);
  for (auto i = 0; i < (G4int)pv->size(); ++i )
  {
    if ( (*pv)(i) == this ) { thisIdx = i; break; }
  }
  if ( thisIdx < 0 ) return false; // -- to ignore pure along processes
  for (std::size_t i=0; i<(fSharedData->fBiasingProcessInterfaces).size(); ++i)
  {
    if ( (fSharedData->fBiasingProcessInterfaces)[i]->fIsPhysicsBasedBiasing || !physOnly )
    {
      G4int thatIdx(-1);
      for (auto j = 0; j < (G4int)pv->size(); ++j )
      {
        if ( (*pv)(j) == (fSharedData->fBiasingProcessInterfaces)[i] )
        {
          thatIdx = j; break;
        }
      }
      if ( thatIdx >= 0 ) // -- to ignore pure along processes
      {
        if ( thisIdx <  thatIdx )
        {
          isLast = false;
          break;
        }
      }
    }
  }
  return isLast;  
}

G4bool G4BiasingProcessInterface::
IsFirstPostStepDoItInterface(G4bool physOnly) const
{
  G4bool isFirst = true;
  const G4ProcessVector* pv = fProcessManager->GetPostStepProcessVector(typeDoIt);
  G4int thisIdx(-1);
  for (auto i = 0; i < (G4int)pv->size(); ++i )
  {
    if ( (*pv)(i) == this ) { thisIdx = i; break; }
  }
  if ( thisIdx < 0 ) return false; // -- to ignore pure along processes
  for (std::size_t i=0; i<(fSharedData->fBiasingProcessInterfaces).size(); ++i)
  {
    if ( (fSharedData->fBiasingProcessInterfaces)[i]->fIsPhysicsBasedBiasing || !physOnly )
    {
      G4int thatIdx(-1);
      for (auto j = 0; j < (G4int)pv->size(); ++j )
      {
        if ( (*pv)(j) == (fSharedData->fBiasingProcessInterfaces)[i] )
        {
          thatIdx = j; break;
        }
      }
      if ( thatIdx >= 0 ) // -- to ignore pure along processes
      {
        if ( thisIdx >  thatIdx )
        {
          isFirst = false;
          break;
        }
      }
    }
  }
  return isFirst;
}

G4bool G4BiasingProcessInterface::
IsLastPostStepDoItInterface(G4bool physOnly) const
{
  G4bool isLast = true;
  const G4ProcessVector* pv = fProcessManager->GetPostStepProcessVector(typeDoIt);
  G4int thisIdx(-1);
  for (auto i = 0; i < (G4int)pv->size(); ++i)
  {
    if ( (*pv)(i) == this ) { thisIdx = i; break; }
  }
  if ( thisIdx < 0 ) return false; // -- to ignore pure along processes
  for (std::size_t i=0; i<(fSharedData->fBiasingProcessInterfaces).size(); ++i)
  {
    if ( (fSharedData->fBiasingProcessInterfaces)[i]->fIsPhysicsBasedBiasing || !physOnly )
    {
      G4int thatIdx(-1);
      for (auto j = 0; j < (G4int)pv->size(); ++j)
      {
        if ( (*pv)(j) == (fSharedData->fBiasingProcessInterfaces)[i] )
        {
          thatIdx = j; break;
        }
      }
      if ( thatIdx >= 0 ) // -- to ignore pure along processes
      {
        if ( thisIdx <  thatIdx )
        {
          isLast = false;
          break;
        }
      }
    }
  }
  return isLast;  
}

void G4BiasingProcessInterface::SetUpFirstLastFlags()
{
  for (G4int iPhys = 0; iPhys < 2; ++iPhys)
  {
    G4bool physOnly = ( iPhys == 1 );
    fFirstLastFlags[IdxFirstLast( 1, 1, iPhys)] = IsFirstPostStepGPILInterface(physOnly);
    fFirstLastFlags[IdxFirstLast( 0, 1, iPhys)] =  IsLastPostStepGPILInterface(physOnly);
    fFirstLastFlags[IdxFirstLast( 1, 0, iPhys)] = IsFirstPostStepDoItInterface(physOnly);
    fFirstLastFlags[IdxFirstLast( 0, 0, iPhys)] =  IsLastPostStepDoItInterface(physOnly);
  }
  
  // -- for itself, for optimization:
  fIamFirstGPIL =  GetIsFirstPostStepGPILInterface( false );
}

void G4BiasingProcessInterface::ResetForUnbiasedTracking()
{
  fOccurenceBiasingOperation  = nullptr;
  fFinalStateBiasingOperation = nullptr;
  fNonPhysicsBiasingOperation = nullptr;
  fBiasingInteractionLaw      = nullptr;
}

void G4BiasingProcessInterface::
InvokeWrappedProcessPostStepGPIL( const G4Track&  track,
                                  G4double previousStepSize,
                                  G4ForceCondition* condition )
{
  G4double usedPreviousStepSize = previousStepSize;
  // -- if the physics process has been under occurrence biasing in the previous step
  // -- we reset it, as we don't know if it will be biased again or not in this
  // -- step. The pity is that PostStepGPIL and interaction length (cross-section)
  // -- calculations are done both in the PostStepGPIL of the process, while here we
  // -- are just interested in the calculation of the cross-section. This is a pity
  // -- as this forces to re-generated a random number for nothing.
  if ( fResetWrappedProcessInteractionLength )
  {
    fResetWrappedProcessInteractionLength = false;
    fWrappedProcess->ResetNumberOfInteractionLengthLeft();
    // -- We set "previous step size" as 0.0, to let the process believe this is first step:
    usedPreviousStepSize = 0.0;
  }
  // -- GPIL response:
  fWrappedProcessPostStepGPIL = fWrappedProcess->PostStepGetPhysicalInteractionLength(track, usedPreviousStepSize, condition);
  fWrappedProcessForceCondition = *condition;
  // -- and (inverse) cross-section:
  fWrappedProcessInteractionLength = fWrappedProcess->GetCurrentInteractionLength();
}

void G4BiasingProcessInterface::ReorderBiasingVectorAsGPIL()
{
  // -- re-order vector of processes to match that of the GPIL:
  std::vector < G4BiasingProcessInterface* > tmpProcess ( fSharedData->fBiasingProcessInterfaces );
  ( fSharedData -> fBiasingProcessInterfaces                 ) . clear();
  ( fSharedData -> fPhysicsBiasingProcessInterfaces          ) . clear();
  ( fSharedData -> fNonPhysicsBiasingProcessInterfaces       ) . clear();
  ( fSharedData -> fPublicBiasingProcessInterfaces           ) . clear();
  ( fSharedData -> fPublicPhysicsBiasingProcessInterfaces    ) . clear();
  ( fSharedData -> fPublicNonPhysicsBiasingProcessInterfaces ) . clear();
  
  const G4ProcessVector* pv = fProcessManager->GetPostStepProcessVector(typeGPIL);
  for (auto i = 0; i < (G4int)pv->size(); ++i) 
  {
    for (std::size_t j = 0; j < tmpProcess.size(); ++j)
    {
      if ( (*pv)(i) == tmpProcess[j] )
      { 
        ( fSharedData->fBiasingProcessInterfaces ).push_back( tmpProcess[j] );
        ( fSharedData->fPublicBiasingProcessInterfaces ).push_back( tmpProcess[j] );
        if ( tmpProcess[j] -> fIsPhysicsBasedBiasing )
        {
          ( fSharedData->fPhysicsBiasingProcessInterfaces ).push_back( tmpProcess[j] );
          ( fSharedData->fPublicPhysicsBiasingProcessInterfaces ).push_back( tmpProcess[j] ); 
        }
        else
        {
          ( fSharedData -> fNonPhysicsBiasingProcessInterfaces ).push_back( tmpProcess[j] );
          ( fSharedData -> fPublicNonPhysicsBiasingProcessInterfaces ).push_back( tmpProcess[j] );  
        }
        break;
      }
    }
  }
}
