"""
Synapses.

Robert A McDougal, 2011
"""

from tokenizer import parse_string
from errors import SimulationFailedError

class Synapse(object):
    """Synapse parent class; all synapses are derived from here."""
    def __init__(self, gsyn, presynaptic_var=None, delay=None):
        self._gsyn = str(gsyn)
        self._presynaptic_var = presynaptic_var
        if delay:
            self._delay = str(delay)
        else:
            self._delay = None
    
    def has_delay(self):
        """Test for synaptic delays."""
        return self._delay not in (0, '0', '0.0', '0.', '.0', None)

class IonSynapse(Synapse):
    def __init__(self, gsyn, vsyn, presynaptic_var=None, delay=None):
        """Define an ionotropic synapse.
        
        Synaptic current has the form:
            presynaptic_var * gsyn * (v - vsyn)
        
        presynaptic_var must be defined unless the synapse is from noise.
        """
        Synapse.__init__(self, gsyn, presynaptic_var, delay)
        self._vsyn = vsyn

    
    def __repr__(self):
        """Return a string representing the contents."""
        if self._presynaptic_var is not None:
            if self._delay is None:
                return 'IonSynapse(gsyn=%r, vsyn=%r, presynaptic_var=%r)' % (self._gsyn, self._vsyn, self._presynaptic_var)
            else:
                return 'IonSynapse(gsyn=%r, vsyn=%r, presynaptic_var=%r, delay=%r)' % (self._gsyn, self._vsyn, self._presynaptic_var, self._delay)

        else:
            if self._delay is None:
                return 'IonSynapse(gsyn=%r, vsyn=%r)' % (self._gsyn, self._vsyn)
            else:
                return 'IonSynapse(gsyn=%r, vsyn=%r, delay=%r)' % (self._gsyn, self._vsyn, self._delay)


    def get_tokens(self, presynaptic_ids, postsynaptic_id, potential_var, syn_ids=None):
        """Return a list of parsed tokens corresponding to synaptic input.
        
        Parameters:
            presynaptic_ids -- list of id numbers of the presynaptic noise sources
            postsynaptic_id -- the id of the receiving cell
            potential_var -- the potential variable for the receiving cell
            syn_ids -- the indices of the synapses (used for delays)
        """
        pre_vars = []
        if syn_ids is None:
            syn_ids = [None] * len(presynaptic_ids)
        for presynaptic_id, syn_id in zip(presynaptic_ids, syn_ids):
            if presynaptic_id >= 0 and self._presynaptic_var is None:
                raise SimulationFailedError('synapse with gsyn = ' + str(self._gsyn) + ', vsyn = ' + str(self._vsyn), 'must specify presynaptic_var unless noise input')
            if self.has_delay():
                pre_vars.append('delay%d' % syn_id)
            elif presynaptic_id >= 0:
                pre_vars.append(self._presynaptic_var + str(presynaptic_id))
            else:
                pre_vars.append('noise%d' % (-presynaptic_id))
        sum_presynaptics = '+'.join(pre_vars)
        return parse_string('(' + sum_presynaptics + ')*(' + str(self._gsyn) + ')*('
                            + potential_var + '-(' + str(self._vsyn) + ')) / capacitance')



class AppliedCurrent(Synapse):
    def __init__(self, gsyn=1, presynaptic_var=None, delay=None):
        """Define an applied current.
        
        Synaptic current has the form:
            - presynaptic_var * gsyn
        
        presynaptic_var must be defined unless the synapse is from noise.
        """
        Synapse.__init__(self, gsyn, presynaptic_var, delay)
    
    def __repr__(self):
        """Return a string representing the contents."""
        if self._presynaptic_var is not None:
            if self._delay is None:
                return 'AppliedCurrent(gsyn=%r, presynaptic_var=%r)' % (self._gsyn, self._presynaptic_var)
            else:
                return 'AppliedCurrent(gsyn=%r, presynaptic_var=%r, delay=%r)' % (self._gsyn, self._presynaptic_var, self._delay)

        else:
            if self._delay is None:
                return 'AppliedCurrent(gsyn=%r)' % self._gsyn
            else:
                return 'AppliedCurrent(gsyn=%r, delay=%r)' % (self._gsyn, self._delay)
                


    def get_tokens(self, presynaptic_ids, postsynaptic_id, potential_var, syn_ids=None):
        """Return a list of parsed tokens corresponding to synaptic input.
        
        Parameters:
            presynaptic_ids -- list of id numbers of the presynaptic noise sources
            postsynaptic_id -- the id of the receiving cell
            potential_var -- the potential variable for the receiving cell
            syn_ids -- the indices of the synapses (used for delays)
        """
        pre_vars = []
        if syn_ids is None:
            syn_ids = [None] * len(presynaptic_ids)
        for presynaptic_id, syn_id in zip(presynaptic_ids, syn_ids):
            if presynaptic_id >= 0 and self._presynaptic_var is None:
                raise SimulationFailedError('AppliedCurrent with gsyn = ' + str(self._gsyn) + 'must specify presynaptic_var unless noise input')

            if self.has_delay():
                pre_vars.append('delay%d' % syn_id)
            elif presynaptic_id >= 0:
                pre_vars.append(self._presynaptic_var + str(presynaptic_id))
            else:
                pre_vars.append('noise%d' % (-presynaptic_id))
        sum_presynaptics = '+'.join(pre_vars)
        return parse_string('- (' + sum_presynaptics + ')*(' + str(self._gsyn) + ') / capacitance')

