package net.heartshield.data;

import android.content.Context;
import android.graphics.Bitmap;

import net.heartshield.signal.DVec;
import net.heartshield.signal.IVec;
import net.heartshield.signal.Series;
import net.heartshield.signal.Signal;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class MockMeasurementDataManager implements IMeasurementDataManager {
    public boolean created = false;
    public boolean updated = false;
    public boolean putAudio = false;

    private List<Measurement> mMeasurements = new ArrayList<>();

    private static MockMeasurementDataManager sInstance = null;

    private MockMeasurementDataManager(String appId) {
        populate(appId);
    }

    private void populate(String appId) {
        if(mMeasurements.size() > 0)
            return;

        mMeasurements.add(fakePPG(appId, (long) 1.4e9 + 100, 25, 60, 0.05, 1.0));
        mMeasurements.add(fakePPG(appId, (long) 1.4e9 + 200, 45, 75, 0.45, 0.3));
        mMeasurements.add(fakePPG(appId, (long) 1.4e9 + 300, 50, 90, 0.55, 0.0));
        mMeasurements.add(fakePPG(appId, (long) 1.4e9 + 400, 99, 0, 0.0, 0.0)); // unclassified
        mMeasurements.add(failedTransmissionPPG(appId, (long) 1.4e9 + 500, 99)); // failed transmission
    }

    private Measurement failedTransmissionPPG(String appId, long ts, int age) {
        Measurement low;
        low = new Measurement(appId, ts);
        low.user = new UserMeta(age, UserMeta.Gender.MALE, 1.96);
        low.result = null;

        low.series = new MeasurementSeries();
        low.series.audioSynced = true;

        return low;
    }

    private Measurement fakePPG(String appId, long ts, int age, int bpm, double pred, double harmonicAmplitude) {
        Measurement low;
        low = new Measurement(appId, ts);
        low.user = new UserMeta(age, UserMeta.Gender.MALE, 1.96);

        MeasurementResult result = new MeasurementResult();
        Series ppg = fakeElasticPPG(bpm, harmonicAmplitude);
        result.ibis = DVec.mul(DVec.ones(bpm), 1e3 * 60.0 / ((double) bpm));
        result.filtered = ppg.x;
        result.idx = IVec.where(Signal.beatdetect(30.0, ppg.x));
        result.status = (bpm != 0) ? MeasurementResult.Status.OK : MeasurementResult.Status.UNCLASSIFIED;
        result.pred = pred;
        low.result = result;

        result.hrvTimeDomain = new MeasurementResult.HrvTimeDomain();
        result.hrvFrequencyDomain = new MeasurementResult.HrvFrequencyDomain();

        low.series = new MeasurementSeries();
        low.series.audioSynced = true;

        return low;
    }

    private Series fakeElasticPPG(int bpm, double harmonicAmplitude) {
        /*

        t = np.arange(0, 60, 1./30.)
        x = np.sin(2*np.pi*1.0*t)
        alpha = np.pi*0.6
        x += np.sin(np.exp(np.sin(2*np.pi*1.0*t + alpha)))
        plt.plot(t[:300], x[:300])
        plt.show()

        */
        final double T = 60.0;
        double[] t = DVec.arange(0, T, 1./30.);
        final double f = ((double) bpm) / 60.0, alpha = Math.PI*0.6;
        double[] x = DVec.sin(DVec.mul(t, 2*Math.PI*f));
        x = DVec.add(x, DVec.mul(DVec.sin(DVec.exp(DVec.sin(DVec.add(DVec.mul(t, 2*Math.PI*f), alpha)))), harmonicAmplitude));
        return new Series(t, x);
    }

    @Override
    public void ping(String appId, PingResultListener listener) {
        listener.onResult();
    }

    @Override
    public void activate(String activationCode, ActivationResultListener listener) {
        listener.onResult(activationCode);
    }

    @Override
    public void create(Measurement measurement, MeasurementResultListener listener) {
        createAndPost("mock-measurement", measurement, listener);
        if(!measurement.series.audioSynced)
            putRemoteAudio(measurement, new MeasurementDataManager.StubListener());
    }

    @Override
    public void logFailure(Measurement measurement, MeasurementResultListener listener) {
        createAndPost("mock-measurement", measurement, listener);
        if(!measurement.series.audioSynced)
            putRemoteAudio(measurement, new MeasurementDataManager.StubListener());
    }

    @Override
    public void email(Measurement measurement, MeasurementResultListener listener) {
        create(measurement, listener);
    }

    @Override
    public Measurement retrieve(MeasurementId id) throws IOException {
        int len = mMeasurements.size();
        for(int i = 0; i < len; i++) {
            if(mMeasurements.get(i).getId().equals(id))
                return mMeasurements.get(i);
        }
        throw new IOException("could not find mock Measurement " + id.toString());
    }

    @Override
    public byte[] retrieveAudio(MeasurementId id) throws IOException {
        throw new FileNotFoundException("mock not implemented");
    }

    @Override
    public void update(Measurement measurement, MeasurementResultListener listener) {
        updated = true;
    }

    @Override
    public MeasurementSummary[] list() {
        MeasurementSummary[] summaries = new MeasurementSummary[mMeasurements.size()];
        for(int i = 0; i < summaries.length; i++)
            summaries[i] = mMeasurements.get(i).getSummary();
        return summaries;
    }

    @Override
    public Bitmap getReportImage(MeasurementId id) {
        return null;
    }

    @Override
    public void createAndPost(String endpoint, Measurement measurement, MeasurementResultListener listener) {
        created = true;
        // measurement.result would normally be set here.
        listener.onResult(measurement);
    }

    @Override
    public void putRemoteAudio(Measurement measurement, MeasurementResultListener listener) {
        putAudio = true;
        listener.onResult(measurement);
    }

    @Override
    public void setSummaryListener(MeasurementSummaryListener listener) {}

    @Override
    public void setUseTestServer(boolean useTestServer) {}

    @Override
    public boolean isUnderTest() { return true; }

    @Override
    public void stopBackgroundThread() {

    }

    @Override
    public void setSyncListener(SyncListener listener) {}

    @Override
    public void startBackgroundThread() {

    }

    public static MockMeasurementDataManager getInstance(String appId) {
        if(sInstance == null) {
            sInstance = new MockMeasurementDataManager(appId);
        }
        return sInstance;
    }


    private int mNumFailedMeasurements = 0;

    public void addFailedMeasurement() {
        mNumFailedMeasurements++;
    }
    public int getNumFailedMeasurements() {
        return mNumFailedMeasurements;
    }

    @Override
    public void logMessage(String appId, String mtype, String message) {}

    @Override
    public boolean isBusy(Measurement measurement) {
        return false;
    }

    @Override
    public void setBusy(Measurement measurement, boolean busy) {

    }

    @Override
    public void setBusyAudio(Measurement measurement, boolean busy) {

    }

    @Override
    public boolean removeFromCache(MeasurementId id) throws IOException {
        return false;
    }

    @Override
    public void startBackgroundSync() {

    }
}
