from director.fieldcontainer import FieldContainer
from director.thirdparty.toposort import toposort_flatten


class ComponentGraph(object):

    def __init__(self):
        self._graph = dict()

    def getComponentGraph(self):
        return self._graph

    def getComponentNames(self):
        return self._graph.keys()

    def getComponentDependencies(self, componentName):
        componentGraph = self.getComponentGraph()
        assert componentName in componentGraph
        allDeps = set()
        toProcess = [componentName]

        while len(toProcess):
            name = toProcess.pop(0)
            deps = componentGraph[name]
            toProcess.extend(list(deps))
            allDeps = allDeps.union(deps)

        return list(allDeps)

    def printComponentGraph(self):

        componentGraph = self.getComponentGraph()

        for name in sorted(componentGraph.keys()):
            deps = self.getComponentDependencies(name)
            wants = componentGraph[name]
            needs = list(set(deps).difference(wants))
            print name
            print '       wants -->', ', '.join(wants) or 'none'
            print '  also needs -->', ', '.join(needs) or 'none'

    def addComponent(self, name, deps):
        self._graph[name] = set(deps)


class ComponentFactory(object):

    def __init__(self):
        self.componentGraph = ComponentGraph()
        self.addComponents(self.componentGraph)

    def addComponents(self, componentGraph):
        pass

    def initDefaultOptions(self, options):
        pass

    def getDisabledOptions(self):
        options = self.getDefaultOptions()
        for opt in options._fields:
            setattr(options, opt, False)
        return options

    def getDefaultOptions(self):

        options = dict()
        for name in self.componentGraph.getComponentNames():
            options['use'+name] = True

        options = FieldContainer(**options)
        self.initDefaultOptions(options)
        return options

    def _toComponentName(self, optionName):
        assert optionName[:3] == 'use'
        return optionName[3:]

    def _toOptionName(self, componentName):
        return 'use' + componentName

    def setDependentOptions(self, options, **kwargs):

        # verify the given args exist in the options fields
        for name in kwargs.keys():
            if name not in options._fields:
                raise Exception('unknown option given: ' + name)

        for name, enabled in kwargs.iteritems():
            setattr(options, name, enabled)

            # if option is being enabled, also enable its dependencies
            if enabled:
                name = self._toComponentName(name)
                dependencies = self.componentGraph.getComponentDependencies(name)
                for dep in dependencies:
                    setattr(options, self._toOptionName(dep), True)

    def _verifyOptions(self, options):

        for name, enabled in options:

            if enabled:
                name = self._toComponentName(name)
                dependencies = self.componentGraph.getComponentDependencies(name)
                for dep in dependencies:
                    if not getattr(options, self._toOptionName(dep)):
                        raise Exception('Component %s depends on component %s, but %s is disabled.' % (name, dep, dep))


    def construct(self, options=None, **kwargs):

        options = options or self.getDefaultOptions()
        self._verifyOptions(options)

        componentGraph = self.componentGraph

        for name in componentGraph.getComponentNames():
            if 'use'+name not in options.__dict__:
                raise Exception('Missing use option: ' + 'use'+name)
            if 'init'+name not in dir(self):
                raise Exception('Missing init function: ' + 'init'+name)

        initOrder = toposort_flatten(componentGraph._graph)

        for name in initOrder:

            initFunction = getattr(self, 'init'+name)
            isEnabled = getattr(options, 'use'+name)

            if isEnabled:
                initFunction(fields)

        return fields

@@ -1,20 +1,56 @@
+from collections import OrderedDict%0A
 from director.fieldc
@@ -80,16 +80,16 @@
 ntainer%0A
-
 from dir
@@ -2221,16 +2221,178 @@
 ntName%0A%0A
+    def _joinFields(self, fieldsList):%0A        f = FieldContainer()%0A        for fields in fieldsList:%0A            f._add_fields(**dict(fields))%0A        return f%0A%0A
     def 
@@ -4084,24 +4084,131 @@
 ph._graph)%0A%0A
+        componentFields = OrderedDict()%0A        defaultFields = FieldContainer(options=options, **kwargs)%0A%0A
         for 
@@ -4381,29 +4381,650 @@
     
-initFunction(fields)%0A
+dependencies = componentGraph.getComponentDependencies(name)%0A                inputFields = self._joinFields(%5BdefaultFields%5D + %5BcomponentFields%5Bdep%5D for dep in dependencies%5D)%0A                newFields = initFunction(inputFields)%0A%0A                if not newFields:%0A                    newFields = FieldContainer()%0A                componentFields%5Bname%5D = newFields%0A%0A%0A#        for componentName, componentFields in app.iteritems():%0A#            print componentName, 'exports fields:'%0A#            for name in componentFields._fields:%0A#                print '  ', name%0A%0A        fields = self._joinFields(%5BdefaultFields%5D + componentFields.values())
 %0A   

change behavior of how input fields are passed to init functions