""" **Description**
        A complex band pass filter produces a complex exponential incident
        signal at a specified normalized frequency and adapts a forward complex
        coefficient to produce a reference signal, which estimates a component
        of interest in a primary signal.  A normalized frequency and rate of
        adaptation are specified.

        .. math::
            x_{n} = e^{\ j\ \pi\ \phi_{n}}

        .. math::
            \phi_{n+1} = \phi_{n} + f_{n}

        .. math::
            y_{n} = b_{n} x_{n}

        .. math::
            e_{n} = d_{n} - y_{n}

        .. math::
            b_{n+1} = b_{n} + \mu e_{n} x_{n}^{*}

    **Example**
        
        ::
        
            from diamondback import ComplexBandPassFilter, ComplexExponentialFilter
            import numpy

            frequency = 0.1
            x = numpy.linspace( -1.0e-4, 1.0e-4, 128 ) + frequency

            # Create a primary signal.

            d = ComplexExponentialFilter( phase = numpy.random.rand( 1 )[ 0 ] * 2.0 - 1.0 ).filter( x )

            # Create an instance.

            obj = ComplexBandPassFilter( frequency = frequency, rate = 5.0e-2 )

            # Filter a primary signal.

            obj.reset( d[ 0 ] )
            y, e, b = obj.filter( d )

    **License**
        `BSD-3C.  <https://github.com/larryturner/diamondback/blob/master/license>`_
        © 2018 - 2021 Larry Turner, Schneider Electric Industries SAS. All rights reserved.

    **Author**
        Larry Turner, Schneider Electric, Analytics & AI, 2018-01-31.
"""

from diamondback.filters.ComplexExponentialFilter import ComplexExponentialFilter
from diamondback.filters.FirFilter import FirFilter
from diamondback.interfaces.IFrequency import IFrequency
from diamondback.interfaces.IRate import IRate
from typing import List, Tuple, Union
import numpy
import scipy

class ComplexBandPassFilter( FirFilter, IFrequency, IRate ) :

    """ Complex band pass filter.
    """

    def __init__( self, frequency : float, rate : float ) -> None :

        """ Initialize.

            Arguments :
                frequency : float - relative to Nyquist in [ -1.0, 1.0 ).
                rate : float - in [ 0.0, 1.0 ].
        """

        if ( ( rate < 0.0 ) or ( rate > 1.0 ) ) :
            raise ValueError( f'Rate = {rate}' )
        super( ).__init__( b = numpy.array( [ numpy.finfo( float ).eps + 0j ] ), s = numpy.zeros( 1, complex ) )
        self._complexexponentialfilter = ComplexExponentialFilter( )
        self.frequency, self.rate = frequency, rate

    def filter( self, d : Union[ List, numpy.ndarray ] ) -> Tuple[ numpy.ndarray, numpy.ndarray, numpy.ndarray ] :

        """ Filters a primary signal and produces a reference signal.

            Signals are Hilbert transformed to complex as necessary.

            Arguments :
                d : Union[ List, numpy.ndarray ] - primary signal.

            Returns :
                y : numpy.ndarray - reference signal.
                e : numpy.ndarray - error signal.
                b : numpy.ndarray - forward coefficient.
        """

        if ( ( not numpy.isscalar( d ) ) and ( not isinstance( d, numpy.ndarray ) ) ) :
            d = numpy.array( list( d ) )
        if ( not len( d ) ) :
            raise ValueError( f'D = {d}' )
        if ( not numpy.iscomplex( d ).any( ) ) :
            d = scipy.signal.hilbert( d )
        x = self._complexexponentialfilter.filter( numpy.ones( len( d ) ) * self.frequency )
        y, e, b = numpy.zeros( len( x ), complex ), numpy.zeros( len( x ), complex ), numpy.zeros( len( x ), complex )
        for ii in range( 0, len( x ) ) :
            y[ ii ] = x[ ii ] * self.b[ 0 ]
            e[ ii ] = d[ ii ] - y[ ii ]
            b[ ii ] = self.b[ 0 ]
            self.b[ 0 ] += self.rate * e[ ii ] * numpy.conjugate( x[ ii ] )
        return y, e, b
