package projectEQ2440.camera;

import java.io.IOException;

import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Looper;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * <b>public class Preview extends SurfaceView implements SurfaceHolder.Callback</b><br/>
 * Class which manage the Hardware Camera, the surface for the preview and the <i>Preview Thread</i> which creates the pictures for the decoding. 
 */
public class Preview extends SurfaceView implements SurfaceHolder.Callback {
	
	// Size of the Preview
	public static final int PREVIEW_WIDTH = 960;
	public static final int PREVIEW_HEIGHT = 720;
	private int CAMERA = Camera.CameraInfo.CAMERA_FACING_BACK;
	
	// the Surface on which the preview is installed
	private SurfaceHolder holder;
	
	// The HardWare Camera 
	private Camera camera;
	
	// Flag to take a picture
	private boolean memorize;
	
	// Flag to confirm the focusing
	private boolean focused;
	
	// Tell if it works in black and white or colors
	private int infoColor;
	
	// The callback for the returning pictures
	private MemorizeCallback memorizeCallback;
	
	// flag alive
	private boolean alive;
	
	/**
	 * <b>public Preview(Context context)</b><br/>
	 * Constructor initializing the flags and the surface
	 * 
	 * @param context : the context of the Activity
	 */
	public Preview(Context context) {
		super(context);
		PreviewPicture.setContext(context);
		
		// Install a SurfaceHolder.Callback so we get notified when the underlying surface is created and destroyed.
		holder = getHolder();
		holder.addCallback(this);
		
		infoColor = PreviewPicture.Info.BW;
		memorizeCallback = null;
		
		memorize = false;
		alive = false;
	}
	
	/**
	 * <b>public void memorize(boolean autoFocus)</b><br/>
	 * Ask to the <i>Preview Thread</i> to create a new picture
	 * 
	 * @param autoFocus : ask or not for the autofocus to be used
	 */
	public void memorize(boolean autoFocus) {
		if (camera == null) return;
		
		memorize = true;
		
		if (autoFocus && CAMERA == Camera.CameraInfo.CAMERA_FACING_BACK) {
			focused = false;
			camera.autoFocus(new Camera.AutoFocusCallback() {
				@Override
				public void onAutoFocus(boolean success, Camera camera) {
					if (success) focused = true;
					else memorize(true);
				}
			});
		} else {
			focused = true;
		}
	}
	
	// TODO comments to change
	/**
	 * <b>public void setMemorizeCallback(final PictureCallback pictureCallback)</b><br/>
	 * Set the callback for a black and white picture. The <i>Preview</i> will automatically set itself in black and white mode.
	 * 
	 * @param pictureCallback : the <i>PictureCallback</i> for returning the pictures
	 */
	public void setMemorizeCallback(MemorizeCallback memorizeCallback) {
		this.memorizeCallback = memorizeCallback;
		
		if (camera == null) return;
		
		if (memorizeCallback == null) camera.setPreviewCallback(null);
		else camera.setPreviewCallback(new Camera.PreviewCallback() {
			@Override
			public void onPreviewFrame(byte[] data, Camera cam) {
				if (memorize && focused) {
					
					memorize = false;
					long time = System.currentTimeMillis();
					
					PreviewPicture.Info info = new PreviewPicture.Info(infoColor, time);
					PreviewPicture picture = new PreviewPicture(data, PREVIEW_WIDTH, PREVIEW_HEIGHT, info);
					
					Preview.this.memorizeCallback.pictureReady(picture);
				}
			}
		});
	}
	
	// TODO comments
	public void setInfoRGB() {
		infoColor = PreviewPicture.Info.RGB;
	}
	
	// TODO comments
	public void setInfoBW() {
		infoColor = PreviewPicture.Info.BW;
	}
	
	/**
	 * <b>public void flashOn()</b><br/>
	 * Turn the flash on
	 */
	public void flashOn() {
		Camera.Parameters params = camera.getParameters();
		params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
		camera.setParameters(params);
	}

	/**
	 * <b>public void flashOff()</b><br/>
	 * Turn the flash off
	 */
	public void flashOff() {
		Camera.Parameters params = camera.getParameters();
		params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
		camera.setParameters(params);
	}
	
	/**
	 * <b>public void flashing()</b><br/>
	 * Turn the flash on for 50 milliseconds.
	 */
	public void flashing() {
		(new Thread() {
			public void run() {
				try {
					flashOn();
					Thread.sleep(50);
					flashOff();
				} catch (InterruptedException e) {
					flashOff();
					e.printStackTrace();
				}
			}
		}).start();
	}

	/**
	 * <b>public void surfaceCreated(SurfaceHolder holder)</b><br/>
	 * Function called at the creation of the surface. It launches the <i>Preview Thread</i> with a new <i>Thread</i>.  
	 */
	@Override 
	public void surfaceCreated(SurfaceHolder holder) {
		born();
	}
	

	/**
	 * <b>public void surfaceDestroyed(SurfaceHolder holder)</b><br/>
	 * Function called at the destruction of the surface. It kills the <i>Preview Thread</i>, releasing the camera.  
	 */
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		kill();
	}
	
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
		
	}
	
	/**
	 * <b>private static interface MemorizeCallback</b><br/>
	 * Common superInterface for the callbacks
	 */
	public static interface MemorizeCallback {
		public void pictureReady(PreviewPicture picture);
	}
	
	/**
	 * <b>protected Thread cameraThread</b><br/>
	 * Thread managing the intialization, the release and the callbacks of <i>Camera</i> 
	 */
	protected Thread cameraThread;
	
	/**
	 * <b>protected Looper cameraLooper</b><br/>
	 * Looper which manages the event on the thread which manages the camera
	 */
	protected Looper cameraLooper;
	 
	/**
	 * <b>public void born()</b><br/>
	 * create the <i>cameraThread</i> and launch the <i>Camera</i> 
	 */
	public void born() {
		if (!alive) {
			alive = true;
			new CameraThread();
		}
	}
	
	/**
	 * <b>public void kill()</b><br/>
	 * release the <i>Camera</i> and kill the <i>cameraThread</i>
	 */
	public void kill() {
		if (cameraLooper != null && alive) {
			alive = false;
			cameraLooper.quit();
		}
	}
	
	/**
	 * <b>private class CameraThread implements Runnable</b><br/>
	 * Class which implements the runnable part of the thread for the camera
	 */
	private class CameraThread implements Runnable {
		
		/**
		 * <b>public CameraThread()</b><br/>
		 * Constructor initializing and launching <i>cameraTthread</i>
		 */
		public CameraThread() {
			cameraThread = new Thread(this,"Preview");
			cameraThread.start();
		}
		
		/**
		 * <b>public void run()</b><br/>
		 * <b>run()</b> function of <i>Thread</i> which implement a new call of <i>Camera</i>, initialized the <i>Looper</i> and wait for instruction.<br/>
		 * A sleeping time is implemented before calling the new <i>Camera</i> (waiting for a probable releasing) and before the resealing (waiting the destruction of the callbacks)
		 */
		@Override
		public void run() {
			// waiting value
			int timeWait = 300;
			try {
				Thread.sleep(2*timeWait);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			// Get the Looper and prepare it
			Looper.prepare();
			cameraLooper = Looper.myLooper();
			
			// create the camera
			camera = Camera.open(CAMERA);
			
			// Set the display for the preview
			try {
				camera.setPreviewDisplay(holder);
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			// Rotate the preview for use
			camera.setDisplayOrientation(90);
			
			// Set the parameters of the preview
			Camera.Parameters params = camera.getParameters();
			params.setPreviewSize(PREVIEW_WIDTH,PREVIEW_HEIGHT);
			params.setPreviewFormat(ImageFormat.YV12);
			params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
			camera.setParameters(params);
			
			// Start the preview 
			camera.startPreview();
			
			// Set the callback for a new picture
			setMemorizeCallback(memorizeCallback);
			
			// Wait for preview callback
			Looper.loop();
			
			// Stop preview in first
			camera.stopPreview();

			// Destroy Callback next
			holder.removeCallback((SurfaceHolder.Callback) Preview.this);
			
			// Wait before killing
			try {
				Thread.sleep(timeWait);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			// Destroy object next
			camera.release();
			camera = null;
			
		}
		
	}
}
