#!/usr/bin/python
"""
Script to generate the configuration files for the replay of AGATA data using the
multi-process distributed system Narval or the single-process emulator femul.
The script is divided in a few sections, some of which are specific to the actual
analysis (and therefore are likely to be changed by the user) while some others are
normally not to be touched:
0) Type of analysis and replacement macros (in the style of the shell) used to parametrize
   the commands listed in 2).
1) The structure of the actual analysis is defined by the variables PROGTYPE and CONFTYPE and
   by the dictionaries Topology and Actors. The dictionary ExtraFiles contains a list of files,
   which are needed by the analysis but are not generated by this script (e.g. calibrations,
   mappings, ...); these files can be copied from a previous analysis if the script is started
   WITH the option -o or --old (python gen_conf.py -h to get the list of accepted options)
2) The command lines to be written in the conf files of the actors defined in Actors{}.
   Uncomment/comment/modify the command lines and their parameters
3) A small database defining the position and the PSA signal basis of the germanium crystals.
   This part should not need to be changed.  

To get a list of command line arguments, launch it as "gen_conf.py -h"

This script is (tentativelly) updated to follow the new naming of crystals

"""

import os

#With the information reported in 1) and 2) it should be rather strightforward to produce the
#topology file xxxxx.xml for narval (or xxxx.conf for femul) and the xxxx script for narval.

###############################################################################################
###################  0  Type of analysis and replacement symbols  #############################
###############################################################################################

PROGTYPE='femul'     # NARVAL or femul   (to choose between os.getcwd() and '' for CWD)
CONFTYPE='OFFLINE'    # ONLINE or OFFLINE (used just to exclude the ReadDataDir line in the Producers)

MACROS={              # various replacements for symbols defined in 2).
'$CONFDIR'      : 'Conf',                      # this will be prefixed by CWD/
'$READDIR'      : 'Data',                      # this will be prefixed by CWD/; if ONLINE this will not be written
'$SAVEDIR'      : 'Out',                       # this will be prefixed by CWD/; if ONLINE this will be replaced by $READDIR
'$ANALYSIS'     : 'Analysis',                  # this will be prefixed by CWD/; if ONLINE this will be replaced by $READDIR
'$BUILDER'      : 'Builder',                   # this will be prefixed by CWD/
'$MERGER'       : 'Merger',                    # this will be prefixed by CWD/
'$SPIDER'       : 'spider',                    # this will be prefixed by CWD/
'$LABR'     	: 'labr',
'$DANTE'     	: 'dante',
'$PSABASE'      : '/agatadisks/bases_ADL',     # standard place at AGATA
'$CRYSTAL_ID'   : "",                          # the actual value is defined in GeDataBase
'$SIGNAL_BASIS' : "",                          # the actual value is defined in GeDataBase
'$CRYSTAL'      : "",                          # the actual value taken from Topology['CRYSTAL']
'$CurrentDir'   : "./",                        # the actual value taken from Topology['CRYSTAL']
}

###############################################################################################
###################  1  Structure of analysis  ################################################
###############################################################################################

Topology={    # The directories to be generated in Conf, Data and Out
'CRYSTAL'   : "00A 00B 00C 01A 01B 01C 02A 02B 02C 04A 04B 04C 05B 05C 06A 06B 06C 07A 07B 08A 08B 09A 09B 09C 10A 10B 10C 11A 11B 11C 14A 14B 14C",
'BUILDER'   : "Builder",
'MERGER'    : "Merger",
'GLOBAL'    : "Global",
'SPIDER'    : "spider",
'LABR'      : "labr",
'DANTE'     : "dante",
'ANALYSIS'  : "Analysis",
}

# The name of the used actors must correspond to one of the tuples defined in the following section.
# This requirement creates a problem for BasicAFP and BasicAFC when they are used in chains of different type
# (e.g. after PSA and after Tracking) and one wants to define chain-specific names for their input/output files.
# The solution is to suffix the name of the chain-type (e.g. _CRYSTAL or _GLOBAL or any other), to the defining tuple. 
# This suffix will be silently removed from the actual name of the generated configuration files.

Actors={      # These are the xxxx.conf files to be generated 
'CRYSTAL'      : "CrystalProducer PreprocessingFilter PSAFilter BasicAFC BasicAFP_CRYSTAL PostPSAFilter PostPSAFilter",
'SPIDER'       : "BasicAFP_SPIDER TB_SPIDER TreeBuilder",
'LABR'         : "BasicAFP_LABR TB_LABR TreeBuilder", 
'DANTE'         : "TB_DANTE TreeBuilder", 
'BUILDER'      : "EventBuilder BasicAFC_BUILDER TrackingFilter BasicAFP_BUILDER TreeBuilder",
'MERGER'       : "EventMerger_MERGER BasicAFC_MERGER TrackingFilter TreeBuilder TB_OSCAR TB_PRISMA TB_SPIDER TB_EUCLIDES TB_LABR TB_DANTE TB_SAURON",  
}

ExtraFiles={  # If not already present, these files can be copied from a directory specified in the command line. CrystalPos LUT is placed at 3 places in order to have eventually tracking at different places offline
'CRYSTAL'   : "CrystalProducerATCA.conf PreprocessingFilterPSA.conf xinv_1325-1340.cal xdir_1325-1340.cal Trapping.cal RecalEnergy2.cal", #Trapping_$CRYSTAL.cal recal2.dat <== files for n damage correction, not used at the moment
'MERGER'    : "CrystalPositionLookUpTable",
'BUILDER'   : "CrystalPositionLookUpTable",
}

###############################################################################################
###################  2  Tuples specifying the content of the configuration files  #############
###############################################################################################

#########################

CrystalProducer=(
"ActualClass           CrystalProducerATCA",     # name of the used daugther class  
"CrystalID             $CRYSTAL_ID",             # position of the crystal in the AGATA frame
"ReadDataDir           $READDIR/$CRYSTAL",       # should not be present in the ONLINE producer, automatically done when flag "online is active"
"SaveDataDir           $SAVEDIR/$CRYSTAL",       # Out/04C... for the OFFLINE; Data/04C... for the online
"TraceLength           100",                     # length of traces (samples)
"WriteDataMask         0",                       # 0=none 1=input_mezzdata 2=event_mezzdata 4=event_mezzhead 8=event_energy 16=event_core 32=tstampdiff; 10=8+2
"WriteTraces           100",                     # number of traces written to "Prod__1000-42-100-S__Traces.samp" at the beginning of the run
# to be uncommented for offline starting from traces !!!
"InputDataFile         event_mezzdata.cdat",     # modify definitions in the online version of CrystalProducerATCA.conf to read from a single raw-data file
#"InputDataFile         arg__4120-UA__.dat",     # modify definitions in the online version of CrystalProducerATCA.conf to read from a single raw-data file
"AllInputFiles",                                 # filename.ext --> filename.ext.0000 and increment the string after last . 
#"WriteBaseLines        10",                     # distribution of preamplifier baselines; events decimated by 10
#"DecimateMezzdata     4",                       # write one out of 4 (if WriteDataMask enables)
#"DecimateMezzener     2",                       # write one out of 2 (if WriteDataMask enables)
#"TimeStep              50",                     # not needed since the online has now a dedicated watchdog
#"TstampCorrection      0",                      # used in the offline to correct for rare cases of bad setup
#"MaxTstampSeconds      3200",                   #
#"WriteDataSplit        1000000",                # Size of event_mezzdata.cdat.xxxx is ~4GB (.cdat as data is compressed) 
#"WriteCompressed",                              # WriteCompressed is the default
#"WriteUnCompressed",                            # to get uncompressed raw data
#Co60 mainly...
#"WriteDataRange        1500 25000",              # range of CC amplitude (in channels) to write the data, not given means write all  
#"StopErrorCode         100",                     # < 100 to avoid stopping everything at the first input error
#"ValidationRate        2000",                    # readout event-rate. Used to reduce the buffer size in the online ACQ so as to gent ~1 read/second
#"NoMultiHist",                                   # exclude local spectra and matrices
#"SmokeCC	    0 0 1",		 #enable calculating smoke plot for the core (0..1); amplitude calibrated as f1+f2*ampli
#"SmokeSG	    0 0 1",		 #enable calculating smoke plot for segment (0..35); amplitude calibrated as f1+f2*ampli
"ProjeM1               10 2",                    # threshold (rescaled) and scaling factor for segment multiplicity=1 projections, calibration pourpose mainly...
#"Verbose",                                       # more verbose terminal-output
#### command lines to be produced only for the specified crystals
{
#'1B' : "WriteDataRange  5500 20000",                    
#'20B': "TstampCorrection      60", 

#'07B': "SmokeCC            1 0 1.418808",  	
#'07B': "SmokeCC            0 0 0.822678",  
#'07B': "SmokeSG            0 0 0.742104",  

#'07C': "SmokeCC            1 0 1.436627",  	
#'07C': "SmokeCC            0 0 0.800169",  	
#'07C': "SmokeSG            0 0 0.753877 ",  # only taking the last one of the 3 smokes		
                     
}
)

#########################

PreprocessingFilter=(
"ActualClass           PreprocessingFilterPSA",  # name of the used daugther class  
"SaveDataDir           $SAVEDIR/$CRYSTAL",       # normally Out/1R...
"EnergyGain            4",                       # channels/keV of the calibrated energy spectra
"XtalkFile             xinv_1325-1340.cal",      # cross talk correction coefficients for the energies
"WriteTraces           100" ,                   # number of traces written to "Prep__1000-42-100-S__Traces.samp" at the beginning of the run
#"SegmentFoldGate       1 3",                    # selection of events based on the number of hit segments
#"SegmentEnergyThreshold  f32",                  # minimum energy on the segments to decide if fired, 10 keV is the default, look to Pre*PSA.conf file
#"PileUpCheck             0 0",                  # Slope Delta (no check if <= 0; default is to check)
#"CFDparamsCC             i32 i32 i32 f32 f32",  # Int Diff Delay Fraction Threshold
#"TriggerWithCoreAlone    bool",                 # don't use net_charge_segments to find local trigger
#"CoreEnergyGate        500 10000",              # possibility to restrict the energy range
#"RisetimeMatrix          bool",                 #produce risetime matrix for segments and core
#"NoMultiHist",                                  # exclude local spectra and matrices
#"Verbose",                                      # more verbose terminal-output
#### command lines to be produced only for the specified crystals
{

'01B' : ("DeadSegment    24  0.94413  0"),
'02C' : ("DeadSegment    9  0.950036  0"),
'04A' : ("DeadSegment    20  0.94571  0"),
'05B' : ("DeadSegment    24  0.952100  0"),
#'06C' : ("DeadSegment    17  0.953663  0"),
#'08A' : ("DeadSegment    9  0.956071  0"),
'08B' : ("DeadSegment    8  0.949936  0"),
'11A' : ("DeadSegment    30  0.949938  0."),
#'06C' : ("DeadSegment    18  0.949078  0"),

}
)

#########################

PSAFilter=(
"ActualClass           PSAFilterGridSearch",      # name of the used daugther class  
"BasisFile             $SIGNAL_BASIS",            # this is generated from the GeDataBase structure (see at line ~230 of this file)  
"SaveDataDir           $SAVEDIR/$CRYSTAL",        # normally Out/Data(online)
"EnergyGain            4",                        # channels/keV of the calibrated energy spectra
"XtalkFile             xdir_1325-1340.cal",       # cross talk correction coefficients for traces (applied to the signal basis)
"Threads               5 300",                    # number of threads, number of events/thread (if PSA threads enabled at compile time)
"GridSearchType        Adaptive",                 # SegCenter, Adaptive, CoarseOnly or Full; default is Adaptive
"WriteTraces           100" ,                   # number of traces written to "Prep__1000-42-100-S__Traces.samp" at the beginning of the run
#"GridSearchType        SegCenter",                # SegCenter, Adaptive, CoarseOnly or Full; default is Adaptive
#"WriteTraces           500",                      # number of traces+fit written to "Prep__1000-42-100-S__Traces.samp" at the beginning of the run
#"GridSearchType        SegCenter",               # SegCenter, Adaptive, CoarseOnly or Full; default is Adaptive
#"TauSlice              35 35 35 35 35 35 35",    # preamp response of the 6 slices and the CC
#"TZeroCycles           10",                      # max number of PSA+FitTZero cycles
#"TauSegment       i32 f32",                      # segment number, its timing response (can be given multiple times, 36==CC)
#"TauDecay         f32",                          # fall time in ns of the preamplifiers (default is not to correct basis for this) 
#"Smooth           f32",                          # final gaussian smoothing of the signal basis (0==no, default is 0.1)
"WritePsaHits",                                  # writes the hits in binary to calibrate neutron damage
#"NoMultiHist",                                   # exclude local spectra and matrices
#"Verbose",                                       # more verbose terminal-output
#### command lines to be produced only for the specified crystals
{


#'00A' : ("DeadSegment    12"),
#'00B' : ("DeadSegment    17"),
#'00B' : ("DeadSegment    15"),
#'01B' : ("DeadSegment      1"),
#'02A' : ("DeadSegment    28"),
#'02B' : ("DeadSegment    0"),
#'04A' : ("DeadSegment    8"),
#'05B' : ("DeadSegment    2"),
#'06A' : ("DeadSegment    7"),
#'06C' : ("DeadSegment    23"),
#'08A' : ("DeadSegment    8"),
#'08C' : ("DeadSegment    4"),
#'10B' : ("DeadSegment    22"),
#'00B' : ("DeadSegment    12"),
'01B' : ("DeadSegment    24 "),
'02C' : ("DeadSegment    9 "),
'04A' : ("DeadSegment    20 "),
'05B' : ("DeadSegment    24"),
#'06C' : ("DeadSegment    17"),
#'08A' : ("DeadSegment    9"),
'08B' : ("DeadSegment    8"),
'11A' : ("DeadSegment    30"),
#'06C' : ("DeadSegment    18"),

}
)

PostPSAFilter=(
"ActualClass           PostPSAFilter",           # name of the used daugther class, uncomment for offline 
"SaveDataDir           $SAVEDIR/$CRYSTAL",       # normally Out
"EnergyGain            4",                       # channels/keV of the calibrated energy spectra
"SmearPos              -4",                       # to randomize points on a 2mm3 volume - negative means special treatment of low energy (< 100 keV)
#"ForceSegmentsToCore",                           # sum of segments forced to energy of the core. Use it EITHER in the PSA OR in the Tracking
#"CoreEnergyGate        500 520 ",               # possibility to restrict the energy range
#"RecalCC                1",                     # recalibration of CC Energy
#"RecalSG                1",                     # recalibration fired Segments
#"TimeShiftCC           f32",                    # time shift of core (ns)
#"Verbose",                                      # more verbose terminal-output
"TrappingFile          Trapping.cal",  # file with the trapping-correction coefficients
#"NoMultiHist",                                  # exclude local spectra and matrices
#"NewCrystalID         i32",                     #change ID of crystal
#### command lines to be produced only for the specified crystals
{





#'00A' : ("TimeShiftCC  -1.638","ForceSegmentsToCore"),
#'00B' : ("TimeShiftCC  -2.862","ForceSegmentsToCore"),
#'00C' : ("TimeShiftCC  -2.628","ForceSegmentsToCore"),
#'01A' : ("TimeShiftCC   2.220","ForceSegmentsToCore"),
#'01B' : ("TimeShiftCC  -1.237" 		     ),
#'01C' : ("TimeShiftCC  -1.867","ForceSegmentsToCore"),
#'02A' : ("TimeShiftCC   6.985","ForceSegmentsToCore"),
#'02B' : ("TimeShiftCC   4.877","ForceSegmentsToCore"),
#'02C' : ("TimeShiftCC   5.106","ForceSegmentsToCore"),
#'04A' : ("TimeShiftCC   2.042","ForceSegmentsToCore"),
#'04B' : ("TimeShiftCC  -6.793","ForceSegmentsToCore"),
#'04C' : ("TimeShiftCC  -1.897","ForceSegmentsToCore"),
#'05A' : ("TimeShiftCC   0.000","ForceSegmentsToCore"), 		     
#'05B' : ("TimeShiftCC   2.798","ForceSegmentsToCore"),
#'05C' : ("TimeShiftCC  -2.504","ForceSegmentsToCore"),
#'06A' : ("TimeShiftCC  -4.517","ForceSegmentsToCore"),
#'06B' : ("TimeShiftCC  -2.842","ForceSegmentsToCore"),
#'06C' : ("TimeShiftCC  -2.229","ForceSegmentsToCore"),
#'07A' : ("TimeShiftCC   0.984","ForceSegmentsToCore"),
#'07B' : ("TimeShiftCC  -6.928" 		     ),
#'07C' : ("TimeShiftCC  -8.065","ForceSegmentsToCore"),
#'08A' : ("TimeShiftCC   0.501","ForceSegmentsToCore"),
#'08B' : ("TimeShiftCC   4.613","ForceSegmentsToCore"),
#'08C' : ("TimeShiftCC  -0.390","ForceSegmentsToCore"),
#'09A' : ("TimeShiftCC  -3.256","ForceSegmentsToCore"),
#'09B' : ("TimeShiftCC   2.213","ForceSegmentsToCore"),
#'09C' : ("TimeShiftCC  -3.409","ForceSegmentsToCore"),
#'10A' : ("TimeShiftCC   3.462","ForceSegmentsToCore"),
#'10B' : ("TimeShiftCC   4.913","ForceSegmentsToCore"),
#'10C' : ("TimeShiftCC   4.281","ForceSegmentsToCore"),
#'11A' : ("TimeShiftCC   2.489","ForceSegmentsToCore"),
#'11B' : ("TimeShiftCC  -0.094","ForceSegmentsToCore"),
#'11C' : ("TimeShiftCC  -1.958","ForceSegmentsToCore"),
#'13A' : ("TimeShiftCC   1.473","ForceSegmentsToCore"),
#'13B' : ("TimeShiftCC   0.268","ForceSegmentsToCore"),
#'13C' : ("TimeShiftCC   5.891","ForceSegmentsToCore"), 								   
#'14A' : ("TimeShiftCC   0.000","ForceSegmentsToCore"),
#'14B' : ("TimeShiftCC   0.000","ForceSegmentsToCore"),
#'14C' : ("TimeShiftCC   0.000","ForceSegmentsToCore"),

#Global time aligment from run 1002 05/12/2023
    '00A' : ("TimeShiftCC  -1.638", "RecalEnergy2 RecalEnergy2.cal"),
'00B' : ("TimeShiftCC  -2.862", "RecalEnergy2 RecalEnergy2.cal"),
'00C' : ("TimeShiftCC  -2.628", "RecalEnergy2 RecalEnergy2.cal"),
'01A' : ("TimeShiftCC   2.220", "RecalEnergy2 RecalEnergy2.cal"),
'01B' : ("TimeShiftCC  -1.237", "RecalEnergy2 RecalEnergy2.cal"),
'01C' : ("TimeShiftCC  -1.867", "RecalEnergy2 RecalEnergy2.cal"),
'02A' : ("TimeShiftCC   6.985", "RecalEnergy2 RecalEnergy2.cal"),
'02B' : ("TimeShiftCC   4.877", "RecalEnergy2 RecalEnergy2.cal"),
'02C' : ("TimeShiftCC   5.106", "RecalEnergy2 RecalEnergy2.cal"),
'04A' : ("TimeShiftCC   2.042", "RecalEnergy2 RecalEnergy2.cal"),
'04B' : ("TimeShiftCC  -6.793", "RecalEnergy2 RecalEnergy2.cal"),
'04C' : ("TimeShiftCC  -1.897", "RecalEnergy2 RecalEnergy2.cal"),
'05A' : ("TimeShiftCC   0.000", "RecalEnergy2 RecalEnergy2.cal"),
'05B' : ("TimeShiftCC   2.798", "RecalEnergy2 RecalEnergy2.cal"),
'05C' : ("TimeShiftCC  -2.504", "RecalEnergy2 RecalEnergy2.cal"),
'06A' : ("TimeShiftCC  -4.517", "RecalEnergy2 RecalEnergy2.cal"),
'06B' : ("TimeShiftCC  -2.842", "RecalEnergy2 RecalEnergy2.cal"),
'06C' : ("TimeShiftCC  -2.229", "RecalEnergy2 RecalEnergy2.cal"),
'07A' : ("TimeShiftCC   0.984", "RecalEnergy2 RecalEnergy2.cal"),
'07B' : ("TimeShiftCC  -6.928", "RecalEnergy2 RecalEnergy2.cal", "RecalCC  0.319 0.996918"),
'08A' : ("TimeShiftCC   0.501", "RecalEnergy2 RecalEnergy2.cal"),
'08B' : ("TimeShiftCC   4.613", "RecalEnergy2 RecalEnergy2.cal"),
'09A' : ("TimeShiftCC  -3.256", "RecalEnergy2 RecalEnergy2.cal"),
'09B' : ("TimeShiftCC   2.213", "RecalEnergy2 RecalEnergy2.cal"),
'09C' : ("TimeShiftCC  -3.409", "RecalEnergy2 RecalEnergy2.cal"),
'10A' : ("TimeShiftCC   3.462", "RecalEnergy2 RecalEnergy2.cal"),
'10B' : ("TimeShiftCC   4.913", "RecalEnergy2 RecalEnergy2.cal"),
'10C' : ("TimeShiftCC   4.281", "RecalEnergy2 RecalEnergy2.cal"),
'11A' : ("TimeShiftCC   2.489", "RecalEnergy2 RecalEnergy2.cal"),
'11B' : ("TimeShiftCC  -0.094", "RecalEnergy2 RecalEnergy2.cal"),
'11C' : ("TimeShiftCC  -1.958", "RecalEnergy2 RecalEnergy2.cal"),
'14A' : ("TimeShiftCC   0.000", "RecalEnergy2 RecalEnergy2.cal"),
'14B' : ("TimeShiftCC   0.000", "RecalEnergy2 RecalEnergy2.cal"),
'14C' : ("TimeShiftCC   0.000", "RecalEnergy2 RecalEnergy2.cal"),


}
)

#########################

TrackingFilter=(
"ActualClass           TrackingFilterOFT",          # name of the used daugther class (TrackingFilterOFT or TrackingFilterMGT)
"SaveDataDir           $SAVEDIR/$MERGER",           # Out/Global
"EnergyGain            4",                          # channels/keV of the calibrated energy spectra
#"ExcludeTracking",                                 # skip the tracking part of the actor; remains only the data processing
"OftParams           0.05 0.02 0.8 1",              #  minprobtrack minprobsing sigma_thet (0==default)
#"MgtParams           0                             #  max value of Chi2 to accept a tracked gamma (0==default)
"SourcePosition        0 0 0",                      # position of source with respect to the center of AGATA Position of source
"DiscardEmpty          0",			    # to discard events that does not pass the tracking (don't discard allows to keep the events in the PSA hits)
#"RecoilDirection       0 0 1",                     # fixed recoil direction for Doppler correction (if not using info from Ancillary) 
#"RecoilBeta            0.05",                      # and beta of recoil
#"CoreEnergyGate        20 20000",                  # possibility to restrict the energy range in the individual detectors (better in PreprocessingFilter)
#"NumDetsGate         i32 i32",                     # event selection based on number of fired detectors
#"NumHitsGate         i32 i32",                     # event selection based on total number of hits
#"WriteRootTree",                                	# enable writing the root tree
#"WriteRootTree         InputHits",              	# enable writing the root tree with the input hits
#"WriteMgtData",                                    # enable writing Mgt_Hits.txt (same as: WriteInputHits WriteMgtHits WriteOftHits)
#"WriteTracked",                                    # write file of input hits for analysis with OFT
#"RotoTranslations CrystalPositionLookUpTable",     # this is the default
#"Matrixgg1             4096 1",                    # size and gain of g-g matrix for CC before tracking (default size is 0 ==> no matrix)
#"Matrixgg2             4096 1",                    # size and gain of g-g matrix for tracked and Doppler corrected gammas (   ""   )
#"MatrixZYX           f32 f32 i32 f32",             # hits and intercepts: zOffset and zScale for hits; range and scale of z-planes
#"OutputModel           kSafe",                    	# kStrict (default)  kSafe  kGrowing, behaviour for ADF
#"Verbose",                                         # more verbose terminal-output
"NumGeDets  39",				    # Overrides the default number of Ge Crystals [20], just for printing of TkT spectra
"SpecMap 0 0", # 00A   // Id-core #core
"SpecMap 1 1", # 00B
"SpecMap 2 2", # 00C
"SpecMap 3 3", # 01A
"SpecMap 4 4", # 01B
"SpecMap 5 5", # 01C
"SpecMap 6 6", # 02A
"SpecMap 7 7", # 02B
"SpecMap 8 8", # 02C
"SpecMap 12 9", # 04A
"SpecMap 13 10", # 04B
"SpecMap 14 11", # 04C
"SpecMap 15 12", # 05A
"SpecMap 16 13", # 05B
"SpecMap 17 14", # 05C
"SpecMap 18 15", # 06A
"SpecMap 19 16", # 06B
"SpecMap 20 17", # 06C
"SpecMap 21 18", # 07A
"SpecMap 22 19", # 07B
"SpecMap 23 20", # 07C
"SpecMap 24 21", # 08A
"SpecMap 25 22", # 08B
"SpecMap 26 23", # 08C
"SpecMap 27 24", # 09A
"SpecMap 28 25", # 09B
"SpecMap 29 26", # 09C
"SpecMap 30 27", # 10A
"SpecMap 31 28", # 10B
"SpecMap 32 29", # 10C
"SpecMap 33 30", # 11A
"SpecMap 34 31", # 11B
"SpecMap 35 32", # 11C
"SpecMap 39 33", # 13A
"SpecMap 40 34", # 13B
"SpecMap 41 35", # 13C
"SpecMap 42 36", # 14A
"SpecMap 43 37", # 14B
"SpecMap 44 38", # 14C







#When active with ancillary ######################33
#"KeepEmpty",                                   	# send to the adf output also events without gammas
#"DiscardEmpty 0",                                	# remove from the adf output events without gammas
#"AncillaryRawFrame     0",                     	# 0 (GSI)	0 (GANIL) 1 (LNL)    default is 1
#"Ancillary",                                   	# to perform tracking only if ancillary data is present
#"Recoiling",                                       # Doppler shift correction to be done using event-by-event recoil direction from Ancillary
#"TimeWindowGeAnc       650 670",                 # values should be taken from Oft__**__TA.spec
#"TimeWindowGeAncillary 1110 1130",               # variant of the previous
#"WriteMgtData          Ancillary Timing Extended", # enable writing Mgt_Hits.txt with ancillary,... data
)   
 

#########################
EventBuilder=(
"ActualClass           EventBuilder",             # name of the used daugther class
"SaveDataDir           $SAVEDIR/$BUILDER",        # Out/Builder
"Window                50",                       # EventNumber also possible but not working well
#"TstampWindow   ui64 ui64",                      # coincidence window 'width' or 'from to' (timestamp units)
"keyIn                 data:psa",                 # key of 1st queue.  
"keyIn                 data:psa",                 # key of 2nd queue. 'None' to not have the surrounding frame
"keyOut                event:data:psa",           # key of the output frame default is event:data. 'None' to not have the surrounding frame
"MinFold               1",                        # 2 if you want to force the coincidence between 2 AGATA                       
#"TimestampCorrect      0  -128",                 # indexed by the input queue number !!
#"TstampLimits   ui32 ui32",                      # discard events outside this data-timestamp interval (seconds)
#"TstampRegions  ui64 ui64 str",                  # tStampMin, tStampStep, TxtFile with 0/1 for each timestamp step
#"RateProfile    ui64 ui64 i32",                  # tStampMin, tStampStep, Length of rate-profile spectrum
#"Details        15 ",                            # bit1=tstamp_snapshot  bit2=tstamp_diff for all input queues, bit3=TAC for all pairs
"Verbose",                                        # more verbose terminal-output
)


#########################
EventMerger_MERGER=(
"ActualClass           EventMerger",             # name of the used daugther class
"SaveDataDir           $SAVEDIR/$MERGER ",        # Out/Merger
"Window               50",            # EventNumber also possible but not working well
#"TstampWindow   ui64 ui64",                      # coincidence window 'width' or 'from to' (timestamp units)
"keyIn                 event:data:psa", 		  # key of 1st queue.  
"keyIn                 event:ranc",
"keyOut                event:data",               # key of the output frame default is event:data. 'None' to not have the surrounding frame
#"MandatoryKey          event:ranc",
#"keyIn                 data:psa",                 # key of 1st queue.  
#"keyIn                 data:psa",                 # key of 2nd queue. 'None' to not have the surrounding frame
#"keyOut                event:data:psa",           # key of the output frame default is event:data. 'None' to not have the surrounding frame
"MinFold               2",                        # 2 if you want to force the coincidence between AGATA and the Ancillary                       
"TstampCorrect      0  0",                 # indexed by the input queue number !!
#"TstampLimits   ui32 ui32",                      # discard events outside this data-timestamp interval (seconds)
#"TstampRegions  ui64 ui64 str",                  # tStampMin, tStampStep, TxtFile with 0/1 for each timestamp step
#"RateProfile    ui64 ui64 i32",                  # tStampMin, tStampStep, Length of rate-profile spectrum
#"Details        i32 ",                           # bit1=tstamp_snapshot  bit2=tstamp_diff for all input queues, bit3=TAC for all pairs
"Verbose",                                        # more verbose terminal-output
)
#########################

# Configuration of the consumers:

BasicAFC_OSCAR=(                                
"$SAVEDIR/$OSCAR      	oscar_ 0",           
)

BasicAFC_PRISMA=(                                
"$SAVEDIR/$PRISMA      	prisma_ 0",           
)

BasicAFC_SPIDER=(                                
"$SAVEDIR/$SPIDER      	spider_ 0",           
)

BasicAFC_EUCLIDES=(                                
"$SAVEDIR/$EUCLIDES     euclides_ 0",           
)

BasicAFC_LABR=(                                
"$SAVEDIR/$LABR labr_ 0",           
)

BasicAFC_DANTE=(                                
"$SAVEDIR/$DANTE labr_ 0",           
)

BasicAFC_BUILDER=(                              # _BUILDER will be removed when building the name of the file
"$SAVEDIR/$BUILDER   	Builder_ 0",            # name of the final adf file "Builder_0000.adf"
)

BasicAFC_MERGER=(                               # _MERGER will be removed when building the name of the file
"$SAVEDIR/$MERGER   	Tracked_ 0",            # name of the final adf file "Merger_0000.adf"
)
 
BasicAFC=(                                       
"$SAVEDIR/$CRYSTAL   	psa_ 0",                # name of the final adf file "Tracked_0000.adf"
)


# Configuration of the producerss:
BasicAFP_CRYSTAL=(                         
"$READDIR/$CRYSTAL 	psa_ 0",         
)

BasicAFP_OSCAR=(                                
"$CurrentDir      	        oscar_ 0",             # it reads adf file "prisma_0000.adf"
)

BasicAFP_PRISMA=(                                
"$CurrentDir      	        prisma_ 0",             # it reads adf file "prisma_0000.adf"
)

BasicAFP_SPIDER=(                                
"$CurrentDir      	        spider_ 0",             # it reads adf file "prisma_0000.adf"
)

BasicAFP_SAURON=(                                
"$CurrentDir      	        sauron_ 0",             # it reads adf file "prisma_0000.adf"
)

BasicAFP_EUCLIDES=(                                
"$CurrentDir      	        euclides_ 0",             # it reads adf file "prisma_0000.adf"
)

BasicAFP_LABR=(                                
"$CurrentDir      	        labr_ 0",             # it reads adf file "prisma_0000.adf"
)

BasicAFP_BUILDER=(                              # _BUILDER will be removed when building the name of the file
"$READDIR/$BUILDER      Builder_ 0",            # name of the final adf file "Builder_0000.adf"
)


TreeBuilder=(  #From 2022                                     
"ActualClass    TreeBuilder",      		 	             # name of the used daugther class
"SaveDataDir    $SAVEDIR/$ANALYSIS   Tree_  TreeMaster ",            # name of the final root file "AgNedDiamTree_0000.root"
"AddDetector    AGATA_BUILDER   event:data:psa         0",           # Add a detector in the Tree (DetName, ADF key, Mode [-1: Must NOT be present, 0: Can be present, 1: Must be present])
"AddDetector    AGATA_TRACKING  data:tracked           0",           # Add a detector in the Tree (DetName, ADF key, Mode [-1: Must NOT be present, 0: Can be present, 1: Must be present])
"AddDetector    SPIDER          event:ranc             0",           # Add a detector in the Tree (DetName, ADF key, Mode [-1: Must NOT be present, 0: Can be present, 1: Must be present])
"AddDetector    LABR		event:ranc             0",           # Add a detector in the Tree (DetName, ADF key, Mode [-1: Must NOT be present, 0: Can be present, 1: Must be present])
"AddDetector    DANTE event:ranc             0",           # Add a detector in the Tree (DetName, ADF key, Mode [-1: Must NOT be present, 0: Can be present, 1: Must be present])
"MaxRootFileSize 600",
"MergerMode",
)


TB_OSCAR=(
"ConfPath       $CONFDIR/oscar",               #Path to Prisma configuration files
"WriteRawTree",                                 #Store the raw data in the output Tree (default 1)
"WriteAnaTree",                                 #Store the analyzed data in the output Tree (default 1)
#"Verbose",                                     # Print warnings in the processing
)

TB_PRISMA=(
"ConfPath       $CONFDIR/prisma",               #Path to Prisma configuration files
"LUTFile        lutPRISMA.txt",   #LUT file name (default is : lutPRISMA.txt)
"ManagerFile    manager.conf",                  #Manager file name (default is : manager.conf)
"WriteRawTree",                                 #Store the raw data in the output Tree (default 1)
"WriteAnaTree",                                 #Store the analyzed data in the output Tree (default 1)
#"DoPrismaAnalysis",                             #Ignore the input analyzed data (if present) and process the prisma lib on the raw data (default 0)
#"Verbose",                                     # Print warnings in the processing
)
TB_SPIDER=(
"ConfPath       $CONFDIR/spider",               #Path to Prisma configuration files
"WriteRawTree",                                 #Store the raw data in the output Tree (default 1)
"WriteAnaTree",                                 #Store the analyzed data in the output Tree (default 1)
#"Verbose",                                     # Print warnings in the processing
)
TB_SAURON=(
"ConfPath       $CONFDIR/sauron",               #Path to Prisma configuration files
"WriteRawTree",                                 #Store the raw data in the output Tree (default 1)
"WriteAnaTree",                                 #Store the analyzed data in the output Tree (default 1)
#"Verbose",                                     # Print warnings in the processing
)
TB_EUCLIDES=(
"ConfPath       $CONFDIR/euclides",               #Path to Prisma configuration files
"WriteRawTree",                                 #Store the raw data in the output Tree (default 1)
"WriteAnaTree",                                 #Store the analyzed data in the output Tree (default 1)
#"Verbose",                                     # Print warnings in the processing
)



TB_LABR=(
"ConfPath       $CONFDIR/labr",               #Path to Prisma configuration files
"WriteRawTree",                                 #Store the raw data in the output Tree (default 1)
"WriteAnaTree",                                 #Store the analyzed data in the output Tree (default 1)
#"Verbose",                                     # Print warnings in the processing
)

TB_DANTE=(
"ConfPath       $CONFDIR/dante",               #Path to Prisma configuration files
"WriteRawTree",                                 #Store the raw data in the output Tree (default 1)
"WriteAnaTree",                                 #Store the analyzed data in the output Tree (default 1)
#"Verbose",                                     # Print warnings in the processing
)

manager_PRISMA=(
"files_path         = $CONFDIR/prisma",
"mcp_conf           = mcp.conf",
"ppac_conf          = ppac.conf ",
"ionch_conf         = ionch.conf",
"side_conf          = side.conf",
"solver_conf        = solver.conf",
"zed_conf           = zed.conf",
"mass_conf          = mass.conf",

"binarypartner_conf = binarypartner.conf",


"manager_ndet   = 4",
"ndet_mcp       = 1",
"ndet_ppac      = 10",
"ndet_ionch     = 10",
"ndet_side      = 2",
  
"ind_mcp        = 0",
"ind_ppac       = 1",
"ind_ionch      = 2",
"ind_side       = 3",




)


##################
#######

###############################################################################################
###################  3  Database of AGATA germanium detectors  ################################
###############################################################################################

# These values should be checked carefully against the detector configuration used to take the data

#LNL 2024 - updated 03/04/2024
GeDataBaseLNL2022={
'00A': ['00', '$PSABASE/LibTrap_A017.dat'], '00B': ['01', '$PSABASE/LibTrap_B018.dat'], '00C': ['02', '$PSABASE/LibTrap_C018.dat'],
'01A': ['03', '$PSABASE/LibTrap_A003.dat'], '01B': ['04', '$PSABASE/LibTrap_B016.dat'], '01C': ['05', '$PSABASE/LibTrap_C015.dat'],
'02A': ['06', '$PSABASE/LibTrap_A016.dat'], '02B': ['07', '$PSABASE/LibTrap_B017.dat'], '02C': ['08', '$PSABASE/LibTrap_C013.dat'],
'04A': ['12', '$PSABASE/LibTrap_A007.dat'], '04B': ['13', '$PSABASE/LibTrap_B014.dat'], '04C': ['14', '$PSABASE/LibTrap_C003.dat'],
'05A': ['15', '$PSABASE/LibTrap_A001.dat'], '05B': ['16', '$PSABASE/LibTrap_B001.dat'], '05C': ['17', '$PSABASE/LibTrap_C006.dat'],
'06A': ['18', '$PSABASE/LibTrap_A018.dat'], '06B': ['19', '$PSABASE/LibTrap_B012.dat'], '06C': ['20', '$PSABASE/LibTrap_C019.dat'],
'07A': ['21', '$PSABASE/LibTrap_A019.dat'], '07B': ['22', '$PSABASE/LibTrap_B019.dat'], '07C': ['23', '$PSABASE/LibTrap_C020.dat'],
'08A': ['24', '$PSABASE/LibTrap_A002.dat'], '08B': ['25', '$PSABASE/LibTrap_B007.dat'], '08C': ['26', '$PSABASE/LibTrap_C007.dat'],
'09A': ['27', '$PSABASE/LibTrap_A014.dat'], '09B': ['28', '$PSABASE/LibTrap_B010.dat'], '09C': ['29', '$PSABASE/LibTrap_C016.dat'],
'10A': ['30', '$PSABASE/LibTrap_A013.dat'], '10B': ['31', '$PSABASE/LibTrap_B015.dat'], '10C': ['32', '$PSABASE/LibTrap_C011.dat'],
'11A': ['33', '$PSABASE/LibTrap_A010.dat'], '11B': ['34', '$PSABASE/LibTrap_B011.dat'], '11C': ['35', '$PSABASE/LibTrap_C009.dat'],
'13A': ['39', '$PSABASE/LibTrap_A018.dat'], '13B': ['40', '$PSABASE/LibTrap_B012.dat'], '13C': ['41', '$PSABASE/LibTrap_C019.dat'],
'14A': ['42', '$PSABASE/LibTrap_A009.dat'], '14B': ['43', '$PSABASE/LibTrap_B020.dat'], '14C': ['44', '$PSABASE/LibTrap_C005.dat'],
}
GeDataBase = GeDataBaseLNL2022

###############################################################################################
######  Code to generate directories and files. You should not need to modify this part.  #####
###############################################################################################

import os.path
import shutil
import sys
import optparse

verbose = False
verbstr = ""

def getGeData(cr):
    """
    get data of crystal cr from GeDataBase
    """
    if cr in GeDataBase:
        if GeDataBase[cr] != ['','']:
          return GeDataBase[cr]
        else:
          print('GeDataBase for',cr,'is empty')
          sys.exit(-1)      
    else:
        print(cr, ' is not in GeDataBase')
        sys.exit(-1)

def replaceMacros(ss, cr):
    """
    replace macros (as defined in MACROS) in string ss. If cr is a Ge crystal (e.g. 1R), its data 
    ($CRYSTAL_ID and $SIGNAL_BASIS are extracted from GeDataBase and replaced before checking MACRO
    """
    if (len(cr) == 2 or len(cr)==3):    # 2 characters for the ge crystals
        dd = getGeData(cr)
        ll = ss.replace("$CRYSTAL_ID",   dd[0])
        ll = ll.replace("$SIGNAL_BASIS", dd[1])
        ll = ll.replace("$CRYSTAL", cr)
    else: 
        ll = ss
    for mm in MACROS:
        ll = ll.replace(mm, MACROS[mm])
    return ll

def printItem(vv, cc, ff):
    """
    item vv relative to the chain cc is printed to file ff, after macro replacement.
    If vv is a tuple or a list, its components are printed in separate lines 
    """
    if isinstance(vv, list) or isinstance(vv, tuple):
        # multi line: recursively call printItem for the elements 
        for vi in vv:
            printItem(vi, cc, ff)
    else:
        # single line: replace macros and print it
        ll = replaceMacros(vv, cc) 
        if CONFTYPE != 'ONLINE' or ll.find('ReadDataDir')!=0:
            ff.write(ll+os.linesep)
            #print ll+os.linesep

def printActor(vv, cc, ff):
    """
    print the command lines in vv to file ff of analysis chain cc
    """
    global verbstr
    #import pdb; pdb.set_trace()
    if len(ff) < 1:
        return
    if os.path.exists(ff):
        if verbose:
            print(" Replaced ", ff)
        else:
            verbstr += "."
    else:
        print(" Created  ", ff)
    with open(ff, "w") as fi:
        for vi in vv:
            if isinstance(vi, dict):
                if cc in vi:
                    # dictionary refers to the present detector
                    printItem(vi[cc], cc, fi)
            else:
                # common item
                printItem(vi, cc, fi)

def checkMakeDirs(dd):
    """
    created sub-directory dd in $CONFDIR, $READDIR and $SAVEDIR
    """
    for cc in ['$CONFDIR', '$SAVEDIR']:
        if cc in MACROS:   
            ff = os.path.join(MACROS[cc], dd)
            if os.path.exists(ff) == False:
                print('Creating  ', ff)
                os.makedirs(ff)

def makeFileName(pre, cc, aa, topo):
    """
    generate the actual name of the configuration file pre/cc/aa
    if the generated filename contains "_topo" this part is removed 
    """
    #import pdb; pdb.set_trace()
    if PROGTYPE == "NARVAL" :
        if aa == "EventBuilder" or aa == "EventMerger" :
            print (" "+aa+".conf").ljust(18), "not generated when using NARVAL"
            return ""
    ff = os.path.join(pre, cc, aa+'.conf')
    ff = ff.replace('_'+topo, '')
    return ff

def checkExtraFiles(new, old, dd, files):
    """
    check existence of the files defined in ExtraFiles. Try to copy it from
    oldDir if file not present and the script is launched with "-o oldDir" 
    """
    global verbstr
    newDir = os.path.join(new, dd)
    oldDir = os.path.join(old, dd)
    for f in files.split():
        fff = os.path.join(newDir, f)
        ff  = replaceMacros(fff, dd) 
        if os.path.exists(ff):
            if verbose:
                print(" Existing ", ff)
            else:
                verbstr += "."
        else:
            if old != "":
                 gg = os.path.join(oldDir, f)
                 if os.path.exists(gg):
                     if os.path.isdir(gg):
                         shutil.copytree(gg, ff)
                     else:
                         shutil.copy(gg, ff)
                     print(" Copied   ", gg)
            if not os.path.exists(ff):
                print(" Missing  ", ff) 

def checkArgsAndMacros():
    """
    decode command line arguments and perform some consistency checks on PROGTYPE and CONFTYPE
    """
    parser = optparse.OptionParser()
    parser.add_option("-o", "--old", dest="confold", help="old Conf where to look for missing auxiliary files", metavar="DIR")
    parser.add_option("-d", "--dir", dest="workdir", help="the directory where to generate the analysis structure", metavar="DIR")
    parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="verbosity of printouts")
    (options, args) = parser.parse_args()
    #print 'confold = ', options.confold
    #print 'workdir = ', options.workdir
    #print 'verbose = ', options.verbose

    global verbose
    verbose = options.verbose

    if options.workdir is None:
        CWD = os.getcwd() #for anodeds5
		#CWD = "/agatadisks/zCurrentNarvalDir"#for anodeds6
    else:
        CWD = options.workdir

    global PROGTYPE, CONFTYPE
    if PROGTYPE != 'NARVAL':
        CONFTYPE = 'OFFLINE'
    if CONFTYPE == 'ONLINE':
        CWD = os.getcwd()
	#CWD = "/agatadisks/zCurrentNarvalDir"#for anodeds6

    for cc in ['$CONFDIR', '$READDIR', '$SAVEDIR', '$CurrentDir']:
        if cc in MACROS:
            MACROS[cc] = os.path.join(CWD, MACROS[cc])  
    if CONFTYPE == 'ONLINE':
        MACROS['$SAVEDIR'] = MACROS['$READDIR']
        del MACROS['$READDIR']

    print
    print('CWD     '.ljust(10), " --> ", CWD)
    print('PROGTYPE'.ljust(10), " --> ", PROGTYPE)
    print('CONFTYPE'.ljust(10), " --> ", CONFTYPE)
    for a in MACROS:
        if MACROS[a] != '':
            print(a.ljust(10), " --> ", MACROS[a])
    print

    newPrefix = MACROS['$CONFDIR']
    if options.confold is None:
        oldPrefix = ''
    else:
        oldPrefix = options.confold    
    return newPrefix, oldPrefix

def main():
    """
    """
    (newConf, oldConf) = checkArgsAndMacros()
    print("newConf = ", newConf)
    print("oldConf = ", oldConf)
    print

    global verbstr
    myGlobals = globals()

    for topo in Topology:
       block = Topology[topo]
       if isinstance(block, dict):
           dire = block.split()
       else:
           dire = block;
       for dd in dire.split():
            checkMakeDirs(dd)
            if topo in Actors:
                 print(dd+'/')
                 verbstr = ""
                 actor = Actors[topo].split()
                 for aa in actor:
                     ff = makeFileName(newConf, dd, aa, topo)
                     printActor(myGlobals[aa], dd, ff)
                 if topo in ExtraFiles:
                     checkExtraFiles(newConf, oldConf, dd, ExtraFiles[topo])
                 if verbstr:
                     print(" " + verbstr) 

###############################################################################################
###############################################################################################

if __name__ == "__main__":
    main()
