package net.heartshield.filter;

import junit.framework.TestCase;

import net.heartshield.signal.DVec;

import org.jfree.data.xy.XYSeries;

import java.io.File;
import java.io.IOException;

import javax.sound.sampled.UnsupportedAudioFileException;

/**
 * @author David Madl (git@abanbytes.eu)
 * @date 2017-05-15
 */
public class PcgFilterTests extends TestCase {
    public void testEnvelopeFilter() throws IOException, UnsupportedAudioFileException {
        AudioData audio = readAudio("/mnt/hsh/data/pcg/2017-05-15/2017-05-14.wav");
        double[] left = audio.left;
        double frontFps = audio.fps;

        System.out.println("min=" + DVec.min(left) + " max=" + DVec.max(left));

        // 1e6 .. 2.3e6

        PcgEnvelopeFilter pcg = new PcgEnvelopeFilter(frontFps);
        DataSink sink = new DataSink();
        pcg.connect(sink);

        long t1 = System.nanoTime();
        //pcg.put(left);

        int step = 4800;
        for(int i = 0; i < left.length; i += step) {
            //System.out.println("put(i=" + i + ")");
            pcg.put(DVec.get(left, i, Math.min(i + step, left.length)));
        }

        long t2 = System.nanoTime();

        // 170-190 ms
        System.out.println("time elapsed: " + (t2-t1)/1e6 + " ms // including DataSink.put()");

        double[] filtered = sink.getData();

        System.out.println("in.length=" + left.length + " out.length=" + filtered.length);

        filtered = DVec.get(filtered, 50000, 60000);

        System.out.println("filtered min=" + DVec.min(filtered) + " max=" + DVec.max(filtered));

        LineChart lc = LineChart.plot(filtered);
        lc.showChart();
        lc.waitChart();
    }

    public void testBeatDetector() throws IOException, UnsupportedAudioFileException {
        AudioData audio = readAudio("../app/src/main/res/raw/canned_pcg_short.wav");
        double[] left = audio.left;
        double frontFps = audio.fps;

        System.out.println("min=" + DVec.min(left) + " max=" + DVec.max(left));

        // 1e6 .. 2.3e6

        PcgBeatDetector pcg = new PcgBeatDetector(frontFps);
        //TestBeatDetector pcg = new TestBeatDetector(frontFps);
        DataSink sinkPerc = new DataSink();
        DataSink sinkOut = new DataSink();
        //pcg.connect(sink);
        //pcg.percentileFilter.connect(sinkPerc);
        //pcg.output.connect(sinkOut);
        //sinkPerc.put(left);
        pcg.connectPercentiles(sinkPerc);
        pcg.connect(sinkOut);

        long t1 = System.nanoTime();
        //pcg.put(left);

        //int step = 48000;
        int step = 4800;
        // 4800/27 = 177.7777777777778   / we have len=200 samples (why?)
        // so PERCENTILE_WIN_SECS = 4.0  ^=  7000 samples... of course, no percentiles.
        for(int i = 0; i < left.length; i += step) {
            //System.out.println("put(i=" + i + ")");
            pcg.put(DVec.get(left, i, Math.min(i + step, left.length)));
        }

        long t2 = System.nanoTime();

        // full put() -> 332.265274 ms
        // with step=4800 -> 3350.72893 ms
        // now better?! 2400.01039 ms
        System.out.println("time elapsed: " + (t2-t1)/1e6 + " ms // including DataSink.put()");

        /*
        double[] filtered = sink1.getData();

        filtered = DVec.get(filtered, 50000 * 24, 60000 * 24);

        System.out.println("filtered min=" + DVec.min(filtered) + " max=" + DVec.max(filtered));

        LineChart lc = LineChart.plot(filtered);
        lc.showChart();
        lc.waitChart();*/

        double[] perc = sinkPerc.getData();
        double[] out = sinkOut.getData();

        final XYSeries percSeries = new XYSeries("in");
        final XYSeries outSeries = new XYSeries("output");

        final double clipMax = 50.0;

        //for(int j = 0; j < perc.length; j++) {
        //for(int j = 40000*27; j < 85000*27; j++) {
        for(int j = 0; j < perc.length; j++) {
            percSeries.add(j, perc[j] > clipMax ? clipMax : perc[j]);
            outSeries.add(j, out[j]);
        }

        double npeaks = DVec.sum(DVec.get(out, 40000*27, Math.min(85000*27, out.length)));
        // 58.0
        System.out.println("npeaks = " + npeaks);

        // in.length=2 723 840 out.length=2 710 224  WHY? TODO
        // 13616 missing
        // 2723840/4800 = 567 steps
        // ah, moving averages maybe?
        System.out.println("in.length=" + left.length + " out.length=" + out.length);

        LineChart lc = new LineChart("beatdet", new XYSeries[]{ percSeries, outSeries });

        //LineChart lc = LineChart.plot(filtered);
        lc.showChart();
        lc.waitChart();
    }

    static class AudioData {
        public double fps;
        public double[] left;

        public AudioData(double fps, double[] left) {
            this.fps = fps;
            this.left = left;
        }
    }

    private AudioData readAudio(String fname) throws IOException, UnsupportedAudioFileException {
        AudioSampleReader reader = new AudioSampleReader(new File(fname));
        int len = (int) reader.getSampleCount();

        double[] left = new double[len];
        //double[] right = new double[len];
        //reader.getStereoSamples(left, right);
        double[] interleaved = new double[len*reader.getFormat().getChannels()];
        reader.getInterleavedSamples(0, len, interleaved);
        reader.getChannelSamples(0, interleaved, left);

        return new AudioData(reader.getFormat().getFrameRate(), left);
    }

    private double[] getBeep() throws IOException, UnsupportedAudioFileException {
        //throw new IllegalStateException(new File("").getAbsolutePath());
        // from prevent-android/signal

        AudioData audio = readAudio("../app/src/main/res/raw/beep.wav");
        return audio.left;
    }

    public void testBeatDetectorPerformance() throws IOException, UnsupportedAudioFileException {
        AudioData audio = readAudio("/mnt/hsh/data/pcg/2017-05-15/2017-05-14.wav");
        double[] left = audio.left;
        double frontFps = audio.fps;

        System.out.println("min=" + DVec.min(left) + " max=" + DVec.max(left));

        // 1e6 .. 2.3e6

        double[] beep = getBeep();
        //double[] beep = new double[(int) (0.08 * frontFps)]; // 3840 -> hopefully 8196-point FFT with the step=4800...
        FirFilter beeper = new FirFilter(frontFps, beep);
        //FirFilterRunning beeper = new FirFilterRunning(beep);
        //mPcgDetector.connect(mBeeper);
        beeper.connect(new IRFilterBlock() {
            @Override
            public void put(double[] x) {
                // ignore
            }
        });


        PcgBeatDetector pcg = new PcgBeatDetector(frontFps);
        //TestBeatDetector pcg = new TestBeatDetector(frontFps);
        DataSink sinkPerc = new DataSink();
        DataSink sinkOut = new DataSink();
        //pcg.connect(sink);
        //pcg.percentileFilter.connect(sinkPerc);
        //pcg.output.connect(sinkOut);
        sinkPerc.put(left);
        //pcg.connect(sinkOut);
        pcg.connect(beeper);

        long t1 = System.nanoTime();
        //pcg.put(left);

        //int step = 48000;
        int step = 4800;
        // 4800/27 = 177.7777777777778   / we have len=200 samples (why?)
        // so PERCENTILE_WIN_SECS = 4.0  ^=  7000 samples... of course, no percentiles.
        for(int i = 0; i < left.length; i += step) {
            //System.out.println("put(i=" + i + ")");
            pcg.put(DVec.get(left, i, Math.min(i + step, left.length)));
        }

        long t2 = System.nanoTime();


        // 2528.27118 ms
        // 2321.839134 ms
        System.out.println("time elapsed: " + (t2-t1)/1e6 + " ms // including DataSink.put()");
    }

    static class TestBeatDetector extends RFilterBlock {
        private PcgEnvelopeFilter mPcgFilter;
        private Splitter mSplitter = new Splitter();
        private Diff mDiff = new Diff(); // could also just have been FIR({-1, 1})
        private FirFilterRunning mMean1;
        private FirFilterRunning mMean2;
        private PcgBeatDetector.DiracUpsampler mUpsampler;

        // both are lower rate (downsampled)
        public PercentileFilter percentileFilter;
        public FirFilterRunning output;

        public TestBeatDetector(double frontFps) {

            mPcgFilter = new PcgEnvelopeFilter(frontFps);
            double fps = mPcgFilter.getFps();
            //mPcgFilter.connect(mSplitter);
            mPcgFilter.connect(mDiff);

            final double PERCENTILE_WIN_SECS = 4.0;
            final double PERCENTILE = 85;
            final double PERCENTILE_INIT = Double.MAX_VALUE; // ignore all beats until PercentileFilter has settled
            percentileFilter = new PercentileFilter((int) (PERCENTILE_WIN_SECS * fps), PERCENTILE_INIT, PERCENTILE);
            output = new FirFilterRunning(new double[]{1.0});

            //final int N1 = (int) (0.08 * fps);
            final int N2 = (int) (0.08 * fps);
            //mMean1 = new FirFilterRunning(DVec.ones(N1));
            mMean2 = new FirFilterRunning(DVec.ones(N2));

            //mDiff.connect(mMean1);
            mDiff.connect(mMean2);
            //mPcgFilter.connect(mMean1);
            //mMean1.connect(mMean2);
            mMean2.connect(mSplitter);

            percentileFilter.connect(mPercentiles);
            mSplitter.connect(percentileFilter); // must be before mBeatDetector, which uses mPercentiles.buf
            mSplitter.connect(output);
        }

        @Override
        protected double[] batch(double[] x) {
            throw new IllegalStateException();
        }

        static class FilterBuf implements IRFilterBlock {
            public double[] buf;

            @Override
            public void put(double[] x) {
                buf = x;
            }
        }

        private FilterBuf mPercentiles = new FilterBuf();

        @Override
        public void put(double[] x) {
            mPcgFilter.put(x);
        }

        @Override
        public void connect(IRFilterBlock consumer) {
            throw new IllegalStateException("connect to percentileFilter or output instead");
        }
    }
}
