import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;

public class TiltSensor implements SensorEventListener {

    private boolean needsRecalc = false;
	private float[] tilt_data = { 0, 0, 0 }, accelerometer = { 0, 0, 0 }, magnetic_field = { 0, 0, 0 }, old_acc = { 0, 0, 0 }, dampened_acc = { 0, 0, 0 };
	private boolean tiltAvailble = false;
	private final SensorManager man;
	final Sensor mag_sensor;
	final Sensor acc_sensor;
	boolean has_mag;
	boolean has_acc;

	// Change this to make the sensors respond quicker, or slower:
	private static final int delay = SensorManager.SENSOR_DELAY_GAME;

	//Call this from your activity to hook listeners if the were removed in pause()
	public void resume() {
		has_mag = man.registerListener(this, mag_sensor, delay);
		has_acc = man.registerListener(this, acc_sensor, delay);
		if (!has_mag && !has_acc) {
			pause();
		}
	}

	//Call this from your activity to save battery while app is not in foreground
	public void pause() {
		man.unregisterListener(this);
	}

	// Special class used to handle sensor events:
	@Override
	public void onSensorChanged(SensorEvent e) {
		final float[] vals = e.values;
		final int type = e.sensor.getType();
		switch (type) {
			case (Sensor.TYPE_ACCELEROMETER): {
				needsRecalc = true;
				if (!has_mag) {
					System.arraycopy(accelerometer, 0, old_acc, 0, 3);
				}
				System.arraycopy(vals, 0, accelerometer, 0, 3);
				if (!has_mag) {
					for (int i = 0; i < 3; i++) {
						//Accumulate changes
						final float sensitivity = 0.08f;
						dampened_acc[i] += (accelerometer[i] - old_acc[i]) * sensitivity;
						//Even out drift over time
						dampened_acc[i] *= 0.99;
					}
				}
			}
				break;
			case (Sensor.TYPE_MAGNETIC_FIELD): {
				needsRecalc = true;
				System.arraycopy(vals, 0, magnetic_field, 0, 3);
			}
				break;
		}
	}

	@Override
	public void onAccuracyChanged(Sensor event, int res) {
	}

	// The constructor will use a context object to register itself for various inputs:
	public TiltSensor(Context c) {
		man = (SensorManager) c.getSystemService(Context.SENSOR_SERVICE);
		mag_sensor = man.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
		acc_sensor = man.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		//Sensor gyr_sensor = man.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
		resume();
		if (has_acc) {
			tiltAvailble = true;
			if (has_mag) {
				Log.d("TiltCalc", "Using accelerometer + compass.");
			}
			else {
				Log.d("TiltCalc", "Using only accelerometer.");
			}
		}
		else {
			tiltAvailble = false;
			Log.d("TiltCalc", "No acceptable hardware found, tilt not available.");
			//No use in having listeners registered
			pause();
		}
	}

	@Override
	protected void finalize() throws Throwable {
		pause();
		super.finalize();
	}

	public boolean isTiltAbailable() {
		return tiltAvailble;
	}

	// Will return the most up-to-date tilt data in the vals[] array
	public void getTilt(float[] vals) {
		// If some of the data has been changed, then we need to recalculate some things...
		if (needsRecalc) {
			if (has_acc) {
				if (has_mag) {
					float[] R = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
					// Calculate the rotation matrix, and use that to get the orientation:
					if (SensorManager.getRotationMatrix(R, null, accelerometer, magnetic_field)) {
						SensorManager.getOrientation(R, tilt_data);
					}
				}
				//Cheat by assuming user will be holding device in a certain way. 
				else {
					tilt_data[1] = dampened_acc[0];
				}
			}
			needsRecalc = false;
		}
		System.arraycopy(tilt_data, 0, vals, 0, 3);
	}

}
