"""Generic storage for a set of dynamics.

Robert A McDougal, 2011
"""

from tokenizer import parse_string
import copy

def _collapse_probabilities(d, sim):
    for var in d:
        rhs = d[var]
        for i, t in enumerate(rhs):
            old = t
            rhs[i] = t.value(sim.random)


class Dynamics(object):
    def __init__(self, algebraic, differential, diff_list, functions, simulation,
                 name, potential_var=None, container=None, index=None, concentrations=[]):
        self.differential = differential
        self.diff_list = diff_list
        self.simulation = simulation
        self._name = name
        self._potential_var = potential_var
        self._verify_defined = True
        self.functions = functions
        self.algebraic = copy.deepcopy(algebraic)
        self.concentrations = copy.deepcopy(concentrations)
        
        self.index = index
        
        # container must be the id # of the parent dynamics
        self._container = container
        self._compartments = {}

        # fix specific values for probablistic numbers
        _collapse_probabilities(self.algebraic, simulation)
        _collapse_probabilities(self.differential, simulation)
    
    def __setattr__(self, name, value):
        """Set parameters using dot notation."""
        if name in ('differential', 'diff_list', 'simulation', 'algebraic', '_name', '_potential_var', 'functions', '_verify_defined', '_container', 'concentrations', 'index', '_compartments'):
            object.__setattr__(self, name, value)
        else:
            self.set(name, value)
    
    def __dir__(self):
        return ['set', '_get_val', 'differential', 'diff_list', 'simulation',
                '_name', '_potential_var', 'algebraic', 'functions'] + self.diff_list + dir(object)
    
    def __getattr__(self, var):
        """Get parameter list."""
        if var in ('differential', 'diff_list', 'simulation', '_name', '_potential_var', 'algebraic', 'functions', '_container', 'index', '_compartments'):
            return object.__getattr__(self, var)
        return self._get_val(var)
            
    def _get_val(self, var):
        if var not in self.algebraic:
            if var[ : 9] == 'external_' and self._container is not None:
                return self.simulation._neurons[self._container]._get_val(var)
            raise AttributeError(var)
        val = self.algebraic[var]
        try:
            if len(val) == 1:
                return float(val[0])
            if len(val) == 2 and val[1].content == '-':
                return -float(val[0])
        except:
            raise Exception(var + ' is non-constant.')
        raise Exception('Syntax error parsing parameter')
    
    def add_compartment(self, kind, compartment_id):
        """Add a compartment.
        
        Parameters:
            kind -- a string identifying the type of compartment
            compartment_id -- the id (in the simulation) of the compartment's Dynamics
        """
        if self.index is None:
            raise Exception('global dynamics cannot have compartments')
        if kind in self._compartments:
            self._compartments[kind].append(compartment_id)
        else:
            self._compartments[kind] = [compartment_id]
        self.simulation._neurons[compartment_id]._container = self.index
    
    def set(self, var, val=None, std_dev_frac=None, uniform=None):
        if (val is None) and (std_dev_frac is None) and (uniform is None):
            raise Exception('Nothing to set ' + var + ' to')
        if std_dev_frac is None:
            self.algebraic[var] = parse_string(val)
        elif uniform is None and std_dev_frac is not None:
            if val is None:
                if var in self.algebraic:
                    val = self.get_val(var)
                self.algebraic[var] = parse_string(str(self.simulation.random.gauss(val, val * std_dev_frac)))
        elif std_dev_frac is None and uniform is not None and val is None:
            self.algebraic[var] = parse_string(self.simulation.random.uniform(uniform))
        else:
            raise Exception('Overdefined set request')
        # fix specific values for probablistic numbers
        _collapse_probabilities(self.algebraic, self.simulation)
    
