'''
Fourier analysis module
'''

import numpy as np
from collections import deque

class aswift():
    '''
    Alpha sliding windowed infinite fourier transform (aSWIFT). The aSWIFT represents the fourier
    transform at t=n as a function of the transform at t=n-1. The aSWIFT should be used
    whenever the window overlap is large compared to the window length.

    In order to normalize, the output should be divided by (taus-tauf).

    Parameters
    ----------
    tau_s : float (seconds)
        Slow time constant
    tau_f : float (seconds)
        Fast time constant
    f : float or array_like (Hz)
        Center frequency(ies) of transform
    fs : float (Hz)
        Sampling frequency

    See Also
    --------
    sdft
    swift
    '''

    def __init__(self, tau_s, tau_f, f, fs):
        self.__tau_s = tau_s
        self.__tau_f = tau_f
        self.__f = f
        self.__fs = fs
              
        self.__slow = swift(self.tau_s,self.f,self.fs)
        self.__fast = swift(self.tau_f,self.f,self.fs)

    def slide(self, x):
        '''
        Slide forward N samples, where N is the length of x.

        Parameters
        ----------
        x : float or array_like
            Sample or array of samples

        Returns
        -------
        Xf : complex
            Numpy.array of complex numbers at frequencies f
        '''
        return self.slow.slide(x) - self.fast.slide(x)
        
    @property
    def tau_s(self):
        '''float : tau slow (s)'''
        return self.__tau_s
    @property
    def tau_f(self):
        '''float : tau fast (s)'''
        return self.__tau_f
    @property
    def f(self):
        '''
        float : center frequency (Hz)
        '''
        return self.__f
    @property
    def fs(self):
        '''float : sampling frequency (Hz)'''
        return self.__fs
    @property
    def slow(self):
        return self.__slow
    @property
    def fast(self):
        return self.__fast
    
class swift():
    '''
    Sliding windowed infinite fourier transform (SWIFT). The SWIFT represents the fourier
    transform at t=n as a function of the transform at t=n-1. The SWIFT should be used
    whenever the window overlap is large compared to the window length.

    In order to normalize, the output should be divided by tau.

    Parameters
    ----------
    tau : float or array_like (seconds)
        Exponential window time constant(s)
    f : float or array_like (Hz)
        Center frequency(ies) of transform
    fs : float (Hz)
        Sampling frequency
    
    See Also
    --------
    aswift
    sdft
    '''

    def __init__(self,tau,f,fs):
        tau,f,fs = self.__paramcheck(tau,f,fs)
        
        self.__tau  = tau
        self.__f    = f
        self.__fs   = fs
        self.__ntau = self.tau*self.fs
        
        self.Xf = np.zeros(len(self.f),dtype=complex)
        
        self.e = np.exp(2j*np.pi*self.f/self.fs)*np.exp(-1./self.ntau)

    def slide(self,x):
        '''
        Slide forward N samples, where N is the length of x.

        Parameters
        ----------
        x : float or array_like
            Sample or array of samples

        Returns
        -------
        Xf : complex
            Numpy.array of complex numbers at frequencies f
        '''
        x = np.asarray(x).reshape(-1)
        for i in range(len(x)):
            self.Xf = self.e*self.Xf + x[i]
        return self.Xf

    def __paramcheck(self,tau,f,fs):
        '''
        Ensures all parameters are the correct type and in range.
        '''
        try: tau = np.asarray(tau).reshape(-1)
        except: raise TypeError('data type not understood. tau must be array_like')
        try: f = np.asarray(f).reshape(-1)
        except: raise TypeError('data type not understood. f must be array_like')
        try: fs = float(fs)
        except: raise TypeError('data type not understood. fs must be float')

        if fs <= 0: raise ValueError('fs must be > 0')
        if np.any(tau <= 0): raise ValueError('tau must be > 0')
        if np.any(f <= 0): raise ValueError('f must be > 0')
        
        return tau,f,fs

    @property
    def tau(self):
        return self.__tau
    @property
    def ntau(self):
        return self.__ntau
    @property
    def f(self):
        return self.__f
    @property
    def fs(self):
        return self.__fs