"""
Noise sources.

Robert A McDougal, 2011
"""

from tokenizer import parse_string

class Noise(object):
    """Noise parent class. All noise sources are derived from here."""
    def _get_val(self, var):
        """No changable parameters."""
        return None

    def _compute_firing_times_until(self, thi):
        t = max(self._firing_times)
        while t <= thi:
            if hasattr(self._firing_rate, '__call__'):
                t += self._firing_rate(self._simulation)
            else:
                t += self._simulation.random.expovariate(self._firing_rate / 1000.)
            self._firing_times.append(t)

    def get_times_and_tokens(self, tlo, thi):
        """Returns a list of start times and tokens for noise over an interval.
        
        Parameters:
            tlo -- start time of interval
            thi -- stop time of interval
        """
        self._compute_firing_times_until(thi)
        return self._get_times_and_tokens(tlo, thi)
        


class ExponentiallyDecayingNoise(Noise):
    def __init__(self, simulation, firing_rate = 5, decay_rate = 2, delay = 0, max_value = 1):
        """
        firing_rate -- a number (in Hz) or a function that returns the delay
                       between events when given the simulation
        """
        firing_rate = simulation.value(firing_rate)
        decay_rate = simulation.value(decay_rate)
        delay = simulation.value(delay)
        max_value = simulation.value(max_value)
        self._firing_rate = firing_rate
        self._decay_rate = decay_rate
        self._simulation = simulation
        self._firing_times = [-(simulation.random.uniform(0, 1000) + decay_rate * 40 + delay)]
        self._delay = delay
        self._max_value = max_value
        if delay < 0:
            raise Exception('delay must be >= 0 for ExponentiallyDecayingNoise')

    def __repr__(self):
        """Return a string representing the contents."""
        return 'ExponentiallyDecayingNoise(sim, firing_rate = %r, decay_rate = %g, delay = %g, max_value = %g)' % (self._firing_rate, self._decay_rate, self._delay, self._max_value)

    def _get_times_and_tokens(self, tlo, thi):
        result = []
        
        # adjust tlo to include previous output that is still relevant
        # e^(-40) = 4.25e-18, so any activity more than 40 * decay_rate ago
        # has negligible effect
        start_time = tlo
        tlo = tlo - 40 * self._decay_rate - self._delay
        
        for i, t in enumerate(self._firing_times):
            if t >= tlo and t <= thi:
                t_next = self._firing_times[i + 1]
                if t_next >= start_time:
                    form = 'exp((%g - t) / %g)' % (t + self._delay, self._decay_rate)
                    if self._max_value != 1:
                        form = '%g * %s' % (self._max_value, form)
                    if t_next <= thi:
                        form = '%s * heav(%g - t)' % (form, t_next)
                    result.append({'t': t + self._delay, 'tokens': parse_string(form)})
                    if self._delay != 0:
                        form = 'heav(%g - t)' % (min(t + self._delay, t_next))
                        if self._max_value != 1:
                            form = '%g * %s' % (self._max_value, form)
                        result.append({'t': t, 'tokens': parse_string(form)})
        
        return result




class SquareWaveNoise(Noise):
    def __init__(self, simulation, firing_rate = 5, duration = 1, max_value = 1):
        """
        firing_rate -- a number (in Hz) or a function that returns the delay
                       between events when given the simulation
        """
        self._firing_rate = simulation.value(firing_rate)
        self._duration = simulation.value(duration)
        self._simulation = simulation
        self._firing_times = [-(simulation.random.uniform(0, 1000) + duration)]
        self._max_value = simulation.value(max_value)

    def __repr__(self):
        """Return a string representing the contents."""
        return ('SquareWaveNoise(sim, firing_rate = %r, duration = %g, max_value = %g)'
            % (self._firing_rate, self._duration, self._max_value))

    def _get_times_and_tokens(self, tlo, thi):
        result = []
        
        # adjust tlo to include previous output that is still relevant
        start_time = tlo
        tlo = tlo - self._duration
        
        for i, t in enumerate(self._firing_times):
            if t >= tlo and t <= thi:
                t_next = self._firing_times[i + 1]
                if t_next >= start_time:
                    form = 'heav(%g - t)' % min(t + self._duration, t_next)
                    if self._max_value != 1:
                        form = '%g * %s' % (self._max_value, form)
                    result.append({'t': t, 'tokens': parse_string(form)})

        return result


###########################################################
#
# Firing Rate Classes
#
###########################################################

class Periodic(object):
    """Periodic Firing."""
    
    def __init__(self, freq):
        self._freq = freq
        self._delay = 1000. / freq
    
    def __call__(self, sim):
        return self._delay
    
    def __repr__(self):
        return 'Periodic(%g)' % self._freq
