import numpy as np
import scipy.interpolate as spinterp

from . import fitfuns
from .kernel import Kernel1D


class TemporalFilter(object):
    def __init__(self):
        self.t_support = []
        self.kernel_data = None

    def imshow(self, t_range=None, threshold=0, reverse=False, rescale=False, **kwargs):
        return self.get_kernel(t_range, threshold, reverse, rescale).imshow(**kwargs)
        
    def to_dict(self):
        return {'class': (__name__, self.__class__.__name__)}

    def get_default_t_grid(self):
        raise NotImplementedError()

    def get_kernel(self, t_range=None, threshold=0, reverse=False, rescale=False):
        if t_range is None:
            t_range = self.get_default_t_grid() 
        
        if len(self.t_support) == 1:
            k = Kernel1D(self.t_support, self.kernel_data, threshold=threshold, reverse=reverse)

        else:
            interpolation_function = spinterp.interp1d(self.t_support, self.kernel_data, fill_value=0,
                                                       bounds_error=False, assume_sorted=True)
            k = Kernel1D(t_range, interpolation_function(t_range), threshold=threshold, reverse=reverse)

        if rescale:
            k.rescale()
            assert(np.abs(np.abs(k.kernel.sum()) - 1) < 1e-14)
        
        return k


class ArrayTemporalFilter(TemporalFilter):
    def __init__(self, mask, t_support):
        super(ArrayTemporalFilter, self).__init__()
        self.mask = mask
        self.t_support = t_support
        assert(len(self.mask) == len(self.t_support))
        self.nkt = 600
        self.kernel_data = self.mask

    def get_default_t_grid(self):
        return np.arange(self.nkt)*0.001


class TemporalFilterCosineBump(TemporalFilter):
    def __init__(self, weights, kpeaks, delays):
        """Creates a time-based filter function by combining two cosine-based peaks into a function for convoluting
        the input with.

        :param weights: (float, float)the magntiude of both peaks, first weight must be positive
        :param kpeaks: (float, float) the spread of each peak, first peak (0) must be sharper
        :param delays: (float, float) the delay of both peaks, peak 0 must be positive occur first.
        """

        assert(len(kpeaks) == 2)
        assert(kpeaks[0] < kpeaks[1])
        assert(weights[0] > 0)
        assert(delays[0] <= delays[1])

        super(TemporalFilterCosineBump, self).__init__()
        self.ncos = len(weights)

        # Not likely to change defaults:
        self.neye = 0
        self.b = .3
        self.nkt = 600

        # Parameters
        self.weights = np.array([weights]).T
        self.kpeaks = kpeaks
        self.delays = np.array([delays]).astype(int)

        # Create two peak arrays (arr0 and arr1) using makeBasisStimKernel. Then merge them using dot product
        # w0*arr0 + w1*arr1.
        kbasprs = {
            'neye': self.neye,
            'ncos': self.ncos,
            'kpeaks': self.kpeaks,
            'b': self.b,
            'delays': self.delays
        }
        nkt = self.nkt
        self.kernel_data = np.dot(fitfuns.makeBasis_StimKernel(kbasprs, nkt), self.weights)[::-1].T[0]

        self.t_support = np.arange(0, len(self.kernel_data)*.001, .001)
        self.kbasprs = kbasprs
        assert len(self.t_support) == len(self.kernel_data)
        
    def __call__(self, t):
        return self.interpolation_function(t)
        
    def get_default_t_grid(self):
        return np.arange(self.nkt)*.001
    
    def to_dict(self):
        param_dict = super(TemporalFilterCosineBump, self).to_dict()
        param_dict.update({'weights': self.weights.tolist(), 'kpeaks': self.kpeaks})
        return param_dict

import numpy as np
import scipy.interpolate as spinterp

from . import fitfuns
from .kernel import Kernel1D


class TemporalFilter(object):
    def __init__(self):
        self.t_support = []
        self.kernel_data = None

    def imshow(self, t_range=None, threshold=0, reverse=False, rescale=False, **kwargs):
        return self.get_kernel(t_range, threshold, reverse, rescale).imshow(**kwargs)
        
    def to_dict(self):
        return {'class': (__name__, self.__class__.__name__)}

    def get_default_t_grid(self):
        raise NotImplementedError()

    def get_kernel(self, t_range=None, threshold=0, reverse=False, rescale=False):
        if t_range is None:
            t_range = self.get_default_t_grid() 
        
        if len(self.t_support) == 1:
            k = Kernel1D(self.t_support, self.kernel_data, threshold=threshold, reverse=reverse)

        else:
            interpolation_function = spinterp.interp1d(self.t_support, self.kernel_data, fill_value=0,
                                                       bounds_error=False, assume_sorted=True)
            k = Kernel1D(t_range, interpolation_function(t_range), threshold=threshold, reverse=reverse)

        if rescale:
            k.rescale()
            assert(np.abs(np.abs(k.kernel.sum()) - 1) < 1e-14)
        
        return k


class ArrayTemporalFilter(TemporalFilter):
    def __init__(self, mask, t_support):
        super(ArrayTemporalFilter, self).__init__()
        self.mask = mask
        self.t_support = t_support
        assert(len(self.mask) == len(self.t_support))
        self.nkt = 600
        self.kernel_data = self.mask

    def get_default_t_grid(self):
        return np.arange(self.nkt)*0.001


class TemporalFilterCosineBump(TemporalFilter):
    def __init__(self, weights, kpeaks, delays):
        """Creates a time-based filter function by combining two cosine-based peaks into a function for convoluting
        the input with.

        :param weights: (float, float)the magntiude of both peaks, first weight must be positive
        :param kpeaks: (float, float) the spread of each peak, first peak (0) must be sharper
        :param delays: (float, float) the delay of both peaks, peak 0 must be positive occur first.
        """

        assert(len(kpeaks) == 2)
        assert(kpeaks[0] < kpeaks[1])
        assert(weights[0] > 0)
        assert(delays[0] <= delays[1])

        super(TemporalFilterCosineBump, self).__init__()
        self.ncos = len(weights)

        # Not likely to change defaults:
        self.neye = 0
        self.b = .3
        self.nkt = 600

        # Parameters
        self.weights = np.array([weights]).T
        self.kpeaks = kpeaks
        self.delays = np.array([delays]).astype(int)

        # Create two peak arrays (arr0 and arr1) using makeBasisStimKernel. Then merge them using dot product
        # w0*arr0 + w1*arr1.
        kbasprs = {
            'neye': self.neye,
            'ncos': self.ncos,
            'kpeaks': self.kpeaks,
            'b': self.b,
            'delays': self.delays
        }
        nkt = self.nkt
        self.kernel_data = np.dot(fitfuns.makeBasis_StimKernel(kbasprs, nkt), self.weights)[::-1].T[0]

        self.t_support = np.arange(0, len(self.kernel_data)*.001, .001)
        self.kbasprs = kbasprs
        assert len(self.t_support) == len(self.kernel_data)
        
    def __call__(self, t):
        return self.interpolation_function(t)
        
    def get_default_t_grid(self):
        return np.arange(self.nkt)*.001
    
    def to_dict(self):
        param_dict = super(TemporalFilterCosineBump, self).to_dict()
        param_dict.update({'weights': self.weights.tolist(), 'kpeaks': self.kpeaks})
        return param_dict

Creates a time-based filter function by combining two cosine-based peaks into a function for convoluting         the input with.          :param weights: (float, float)the magntiude of both peaks, first weight must be positive         :param kpeaks: (float, float) the spread of each peak, first peak (0) must be sharper         :param delays: (float, float) the delay of both peaks, peak 0 must be positive occur first. # Not likely to change defaults: # Parameters # Create two peak arrays (arr0 and arr1) using makeBasisStimKernel. Then merge them using dot product # w0*arr0 + w1*arr1.