/// <reference path="../../typings/functions.d.ts" />
import {transform, inverseTransform} from "../freeFFT";
import {xtract_get_data_frames} from "./xtract_get_data_frames";
import {xtract_assert_array} from "./xtract_assert_array";

function filter(N, c) {
    var c_b, Re, Im, b;
    c_b = Math.floor(c * N);
    Re = new Float64Array(N);
    Im = new Float64Array(N);
    var i, j;
    for (i = 0; i < c_b; i++) {
        Re[i] = 1;
    }
    for (i = N - c_b + 1; i < N; i++) {
        Re[i] = 1;
    }
    inverseTransform(Re, Im);
    // Scale and shift into Im
    for (i = 0; i < N; i++) {
        j = (i + (N >> 1)) % N;
        Im[i] = Re[j] / N;
        // Apply compute blackman-harris to Im
        var rad = (Math.PI * i) / (N);
        Im[i] *= 0.35875 - 0.48829 * Math.cos(2 * rad) + 0.14128 * Math.cos(4 * rad) - 0.01168 * Math.cos(6 * rad);
    }
    return Im;
}

function polyn(data, K) {
    var N = data.length;
    var x = [0, data[0], data[1]];
    var dst = new Float64Array(K);
    var ratio = K / N;
    var tinc = 1 / ratio;
    var n = 0,
        t = 0,
        k;
    for (k = 0; k < K; k++) {
        if (t === n) {
            // Points lie on same time
            dst[k] = data[n];
        } else {
            var y = (t - n - 1) * (t - n) * x[0] - 2 * (t - n - 1) * (t - n + 1) * x[1] + (t - n) * (t - n + 1) * x[2];
            dst[k] = y / 2;
        }
        t += tinc;
        if (t >= n + 1) {
            n = Math.floor(t);
            x[0] = data[n - 1];
            x[1] = data[n];
            if (n + 1 < N) {
                x[2] = data[n + 1];
            } else {
                x[2] = 0.0;
            }
        }
    }
    return dst;
}

function zp(a) {
    var b = new Float64Array(a.length * 2);
    for (var n = 0; n < a.length; n++) {
        b[n] = a[n];
    }
    return b;
}

function r2c(x) {
    var real = zp(x);
    var imag = new Float64Array(real.length);
    transform(real, imag);
    return {
        real: real,
        imag: imag
    };
}

function W(N) {
    var w = new Float64Array(N),
        i;
    for (i = 0; i < N; i++) {
        var rad = (Math.PI * i) / (N);
        w[i] = 0.35875 - 0.48829 * Math.cos(2 * rad) + 0.14128 * Math.cos(4 * rad) - 0.01168 * Math.cos(6 * rad);
    }
    return w;
}

function overlap(X, b) { // eslint-disable-line max-statements
    var i, f;
    var Y = new Float64Array(X.length);
    var N = b.length;
    var N2 = 2 * N;
    var B = r2c(b);
    var Xi = xtract_get_data_frames(X, N, N, false);
    var Yi = xtract_get_data_frames(Y, N, N, false);
    var x_last = new Float64Array(N);
    var y_last = new Float64Array(N);
    var w = W(N2);
    var xF = {
        real: new Float64Array(N2),
        imag: new Float64Array(N2)
    };
    var yF = {
        real: new Float64Array(N2),
        imag: new Float64Array(N2)
    };
    for (f = 0; f < Xi.length; f++) {
        for (i = 0; i < N; i++) {
            xF.real[i] = x_last[i] * w[i];
            xF.real[i + N] = Xi[f][i] * w[i + N];
            x_last[i] = Xi[f][i];
            xF.imag[i] = 0;
            xF.imag[i + N] = 0;
        }
        transform(xF.real, xF.imag);
        for (i = 0; i < N2; i++) {
            yF.real[i] = xF.real[i] * B.real[i] - xF.imag[i] * B.imag[i];
            yF.imag[i] = xF.imag[i] * B.real[i] + xF.real[i] * B.imag[i];
        }
        transform(yF.imag, yF.real);
        // Perform fft_shift and scale
        for (i = 0; i < N; i++) {
            var h = yF.real[i + N] / N;
            yF.real[i + N] = yF.real[i] / N;
            yF.real[i] = h;
        }
        for (i = 0; i < N; i++) {
            Yi[f][i] = (yF.real[i] + y_last[i]);
            y_last[i] = yF.real[i + N];
        }
    }
    return Y;
}

export function xtract_resample(data, p, q, n) {
    if (!xtract_assert_array(data))
        return 0;
    // Same function call as matlab:
    // data is the array
    // p is the target sample rate
    // q is the source sample rate
    // n is the desired filter order, n==1024 if undefined

    // Determine which way to go
    var b, N = data.length;
    if (typeof n !== "number" || n <= 0) {
        n = 512;
    }
    if (p === q) {
        return data;
    }
    var ratio = (p / q);
    var K = Math.floor(N * ratio);
    var dst;
    if (p > q) {
        // Upsampling
        // 1. Expand Data range
        dst = polyn(data, K);
        // 2. Filter out spurious energy above q
        b = filter(n, 1 / ratio);
        overlap(data, b);
    } else {
        // Downsampling
        // 1. Filter out energy above p
        b = filter(n, ratio / 2);
        var ds1 = overlap(data, b);
        // 2. Decrease data range
        dst = polyn(ds1, K);
    }
    return dst;
}
