package de.dennisguse.opentracks.sensors;

import android.content.Context;
import android.os.Handler;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import de.dennisguse.opentracks.data.models.AtmosphericPressure;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.driver.BarometerInternal;

/**
 * Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
 */
public class AltitudeSumManager implements SensorConnector {

    private static final String TAG = AltitudeSumManager.class.getSimpleName();

    private final BarometerInternal driver;

    private AtmosphericPressure lastAcceptedSensorValue;

    private AtmosphericPressure lastSeenSensorValue;

    private Float altitudeGain_m;
    private Float altitudeLoss_m;

    public AltitudeSumManager() {
        driver = new BarometerInternal();
    }

    @VisibleForTesting
    public AltitudeSumManager(BarometerInternal mock) {
        this.driver = mock;
    }

    public void start(Context context, Handler handler) {
        driver.connect(context, handler, this);

        lastAcceptedSensorValue = null;
        reset();
    }

    public void stop(Context context) {
        Log.d(TAG, "Stop");

        driver.disconnect(context);
        reset();
    }

    public void fill(@NonNull TrackPoint trackPoint) {
        trackPoint.setAltitudeGain(altitudeGain_m);
        trackPoint.setAltitudeLoss(altitudeLoss_m);
    }

    @Nullable
    public Float getAltitudeGain_m() {
        return driver.isConnected() ? altitudeGain_m : null;
    }

    @VisibleForTesting
    public void setAltitudeGain_m(float altitudeGain_m) {
        this.altitudeGain_m = altitudeGain_m;
    }

    @Nullable
    public Float getAltitudeLoss_m() {
        return driver.isConnected() ? altitudeLoss_m : null;
    }

    @VisibleForTesting
    public void setAltitudeLoss_m(float altitudeLoss_m) {
        this.altitudeLoss_m = altitudeLoss_m;
    }

    public void reset() {
        Log.d(TAG, "Reset");
        altitudeGain_m = null;
        altitudeLoss_m = null;
    }

    public void onSensorValueChanged(AtmosphericPressure currentSensorValue) {
        if (lastAcceptedSensorValue == null) {
            lastAcceptedSensorValue = currentSensorValue;
            lastSeenSensorValue = currentSensorValue;
            return;
        }

        altitudeGain_m = altitudeGain_m != null ? altitudeGain_m : 0;
        altitudeLoss_m = altitudeLoss_m != null ? altitudeLoss_m : 0;

        PressureSensorUtils.AltitudeChange altitudeChange = PressureSensorUtils.computeChangesWithSmoothing_m(lastAcceptedSensorValue, lastSeenSensorValue, currentSensorValue);
        if (altitudeChange != null) {
            altitudeGain_m += altitudeChange.getAltitudeGain_m();

            altitudeLoss_m += altitudeChange.getAltitudeLoss_m();

            lastAcceptedSensorValue = altitudeChange.currentSensorValue();
        }

        lastSeenSensorValue = currentSensorValue;

        Log.v(TAG, "altitude gain: " + altitudeGain_m + ", altitude loss: " + altitudeLoss_m);
    }
}
