package com.netfluke.sergey.accelintegrator;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

/*
 *   This code fails spectacularly. When at rest on a table, the phone
 *     still believes it is traveling, with increasing speed. See
 *     David Sachs, ""Sensor Fusion on Android Devices", at ~ 23:20 for why:
 *       https://www.youtube.com/watch?v=C7JQ7Rpwn2k
 *
 *   Note: you can add a calibration phase to detect the accelerometer biases (offsets)
 *     and then subtract them from the readings, as per
 *     https://developer.android.com/guide/topics/sensors/sensors_motion.html#sensors-motion-linear
 *     See how much it helps.
 */


public class MainActivity extends AppCompatActivity implements SensorEventListener {

    private SensorManager mSensorManager;
    private Sensor mAccel;

    float x, y, z; // integrated distances
    float vx, vy, vz; // integrated speeds
    float vx0, vy0, vz0; // speeds at a previous turn dT

    private static final float NS2S = 1.0f / 1000000000.0f; // nanoseconds to seconds conversion factor
    private float timestamp = 0;

    // low pass filtering
    float[] accl_lp = new float[3];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mAccel = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);

        x = y = z = 0f;
        vx = vy = vz = 0f;
        vx0 = vy0 = vz0 = 0f;
    }

    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.registerListener(this, mAccel, SensorManager.SENSOR_DELAY_GAME);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mSensorManager.unregisterListener(this, mAccel);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {

        float[] accl = new float[3];
        if (event.sensor == mAccel) {
            System.arraycopy(event.values, 0, accl, 0, event.values.length);
            Log.d("ACCL", "x: " + accl[0] + " y: " + accl[1] + " z: " + accl[2]);

            // Rounded-off values. Try rounding to a different number of decimal digits
            float xr = Math.round(accl[0]*100)/100;
            float yr = Math.round(accl[1]*100)/100;
            float zr = Math.round(accl[2]*100)/100;
            Log.d("ROUNDED", "x: " + xr + " y: " + yr + " z: " + zr);


            // Apply a low-pass filter to smooth the sensor jitter.
            //   This doesn't help the double integration drift problem,
            //    because it's caused by low-frequency noise.
            //
            // final float alpha = 0.8f;
            // accl_lp[0] = alpha * accl_lp[0] + (1 - alpha) * accl[0];
            // accl_lp[1] = alpha * accl_lp[1] + (1 - alpha) * accl[1];
            // accl_lp[2] = alpha * accl_lp[2] + (1 - alpha) * accl[2];

            // Unfiltered. Comment this out & uncomment the above to apply low-pass filtering
            accl_lp[0] = accl[0];
            accl_lp[1] = accl[1];
            accl_lp[2] = accl[2];

            if( timestamp != 0) {
                final float dT = (event.timestamp - timestamp) * NS2S;

                // integrate once, for speed
                vx += accl_lp[0] * dT;
                vy += accl_lp[1] * dT;
                vz += accl_lp[2] * dT;

                // integrate twice, for distance
                x += (vx+vx0)/2f * dT;
                y += (vy+vy0)/2f * dT;
                z += (vz+vz0)/2f * dT;

                // save previous values of speed
                vx0 = vx;
                vy0 = vy;
                vz0 = vz;
            }
            timestamp = event.timestamp;

            TextView t = findViewById(R.id.txt);
            t.setText( "x: " + x + "\ny: " + y + "\nz: " + z);


        } else
            Log.d("ACCL", "Not an acceleration sensor");
    }


    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // TODO Auto-generated method stub
    }

}
