package net.heartshield.filter;

import net.heartshield.signal.DVec;
import net.heartshield.signal.FirFilters;
import net.heartshield.signal.Signal;

/**
 * Processes uneven addition of samples into a curve that is plotted.
 * introduces a delay of TAPS_LEN_RIGHT.
 *
 * Results are not precise. Rather, this filter is optimized for less delay, as intended for visuals.
 *
 * @author David Madl (git@abanbytes.eu)
 * @date 2017-03-19
 */
public class PlotFilter extends EveningResampler {
    private static final double[] TAPS; // FIR highpass fps=30 fc=0.5 tw=0.5
    private static final int TAPS_LEN_RIGHT;  // length from [center : last non-zero)
    private static final double[] TAPS_SHORT; // non-centered active taps
    private static final int PLOT_SMOOTHING_N;

    private int lpad;
    private int ltaps;
    private double[] x;
    private double[] y;
    private double[] t;
    private int iw;
    private int ir;
    private int iy;
    private boolean initialized;
    private int ypad_left;
    private int measure_len;

    public PlotFilter(double fps, int measure_len, int ypad_left) {
        super(fps);
        lpad = TAPS_SHORT.length;
        ltaps = TAPS_SHORT.length;
        x = DVec.stack(DVec.ones(lpad), DVec.zeros(measure_len));
        y = DVec.zeros(ypad_left + TAPS_LEN_RIGHT + measure_len);
        t = DVec.zeros(ypad_left + TAPS_LEN_RIGHT + measure_len);
        iw = lpad-1;
        ir = 0;
        iy = ypad_left;
        initialized = false;
        this.ypad_left = ypad_left;
        this.measure_len = measure_len;
    }

    public void clear() {
        super.clear();

        x = DVec.stack(DVec.ones(lpad), DVec.zeros(measure_len));
        y = DVec.zeros(ypad_left + TAPS_LEN_RIGHT + measure_len);
        t = DVec.zeros(ypad_left + TAPS_LEN_RIGHT + measure_len);
        iw = lpad-1;
        ir = 0;
        iy = ypad_left;
        initialized = false;
    }

    @Override
    void add(double time, double frame) {
        if(!initialized) {
            initialized = true;
            // pad to left with initial value
            for(int i = 0; i < lpad; i++)
                //x.set(i, frame);
                x[i] = frame;

            // retrofit some timestamps
            for(int i = 0; i < ypad_left; i++)
                t[i] = time + (i - ypad_left) / this.mFps;
        }

        // this should not happen! alas, if FPS is off, or user waits for too long without starting a measurement, it does.

        if(iw >= x.length)
            x = DVec.stack(x, DVec.zeros(x.length));

        if(iy >= y.length) {
            y = DVec.stack(y, DVec.zeros(y.length));
            t = DVec.stack(t, DVec.zeros(t.length));
        }

        x[iw++] = frame;

        y[iy] = DVec.sum(DVec.mul(DVec.get(x, ir, ir+ltaps), TAPS_SHORT));
        t[iy] = time;

        ir++;
        iy++;
    }

    public int size() { return ir; }

    double get() {
        return y[iy-1];
    }

    public double[] getPlotPointsUnscaled() {
        double[] buf = DVec.get(y, iy-ypad_left, iy);
        buf = Signal.running_mean(Signal.running_mean(buf, PLOT_SMOOTHING_N), PLOT_SMOOTHING_N);

        final int min_len = ypad_left-2*(PLOT_SMOOTHING_N-1);
        if(buf.length < min_len) {
            // left-pad with zeros if necessary, to come up with a plot
            buf = DVec.stack(DVec.zeros(min_len), buf);
        }

        /*buf = DVec.mul(buf, 0.5 / DVec.max(DVec.abs(buf)));
        buf = DVec.add(buf, 0.5);*/

        return buf;
    }

    private double[] getPlotPoints() {
        double[] buf = getRecentPoints(ypad_left);
        buf = DVec.mul(buf, -1.0);
        buf = DVec.mul(buf, 0.5 / DVec.max(DVec.abs(buf)));
        buf = DVec.add(buf, 0.5);
        return buf;
    }
    private double[] getPlotTimes() {
        // to do: still off by a few samples? see PlotFilterTests
        return getRecentTimes(ypad_left);
    }

    public synchronized Series getPlotSeries() {
        return new Series(getPlotTimes(), getPlotPoints());
    }

    // needs synchronized access!
    private double[] getRecentPoints(int length) {
        double[] buf = DVec.get(y, iy-length, iy);
        buf = Signal.running_mean(Signal.running_mean(buf, PLOT_SMOOTHING_N), PLOT_SMOOTHING_N);

        final int min_len = length-2*(PLOT_SMOOTHING_N-1);
        if(buf.length < min_len) {
            // left-pad with zeros if necessary, to come up with a plot
            buf = DVec.stack(DVec.zeros(min_len), buf);
        }

        return buf;
    }

    // needs synchronized access!
    private double[] getRecentTimes(int length) {
        // to do: still off by a few samples? see PlotFilterTests
        return DVec.get(t, iy-length +1*(PLOT_SMOOTHING_N-1), iy - 1*(PLOT_SMOOTHING_N-1));
    }

    public static class Series {
        public double[] t;
        public double[] x;

        public Series(double[] t, double[] x) {
            this.t = t;
            this.x = x;
        }
    }

    public synchronized Series getRecentSeries(int length) {
        return new Series(getRecentTimes(length), getRecentPoints(length));
    }

    static {
        // TODO(david): fix FPS dependency (TAPS and TAPS_LEN_RIGHT are designed for 30 fps)
        //TAPS = new double[]{ -2.54277053e-04, -2.76789215e-04, -2.99490261e-04, -3.22486390e-04, -3.45740002e-04, -3.69053741e-04, -3.92059737e-04, -4.14213369e-04, -4.34791815e-04, -4.52898588e-04, -4.67473350e-04, -4.77307389e-04, -4.81064926e-04, -4.77309397e-04, -4.64535347e-04, -4.41204436e-04, -4.05785773e-04, -3.56799457e-04, -2.92862998e-04, -2.12739193e-04, -1.15384959e-04, 2.91419969e-19, 1.33925787e-04, 2.86567025e-04, 4.57721006e-04, 6.46770000e-04, 8.52648926e-04, 1.07381924e-03, 1.30824978e-03, 1.55340519e-03, 1.80624390e-03, 2.06322316e-03, 2.32031452e-03, 2.57303007e-03, 2.81645381e-03, 3.04528838e-03, 3.25390534e-03, 3.43640824e-03, 3.58670158e-03, 3.69856670e-03, 3.76574416e-03, 3.78202391e-03, 3.74133443e-03, 3.63783888e-03, 3.46602965e-03, 3.22082476e-03, 2.89766071e-03, 2.49258382e-03, 2.00233469e-03, 1.42442901e-03, 7.57227419e-04, -9.38187527e-19, -8.47022515e-04, -1.78260717e-03, -2.80449004e-03, -3.90936062e-03, -5.09286067e-03, -6.34959433e-03, -7.67315552e-03, -9.05616954e-03, -1.04903458e-02, -1.19665479e-02, -1.34748695e-02, -1.50047382e-02, -1.65450070e-02, -1.80840734e-02, -1.96100064e-02, -2.11106613e-02, -2.25738306e-02, -2.39873696e-02, -2.53393278e-02, -2.66181119e-02, -2.78125945e-02, -2.89122574e-02, -2.99073178e-02, -3.07888426e-02, -3.15488540e-02, -3.21804322e-02, -3.26777995e-02, -3.30363698e-02, -3.32528502e-02, 9.66431737e-01, -3.32528502e-02, -3.30363698e-02, -3.26777995e-02, -3.21804322e-02, -3.15488540e-02, -3.07888426e-02, -2.99073178e-02, -2.89122574e-02, -2.78125945e-02, -2.66181119e-02, -2.53393278e-02, -2.39873696e-02, -2.25738306e-02, -2.11106613e-02, -1.96100064e-02, -1.80840734e-02, -1.65450070e-02, -1.50047382e-02, -1.34748695e-02, -1.19665479e-02, -1.04903458e-02, -9.05616954e-03, -7.67315552e-03, -6.34959433e-03, -5.09286067e-03, -3.90936062e-03, -2.80449004e-03, -1.78260717e-03, -8.47022515e-04, -9.38187527e-19, 7.57227419e-04, 1.42442901e-03, 2.00233469e-03, 2.49258382e-03, 2.89766071e-03, 3.22082476e-03, 3.46602965e-03, 3.63783888e-03, 3.74133443e-03, 3.78202391e-03, 3.76574416e-03, 3.69856670e-03, 3.58670158e-03, 3.43640824e-03, 3.25390534e-03, 3.04528838e-03, 2.81645381e-03, 2.57303007e-03, 2.32031452e-03, 2.06322316e-03, 1.80624390e-03, 1.55340519e-03, 1.30824978e-03, 1.07381924e-03, 8.52648926e-04, 6.46770000e-04, 4.57721006e-04, 2.86567025e-04, 1.33925787e-04, 2.91419969e-19, -1.15384959e-04, -2.12739193e-04, -2.92862998e-04, -3.56799457e-04, -4.05785773e-04, -4.41204436e-04, -4.64535347e-04, -4.77309397e-04, -4.81064926e-04, -4.77307389e-04, -4.67473350e-04, -4.52898588e-04, -4.34791815e-04, -4.14213369e-04, -3.92059737e-04, -3.69053741e-04, -3.45740002e-04, -3.22486390e-04, -2.99490261e-04, -2.76789215e-04, -2.54277053e-04 };
        double fps=30.0;
        double cf=0.5;
        double tw=0.5;
        TAPS = FirFilters.highPass(fps, cf, tw);
        double tapsLenRightSecs = 0.5;
        TAPS_LEN_RIGHT = (int) (tapsLenRightSecs * fps); // 15

        double plotSmoothingWinSecs = 0.2;
        PLOT_SMOOTHING_N = (int) (plotSmoothingWinSecs * fps); // 6

        double[] sl = DVec.get(TAPS, 0, TAPS.length/2 + TAPS_LEN_RIGHT);
        TAPS_SHORT = DVec.sub(sl, DVec.sum(sl) / (double) sl.length);
    }
}
