__all__ = ["ParameterSystem", "SystemSolver"]

from .. import functions as fn
from .parameterTypes import GroupParameter
from .SystemSolver import SystemSolver


class ParameterSystem(GroupParameter):
    """
    ParameterSystem is a subclass of GroupParameter that manages a tree of 
    sub-parameters with a set of interdependencies--changing any one parameter
    may affect other parameters in the system.
    
    See parametertree/SystemSolver for more information.
    
    NOTE: This API is experimental and may change substantially across minor 
    version numbers. 
    """
    def __init__(self, *args, **kwds):
        GroupParameter.__init__(self, *args, **kwds)
        self._system = None
        self._fixParams = []  # all auto-generated 'fixed' params
        sys = kwds.pop('system', None)
        if sys is not None:
            self.setSystem(sys)
        self._ignoreChange = [] # params whose changes should be ignored temporarily
        self.sigTreeStateChanged.connect(self.updateSystem)
        
    def setSystem(self, sys):
        self._system = sys
        
        # auto-generate defaults to match child parameters
        defaults = {}
        vals = {}
        for param in self:
            name = param.name()
            constraints = ''
            if hasattr(sys, '_' + name):
                constraints += 'n'
                
            if not param.readonly():
                constraints += 'f'
                if 'n' in constraints:
                    ch = param.addChild(dict(name='fixed', type='bool', value=False))
                    self._fixParams.append(ch)
                    param.setReadonly(True)
                    param.setOpts(expanded=False)
                else:
                    vals[name] = param.value()
                    ch = param.addChild(dict(name='fixed', type='bool', value=True, readonly=True))
                    #self._fixParams.append(ch)
                
            defaults[name] = [None, param.type(), None, constraints]
        
        sys.defaultState.update(defaults)
        sys.reset()
        for name, value in vals.items():
            setattr(sys, name, value)
        
        self.updateAllParams()
    
    def updateSystem(self, param, changes):
        changes = [ch for ch in changes if ch[0] not in self._ignoreChange]
        
        sets = [ch[0] for ch in changes if ch[1] == 'value']

        for param in sets:
            if param in self._fixParams:
                parent = param.parent()
                setattr(self._system, parent.name(), parent.value() if parent.hasValue() else None)
            else:
                setattr(self._system, param.name(), param.value())
            
        self.updateAllParams()
    
    def updateAllParams(self):
        try:
            self.sigTreeStateChanged.disconnect(self.updateSystem)
            for name, state in self._system._vars.items():
                param = self.child(name)
                try:
                    v = getattr(self._system, name)
                    if self._system._vars[name][2] is None:
                        self.updateParamState(self.child(name), 'autoSet')
                        param.setValue(v)
                    else:
                        self.updateParamState(self.child(name), 'fixed')
                except RuntimeError:
                    self.updateParamState(param, 'autoUnset')
        finally:
            self.sigTreeStateChanged.connect(self.updateSystem)
                
    def updateParamState(self, param, state):
        if state == 'autoSet':
            bg = fn.mkBrush((200, 255, 200, 255))
            bold = False
            readonly = True
        elif state == 'autoUnset':
            bg = fn.mkBrush(None)
            bold = False
            readonly = False
        elif state == 'fixed':
            bg = fn.mkBrush('y')
            bold = True
            readonly = False
        else:
            raise ValueError("'state' must be one of 'autoSet', 'autoUnset', or 'fixed'")
            
        param.setReadonly(readonly)
        
        #for item in param.items:
            #item.setBackground(0, bg)
            #f = item.font(0)
            #f.setWeight(f.Bold if bold else f.Normal)
            #item.setFont(0, f)
            
            
            
