package net.heartshield.signal;

public class FirFilters {
    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    //public native String stringFromJNI();

    // Used to load the 'native-lib' library on application startup.
    /*static {
        System.loadLibrary("native-lib");
    }*/

    /** rect-window Hilbert transform impulse response */
    public static double[] hilbert(int len) {
        if(len % 2 == 0)
            throw new IllegalArgumentException("len must be odd");

        int hwin = (len-1) / 2;
        double[] arr = new double[len];
        double gain = 0.0;
        for(int i = 1; i <= hwin; i++) {
            if(i % 2 == 1) {
                arr[i + hwin] = 1.0 / i;
                arr[hwin - i] = -1.0 / i;
                gain = arr[i + hwin] - gain;
            } else {
                arr[i + hwin] = 0;
                arr[hwin - i] = 0;
            }
        }
        gain = 2 * Math.abs(gain);
        for(int i = 0; i < len; i++)
            arr[i] /= gain;
        return arr;
    }

    public static double[] hamming(int len) {
        double[] arr = new double[len];
        double M = len-1;
        for(int i = 0; i < len; i++)
            arr[i] = 0.54 - 0.46 * Math.cos(2 * Math.PI * i / M);
        return arr;
    }

    /** low-pass filter impulse response design */
    public static double[] lowPass(double fps, double cutoffFreq, double transitionWidth) {
        int len = filterLength(fps, transitionWidth);
        double[] arr = new double[len];
        double[] win = hamming(len);
        int M = (len-1) / 2;
        double tpkm = 2 * Math.PI * cutoffFreq / fps;
        for(int i = 0; i < len; i++) {
            if(i == M)
                arr[i] = tpkm / Math.PI;
            else
                arr[i] = Math.sin(tpkm * (i-M)) / (Math.PI * (i-M));
            arr[i] *= win[i];
        }

        double denom = DVec.sum(arr);
        for(int i = 0; i < len; i++)
            arr[i] /= denom;
        return arr;
    }

    /** high-pass filter impulse response design */
    public static double[] highPass(double fps, double cutoffFreq, double transitionWidth) {
        int len = filterLength(fps, transitionWidth);
        double[] arr = new double[len];
        double[] win = hamming(len);
        int M = (len-1) / 2;
        double tpkm = 2 * Math.PI * cutoffFreq / fps;
        for(int i = 0; i < len; i++) {
            if(i == M)
                arr[i] = 1 - tpkm / Math.PI;
            else
                arr[i] = -Math.sin(tpkm * (i-M)) / (Math.PI * (i-M));
            arr[i] *= win[i];
        }

        double denom = 0;
        for(int i = 0; i < len; i++)
            denom += arr[i] * Math.cos(i * Math.PI);

        for(int i = 0; i < len; i++)
            arr[i] /= denom;
        return arr;
    }

    /** band-pass filter impulse response design */
    public static double[] bandPass(double fps, double lowCutoffFreq, double highCutoffFreq, double transitionWidth) {
        int len = filterLength(fps, transitionWidth);
        double[] arr = new double[len];
        double[] win = hamming(len);
        int M = (len-1) / 2;
        double tpkmLow = 2 * Math.PI * lowCutoffFreq / fps;
        double tpkmHigh = 2 * Math.PI * highCutoffFreq / fps;
        for(int i = 0; i < len; i++) {
            if(i == M)
                arr[i] = (tpkmHigh - tpkmLow) / Math.PI;
            else
                arr[i] = (Math.sin(tpkmHigh * (i-M)) - Math.sin(tpkmLow * (i-M))) / (Math.PI * (i-M));
            arr[i] *= win[i];
        }


        double denom = 0;
        for(int i = 0; i < len; i++)
            denom += arr[i] * Math.cos((i-M) * (tpkmLow + tpkmHigh) / 2.0);

        for(int i = 0; i < len; i++)
            arr[i] /= denom;
        return arr;
    }

    /** band-reject filter impulse response design */
    public static double[] bandReject(double fps, double lowCutoffFreq, double highCutoffFreq, double transitionWidth) {
        int len = filterLength(fps, transitionWidth);
        double[] arr = new double[len];
        double[] win = hamming(len);
        int M = (len-1) / 2;
        double tpkmLow = 2 * Math.PI * lowCutoffFreq / fps;
        double tpkmHigh = 2 * Math.PI * highCutoffFreq / fps;
        for(int i = 0; i < len; i++) {
            if(i == M)
                arr[i] = 1.0 - (tpkmHigh - tpkmLow) / Math.PI;
            else
                arr[i] = -(Math.sin(tpkmHigh * (i-M)) - Math.sin(tpkmLow * (i-M))) / (Math.PI * (i-M));
            arr[i] *= win[i];
        }

        double denom = DVec.sum(arr);
        for(int i = 0; i < len; i++)
            arr[i] /= denom;
        return arr;
    }

    private static void throwIfInvalidFreqs(double fps, double cutoffFreq, double transitionWidth) {
        if(fps < 0)
            throw new IllegalArgumentException("fps must be >= 0");
        if(cutoffFreq < 0 || cutoffFreq > fps / 2.0)
            throw new IllegalArgumentException("0 < cutoffFreq < fps/2");
        if(transitionWidth < 0)
            throw new IllegalArgumentException("transitionWidth must be >= 0");
    }

    private static int filterLength(double fps, double transitionWidth) {
        int len = (int) (3.0 * fps / transitionWidth);
        len += 1 - (len & 1);
        return len;
    }
}
