/**
 * Copyright 2012 52°North Initiative for Geospatial Open Source Software GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.n52.geoar.ar.view.gl;

import java.util.List;

import org.n52.geoar.R;
import org.n52.geoar.ar.view.ARObject;
import org.n52.geoar.ar.view.ARView;
import org.n52.geoar.ar.view.gl.ARSurfaceViewRenderer.IRotationMatrixProvider;
import org.n52.geoar.ar.view.gl.ARSurfaceViewRenderer.OnInitializeInGLThread;
import org.n52.geoar.tracking.camera.RealityCamera;
import org.n52.geoar.tracking.location.AdaptiveLowPassSensorBuffer;
import org.n52.geoar.tracking.location.LocationHandler;
import org.n52.geoar.tracking.location.SensorBuffer;
import org.n52.geoar.tracking.location.LocationHandler.OnLocationUpdateListener;
import org.n52.geoar.view.InfoView;

import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.view.Display;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.view.View.MeasureSpec;

/**
 * View to show virtual information based on the camera's settings. It also
 * performs sensor based tracking
 * 
 * @author Arne de Wall
 * @author Holger Hopmann
 */
public class ARSurfaceView extends GLSurfaceView implements
		SensorEventListener, IRotationMatrixProvider, OnLocationUpdateListener {

	// Sensor related
	private SensorBuffer magnetValues = new AdaptiveLowPassSensorBuffer(3, 1,
			10, 0.002f, 0.1f); // new LowPassSensorBuffer(3, 0.05f);
	private SensorBuffer accelValues = new AdaptiveLowPassSensorBuffer(3, 0.5f,
			4, 0.01f, 0.15f); // new LowPassSensorBuffer(3, 0.15f);
	private Sensor magnet, accel;
	private SensorManager mSensorManager;
	private Display display;

	// Arrays to maintain transformation matrices
	private float[] rotMatrixSensor = new float[16];
	private float[] rotMatrix = new float[16];

	private ARSurfaceViewRenderer renderer;

	private boolean updateMagneticVector = true;
	private boolean sensorValuesChanged;

	private Location mLastLocation;
	private Object locationInfoKey = new Object();
	private ARView mARView;

	public ARSurfaceView(ARView arView) {
		super(arView.getContext());
		mARView = arView;
		init();
	}

	private void init() {
		// System services
		mSensorManager = (SensorManager) getContext().getSystemService(
				Context.SENSOR_SERVICE);
		magnet = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
		accel = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		display = ((WindowManager) getContext().getSystemService(
				Context.WINDOW_SERVICE)).getDefaultDisplay();

		mLastLocation = LocationHandler.getLastKnownLocation();

		renderer = new ARSurfaceViewRenderer(this, this);
		setEGLContextClientVersion(2);
		setEGLConfigChooser(new MultisampleConfigs());
		// XXX Does this support transparent background?

		// setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Forces to make translucent
		// drawing available
		getHolder().setFormat(PixelFormat.TRANSLUCENT);
		setRenderer(renderer);
	}

	/**
	 * Get a {@link NoiseGridValueProvider} to access the raw noise
	 * interpolation data currently in use
	 * 
	 * @return
	 */
	// public NoiseGridValueProvider getNoiseGridValueProvider() {
	// // return renderer;
	// return null;
	// }

	public float[] getRotationMatrix() {
		if (sensorValuesChanged) {
			computeRotationMatrix();
			sensorValuesChanged = false;
		}
		return rotMatrix;
	}

	/**
	 * Computes the Transformation from device to world coordinates
	 */
	private void computeRotationMatrix() {
		synchronized (rotMatrix) {
			if (magnetValues.hasValues() && accelValues.hasValues()) {
				SensorManager.getRotationMatrix(rotMatrixSensor, null,
						accelValues.get(), magnetValues.get());
				// transforms from sensor to world

				switch (display.getOrientation()) {
				case Surface.ROTATION_0:
					// No adjustment
					SensorManager.remapCoordinateSystem(rotMatrixSensor,
							SensorManager.AXIS_X, SensorManager.AXIS_Y,
							rotMatrix);
					break;
				case Surface.ROTATION_90:
					SensorManager.remapCoordinateSystem(rotMatrixSensor,
							SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X,
							rotMatrix);
					break;
				case Surface.ROTATION_180:
					SensorManager.remapCoordinateSystem(rotMatrixSensor,
							SensorManager.AXIS_MINUS_X,
							SensorManager.AXIS_MINUS_Y, rotMatrix);
					break;
				case Surface.ROTATION_270:
					SensorManager.remapCoordinateSystem(rotMatrixSensor,
							SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X,
							rotMatrix);
					break;
				}
				// transforms from device to world

				Matrix.rotateM(rotMatrix, 0, 90, 1, 0, 0);
				// Account for the upward usage of this app
			}
		}
	}

	@Override
	protected void onVisibilityChanged(View changedView, int visibility) {
		if (renderer != null) {
			if (isShown()) {
				onResume();
			} else {
				onPause();
			}
		}
		super.onVisibilityChanged(changedView, visibility);

	}

	@Override
	public void onPause() {
		RealityCamera.removeCameraUpdateListener(renderer);
		LocationHandler.removeLocationUpdateListener(this);
		mSensorManager.unregisterListener(this);

		InfoView.clearStatus(locationInfoKey);

		super.onPause();
	}

	@Override
	public void onResume() {
		RealityCamera.addCameraUpdateListener(renderer);
		LocationHandler.addLocationUpdateListener(this);
		if (!mSensorManager.registerListener(this, magnet,
				SensorManager.SENSOR_DELAY_GAME)) {
			InfoView.setStatus(R.string.magnetic_field_not_started, 5000,
					magnet);
		}
		if (!mSensorManager.registerListener(this, accel,
				SensorManager.SENSOR_DELAY_GAME)) {
			InfoView.setStatus(R.string.accel_not_started, 5000, accel);
		}

		getUserLocation();

		super.onResume();
	}

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		if (accuracy != SensorManager.SENSOR_STATUS_ACCURACY_HIGH) {
			if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
				InfoView.setStatus(R.string.magnetic_field_not_calibrated,
						5000, magnet);
			} else if (sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
				InfoView.setStatus(R.string.accel_not_calibrated, 5000, accel);
			}
		}
	}

	public void onSensorChanged(SensorEvent event) {
		if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
			if (updateMagneticVector) {
				magnetValues.put(event.values);
			}
		} else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
			accelValues.put(event.values);
		}
		sensorValuesChanged = true;
	}

	public Location getUserLocation() {
		if (mLastLocation == null) {
			InfoView.setStatus(R.string.waiting_for_location_fix, -1,
					locationInfoKey);
		} else {
			InfoView.clearStatus(locationInfoKey);
		}
		return mLastLocation;
	}

	@Override
	public void onLocationChanged(Location location) {
		mLastLocation = location;
		renderer.notifyLocationChanged();
	}

	public void notifyARObjectsChanged() {
		renderer.notifyARObjectsChanged();
	}

	public List<ARObject> getARObjects() {
		return mARView.getARObjects();
	}

}