# Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration

from AthenaCommon.Logging import logging
log = logging.getLogger( __name__ )

from DecisionHandling.DecisionHandlingConf import RoRSeqFilter
from TriggerMenuMT.HLTMenuConfig.Menu.MenuComponentsNaming import CFNaming
from AthenaCommon.CFElements import parOR, seqAND


class Node(object):
    """base class representing one Alg + inputs + outputs, to be used to Draw dot diagrams and connect objects"""
    def __init__(self, Alg):
        self.name = ("%sNode")%( Alg.name())
        self.Alg=Alg
        self.inputs=[]
        self.outputs=[]

    def addOutput(self, name):
        self.outputs.append(name)

    def addInput(self, name):
        self.inputs.append(name)

    def getOutputList(self):
        return self.outputs

    def getInputList(self):
        return self.inputs

    def __repr__(self):
        return "Node::%s  [%s] -> [%s]"%(self.Alg.name(), ' '.join(map(str, self.getInputList())), ' '.join(map(str, self.getOutputList())))



class AlgNode(Node):
    """Node class that connects inputs and outputs to basic alg. properties """
    def __init__(self, Alg, inputProp, outputProp):
        Node.__init__(self, Alg)
        self.outputProp=outputProp
        self.inputProp=inputProp

    def addDefaultOutput(self):
        if self.outputProp != '':
            self.addOutput(("%s_%s"%(self.Alg.name(),self.outputProp)))

    def setPar(self, prop, name):
        cval = self.Alg.getProperties()[prop]
        if type(cval) is list:
            cval.append(name)
            return setattr(self.Alg, prop, cval)
        else:
            return setattr(self.Alg, prop, name)

    def resetPar(self, prop):
        cval = self.Alg.getProperties()[prop]
        if type(cval) is list:
            return setattr(self.Alg, prop, [])
        else:
            return setattr(self.Alg, prop, "")

    def getPar(self, prop):
        if hasattr(self.Alg, prop):
            return getattr(self.Alg, prop)
        else:
            return self.Alg.getDefaultProperty(prop)


    def resetOutput(self):
        self.resetPar(self.outputProp)

    def resetInput(self):
        self.resetPar(self.inputProp)

    def addOutput(self, name):
        outputs = self.readOutputList()
        if name in outputs:
            log.debug("Output DH not added in %s: %s already set!", self.name, name)
        else:
            if self.outputProp != '':
                self.setPar(self.outputProp,name)
            else:
                log.error("no OutputProp set")
        Node.addOutput(self, name)


    def readOutputList(self):
        outputs = []
        cval = self.getPar(self.outputProp)
        if cval == '':
            return outputs
        if type(cval) is list:
            outputs.extend(cval)
        else:
            outputs.append(cval)
        return outputs

    def addInput(self, name):
        inputs = self.readInputList()
        if name in inputs:
            log.debug("Input DH not added in %s: %s already set!", self.name, name)
        else:
            if self.inputProp != '':
                self.setPar(self.inputProp,name)
            else:
                log.error("no InputProp set")
        Node.addInput(self, name)


    def readInputList(self):
        inputs = []
        cval = self.getPar(self.inputProp)
        if cval =='':
            return inputs
        if type(cval) is list:
            inputs.extend(cval)
        else:
            inputs.append(cval)
        return inputs

    def __repr__(self):
        return "Alg::%s  [%s] -> [%s]"%(self.Alg.name(), ' '.join(map(str, self.getInputList())), ' '.join(map(str, self.getOutputList())))


def algColor(alg):
    """ Set given color to Alg type"""
    if isHypoBase(alg):
        return "darkorchid1"
    if isInputMakerBase(alg):
        return "cyan3"
    if isFilterAlg(alg):
        return "chartreuse3"
    if isComboHypoAlg(alg):
        return "darkorange"
    return "cadetblue1"


class HypoToolConf(object):
    """ Class to group info on hypotools for ChainDict"""
    def __init__(self, hypoToolGen):
        self.hypoToolGen = hypoToolGen
        self.name=hypoToolGen.__name__


    def setConf( self, chainDict):
        if type(chainDict) is not dict:
            raise RuntimeError("Configuring hypo with %s, not good anymore, use chainDict" % str(chainDict) )
        self.chainDict = chainDict


    def create(self):
        """creates instance of the hypo tool"""
        return self.hypoToolGen( self.chainDict )
    
    
    def confAndCreate(self, chainDict):
        """sets the configuration and creates instance of the hypo tool"""
        self.setConf(chainDict)
        return self.create()


class HypoAlgNode(AlgNode):
    """Node for HypoAlgs"""
    initialOutput= 'StoreGateSvc+UNSPECIFIED_OUTPUT'
    def __init__(self, Alg):
        assert isHypoBase(Alg), "Error in creating HypoAlgNode from Alg "  + Alg.name()
        AlgNode.__init__(self, Alg, 'HypoInputDecisions', 'HypoOutputDecisions')
        self.tools = []
        self.previous=[]

    def addOutput(self, name):
        outputs = self.readOutputList()
        if name in outputs:
            log.debug("Output DH not added in %s: %s already set!", self.name, name)
        elif self.initialOutput in outputs:
            AlgNode.addOutput(self, name)
        else:
            log.error("Hypo " + self.name +" has already %s as configured output: you may want to duplicate the Hypo!" + outputs[0])




    def addHypoTool (self, hypoToolConf):
        log.debug("   Adding HypoTool %s to %s", hypoToolConf.chainDict['chainName'], self.Alg.name())
        if hypoToolConf.chainDict['chainName'] not in self.tools:
            ## HypoTools are private, so need to be created when added to the Alg
            ## this incantation may seem strange, however it is the only one which works
            ## trying tool = hypoToolConf.create() and then assignement does not work! will be no problem in run3 config
            tools = self.Alg.HypoTools
            self.Alg.HypoTools = tools+[hypoToolConf.create()]
            self.tools.append( self.Alg.HypoTools[-1].getName() ) # should not be needed anymore
        else:
            raise RuntimeError("The hypo tool of name "+ hypoToolConf.chainDict['chainName'] +" already present")


    def setPreviousDecision(self,prev):
        self.previous.append(prev)
        return self.addInput(prev)

    def resetDecisions(self):
        self.previous = []
        self.resetOutput()
        self.resetInput()

    def __repr__(self):
        return "HypoAlg::%s  [%s] -> [%s], previous = [%s], HypoTools=[%s]"%(self.Alg.name(),' '.join(map(str, self.getInputList())),
                                                                                 ' '.join(map(str, self.getOutputList())),
                                                                                 ' '.join(map(str, self.previous)),
                                                                                 ' '.join(map(str, self.tools)))


class SequenceFilterNode(AlgNode):
    """Node for any kind of sequence filter"""
    def __init__(self, Alg, inputProp, outputProp):
        AlgNode.__init__(self,  Alg, inputProp, outputProp)

    def setChains(self, name):
        return self.setPar("Chains", name)

    def getChains(self):
        return self.getPar("Chains")

    def __repr__(self):
        return "SequenceFilter::%s  [%s] -> [%s], chains=%s"%(self.Alg.name(),' '.join(map(str, self.getInputList())),' '.join(map(str, self.getOutputList())), self.getChains())


class RoRSequenceFilterNode(SequenceFilterNode):
    def __init__(self, name):
        Alg= RoRSeqFilter(name)
        SequenceFilterNode.__init__(self,  Alg, 'Input', 'Output')



class InputMakerNode(AlgNode):
    def __init__(self, Alg):
        assert isInputMakerBase(Alg), "Error in creating InputMakerNode from Alg "  + Alg.name()
        AlgNode.__init__(self,  Alg, 'InputMakerInputDecisions', 'InputMakerOutputDecisions')
        input_maker_output = CFNaming.inputMakerOutName(self.Alg.name(),"out")
        self.addOutput(input_maker_output)


from DecisionHandling.DecisionHandlingConf import ComboHypo
class ComboMaker(AlgNode):
    def __init__(self, name, multiplicity):
        Alg = RecoFragmentsPool.retrieve( self.create, name )
        log.debug("ComboMaker init: Alg %s", name)
        AlgNode.__init__(self,  Alg, 'HypoInputDecisions', 'HypoOutputDecisions')
        self.prop="MultiplicitiesMap"
        self.mult=list(multiplicity)
        self._hypoToolConf = []

    def create (self, name):
        log.debug("ComboMaker.create %s",name)
        return ComboHypo(name)

    def addChain(self, chainDict):
        chainName = chainDict['chainName']
        log.debug("ComboMaker %s adding chain %s", self.Alg.name(),chainName)
        allMultis = self.mult
        newdict = {chainName : allMultis}

        cval = self.Alg.getProperties()[self.prop]  # check necessary to see if chain was added already?
        if type(cval) is dict:
            if chainName in cval.keys():
                log.error("ERROR in cofiguration: ComboAlg %s has already been configured for chain %s", self.Alg.name(), chainName)
            else:
                cval[chainName]=allMultis
        else:
            cval=newdict

        setattr(self.Alg, self.prop, cval)
        log.debug("ComboAlg %s has now these chains chain %s", self.Alg.name(), self.getPar(self.prop))


    def getChains(self):
        cval = self.Alg.getProperties()[self.prop]
        return cval

    def addComboHypoToolConfs(self, comboToolConfs):
        self._hypoToolConf = [ HypoToolConf( tool ) for tool in comboToolConfs ]
        for conf in self._hypoToolConf:
            log.debug("ComboMaker.addComboHypoToolConfs %s %s", self.Alg.name(), conf.name)

    def createComboHypoTools(self, chainDict):
        """Ccreated the ComboHypoTools"""
        if not len(self._hypoToolConf):
            return
        log.debug("ComboMaker.createComboHypoTools for %s with %d tools", self.Alg.name(), len(self._hypoToolConf))        
        self.Alg.ComboHypoTools = [conf.confAndCreate( chainDict ) for conf in self._hypoToolConf]
        


#########################################################
# USEFULL TOOLS
#########################################################

def isHypoBase(alg):
    if  'HypoInputDecisions'  in alg.__class__.__dict__:
        return True
    prop = alg.__class__.__dict__.get('_properties')
    return  ('HypoInputDecisions'  in prop)

def isInputMakerBase(alg):
    return  ('InputMakerInputDecisions'  in alg.__class__.__dict__)

def isFilterAlg(alg):
    return isinstance(alg, RoRSeqFilter)

def isComboHypoAlg(alg):
    return isinstance(alg, ComboHypo)


##########################################################
# NOW sequences and chains
##########################################################

class MenuSequence(object):
    """ Class to group reco sequences with the Hypo"""
    """ By construction it has one Hypo Only; behaviour changed to support muFastOvlpRmSequence() which has two, but this will change"""

    def __init__(self, Sequence, Maker,  Hypo, HypoToolGen):
        assert Maker.name().startswith("IM"), "The input maker {} name needs to start with letter: IM".format(Maker.name())
        self._sequence     = Node( Alg=Sequence)
        self._maker       = InputMakerNode( Alg = Maker )
        self._seed=''

        if isinstance(Hypo, list): # we will remove support for this and will issue error
            log.warning("Sequence %s has more than one Hypo; correct your sequence in the next development cycle", self.name)
            assert len(Hypo) == len(HypoToolGen), "The number of hypo algs {} and hypo tools {} not the same".format( len(Hypo), len(HypoToolGen) )

        input_maker_output= self.maker.readOutputList()[0] # only one since it's merged

        #### Add input/output Decision to Hypo, handle first somewhat ill-defined case
        if isinstance(Hypo, list):
            self._hypo         = [ HypoAlgNode( Alg = alg ) for alg in Hypo ]
            self._hypoToolConf = [ HypoToolConf( tool ) for tool in HypoToolGen ]
            self._name         = [ CFNaming.menuSequenceName(alg.name()) for alg in self._hypo ]

            hypo_input = input_maker_output
            for hypo_alg_node in self._hypo:
                hypo_output = CFNaming.hypoAlgOutName(hypo_alg_node.Alg.name())
                hypo_alg_node.addOutput(  hypo_output )
                hypo_alg_node.setPreviousDecision( hypo_input )
                hypo_input = hypo_output

        else:
            self._name = CFNaming.menuSequenceName(Hypo.name())
            self._hypoToolConf = HypoToolConf( HypoToolGen )
            self._hypo = HypoAlgNode( Alg = Hypo )
            hypo_output = CFNaming.hypoAlgOutName(Hypo.name())
            self._hypo.addOutput(hypo_output)
            self._hypo.setPreviousDecision( input_maker_output)

        log.debug("MenuSequence.connect: connecting InputMaker and HypoAlg and OverlapRemoverAlg, adding: \n\
        InputMaker::%s.output=%s",\
                        self.maker.Alg.name(), input_maker_output)

        if type(self._hypo) is list:
            hypo_input_total = []
            [ hypo_input_total.extend( alg_node.Alg.getInputList() )  for alg_node in self._hypo ]
            hypo_output_total = []
            [ hypo_output_total.extend( alg_node.Alg.getOutputList() )  for alg_node in self._hypo ]

            for hp, hp_in, hp_out in zip( self._hypo, hypo_input_total, hypo_output_total):
                log.debug("HypoAlg::%s.previousDecision=%s, \n\
                HypoAlg::%s.output=%s",\
                          hp.Alg.name(), hp_in, hp.Alg.name(), hp_out)
        else:
           log.debug("HypoAlg::%s.previousDecision=%s, \n \
           HypoAlg::%s.output=%s",\
                           self.hypo.Alg.name(), input_maker_output, self.hypo.Alg.name(), self.hypo.readOutputList()[0])

    @property
    def seed(self):
        return self._seed

    @property
    def name(self):
        return self._name

    @property
    def sequence(self):
        return self._sequence

    @property
    def maker(self):
        return self._maker


    @property
    def hypo(self):
        return self._hypo


    def getOutputList(self):
        outputlist = []
        if type(self._hypo) is list:

            for hypo in self._hypo:
                outputlist.append(hypo.readOutputList()[0])
        else:
            outputlist.append(self._hypo.readOutputList()[0])

        return outputlist


    def connectToFilter(self, outfilter):
        """ Connect filter to the InputMaker"""
        self._maker.addInput(outfilter)
      

    def connect(self, Hypo, HypoToolGen):
        """ Sets the input and output of the hypo, and links to the input maker """
        input_maker_output= self._maker.readOutputList()[0] # only one since it's merged

         #### Add input/output Decision to Hypo
        if type(Hypo) is list:
            self.name=[]
            self.hypoToolConf=[]
            self._hypo=[]
            hypo_input_total=[]
            hypo_output_total=[]
            hypo_input = input_maker_output
            for hypo_alg, hptool in zip(Hypo, HypoToolGen):
              self.name.append( CFNaming.menuSequenceName(hypo_alg.name()) )
              self.hypoToolConf.append( HypoToolConf( hptool ) )

              hypo_input_total.append(hypo_input)
              hypo_output = CFNaming.hypoAlgOutName(hypo_alg.name())
              hypo_output_total.append(hypo_output)

              hypo_node = HypoAlgNode( Alg = hypo_alg )
              hypo_node.addOutput(hypo_output)
              hypo_node.setPreviousDecision(hypo_input)
              
              self._hypo.append( hypo_node )
              hypo_input = hypo_output
            log.warning("Sequence %s has more than one Hypo; correct your sequence for next develpments", self.name)
        else:
           self.name = CFNaming.menuSequenceName(Hypo.name())
           self.hypoToolConf = HypoToolConf( HypoToolGen )
           self._hypo = HypoAlgNode( Alg = Hypo )
           hypo_output = CFNaming.hypoAlgOutName(Hypo.name())
           self._hypo.addOutput(hypo_output)
           self._hypo.setPreviousDecision( input_maker_output)


        log.debug("MenuSequence.connect: connecting InputMaker and HypoAlg and OverlapRemoverAlg, adding: \n\
        InputMaker::%s.output=%s",\
                        self._maker.Alg.name(), input_maker_output)
        if type(self._hypo) is list:
           for hp, hp_in, hp_out in zip( self._hypo, hypo_input_total, hypo_output_total):
              log.debug("HypoAlg::%s.previousDecision=%s, \n\
                         HypoAlg::%s.output=%s",\
                              hp.Alg.name(), hp_in, hp.Alg.name(), hp_out)
        else:
           log.debug("HypoAlg::%s.previousDecision=%s, \n\
                      HypoAlg::%s.output=%s",\
                           self._hypo.Alg.name(), input_maker_output, self._hypo.Alg.name(), self._hypo.readOutputList()[0])


    def createHypoTools(self, chainDict):
        if type(self._hypoToolConf) is list:
            log.warning ("This sequence %s has %d multiple HypoTools ",self.sequence.name, len(self.hypoToolConf))
            for hypo, hypoToolConf in zip(self._hypo, self._hypoToolConf):
                hypoToolConf.setConf( chainDict )
                hypo.addHypoTool(self._hypoToolConf)
        else:
            self._hypoToolConf.setConf( chainDict )
            self._hypo.addHypoTool(self._hypoToolConf) #this creates the HypoTools  


    def addToSequencer(self, stepReco, seqAndView, already_connected):
        ath_sequence = self.sequence.Alg
        name = ath_sequence.name()
        if name in already_connected:
            log.debug("AthSequencer %s already in the Tree, not added again",name)
            return stepReco, seqAndView, already_connected        
        else:
            already_connected.append(name)
            stepReco += ath_sequence
        if type(self._hypo) is list:
           for hp in self._hypo:
              seqAndView += hp.Alg
        else:
           seqAndView += self._hypo.Alg
        return stepReco, seqAndView, already_connected        


    def buildCFDot(self, cfseq_algs, all_hypos, isCombo, last_step_hypo_nodes, file):
        cfseq_algs.append(self._maker)
        cfseq_algs.append(self.sequence )

        file.write("    %s[fillcolor=%s]\n"%(self._maker.Alg.name(), algColor(self._maker.Alg)))
        file.write("    %s[fillcolor=%s]\n"%(self.sequence.Alg.name(), algColor(self.sequence.Alg)))
        
        if type(self._hypo) is list:
            for hp in self._hypo:
                cfseq_algs.append(hp)
                file.write("    %s[color=%s]\n"%(hp.Alg.name(), algColor(hp.Alg)))
                all_hypos.append(hp)
        else:
            cfseq_algs.append(self._hypo)
            file.write("    %s[color=%s]\n"%(self._hypo.Alg.name(), algColor(self._hypo.Alg)))
            all_hypos.append(self._hypo)
            if not isCombo:
                if type(self._hypo) is list:
                    last_step_hypo_nodes.append(self._hypo[-1])
                else:
                    last_step_hypo_nodes.append(self._hypo)

        return cfseq_algs, all_hypos, last_step_hypo_nodes


    def getTools(self):
        if type(self._hypo) is list:
            return self._hypo[0].tools
        else:
            return self._hypo.tools

    def setSeed( self, seed ):
        self._seed = seed

    def __repr__(self):
        if type(self._hypo) is list:
           hyponame=[]
           hypotool=[]
           for hp, hptool in zip(self._hypo, self._hypoToolConf):
              hyponame.append( hp.Alg.name() )
              hypotool.append( hptool.name )
           return "MenuSequence::%s \n Hypo::%s \n Maker::%s \n Sequence::%s \n HypoTool::%s"\
           %(self.name, hyponame, self._maker.Alg.name(), self.sequence.Alg.name(), hypotool)
        else:
           hyponame = self._hypo.Alg.name()
           hypotool = self._hypoToolConf.name
           return "MenuSequence::%s \n Hypo::%s \n Maker::%s \n Sequence::%s \n HypoTool::%s\n"\
           %(self.name, hyponame, self._maker.Alg.name(), self.sequence.Alg.name(), hypotool)


class CAMenuSequence(MenuSequence):
    ''' MenuSequence with Compoment Accumulator '''

    def __init__(self, Sequence, Maker,  Hypo, HypoToolGen, CA):
        self.ca = CA
        MenuSequence.__init__(self, Sequence, Maker,  Hypo, HypoToolGen)

    @property
    def sequence(self):
        makerAlg = self.ca.getEventAlgo(self._maker.Alg.name())
        self._maker.Alg = makerAlg
        return self._sequence

    @property
    def maker(self):
        makerAlg = self.ca.getEventAlgo(self._maker.Alg.name())
        self._maker.Alg = makerAlg
        return self._maker

    @property
    def hypo(self):
        hypoAlg = self.ca.getEventAlgo(self._hypo.Alg.name())
        self._hypo.Alg = hypoAlg
        return self._hypo


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

#from TriggerMenuMT.HLTMenuConfig.Menu.DictFromChainName import getAllThresholdsFromItem, getUniqueThresholdsFromItem


class Chain(object):
    """Basic class to define the trigger menu """
    __slots__='name','steps','vseeds','L1decisions'
    def __init__(self, name, ChainSteps, L1Thresholds):
        """
        Construct the Chain from the steps
        Out of all arguments the ChainSteps & L1Thresholds are most relevant, the chain name is used in debug messages
        """
        self.name = name
        self.steps=ChainSteps
        self.vseeds=L1Thresholds

        from L1Decoder.L1DecoderConfig import mapThresholdToL1DecisionCollection
        # L1decisions are used to set the seed type (EM, MU,JET), removing the actual threshold
        # in practice it is the L1Decoder Decision output
        self.L1decisions = [ mapThresholdToL1DecisionCollection(stri) for stri in L1Thresholds]
        log.debug("L1Decisions: %s", ' '.join(self.L1decisions))

        self.setSeedsToSequences()
        isCombo=False
        #TO DO: check that all the steps are combo
        for step in self.steps:
            if step.isCombo:
                isCombo=True

        log.debug("Made %s Chain %s with seeds: %s ", "combo" if isCombo else "", name, self.L1decisions)


    def checkMultiplicity(self):
        if len(self.steps) == 0:
            return 0
        mult=[sum(step.multiplicity) for step in self.steps] # on mult per step
        not_empty_mult = [m for m in mult if m!=0]
        if len(not_empty_mult) == 0: #empty chain?
            log.error("checkMultiplicity: Chain %s has all steps with multiplicity =0: what to do?", self.name)
            return 0
        if not_empty_mult.count(not_empty_mult[0]) != len(not_empty_mult):
            log.error("checkMultiplicity: Chain %s has steps with differnt multiplicities: %s", self.name, ' '.join(mult))
            return 0

        if not_empty_mult[0] != len(self.vseeds):
            log.error("checkMultiplicity: Chain %s has %d multiplicity per step, and %d L1Decisions", self.name, mult, len(self.vseeds))
            return 0
        return not_empty_mult[0]



    def setSeedsToSequences(self):
        """ Set the L1 seeds (L1Decisions) to the menu sequences """
        if len(self.steps) == 0:
            return

        # TODO: check if the number of seeds is sufficient for all the seuqences, no action of no steps are configured
        for step in self.steps:
            for seed, seq in zip(self.L1decisions, step.sequences):
                seq.setSeed( seed )
                log.debug( "setSeedsToSequences: Chain %s adding seed %s to sequence in step %s", self.name, seed, step.name )

    def getChainLegs(self):
        """ This is extrapolating the chain legs from the chain dictionary"""
        from TriggerMenuMT.HLTMenuConfig.Menu.ChainDictTools import splitChainInDict
        listOfChainDictsLegs = splitChainInDict(self.name)
        legs = [part['chainName'] for part in listOfChainDictsLegs]      
        return legs


    def createHypoTools(self):
        """ This is extrapolating the hypotool configuration from the chain name"""
        log.debug("decodeHypoToolConfs for chain %s", self.name)
        from TriggerMenuMT.HLTMenuConfig.Menu.ChainDictTools import splitChainInDict

        # this spliting is only needed for chains which don't yet attach
        # the dictionaries to the chain steps. It should be removed
        # later once that migration is done.
        listOfChainDictsLegs = splitChainInDict(self.name)
        
        for step in self.steps:
            if len(step.sequences) == 0:
                continue

            step_mult = [str(m) for m in step.multiplicity]

            if len(step.chainDicts) > 0:
                # new way to configure hypo tools, works if the chain dictionaries have been attached to the steps
                log.info('%s in new hypo tool creation method', self.name)
                for seq, onePartChainDict in zip(step.sequences, step.chainDicts):
                    log.info('    onePartChainDict:')
                    log.info('    ' + str(onePartChainDict))
                    seq.createHypoTools( onePartChainDict )              

            else:
                # legacy way, to be removed once all signatures pass the chainDicts to the steps
                log.info('%s in old hypo tool creation method', self.name)
                menu_mult = [ part['chainParts'][0]['multiplicity'] for part in listOfChainDictsLegs ]
                #print 'step, step_mult, menu_mult: ' + step.name + ' ' + str(step_mult) + ' ' + str(menu_mult)
                if step_mult != menu_mult:
                    # Probably this shouldn't happen, but it currently does
                    log.warning("Got multiplicty %s from chain parts, but have %s legs. This is expected only for jet chains, but it has happened for %s, using the first chain dict", menu_mult, step_mult, self.name)
                    firstChainDict = listOfChainDictsLegs[0]
                    firstChainDict['chainName']= self.name # rename the chaindict to remove the leg name
                    for seq in step.sequences:
                        seq.createHypoTools( firstChainDict )

                else:
                    # add one hypotool per sequence and chain part
                    for seq, onePartChainDict in zip(step.sequences, listOfChainDictsLegs):
                        seq.createHypoTools( onePartChainDict )

           
            step.createComboHypoTools(self.name) 


    def __repr__(self):
        return "--- Chain %s --- \n + Seeds: %s \n + Steps: \n %s \n"%(\
                    self.name, ' '.join(map(str, self.L1decisions)), '\n '.join(map(str, self.steps)))




class CFSequence(object):
    """Class to describe the flow of decisions through ChainStep + filter with their connections (input, output)
    A Filter can have more than one input/output if used in different chains, so this class stores and manages all of them (when doing the connect)
    """
    def __init__(self, ChainStep, FilterAlg):
        self.filter = FilterAlg
        self.step = ChainStep
        if self.step.isCombo:
            self.connectCombo()
        self.setDecisions()
        log.debug("CFSequence.__init: created %s ",self)

    def setDecisions(self):
        """ Set the output decision of this CFSequence as the hypo outputdecision; In case of combo, takes the Combo outputs"""
        self.decisions=[]
        if not len(self.step.sequences):
            self.decisions.extend(self.filter.readOutputList())
        else:
            if self.step.isCombo:
                self.decisions.extend(self.step.combo.getOutputList())
            else:
                for sequence in self.step.sequences:
                    sequence_outputs=sequence.getOutputList()
                    for output in sequence_outputs:
                        self.decisions.append(output)

        log.debug("CFSequence: set out decisions: %s", self.decisions)


    def connect(self, connections):
        """Connect filter to ChainStep (and all its sequences) through these connections (which are sets of filter outputs)
        if a ChainStep contains the same sequence multiple times (for multi-object chains),
        the filter is connected only once (to avoid multiple DH links)
        """
        log.debug("CFSequence: connect Filter %s with %d menuSequences of step %s, using %d connections", self.filter.Alg.name(), len(self.step.sequences), self.step.name, len(connections))
        if len(connections) == 0:
            log.error("ERROR, no filter outputs are set!")
            #raise("CFSequence: Invalid Filter Configuration")

        if len(self.step.sequences):
            # check whether the number of filter outputs are the same as the number of sequences in the step
            if len(connections) != len(self.step.sequences):
                log.error("Found %d connections and %d MenuSequences in Step %s", len(connections), len(self.step.sequences), self.step.name)
                #raise("CFSequence: Invalid Filter Configuration")
            nseq=0
            for seq in self.step.sequences:
                filter_out = connections[nseq]
                log.debug("CFSequence: Found input %s to sequence::%s from Filter::%s (from seed %s)", filter_out, seq.name, self.filter.Alg.name(), seq.seed)
                seq.connectToFilter( filter_out )
                nseq+=1
        else:
          log.debug("This CFSequence has no sequences: outputs are the Filter outputs")



    def connectCombo(self):
        """ connect Combo to Hypos"""
        for seq in self.step.sequences:
            if type(seq.getOutputList()) is list:
               combo_input=seq.getOutputList()[-1] # last one?
            else:
               combo_input=seq.getOutputList()[0]
            self.step.combo.addInput(combo_input)
            log.debug("CFSequence.connectCombo: adding input to  %s: %s",  self.step.combo.Alg.name(), combo_input)
            # inputs are the output decisions of the hypos of the sequences
            combo_output=CFNaming.comboHypoOutputName (self.step.combo.Alg.name(), combo_input)
            self.step.combo.addOutput(combo_output)
            log.debug("CFSequence.connectCombo: adding output to  %s: %s",  self.step.combo.Alg.name(), combo_output)



    def __repr__(self):
        return "--- CFSequence ---\n + Filter: %s \n + decisions: %s\n +  %s \n"%(\
                    self.filter.Alg.name(), self.decisions, self.step)



class StepComponent(object):
    """ Class to build hte ChainStep, for including empty sequences"""
    def __init__(self, sequence, multiplicity,empty):
        self.sequence=sequence
        self.multiplicity=multiplicity
        self.empty=empty

class ChainStep(object):
    """Class to describe one step of a chain; if multiplicity is greater than 1, the step is combo/combined.  Set one multiplicity value per sequence"""
    def __init__(self, name,  Sequences=[], multiplicity=[1], chainDicts=[], comboToolConfs=[]):

        # sanity check on inputs
        if len(Sequences) != len(multiplicity):
            # empty steps have one entry in multiplicity
            if not (len(Sequences)==0 and len(multiplicity)==1):
                raise RuntimeError("Tried to configure a ChainStep %s with %i Sequences and %i multiplicities. These lists must have the same size" % (name, len(Sequences), len(multiplicity)) )

        self.name = name
        self.sequences=Sequences
        self.multiplicity = multiplicity
        self.comboToolConfs=comboToolConfs
        self.isCombo=sum(multiplicity)>1
        self.combo=None
        self.chainDicts = chainDicts
        if self.isCombo:
            self.makeCombo()


    def addCombHypoTools(self,  tools):
        self.comboToolConfs=tools
        self.combo.addComboHypoToolConfs(self.comboToolConfs)

    def makeCombo(self):
        if len(self.sequences)==0:
            return
        hashableMult = tuple(self.multiplicity)
        self.combo =  RecoFragmentsPool.retrieve(createComboAlg, None, name=CFNaming.comboHypoName(self.name), multiplicity=hashableMult)
        self.combo.addComboHypoToolConfs(self.comboToolConfs)

    def createComboHypoTools(self, chainName):
        if self.isCombo:
            from TriggerMenuMT.HLTMenuConfig.Menu.TriggerConfigHLT import TriggerConfigHLT
            chainDict = TriggerConfigHLT.getChainDictFromChainName(chainName)
            self.combo.createComboHypoTools(chainDict)
        
        


    def __repr__(self):
        return "--- ChainStep %s ---\n + isCombo = %d, multiplicity = %d \n + MenuSequences = %s  \n + ComboHypoTools = %s"%(self.name, self.isCombo,sum(self.multiplicity), ' '.join(map(str, [seq.name for seq in self.sequences]) ),  ' '.join(map(str, [tool for tool in self.comboToolConfs]) ))


def createComboAlg(dummyFlags, name, multiplicity):
    return ComboMaker(name, multiplicity)


# this is fragment for New JO


from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
class InEventReco( ComponentAccumulator ):
    """ Class to handle in-event reco """
    def __init__(self, name, inputMaker=None):
        super( InEventReco, self ).__init__()
        self.name = name
        from AthenaCommon.CFElements import parOR, seqAND
        self.mainSeq = seqAND( name )
        self.addSequence( self.mainSeq )

        # Details below to be checked
        self.inputMakerAlg = inputMaker

        # Avoid registering a duplicate
        self.addEventAlgo( self.inputMakerAlg, self.mainSeq.name() )
        self.recoSeq = parOR( "InputSeq_"+self.inputMakerAlg.name())
        self.addSequence( self.recoSeq, self.mainSeq.name() )
    pass

    def mergeReco( self, ca ):
        """ Merged CA movnig reconstruction algorithms into the right sequence """
        return self.merge( ca, sequenceName=self.recoSeq.getName() )

    def addRecoAlg( self, alg ):
        """Reconstruction alg to be run per event"""
        log.warning( "InViewReco.addRecoAlgo: consider using mergeReco that takes care of the CA accumulation and moving algorithms" )
        self.addEventAlgo( alg, self.recoSeq.name() )

    def addHypoAlg(self, alg):
        self.addEventAlgo( alg, self.mainSeq.name() )

    def sequence( self ):
        return self.mainSeq

    def inputMaker( self ):
        return self.inputMakerAlg



class InViewReco( ComponentAccumulator ):
    """ Class to handle in-view reco, sets up the View maker if not provided and exposes InputMaker so that more inputs to it can be added in the process of assembling the menu """
    def __init__(self, name, viewMaker=None):
        super( InViewReco, self ).__init__()
        self.name = name
        from AthenaCommon.CFElements import parOR, seqAND
        self.mainSeq = seqAND( name )
        self.addSequence( self.mainSeq )

        from ViewAlgs.ViewAlgsConf import EventViewCreatorAlgorithm, ViewCreatorInitialROITool
        if viewMaker:
            self.viewMakerAlg = viewMaker
        else:
            self.viewMakerAlg = EventViewCreatorAlgorithm("IM"+name,
                                                          ViewFallThrough = True,
                                                          RoIsLink        = 'initialRoI',
                                                          RoITool         = ViewCreatorInitialROITool(),
                                                          InViewRoIs      = name+'RoIs',
                                                          Views           = name+'Views',
                                                          ViewNodeName    = name+"InView")

        self.addEventAlgo( self.viewMakerAlg, self.mainSeq.name() )
        self.viewsSeq = parOR( self.viewMakerAlg.ViewNodeName )
        self.addSequence( self.viewsSeq, self.mainSeq.name() )

    def addInputFromFilter(self, filterAlg ):
        assert len(filterAlg.Output) == 1, "Can only oprate on filter algs with one configured output, use addInput to setup specific inputs"
        self.addInput( filterAlg.Output[0], "Reco_"+( filterAlg.Output[0].replace("Filtered_", "") ) )

    def addInput(self, inKey, outKey ):
        """Adds input (DecisionsContainer) from which the views should be created """
        self.viewMakerAlg.InputMakerInputDecisions += [ inKey ]
        self.viewMakerAlg.InputMakerOutputDecisions = outKey

    def mergeReco( self, ca ):
        """ Merged CA movnig reconstruction algorithms into the right sequence """
        return self.merge( ca, sequenceName=self.viewsSeq.getName() )

    def addRecoAlg( self, alg ):
        """Reconstruction alg to be run per view"""
        log.warning( "InViewReco.addRecoAlgo: consider using mergeReco that takes care of the CA accumulation and moving algorithms" )
        self.addEventAlgo( alg, self.viewsSeq.name() )

    def addHypoAlg(self, alg):
        self.addEventAlgo( alg, self.mainSeq.name() )

    def sequence( self ):
        return self.mainSeq

    def inputMaker( self ):
        return self.viewMakerAlg

#
class RecoFragmentsPool(object):
    """ Class to host all the reco fragments that need to be reused """
    fragments = {}
    @classmethod
    def retrieve( cls,  creator, flags, **kwargs ):
        """ create, or return created earlier reco fragment

        Reco fragment is uniquelly identified by the function and set og **kwargs.
        The flags are not part of unique identifier as creation of new reco fragments should not be caused by difference in the unrelated flags.
        TODO, if that code survives migration to New JO we need to handle the case when the creator is an inner function
        """
        requestHash = hash( ( creator, tuple(kwargs.keys()), tuple(kwargs.values()) ) )
        if requestHash not in cls.fragments:
            recoFragment = creator( flags, **kwargs )
            cls.fragments[requestHash] = recoFragment
            return recoFragment
        else:
            return cls.fragments[requestHash]


def getChainStepName(chainName, stepNumber):
    return '{}_step{}'.format(chainName, stepNumber)

def createStepView(stepName):
    stepReco = parOR(CFNaming.stepRecoName(stepName))
    stepView = seqAND(CFNaming.stepViewName(stepName), [stepReco])
    return stepReco, stepView