using Base.Test
import Multirate
import DSP
import Multirate.NaiveResamplers

# Disable time and printf macros when not running interactivly ( for travis )
if !isinteractive()
    macro time( ex )
        quote
            $(esc(ex))
        end
    end

    macro printf( args... )
    end
end



#==============================================================================#
#               ____ _ _  _ ____ _    ____    ____ ____ ___ ____               #
#               [__  | |\ | | __ |    |___    |__/ |__|  |  |___               #
#               ___] | | \| |__] |___ |___    |  \ |  |  |  |___               #
#==============================================================================#

function test_singlerate( h, x; atol = 0 )
    xLen       = length( x )
    hLen       = length( h )
    pivotPoint = min( rand(50:150, 1)[1], floor( Int, xLen/4 ))
    x1         = x[ 1 : pivotPoint ]
    x2         = x[ pivotPoint+1 : end ]

    @printf( "\n\n" )
    @printf( "____ _ _  _ ____ _    ____    ____ ____ ___ ____\n" )
    @printf( "[__  | |\\ | | __ |    |___    |__/ |__|  |  |___\n" )
    @printf( "___] | | \\| |__] |___ |___    |  \\ |  |  |  |___\n" )
    @printf( "\nTesting single-rate fitering, h is %s, x is %s. xLen = %d, hLen = %d", string(eltype(h)), string(eltype(x)), xLen, hLen )

    @printf( "\n\tBase.filt\n\t\t")
    @time baseResult = Base.filt( h, 1.0, x )

    if method_exists( DSP.filt, ( typeof(h), typeof(x) ))
        @printf( "\n\tDSP.filt\n\t\t")
        @time dspResult = DSP.filt( h, x )
    end

    @printf( "\n\tMultirate.filt( h, x, 1//1 )\n\t\t" )
    @time statelessResult = Multirate.filt( h, x )

    @printf( "\n\tMultirate.filt. length( x1 ) = %d, length( x2 ) = %d\n\t\t", length( x1 ), length( x2 ) )
    self = Multirate.FIRFilter( h, 1//1 )
    @time begin
        y1 = Multirate.filt( self, x1 )
        y2 = Multirate.filt( self, x2 )
    end
    statefulResult = [ y1; y2 ]

    @printf( "\n\tMultirate.filt filt. Piecewise for first %d inputs\n\t\t", length( x1 ) )
    Multirate.reset( self )
    @time begin
        for i in 1:length(x1)
            y1[i] = Multirate.filt( self, x1[i:i] )[1]
        end
        y2 = Multirate.filt( self, x2 )
    end
    piecewiseResult = [ y1; y2 ]


     if isapprox( baseResult, statelessResult, atol=atol ) && isapprox( baseResult, statefulResult, atol=atol ) && isapprox( baseResult, piecewiseResult, atol=atol )
         return true
     end

     display( [ baseResult statelessResult statefulResult piecewiseResult ] )

     return false

end




#==============================================================================#
#                      ___  ____ ____ _ _  _ ____ ___ ____                     #
#                      |  \ |___ |    | |\/| |__|  |  |___                     #
#                      |__/ |___ |___ | |  | |  |  |  |___                     #
#==============================================================================#

function test_decimation( h, x, decimation; atol=0 )
    xLen       = length( x )
    hLen       = length( h )
    pivotPoint = min( rand(50:150, 1)[1], floor( Int, xLen/4 ))
    x1         = x[ 1 : pivotPoint ]
    x2         = x[ pivotPoint+1 : end ]

    @printf( "\n\n" )
    @printf( "___  ____ ____ _ _  _ ____ ___ _ ____ _  _ \n" )
    @printf( "|  \\ |___ |    | |\\/| |__|  |  | |  | |\\ | \n" )
    @printf( "|__/ |___ |___ | |  | |  |  |  | |__| | \\| \n" )
    @printf( "\nTesting decimation. h::%s, x::%s. xLen = %d, hLen = %d, decimation = %d", string(typeof(h)), string(typeof(h)), xLen, hLen, decimation )

    @printf( "\n\tNaive decimation with Base.filt\n\t\t")
    @time begin
        baseResult   = Base.filt( h, one(eltype(h)), x )
        baseResult   = baseResult[1:decimation:end]
    end

    if method_exists( DSP.filt, ( typeof(h), typeof(x) ))
        @printf( "\n\tNaive decimation with DSP.filt\n\t\t")
        @time begin
            dspResult = DSP.filt( h, x )
            dspResult = dspResult[1:decimation:end]
        end
    end

    @printf( "\n\tMultirate.filt( h, x, 1//%d)\n\t\t", decimation )
    @time statelessResult = Multirate.filt( h, x, 1//decimation )

    @printf( "\n\tMultirate.filt decimation. length( x1 ) = %d, length( x2 ) = %d\n\t\t", length( x1 ), length( x2 ) )
    self = Multirate.FIRFilter( h, 1//decimation )
    @time begin
        y1 = Multirate.filt( self, x1 )
        y2 = Multirate.filt( self, x2 )
    end
    statefulResult = [ y1; y2 ]

    @printf( "\n\tMultirate.filt decimation. Piecewise for first %d inputs.\n\t\t", length( x1 ) )
    Multirate.reset( self )
    y1 = similar( x, 0 )
    @time begin
        for i in 1:length(x1)
            append!( y1, Multirate.filt( self, x1[i:i] ) )
        end
        y2 = Multirate.filt( self, x2 )
    end
    piecewiseResult = [ y1; y2 ]

    if isapprox( baseResult, statelessResult, atol=atol ) && isapprox( baseResult, statefulResult, atol=atol ) && isapprox( baseResult, piecewiseResult, atol=atol )
        return true
    end

    display( [ baseResult statelessResult statefulResult piecewiseResult (baseResult.-statelessResult) (baseResult.-statefulResult) (baseResult.-piecewiseResult)] )
    println()
    
    return false

end




#==============================================================================#
#               _ _  _ ___ ____ ____ ___  _    ____ ____ ___ ____              #
#               | |\ |  |  |___ |__/ |__] |    |  | |__|  |  |___              #
#               | | \|  |  |___ |  \ |    |___ |__| |  |  |  |___              #
#==============================================================================#

function test_interpolation( h, x, interpolation; atol::Real = 0 )
    xLen       = length( x )
    hLen       = length( h )
    pivotPoint = min( rand(50:150, 1)[1], floor( Int, xLen/4 ))
    x1         = x[ 1 : pivotPoint ]
    x2         = x[ pivotPoint+1 : end ]

    @printf( "\n\n" )
    @printf( "_ _  _ ___ ____ ____ ___  _    ____ ____ ___ _ ____ _  _ \n" )
    @printf( "| |\\ |  |  |___ |__/ |__] |    |  | |__|  |  | |  | |\\ | \n" )
    @printf( "| | \\|  |  |___ |  \\ |    |___ |__| |  |  |  | |__| | \\| \n" )
    @printf( "\nTesting interpolation, h::%s, x::%s. xLen = %d, hLen = %d, interpolation = %d", typeof(h), typeof(x), xLen, hLen, interpolation )

    @printf( "\n\tNaive interpolation with Base.filt\n\t\t")
    @time begin
        xZeroStuffed = zeros( eltype(x), xLen * interpolation )
        for n = 0:xLen-1;
            xZeroStuffed[ n*interpolation+1 ] = x[ n+1 ]
        end
        baseResult = Base.filt( h, one(eltype(h)), xZeroStuffed )
    end

    if method_exists( DSP.filt, ( typeof(h), typeof(x) ))
        @printf( "\n\tNaive interpolation with DSP.filt\n\t\t")
        @time begin
            xZeroStuffed = zeros( eltype(x), xLen * interpolation )
            for n = 0:xLen-1;
                xZeroStuffed[ n*interpolation+1 ] = x[ n+1 ]
            end
            dspResult = DSP.filt( h, xZeroStuffed )
        end
    end

    @printf( "\n\tMultirate.filt( h, x, %d//1 )\n\t\t", interpolation )
    @time statelessResult = Multirate.filt( h, x, interpolation//1 )

    @printf( "\n\tMultirate.filt interpolation. length( x1 ) = %d, length( x2 ) = %d\n\t\t", length( x1 ), length( x2 ) )
    self = Multirate.FIRFilter( h, interpolation//1 )
    @time begin
        y1 = Multirate.filt( self, x1 )
        y2 = Multirate.filt( self, x2 )
    end
    statefulResult = [ y1; y2 ]

    @printf( "\n\tMultirate.filt interpolation. Piecewise for first %d inputs\n\t\t", length( x1 ) )
    Multirate.reset( self )
    y1 = similar( x, 0 )
    @time begin
        for i in 1:length(x1)
            append!( y1, Multirate.filt( self, x1[i:i] ) )
        end
        y2 = Multirate.filt( self, x2 )
    end
    piecewiseResult = [ y1; y2 ]

    if isapprox( baseResult, statelessResult, atol=atol ) && isapprox( baseResult, statefulResult, atol=atol ) && isapprox( piecewiseResult, baseResult, atol=atol )
        return true
    end

    display( [ (1:length(baseResult)) baseResult statelessResult statefulResult piecewiseResult (baseResult.-statelessResult) (baseResult.-statefulResult) (baseResult.-piecewiseResult) ] )

    return false
end




#==============================================================================#
#           ____ ____ ___     ____ ____ ____ ____ _  _ ___  _    ____          #
#           |__/ |__|  |      |__/ |___ [__  |__| |\/| |__] |    |___          #
#           |  \ |  |  |  .   |  \ |___ ___] |  | |  | |    |___ |___          #
#==============================================================================#

function test_rational( h, x, ratio )
    xLen       = length( x )
    hLen       = length( h )
    pivotPoint = min( rand(50:150, 1)[1], floor( Int, xLen/4 ))
    x1         = x[ 1 : pivotPoint ]
    x2         = x[ pivotPoint+1 : end ]
    upfactor   = numerator( ratio )
    downfactor = denominator( ratio )
    resultType = promote_type( eltype(h), eltype(x) )

    @printf( "\n\n" )
    @printf( "      ____ ____ ___ _ ____ _  _ ____ _    \n" )
    @printf( "      |__/ |__|  |  | |  | |\\ | |__| |    \n" )
    @printf( "      |  \\ |  |  |  | |__| | \\| |  | |___ \n" )
    @printf( "                                          \n" )
    @printf( "____ ____ ____ ____ _  _ ___  _    _ _  _ ____\n" )
    @printf( "|__/ |___ [__  |__| |\\/| |__] |    | |\\ | | __\n" )
    @printf( "|  \\ |___ ___] |  | |  | |    |___ | | \\| |__]\n" )
    @printf( "\n\nTesting rational resampling, h::%s, x::%s. xLen = %d, hLen = %d, ratio = %d//%d", string(typeof(h)), string(typeof(x)), xLen, hLen, upfactor, downfactor )

    @printf( "\n\tNaive rational resampling with Base.filt\n\t\t")
    @time begin
        xStuffed   = zeros( resultType, length(x) * upfactor )
        baseResult = Array{resultType}( ceil( Int, length(x) * ratio ))

        for n = 0:length(x)-1;
            xStuffed[ n*upfactor+1 ] = x[ n+1 ]
        end

        baseResult = Base.filt( h, one(eltype(h)), xStuffed );
        baseResult = [ baseResult[n] for n = 1:downfactor:length( baseResult ) ]
    end

    if method_exists( DSP.filt, ( typeof(h), typeof(x) ))
        @printf( "\n\tNaive rational resampling DSP.filt\n\t\t")
        @time begin
            xStuffed  = zeros( resultType, length(x) * upfactor )
            dspResult = Array{resultType}( ceil( Int, length(x) * ratio ))

            for n = 0:length(x)-1;
                xStuffed[ n*upfactor+1 ] = x[ n+1 ]
            end

            dspResult = DSP.filt( h, xStuffed );
            dspResult = [ dspResult[n] for n = 1:downfactor:length( dspResult ) ]
        end
    end

    @printf( "\n\tMultirate.filt( h, x, %d//%d )\n\t\t", upfactor, downfactor )
    @time statelessResult = Multirate.filt( h, x, ratio )

    @printf( "\n\tMultirate.filt rational resampling. length( x1 ) = %d, length( x2 ) = %d\n\t\t", length( x1 ), length( x2 ) )
    self = Multirate.FIRFilter( h, ratio )
    @time begin
        s1 = Multirate.filt( self, x1 )
        s2 = Multirate.filt( self, x2 )
    end
    statefulResult = [ s1; s2 ]

    @printf( "\n\tMultirate.filt rational. Piecewise for all %d inputs\n\t\t", length( x ) )
    self = Multirate.FIRFilter( h, ratio )
    y1 = similar( x, 0 )
    @time begin
        for i in 1:length(x)
            append!( y1, Multirate.filt( self, x[i:i] ) )
        end
    end
    piecewiseResult = y1

    if isapprox( baseResult, statelessResult ) && isapprox( baseResult, statefulResult ) && isapprox( baseResult, piecewiseResult )
        return true
    end

    display( [  baseResult statefulResult statefulResult piecewiseResult ] )

    return false
end




#==============================================================================#
#        ____ ____ ___      ____ ____ ____ ____ _  _ ___  _    _ _  _ ____     #
#        |__| |__/ |__]     |__/ |___ [__  |__| |\/| |__] |    | |\ | | __     #
#        |  | |  \ |__] .   |  \ |___ ___] |  | |  | |    |___ | | \| |__]     #
#==============================================================================#

function test_arbitrary( Th, x, resampleRate, numFilters; atol=0 )
    cutoffFreq      = 0.45
    transitionWidth = 0.05
    (hLen, β)       = Multirate.kaiserlength( transitionWidth, samplerate = numFilters )
    hLen            = ceil(Int,  hLen/numFilters  ) .* numFilters
    h               = Multirate.firdes( hLen, cutoffFreq, DSP.kaiser, samplerate = 32, beta = β ) .* numFilters
    h               = convert( Vector{Th}, h )

    @printf( "____ ____ ___      ____ ____ ____ ____ _  _ ___  _    _ _  _ ____\n" )
    @printf( "|__| |__/ |__]     |__/ |___ [__  |__| |\\/| |__] |    | |\\ | | __\n" )
    @printf( "|  | |  \\ |__] .   |  \\ |___ ___] |  | |  | |    |___ | | \\| |__]\n" )
    @printf( "\n\nh::%s, x::%s, rate = %f, N𝜙 = %d, xLen = %d, ", string(typeof(h)), string(typeof(x)), resampleRate, numFilters, length(x) )

    @printf( "\n\tNaive arbitrary resampling\n\t\t" )
    @time naiveResult = NaiveResamplers.naivefilt( h, x, resampleRate, numFilters )

    @printf( "\n\tStateless arbitrary resampling\n\t\t" )
    @time statelessResult = Multirate.filt( h, x, resampleRate, numFilters )

    @printf( "\n\tPiecewise arbitrary resampling\n\t\t" )
    self           = Multirate.FIRFilter( h, resampleRate, numFilters )
    piecewiseResult = eltype(x)[]
    sizehint!( piecewiseResult, ceil( Int, length(x)*resampleRate ) )
    @time for i in 1:length( x )
        thisY = filt( self, x[i:i] )
        append!( piecewiseResult, thisY )
    end

    commonLen = min( length(naiveResult), length(statelessResult), length(piecewiseResult) )

    resize!( naiveResult, commonLen )
    resize!( statelessResult, commonLen )
    resize!( piecewiseResult, commonLen )

    if isapprox( naiveResult, statelessResult, atol=atol ) && isapprox( naiveResult, piecewiseResult, atol=atol )
        return true
    end

    display( [  (1:commonLen) naiveResult statelessResult  piecewiseResult abs.(naiveResult.-statelessResult) abs.(naiveResult.-piecewiseResult) ] )


    display( [ (1:(commonLen-1)) diff(naiveResult) diff(statelessResult) diff(piecewiseResult) ] )
    return false
end




#==============================================================================#
#                  ____ _  _ _  _    ___ ____ ____ ___ ____                    #
#                  |__/ |  | |\ |     |  |___ [__   |  [__                     #
#                  |  \ |__| | \|     |  |___ ___]  |  ___]                    #
#==============================================================================#

function test_all()
    for interpolation in sort([1; unique(rand(2:32,8))] ),
            decimation in sort([1; unique(rand(2:32,8))] ),
                Th in [Float32, Float64],
                    Tx in [Float32, Float64, Complex64, Complex128]

        h     = rand(Th, rand(16:128,1)[1] )
        xLen  = Int(rand( 200:300, 1 )[1])
        xLen  = xLen-mod( xLen, decimation )
        x     = rand( Tx, xLen )
        ratio = interpolation//decimation

        if ratio == 1
            @test test_singlerate( h, x )
        end

        if decimation != 1
            @test test_decimation( h, x, decimation )
        end

        if interpolation != 1
            @test test_interpolation( h, x, interpolation )
        end


        if numerator(ratio) == interpolation && denominator(ratio) == decimation && numerator(ratio) != 1 && denominator(ratio) != 1
            @test test_rational( h, x, ratio )
            if Tx in [ Float32, Complex64 ]
                @test test_arbitrary( Th, x, Float64(ratio)+rand(), 32 )
            end
        end
    end
end

function test_nextphase()
    for interpolation in 1:8
        for decimation in 1:8
            ratio           = interpolation//decimation
            interpolation   = numerator(ratio)
            decimation      = denominator(ratio)
            x               = repmat( (1:interpolation), decimation )
            reference = [ x[n] for n in 1:decimation:length( x ) ]
            result = [ 1 ]
            for i in 2:interpolation
                append!( result, [ Multirate.nextphase( result[end], ratio ) ] )
            end
            @test isapprox( reference, result )
        end
    end
end

test_nextphase()
test_all()
