package org.uoc.pac3.DroidEye;

import java.util.Locale;


import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.highgui.Highgui;
import org.uoc.pac3.DroidEye.R;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnTouchListener;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;



/**
 * @author Jordi Anducas Aregall
 *
 *Classe principal del DroidEye, en aquesta classe es controlen els següents elements
 *
 *-Adquisició d'imatges
 *-Processat d'imatges
 *-Control de la vibració
 *-Control de la veu
 *
 */
public class DroidEyeActivity extends Activity implements OnTouchListener, CvCameraViewListener, OnInitListener {
	private static final String  TAG              = "DroidEye::Activity";

	// protected PowerManager.WakeLock wakelock;

	private boolean              	mIsDebugSelected = false;				//Controlem si volem veure que està passant en l'aplicatiu
	private boolean             	mIsStartSelected = false;				//Comencem el processat de la imatge en el següent frame
	private boolean              	mIsDrawTime = true;						//Proces que redibuixa l'aplicació, els tiles
	private boolean              	mIsHapticSelected = true;				//Per activar la vibració del phone
	private boolean              	mIsProcessShowImageSelected = true;		//Per mostrar la imatge que s'ha processat
	private boolean             	mIsTorchSelected = false;				//Control de si la llum de la camara es pot activar
	private int             		mIsContourRecognizeSelected = 1;		//Sistema que controla quin tipus de process volem
																			//1.- Per Hough de Cercles
																			//2.- Per analisis del contorn d'una figura
																			//3.- Per Haar
																			
	
	//*****************************************************
	//Variable que conté el tipus de funcionament
	//1. Funcionament per defecte, Conta les monedes
	//2. Funcionament que reconeix quina és la moneda
	private int              		mTypeFunction = 1;
	//*****************************************************
	//Variable que conte la imatge del últim frame capturat
	private Mat                  	mImatge;
	//*****************************************************
	
	//Colors Tiles de Resultats,
	private Scalar                	mTileGeneral;			//Variable que emmagatzema el color del Tile General
	private Scalar                	mTileResults;			//Variable que emmagatzema el color del Tile de resultats. És on es mostren els logs
	private Scalar                	mTileProces;			//Variable que emmagatzema el color del Tile d'inici de process

	private Scalar					mTileProcesSize;		//Variable que emmagatzema el tamany del Tile de process, un 60% 
	private Scalar					mTileProcesSizeDebug;	//Variable que emmagatzema el tamany del Tile de process en mode debug, un 10% ja que volem veure la captura del frame 
	private Scalar					mTileResultsSize;		//Variable que emmagatzema el tamany del Tile de resultats, un 20% 

	private drawResults				mdrawresults;			//Variable de la classe que permet escriure en la pantalla
	private Vibracio 				mVibracio;				//Variable de la classe de vibració

	//*****************************************************
	//TTS objecte
	private TextToSpeech mTTS;
	//estat TTS check code
	private int 					TTS_DATA_CHECK_CODE = 0;

	//*****************************************************
	//Variables que es creen per mostrar el menu
	private MenuItem             	mItemSettings = null;
	private MenuItem             	mItemCalibrate = null;
	private MenuItem             	mItemSwitchTypeFunctions = null;
	private MenuItem             	mItemUpdate = null; //Actualitzem valors
	//*****************************************************
	
	//Variable que conté la camera de l'android, solicita i mostra el resultats
	//mitjançant un callback propi
	private CameraBridgeViewBase 	mCameraView;
	
	//*****************************************************
	//Variable que procesa i emmagatzema els resultats
	LookforCoins 					HC = new LookforCoins(getApplicationContext());
	//*****************************************************
	

	private BaseLoaderCallback  mLoaderCallback = new BaseLoaderCallback(this) {
		@Override
		public void onManagerConnected(int status) {
			switch (status) {
			case LoaderCallbackInterface.SUCCESS:
			{
				Log.i(TAG, "OpenCV carregat correctament");
				mCameraView.enableView();
				mCameraView.setOnTouchListener(DroidEyeActivity.this);
				
			} break;
			default:
			{
				super.onManagerConnected(status);
			} break;
			}
		}
	};

	public DroidEyeActivity() {
		Log.i(TAG, "Nova instancia " + this.getClass());
	}

	/** S'inicia quan l'android carrega l'aplicació */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		Log.i(TAG, "called onCreate");
		super.onCreate(savedInstanceState);

		// carreguem el fitxer de preferències XML
		//Aquest fitxer s'accedeix atraves del settings de l'aplicació
		PreferenceManager.setDefaultValues(this,R.xml.preferences, false);

		//Iniciem el DroidEyes, només s'ha de veure l'aplicació
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

		setContentView(R.layout.droideye_surface_view);

		mCameraView = (CameraBridgeViewBase) findViewById(R.id.droideye_activity_surface_view);
		mCameraView.setCvCameraViewListener(this);

		
		//**************************************************************
		//Iniciem la clase de la vibració
		mVibracio=new Vibracio(getApplicationContext());
		

		//**************************************************************
		//check for TTS data
		Intent checkTTSIntent = new Intent();
		checkTTSIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
		startActivityForResult(checkTTSIntent, TTS_DATA_CHECK_CODE);
		//**************************************************************

		//evitar que la pantalla s'apagui
		//PowerManager pm=(PowerManager)getSystemService(Context.POWER_SERVICE);
		//this.wakelock=pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "etiqueta");
		//wakelock.acquire();

		//PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
		//wakelock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "My Tag");
		// wakelock.acquire();

		//**************************************************************
		//PREFERENCIES
		//**************************************************************
		UpdateSettings();
		//**************************************************************
		
		mVibracio.StateHaptic(mIsHapticSelected);
		HC.Show_Process_Image(mIsProcessShowImageSelected);
		
		mdrawresults = new drawResults();
		
		//afegim amb codi morse que estem en el primer pas
		mVibracio.Pas1();
	}


	//setup TTS
	public void onInit(int initStatus) {
		//check for successful instantiation
		if (initStatus == TextToSpeech.SUCCESS) 
		{
			mTTS.setLanguage(Locale.US);
			mTTS.setSpeechRate(0.95f);
			SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);

			if(sharedPrefs.getBoolean("perform_Tutorial_Messages", true))
			{
				String mstr=getString(R.string.Tutorial) + getString(R.string.Tutorial2);
				mTTS.speak(mstr, TextToSpeech.QUEUE_FLUSH, null);
			}
			else if(sharedPrefs.getBoolean("perform_Initial_Messages", true))
			{
				mTTS.speak(getString(R.string.welcome), TextToSpeech.QUEUE_FLUSH, null);
				//mTTS.speak("Hello Marc, Gisela, Elo, Aleix, how are you.", TextToSpeech.QUEUE_FLUSH, null);
			}
		}
		else if (initStatus == TextToSpeech.ERROR) {
			// To Speech failed...
		}
	}


	//act on result of TTS data check
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (requestCode == TTS_DATA_CHECK_CODE) {
			if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS)
			{
				//creem el TTS
				mTTS = new TextToSpeech(this, this);

			}
			else 
			{
				//sense dades, necessari instalar
				Intent installTTSIntent = new Intent();
				installTTSIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
				startActivity(installTTSIntent);
			}
		}
		
		//Si retornem alguna cosa dels settings volem tornar a dibuixar
		mIsDrawTime=true;
		
	}

	@Override
	public void onPause()
	{
		//this.wakelock.release();
		if (mCameraView != null)
			mCameraView.disableView();
		super.onPause();
	}

	@Override
	public void onResume()
	{
		super.onResume();
		//wakelock.acquire();
		OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
	}

	public void onDestroy() {
		
		//Close the Text to Speech Library
	    if(mTTS != null) {

	    	mTTS.stop();
	        mTTS.shutdown();
	        Log.d(TAG, "TTS Destroyed");
	    }
	    
		super.onDestroy();

		if (mCameraView != null)
			mCameraView.disableView();
	}

	public void onSaveInstanceState(Bundle icicle) {
		super.onSaveInstanceState(icicle);
		// this.wakelock.release();
	}


	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		Log.i(TAG, "called onPrepareOptionsMenu");
		//netegem el menu, ja que s'ha actualitzat alguns paràmetres
		menu.clear();
		
		mItemSettings = menu.add(getString(R.string.Settings));
		mItemCalibrate = menu.add(getString(R.string.Calibrate));
		
		String mstrType="";
		if(mTypeFunction==1) mstrType=getString(R.string.RecognizeCoins);
		else if(mTypeFunction==2) mstrType=getString(R.string.CountCoins);
		
		mItemSwitchTypeFunctions= menu.add(mstrType);
		
		mItemUpdate= menu.add(R.string.Update);
		
		return super.onPrepareOptionsMenu(menu);
	}


	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		Log.i(TAG, "called onOptionsItemSelected; selected item: " + item);

		if (item == mItemSettings) 
		{
			startActivityForResult(new Intent(this, SettingsActivity.class),1);
		}
		else if(item == mItemCalibrate)
		{

		}
		else if(item == mItemSwitchTypeFunctions)
		{
			if(mTypeFunction==1) 
			{
				//AQuesta funció és nova i es una modificació del OPENCV
				//s'ha de fer així per tenir acces al control del focus
				mCameraView.SetCaptureFocus(Highgui.CV_CAP_ANDROID_FOCUS_MODE_MACRO);
				mCameraView.SetTorchOn(mIsTorchSelected);
				mTypeFunction=2;
			}
			else if(mTypeFunction==2)
			{
				//AQuesta funció és nova i es una modificació del OPENCV
				//s'ha de fer així per tenir acces al control del focus
				mCameraView.SetCaptureFocus(Highgui.CV_CAP_ANDROID_FOCUS_MODE_CONTINUOUS_VIDEO);	
				mTypeFunction=1;
			}
			HC.RecognizeType(mTypeFunction);
			
		}
		else if(item == mItemUpdate)
		{

			//**************************************************************
			//PREFERENCIES
			//**************************************************************
			UpdateSettings();
			//**************************************************************
			mVibracio.StateHaptic(mIsHapticSelected);
			HC.Show_Process_Image(mIsProcessShowImageSelected);
		}

		return true;
	}


	public void onCameraViewStarted(int width, int height) {
		mImatge = new Mat(height, width, CvType.CV_8UC4);


		//Color del Tile d'iniciar el process
		mTileGeneral = 			new Scalar(20,70,150);
		mTileProces = 			new Scalar(255,85,45);
		mTileResults = 			new Scalar(100,60,170);

		//Percentage que ocupa el tile en la imatge
		mTileProcesSize = 		new Scalar(2,68,2,98);
		mTileProcesSizeDebug = 	new Scalar(55,68,40,60);
		mTileResultsSize =		new Scalar(2,98,70,98);



	}

	public void onCameraViewStopped() {
		mImatge.release();
	}

	/***************************************************************/
	//Base de l'infraestructura, a partir dels llocs que es toca en la pantalla
	//es comença a proessar les diferents escenes de la imatge
	/***************************************************************/
	public boolean onTouch(View v, MotionEvent event) {
		int cols = mImatge.cols();
		int rows = mImatge.rows();

		int xOffset = (mCameraView.getWidth() - cols) / 2;
		int yOffset = (mCameraView.getHeight() - rows) / 2;

		int x = (int)event.getX() - xOffset;
		int y = (int)event.getY() - yOffset;

		boolean controlProcess=false;
		
		Log.i(TAG, "Touch image coordinates: (" + x + ", " + y + ")");

		if ((x < 0) || (y < 0) || (x > cols) || (y > rows)) return false;


		/***************************************************************/
		//En el cas que el sistema hagi pulsat la zona de debug, habilitem o no tota la escena
		//Mirem que em tocat en el 10% superior de la imatge
		int offset_amplada=(mCameraView.getWidth()*90)/100;
		int offset_llargada=(mCameraView.getHeight()*10)/100;

		//Ara comprovem si la pulsació està situada en aquesta regió de la imatge
		if(x>offset_amplada&&y<offset_llargada)
		{
			String text;
			mIsDebugSelected=!mIsDebugSelected;
			if(mIsDebugSelected)	text=getString(R.string.debug_enabled);
			else					text=getString(R.string.debug_disabled);

			mTTS.speak(text, TextToSpeech.QUEUE_FLUSH, null);
			controlProcess=true;
		}
		
		/***************************************************************/
		//En el cas que el sistema hagi pulsat la zona de Tutorial de Tiles
		//Mirem que em tocat en el 10% superior de la imatge
		offset_amplada=(mCameraView.getWidth()*10)/100;
		offset_llargada=(mCameraView.getHeight()*10)/100;
		//Ara comprovem si la pulsació està situada en aquesta regió de la imatge
		if(x<offset_amplada&&y<offset_llargada)
		{
			String text;
			text=getString(R.string.Tutorial2);
			mTTS.speak(text, TextToSpeech.QUEUE_FLUSH, null);
			controlProcess=true;
		}
		
		/***************************************************************/
		//En el cas que el sistema hagi pulsat la zona de que mostra la imatge processada
		//Mirem que em tocat en el 10% superior de la imatge
		offset_amplada=(mCameraView.getWidth()*90)/100;
		offset_llargada=(mCameraView.getHeight()*90)/100;
		//Ara comprovem si la pulsació està situada en aquesta regió de la imatge
		if(x>offset_amplada&&y>offset_llargada)
		{
			mIsProcessShowImageSelected=!mIsProcessShowImageSelected;
			HC.Show_Process_Image(mIsProcessShowImageSelected);
			controlProcess=true;
		}

		/***************************************************************/
		//En aquest cas es comprova que la pulsació es troba en la zona 
		//d'inici del processat de la imatge
		Scalar mTile;
		if(mIsDebugSelected)	mTile=mTileProcesSizeDebug;
		else					mTile=mTileProcesSize;

		int offset_amplada_I=	(int) ((mCameraView.getWidth()*mTile.val[0])/100);
		int offset_llargada_I=	(int) ((mCameraView.getHeight()*mTile.val[2])/100);
		int offset_amplada_F=	(int) ((mCameraView.getWidth()*mTile.val[1])/100);
		int offset_llargada_F=	(int) ((mCameraView.getHeight()*mTile.val[3])/100);

		if((x>offset_amplada_I&&x<offset_amplada_F&&y>offset_llargada_I&&y<offset_llargada_F)&&!controlProcess)
		{
			//Sino estic en aquesta zona começarem a prosessar la imatge
			mIsStartSelected=true;    	
		}

		/***************************************************************/
		///Cormprovem si em de dir quin és el resultat
		mTile=mTileResultsSize;
		offset_llargada_I=		(int) ((mImatge.height()*mTileResultsSize.val[0])/100);
		offset_llargada_F=		(int) ((mImatge.height()*mTileResultsSize.val[1])/100);

		offset_amplada_I=		(int) ((mImatge.width()*mTileResultsSize.val[2])/100);
		offset_amplada_F=		(int) ((mImatge.width()*mTileResultsSize.val[3])/100);
		
		if((x>offset_amplada_I&&x<offset_amplada_F&&y>offset_llargada_I&&y<offset_llargada_F)&&!controlProcess)
		{
			//Repetim els resultats obtinguts en el processat
			String mstr="";
			
			if(mIsContourRecognizeSelected==1)
			{
				mstr=getString(R.string.resultats_p2_1) + HC.ListCoins.size() + getString(R.string.resultats_p2_2);
			}
			else if(mIsContourRecognizeSelected==2)
			{
				if(HC.ListCoins.size()>0)
					mstr=getString(R.string.resultats_p2_3) + " " + HC.getCoinResults();
				else
					mstr=getString(R.string.resultats_p2_4);
			}
			
			mTTS.speak(mstr, TextToSpeech.QUEUE_FLUSH, null);
		}
		/***************************************************************/
		//encenem o apaguem la llum, 10% inferior
		offset_amplada=(mCameraView.getWidth()*10)/100;
		offset_llargada=(mCameraView.getHeight()*90)/100;
		
		if(x<offset_amplada&&y>offset_llargada)
		{
			mIsTorchSelected=!mIsTorchSelected;
			mCameraView.SetTorchOn(mIsTorchSelected);
		}
		
		return false; 
	}

	public Mat onCameraFrame(Mat inputFrame) 
	{
		//**************************************************************
		//Comencem el processat en el següent frame
		if(mIsStartSelected||mIsDebugSelected||mIsDrawTime)
		{

			inputFrame.copyTo(mImatge);

			//No executem, només dibuixem els tiles
			if(!mIsDrawTime)
			{
				//Diem si em d'utilitzar la torch
				HC.extraLight(mIsTorchSelected);
				
				if(mIsContourRecognizeSelected==2/*&&mTypeFunction==2*/)
					HC.ProcessContours(mImatge);
				else if(mIsContourRecognizeSelected==1)
					HC.ProcessHough(mImatge);
				else if(mIsContourRecognizeSelected==3)
					HC.ProcessSurfDetector(mImatge);
				else
					Log.i(TAG, "mIsContourRecognizeSelected == 3 not developed...");
				
				//No volem escolatr els resultats
				if (!mIsDebugSelected) 
				{
					//speak straight away
					String mstr="";
					if(mIsContourRecognizeSelected==1)
					{
						mstr=getString(R.string.resultats_p2_1) + HC.ListCoins.size() + getString(R.string.resultats_p2_2);
					}
					else if(mIsContourRecognizeSelected==2)
					{
						mstr=getString(R.string.resultats_p2_3) + " " + HC.getCoinResults();
					}
					mTTS.speak(mstr, TextToSpeech.QUEUE_FLUSH, null);
					mVibracio.Inici_proces();
				}
				
			}

			
			//mIsDrawTime=false;

			//**************************************************************
			//El següent codi és per colocar el Tile d'inici de l'aplicatiu
			//**************************************************************
			Scalar mTile;
			if (mIsDebugSelected) {
				//Ens possem a la zona de Debug, ens serveix per poder veure la imatge que es processa
				mTile=mTileProcesSizeDebug;

			}
			else
			{
				//Aquest és el Tile General, tapa tot el Layer de l'aplicatiu
				Mat posTile0 = mImatge.submat(0, mImatge.height(), 0, mImatge.width());
				posTile0.setTo(mTileGeneral);

				mTile=mTileProcesSize;
			}
			//**************************************************************
			//Posició del Tile Resultats
			int offset_altura_inici_R=		(int) ((mImatge.height()*mTileResultsSize.val[0])/100);
			int offset_altura_fi_R=			(int) ((mImatge.height()*mTileResultsSize.val[1])/100);

			int offset_amplada_inici_R=		(int) ((mImatge.width()*mTileResultsSize.val[2])/100);
			int offset_amplada_fi_R=		(int) ((mImatge.width()*mTileResultsSize.val[3])/100);
			//**************************************************************
			//Ara posem el Tile dels Resultats
			//**************************************************************
			Mat posTile2 = mImatge.submat(offset_altura_inici_R, offset_altura_fi_R, offset_amplada_inici_R, offset_amplada_fi_R);
			posTile2.setTo(mTileResults);
			//**************************************************************	        
			//Posició del Tile de Inici
			int offset_amplada_inici=	(int) ((mImatge.width()*mTile.val[0])/100);
			int offset_amplada_fi=		(int) ((mImatge.width()*mTile.val[1])/100);

			int offset_altura_inici=	(int) ((mImatge.height()*mTile.val[2])/100);
			int offset_altura_fi=		(int) ((mImatge.height()*mTile.val[3])/100);
			//**************************************************************
			//Ara posem el Tile d'inici de proces
			//**************************************************************
			Mat posTile1 = mImatge.submat(offset_altura_inici, offset_altura_fi, offset_amplada_inici, offset_amplada_fi);
			posTile1.setTo(mTileProces);



			if(!mIsDrawTime)
			{
				//**************************************************************
				// Mostrem el resultat per pantalla sobre del tile
				//**************************************************************
				mdrawresults.DrawText(mImatge, "Variables Logs, " + HC.ListCoins.size() + " coins", new Point(offset_amplada_inici_R+10, offset_altura_inici_R+20));
				mdrawresults.DrawList(mImatge,HC.ListCoins, new Point(offset_amplada_inici_R+20, offset_altura_inici_R+60), 30);
				//**************************************************************
				//Mostrem informació del tipus de processat
				if (mIsDebugSelected) {
					String mstrType="";
					if(mTypeFunction==2) mstrType=getString(R.string.RecognizeCoins);
					else if(mTypeFunction==1) mstrType=getString(R.string.CountCoins);
						
					mdrawresults.DrawText(mImatge, "Process Type: " + mIsContourRecognizeSelected + "  " + mstrType, new Point(30, 25),0.8,new Scalar(0, 127, 255, 255),2);
				}
			}
			
			mIsDrawTime=false;
			mIsStartSelected=false;
		}
		return mImatge;
	}

	
	private void UpdateSettings()
	{
		SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
		//Comencem amb el sistema en debug
		
		try {
			mIsDebugSelected=sharedPrefs.getBoolean("Debug_start", false);
			mIsHapticSelected=sharedPrefs.getBoolean("haptic_Messages", true);
			mIsProcessShowImageSelected=sharedPrefs.getBoolean("Debug_ProcessShowImage", false);
			mIsTorchSelected=sharedPrefs.getBoolean("uses_torch", false);
			mIsContourRecognizeSelected=Integer.parseInt(sharedPrefs.getString("Type_ContourRecognize", "1"));
			
			mTypeFunction=Integer.parseInt(sharedPrefs.getString("mTypeFunction", "1"));
			HC.RecognizeType(mTypeFunction);
		}
		catch (Exception e) 
		{
			Log.e(TAG, "Error "+e.toString()); 
		}
	}
	
	

}