package com.opencv.camera;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.opencv.camera.NativeProcessor.NativeProcessorCallback;
import com.opencv.camera.NativeProcessor.PoolCallback;

public class NativePreviewer extends SurfaceView implements
		SurfaceHolder.Callback, Camera.PreviewCallback, NativeProcessorCallback {
	SurfaceHolder mHolder;
	Camera mCamera;

	private NativeProcessor processor;

	private int preview_width, preview_height;
	private int pixelformat;
	private PixelFormat pixelinfo;

	public NativePreviewer(Context context,AttributeSet attributes){
		super(context,attributes);
		listAllCameraMethods();
		// Install a SurfaceHolder.Callback so we get notified when the
		// underlying surface is created and destroyed.
		mHolder = getHolder();
		mHolder.addCallback(this);
		mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
	
		this.preview_width = attributes.getAttributeIntValue("opencv", "preview_width", 600);
		this.preview_height= attributes.getAttributeIntValue("opencv", "preview_height", 600);

		processor = new NativeProcessor();
		
		 setZOrderMediaOverlay(false);
	}
	public NativePreviewer(Context context, int preview_width,
			int preview_height) {
		super(context);
	

		listAllCameraMethods();
		// Install a SurfaceHolder.Callback so we get notified when the
		// underlying surface is created and destroyed.
		mHolder = getHolder();
		mHolder.addCallback(this);
		mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

		this.preview_width = preview_width;
		this.preview_height = preview_height;

		processor = new NativeProcessor();
		 setZOrderMediaOverlay(false);

	}
	Handler camerainiter = new Handler();
	void initCamera(SurfaceHolder holder) throws InterruptedException{
		if(mCamera == null){
			// The Surface has been created, acquire the camera and tell it where
			// to draw.
			int i = 0;
			while(i++ < 5){
				try{
					mCamera = Camera.open();
					break;
				}catch(RuntimeException e){
					Thread.sleep(200);
				}
			}
			try {
				mCamera.setPreviewDisplay(holder);
			} catch (IOException exception) {
				mCamera.release();
				mCamera = null;
	
			}catch(RuntimeException e){
				Log.e("camera", "stacktrace", e);
			}
		}
	}
	void releaseCamera(){
		if(mCamera !=null){
			// Surface will be destroyed when we return, so stop the preview.
			// Because the CameraDevice object is not a shared resource, it's very
			// important to release it when the activity is paused.
			mCamera.stopPreview();
			mCamera.release();
		}

		// processor = null;
		mCamera = null;
		mAcb = null;
		mPCWB = null;
	}

	public void surfaceCreated(SurfaceHolder holder) {

		

	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		
		releaseCamera();

	}

	private boolean hasAutoFocus = false;
	public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

		try {
			initCamera(mHolder);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return;
		}
		
		
		// Now that the size is known, set up the camera parameters and begin
		// the preview.

		Camera.Parameters parameters = mCamera.getParameters();
		List<Camera.Size> pvsizes = mCamera.getParameters().getSupportedPreviewSizes();
		int best_width = 1000000;
		int best_height = 1000000;
		for(Size x: pvsizes){
			if(x.width - preview_width >= 0 && x.width <= best_width){
				best_width = x.width;
				best_height = x.height;
			}
		}
		preview_width = best_width;
		preview_height = best_height;
		List<String> fmodes = mCamera.getParameters().getSupportedFocusModes();
		
		
		int idx = fmodes.indexOf(Camera.Parameters.FOCUS_MODE_INFINITY);
		if(idx != -1){
			parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
		}else if(fmodes.indexOf(Camera.Parameters.FOCUS_MODE_FIXED) != -1){
			parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
		}

		if(fmodes.indexOf(Camera.Parameters.FOCUS_MODE_AUTO) != -1){
			hasAutoFocus  = true;
		}
		
		List<String> scenemodes = mCamera.getParameters().getSupportedSceneModes();
		if(scenemodes != null)
		if(scenemodes.indexOf(Camera.Parameters.SCENE_MODE_STEADYPHOTO) != -1){
			parameters.setSceneMode(Camera.Parameters.SCENE_MODE_STEADYPHOTO);
		}
		
		

		parameters.setPreviewSize(preview_width, preview_height);

		mCamera.setParameters(parameters);

		pixelinfo = new PixelFormat();
		pixelformat = mCamera.getParameters().getPreviewFormat();
		PixelFormat.getPixelFormatInfo(pixelformat, pixelinfo);

		Size preview_size = mCamera.getParameters().getPreviewSize();
		preview_width = preview_size.width;
		preview_height = preview_size.height;
		int bufSize = preview_width * preview_height * pixelinfo.bitsPerPixel
				/ 8;

		// Must call this before calling addCallbackBuffer to get all the
		// reflection variables setup
		initForACB();
		initForPCWB();

		// Use only one buffer, so that we don't preview to many frames and bog
		// down system
		byte[] buffer = new byte[bufSize];
		addCallbackBuffer(buffer);
		setPreviewCallbackWithBuffer();

		mCamera.startPreview();

		//postautofocus(0);
	}
	public void postautofocus(int delay) {
		if(hasAutoFocus)
			handler.postDelayed(autofocusrunner, delay);
		
	}
	private Runnable autofocusrunner = new Runnable() {
		
		@Override
		public void run() {
			mCamera.autoFocus(autocallback);
			
		}
	};
	
	Camera.AutoFocusCallback autocallback = new Camera.AutoFocusCallback() {
		
		@Override
		public void onAutoFocus(boolean success, Camera camera) {
			if(!success)
				postautofocus(1000);		
		}
	};
	Handler handler = new Handler();

	/**
	 * This method will list all methods of the android.hardware.Camera class,
	 * even the hidden ones. With the information it provides, you can use the
	 * same approach I took below to expose methods that were written but hidden
	 * in eclair
	 */
	private void listAllCameraMethods() {
		try {
			Class<?> c = Class.forName("android.hardware.Camera");
			Method[] m = c.getMethods();
			for (int i = 0; i < m.length; i++) {
				Log.d("NativePreviewer", "  method:" + m[i].toString());
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			Log.e("NativePreviewer", e.toString());
		}
	}

	/**
	 * These variables are re-used over and over by addCallbackBuffer
	 */
	Method mAcb;

	private void initForACB() {
		try {

			mAcb = Class.forName("android.hardware.Camera").getMethod(
					"addCallbackBuffer", byte[].class);

		} catch (Exception e) {
			Log
					.e("NativePreviewer",
							"Problem setting up for addCallbackBuffer: "
									+ e.toString());
		}
	}

	/**
	 * This method allows you to add a byte buffer to the queue of buffers to be
	 * used by preview. See:
	 * http://android.git.kernel.org/?p=platform/frameworks
	 * /base.git;a=blob;f=core/java/android/hardware/Camera.java;hb=9d
	 * b3d07b9620b4269ab33f78604a36327e536ce1
	 * 
	 * @param b
	 *            The buffer to register. Size should be width * height *
	 *            bitsPerPixel / 8.
	 */
	private void addCallbackBuffer(byte[] b) {

		try {

			mAcb.invoke(mCamera, b);
		} catch (Exception e) {
			Log.e("NativePreviewer", "invoking addCallbackBuffer failed: "
					+ e.toString());
		}
	}

	Method mPCWB;

	private void initForPCWB() {

		try {

			mPCWB = Class.forName("android.hardware.Camera").getMethod(
					"setPreviewCallbackWithBuffer", PreviewCallback.class);

		} catch (Exception e) {
			Log.e("NativePreviewer",
					"Problem setting up for setPreviewCallbackWithBuffer: "
							+ e.toString());
		}

	}

	/**
	 * Use this method instead of setPreviewCallback if you want to use manually
	 * allocated buffers. Assumes that "this" implements Camera.PreviewCallback
	 */
	private void setPreviewCallbackWithBuffer() {
		// mCamera.setPreviewCallback(this);
		// return;
		try {

			// If we were able to find the setPreviewCallbackWithBuffer method
			// of Camera,
			// we can now invoke it on our Camera instance, setting 'this' to be
			// the
			// callback handler
			mPCWB.invoke(mCamera, this);

			// Log.d("NativePrevier","setPreviewCallbackWithBuffer: Called method");

		} catch (Exception e) {

			Log.e("NativePreviewer", e.toString());
		}
	}

	protected void clearPreviewCallbackWithBuffer() {
		// mCamera.setPreviewCallback(this);
		// return;
		try {

			// If we were able to find the setPreviewCallbackWithBuffer method
			// of Camera,
			// we can now invoke it on our Camera instance, setting 'this' to be
			// the
			// callback handler
			mPCWB.invoke(mCamera, (PreviewCallback) null);

			// Log.d("NativePrevier","setPreviewCallbackWithBuffer: cleared");

		} catch (Exception e) {

			Log.e("NativePreviewer", e.toString());
		}
	}

	Date start;
	int fcount = 0;
	boolean processing = false;

	/**
	 * Demonstration of how to use onPreviewFrame. In this case I'm not
	 * processing the data, I'm just adding the buffer back to the buffer queue
	 * for re-use
	 */
	public void onPreviewFrame(byte[] data, Camera camera) {

		if (start == null) {
			start = new Date();
		}

	
		processor.post(data, preview_width, preview_height, pixelformat, System.nanoTime(),
				this);
				
		fcount++;
		if (fcount % 100 == 0) {
			double ms = (new Date()).getTime() - start.getTime();
			Log.i("NativePreviewer", "fps:" + fcount / (ms / 1000.0));
			start = new Date();
			fcount = 0;
		}

		

	}

	@Override
	public void onDoneNativeProcessing(byte[] buffer) {
		addCallbackBuffer(buffer);
	}

	public void addCallbackStack(LinkedList<PoolCallback> callbackstack) {
		processor.addCallbackStack(callbackstack);
	}

	/**This must be called when the activity pauses, in Activity.onPause
	 * This has the side effect of clearing the callback stack.
	 * 
	 */
	public void onPause() {
		
		releaseCamera();
		
		addCallbackStack(null);
		
		processor.stop();
		
		
		
		
		
	}

	public void onResume() {
	
		
		processor.start();
		
		
	}

}