
package org.uoc.pac3.DroidEye;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Vector;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.features2d.DMatch;
import org.opencv.features2d.DescriptorExtractor;
import org.opencv.features2d.DescriptorMatcher;
import org.opencv.features2d.FeatureDetector;
import org.opencv.features2d.Features2d;
import org.opencv.features2d.KeyPoint;
import org.opencv.highgui.Highgui;
import org.opencv.imgproc.Imgproc;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;


/**
 * @author Jordi Anducas Aregall
 *
 *Classe principal de processat de la imatge. en aquesta classe es procesa
 *i s'obtenen els resultats
 *Hi ha dos metodes implementats:
 *	1.- Per Hough de Cercles
 *	2.- Per analisis del contorn d'una figura
 *
 *El segon métode es el que s'utilitza per reconeixement morfologic ja que
 *es molt més estable que el primer. es basa en l'analisis del contornde les figures
 *això permet descartar falses monedes
 *
 *
 */
public class LookforCoins {
	
	private static final String  TAG              = "DroidEye::Looking";
	
	
	public class Vec3f{
		float x;
		float y;
		float z;
	}
	
	public List<CoinResult> 	ListCoins = new ArrayList<CoinResult>();
	private int					mRecognizeType = 1;
	private int					mCalibrateCoin = 0;
	private boolean 			mIsProcessShowImageSelected = false;
	private boolean 			mIsextraLightSelected = false;
	private Context 			mcontext;
	
	LookforCoins(Context context)
	{
		mcontext=context;
	}
	
	public void RecognizeType(int Type)
	{
		mRecognizeType=Type;
	}
	
	public void Show_Process_Image(boolean IsProcessShowImageSelected )
	{
		mIsProcessShowImageSelected=IsProcessShowImageSelected ;
	}
	
	public void extraLight(boolean IsextraLightSelected )
	{
		mIsextraLightSelected=IsextraLightSelected ;
	}
	
	
	/**
	 * @param src
	 * La funció fa el processat de la imatge a traves del cercles de hough
	 * és un metode sencill i que estabilitza el resultats en distancies grans
	 * és un metode que si es vol mesurar la repetibilitat de la mesura
	 * no és aconsellable ja que genera modificacions d'aquestes degut
	 * a que el processat de les sombres no es tan exhaustiu
	 */
	public void ProcessHough(Mat src)
	{
				
		Mat src_gray = new Mat();
		Mat src_origen = new Mat();
		Mat roi=new Mat();
		
		roi = src.submat((int)(src.height()*0.05),(int)(src.height()*0.95),(int)(src.width()*0.01),(int)(src.width()*0.68));
		  
		
		src.copyTo(src_origen);
		 /// Convert it to gray
		Imgproc.cvtColor( roi, src_gray, Imgproc.COLOR_BGR2GRAY );

		if(!mIsextraLightSelected)
		{
			if(mRecognizeType==2)
			{
				//Apliquem un Blur de la imatge per truere el soroll
				Imgproc.GaussianBlur(src_gray, src_gray, new Size(7,7),2,2);
				Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(3,3));
				//obrim i tanquem, serveix per millorar la resposta a la normalització
				Imgproc.morphologyEx(src_gray, src_gray, Imgproc.MORPH_CLOSE, kernel);
				Imgproc.morphologyEx(src_gray, src_gray, Imgproc.MORPH_OPEN, kernel);
				//normalitzem les escales de la imatge
				Core.normalize(src_gray, src_gray, 0, 255, Core.NORM_MINMAX);

			}
			else
			{
				//Apliquem un Blur de la imatge per truere el soroll
				Imgproc.GaussianBlur(src_gray, src_gray, new Size(7,7),2,2);
				//normalitzem les escales de la imatge a nivells baixes de thresholdd
				Core.normalize(src_gray, src_gray, 0, 150, Core.NORM_MINMAX);
			}
			
		}
		else
		{
			//Apliquem un Blur de la imatge per truere el soroll
			Imgproc.GaussianBlur( src_gray, src_gray,new Size(9,9),2,2);
		}

		
		Mat circles = new Mat();
		//Radi que s'analitzen
		int maxRadius=150;
		int minRadius=30;
		
		//El tipus dos vol dir que s'activa la funció de la cmara macro, per tant estem més aprop i els tamanys canvien
		if(mRecognizeType==2)
		{
			maxRadius=400;
			minRadius=50;
			Imgproc.HoughCircles( src_gray, circles, Imgproc.CV_HOUGH_GRADIENT,2.0, src_gray.height()/10, 100, 100, minRadius, maxRadius);
		}
		else
		{
			Imgproc.HoughCircles( src_gray, circles, Imgproc.CV_HOUGH_GRADIENT,2.0, src_gray.height()/8, 100, 100, minRadius, maxRadius);
		}
		
				
		
		//Opció que mostra la imatge procesada per la sortida de la pantalla
		if(mIsProcessShowImageSelected)
		{
			Imgproc.cvtColor(src_gray, roi, Imgproc.COLOR_GRAY2BGR );
		}
		
		
		//reset previous results
		ListCoins.clear();
		
		/// Dibuixem els cercles detectats, els cercles es posem en una llista de classificació
		for( int i=0; i<circles.cols(); i++) 
		{
			double vCircle[]=circles.get(0,i);
			Point center = new Point(0,0);
			center.x=Math.round(vCircle[0]);
			center.y=Math.round(vCircle[1]);
			int	radius=(int) Math.round(vCircle[2]);
			
			// punt central
			Core.circle( roi, center,3,new Scalar(0,255,0),-1,8,0);
			// contorn del cercle
			Core.circle( roi, center, radius, new Scalar(0,0,255),3,8,0);
			
			
			//***************************************
			//Creem l'objecte coin
			Scalar mColorRegion;
			mColorRegion=new Scalar(255);
			mColorRegion=ColorCenterCircle(src_origen,center);
			
			//Creem la moneda de resultat
			CoinResult coins=new CoinResult(mcontext,center,radius, mColorRegion, mRecognizeType);
			//Introduim l'element a la llista de resultats
			ListCoins.add(coins);
			//****************************************
			
		}
		
		src = roi.clone();
		//Ordenem la seqüència de resultats
		Collections.sort(ListCoins, new CoinComparator());
	}
	
	
	/**
	 * @param src
	 * 
	 * Aquest métode, és molt més complicat que l'anterior. es basa en la obtenció dels contorns
	 * un cop obtingut s'analitza de forma individual i es fa un fit de l'ellipse que apareix.
	 * El metode permet calcular els moments, centre de masses, i conèixer la informació
	 * Aques métode és molt més estable. es la base del model de reconeixement per morfologia
	 * ja que permet una estabilitat en els radir molt superior.
	 * 
	 * A partir d'aquest es pot iniciar el haar o el surf.
	 * 
	 */
	public void ProcessContours( Mat src)
	{
		//Mat closedImage = new Mat();
		
	  Mat roi=new Mat();
	  roi = src.submat((int)(src.height()*0.05),(int)(src.height()*0.95),(int)(src.width()*0.01),(int)(src.width()*0.68));
	        

	  	
		
		Mat src_gray = new Mat();
		Mat src_origen = new Mat();
		
		roi.copyTo(src_origen);
		
		 /// Convert it to gray
		Imgproc.cvtColor( roi, src_gray, Imgproc.COLOR_BGR2GRAY );
		
		Imgproc.GaussianBlur(src_gray, src_gray, new Size(7,7),2,2);
		Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(3,3));
		Imgproc.morphologyEx(src_gray, src_gray, Imgproc.MORPH_CLOSE, kernel);
		Imgproc.morphologyEx(src_gray, src_gray, Imgproc.MORPH_OPEN, kernel);
		//Core.divide(src_gray, closedImage, src_gray);
		//Core.normalize(src_gray, src_gray, 0, 150, Core.NORM_MINMAX);
		//Imgproc.threshold(src_gray, src_gray, 0, 150, Imgproc.THRESH_BINARY|Imgproc.THRESH_OTSU);
		Imgproc.adaptiveThreshold(src_gray, src_gray, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY_INV, 15, 3);
		
		int iter=0;
		for(iter=0;iter<10;iter++)
			Imgproc.dilate(src_gray, src_gray, Imgproc.getStructuringElement(Imgproc.MORPH_DILATE, new Size(3,3))); 
		
		//********************************************************************
		List<MatOfPoint> contours1 = new ArrayList<MatOfPoint>();
		Core.bitwise_not(src_gray, src_gray);
		Mat src_gray_cpy = new Mat();
		src_gray.copyTo(src_gray_cpy);
		
		Imgproc.findContours(src_gray_cpy, contours1, new Mat(), Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE); 

		double maxArea = 4000;
		
		//Borrem totes les areas inferiors a maxArea, de color 255 
		Mat src_mask = Mat.zeros(src_gray.rows(), src_gray.cols(), CvType.CV_8U);

	    for( int idx = 0; idx<contours1.size();idx++ ){
	        double area = Imgproc.contourArea(contours1.get(idx));
	        if (area < maxArea) {
	        	Imgproc.drawContours(src_mask, contours1, idx, new Scalar(255), Core.FILLED);
	        }
	    }
	    Core.subtract(src_gray, src_mask, src_gray);
	    Core.bitwise_not(src_gray, src_gray);
	    
	  
		
		for(iter=0;iter<10;iter++)
			Imgproc.erode(src_gray, src_gray, Imgproc.getStructuringElement(Imgproc.MORPH_ERODE, new Size(3,3))); 
		
		
		//********************************************************************
		  //********************************************************************
		    //treiem els blancs
		   /* src_gray.copyTo(src_gray_cpy);
			
			Imgproc.findContours(src_gray_cpy, contours1, new Mat(), Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE); 

			maxArea = 4000;
			
			//Borrem totes les areas inferiors a maxArea, de color 255 
			Mat src_mask1 = Mat.zeros(src_gray.rows(), src_gray.cols(), CvType.CV_8U);

		    for( int idx = 0; idx<contours1.size();idx++ ){
		        double area = Imgproc.contourArea(contours1.get(idx));
		        if (area < maxArea) {
		        	Imgproc.drawContours(src_mask1, contours1, idx, new Scalar(255), Core.FILLED);
		        }
		    }
		    //Core.bitwise_not(src_gray, src_gray);
		    Core.subtract(src_gray, src_mask1, src_gray);
		    //Core.bitwise_not(src_gray, src_gray);*/
		  //********************************************************************
		
		//********************************************************************
		List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
		Mat hierarchy = new Mat();
		
		if(mIsProcessShowImageSelected)
		{
			Imgproc.cvtColor(src_gray, roi, Imgproc.COLOR_GRAY2BGR,4);
		}

		
		Imgproc.findContours(src_gray, contours, hierarchy,
				Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);
				 
		
		
		int minContour=200;
		int maxContour=700;
		
		int maxRadius=400;
		int minRadius=50;
		
		minContour=200;
		maxContour=1000;		
		maxRadius=800;
		minRadius=150;
		
		//Donem més facilitats de busqueda de punts petits
		if(mRecognizeType==1)
		{
			minContour=50;
			maxContour=500;
			
			maxRadius=250;
			minRadius=30;
		}
		
		//reset previous results
		ListCoins.clear();
				
		Imgproc.drawContours(roi, contours, -1, new Scalar(0, 255, 0), 2);
		
		for (int idx = 0; idx < contours.size(); idx++) {
			
			Mat contour = contours.get(idx);

			int num = (int) contour.total();
			
			if(num<maxContour&&num>minContour)
			{
				int buff[] = new int[num * 2]; 
				contour.get(0, 0, buff);
				
				//*******************************************
				Point center=new Point(0,0);
				//Calculem el centre de mases
				for (int q = 0; q < num * 2; q = q + 2) {
					center.x+=buff[q];
					center.y+=buff[q + 1];
				}
				center.x=center.x/num;
				center.y=center.y/num;
				// dibuixem el punt central
				//Core.circle( src, center,3,new Scalar(0,255,0),-1,8,0);
				//*******************************************

				List<Point> points=new ArrayList<Point>();
				MatOfPoint2f MatOfPoint = new MatOfPoint2f();
				
				for(int j=0;j<num*2;j=j+2)
		    		points.add(new Point(buff[j], buff[j+1]));
				
				MatOfPoint.fromList(points);
				RotatedRect bound=Imgproc.fitEllipse(MatOfPoint);
				Point e_center=bound.center;
				e_center.x=(int)e_center.x;
				e_center.y=(int)e_center.y;
				//*******************************************
				int radius=(int) ((bound.size.height+bound.size.width)/2);
				
				//Mirem si el radi obtingut és compatible amb el que esprem trobar	
				if(radius<maxRadius&&radius>minRadius)
				{
					Scalar mColorRegion;
					mColorRegion=new Scalar(255);
					mColorRegion=ColorCenterCircle(src_origen,e_center);
					
					//Creem la moneda de resultat
					CoinResult coins=new CoinResult(mcontext,e_center,radius, mColorRegion, mRecognizeType);
					
					ListCoins.add(coins);
					
					Core.circle( roi, e_center,3,new Scalar(0,0,255),-1,8,0);
					Core.ellipse(roi, bound, new Scalar(0,0,255),3);
				}
				else
				{
					Core.circle( roi, e_center,3,new Scalar(255,0,0),-1,8,0);
					Core.ellipse(roi, bound, new Scalar(255,0,0),3);
				}
				//*******************************************
			}
			
			
		}
		
		
		src = roi.clone();
		
		//Ordenem la seqüència de resultats
		Collections.sort(ListCoins, new CoinComparator());
		
		//Ara mirem si és una moneda de calibració
		//en el cas que existeixi només una, grabarem l'escala resultant de la moneda
		if(mCalibrateCoin==1)
		{
			if(ListCoins.size()==1)
			{
				CoinResult mCoin=ListCoins.get(0);
				mCoin.isCalibratedCoin(mCoin.getCoin(),"50c");
			}
			mCalibrateCoin=0;
		}

	}
	
	public void setCalibrateCoin(int CalibrateCoin)
	{
		mCalibrateCoin=CalibrateCoin;
	}
	
	public void ProcessSurfDetector( Mat src)
	{
		//scene = query; object = train
		//reset previous results
				ListCoins.clear();
				
		//FeatureDetector detector = FeatureDetector.create(FeatureDetector.SURF);
	    FeatureDetector detector = FeatureDetector.create(FeatureDetector.FAST);
	    Imgproc.cvtColor(src, src, Imgproc.COLOR_RGBA2RGB);
	    
	    
	    MatOfKeyPoint keypointsScene = new MatOfKeyPoint();

	    Mat roi=new Mat();
	    Rect r = new Rect((int)(src.height()*0.05),(int)(src.height()*0.95),(int)(src.width()*0.01),(int)(src.width()*0.68));
	    roi =  src.submat((int)(src.height()*0.05),(int)(src.height()*0.95),(int)(src.width()*0.01),(int)(src.width()*0.68));
	    
	    detector.detect(roi, keypointsScene);
	    
		/*List<KeyPoint> v = keypointsScene.toList();
		for (KeyPoint keyPoint : v) {	 
			Core.circle(roi, new Point(keyPoint.pt.x, keyPoint.pt.y), 10, new Scalar(0, 255, 255));
		}*/
	    
		Mat image50 = Highgui.imread("/sdcard/DroidEye/Detection/cent50.jpg");
		Mat image5 = Highgui.imread("/sdcard/DroidEye/Detection/cent5.jpg");

		DescriptorExtractor extractor = DescriptorExtractor.create(DescriptorExtractor.FREAK);
		Mat descriptors_Scene = new Mat(), descriptors_Object = new Mat();
		extractor.compute(roi, keypointsScene, descriptors_Scene);
		//Log.i(TAG, "keypoints1===" + keypointsScene.toString());
		//Log.i(TAG, "descriptors_1===" + descriptors_Scene.toString());

		
		MatOfKeyPoint keypointsObject = new MatOfKeyPoint();
		detector.detect(image5, keypointsObject);
		extractor.compute(image5, keypointsObject, descriptors_Object);
		//Log.i(TAG, "keypoints2===" + keypointsObject.toString());
		//Log.i(TAG, "descriptors_2===" + descriptors_Object.toString());

		List<KeyPoint> v = keypointsScene.toList();
		int sizeKPS=v.size();
		v = keypointsObject.toList();
		int sizeKPO=v.size();
		
		if(sizeKPS>0&&sizeKPO>0)
		{
			DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
			MatOfDMatch matches_tmp = new MatOfDMatch();
			matcher.add(Arrays.asList(descriptors_Scene));
			matcher.train();

			matcher.match(descriptors_Object,matches_tmp);
			//Log.i(TAG, "matches_tmp===" + matches_tmp.toString());

			// filter
			List<DMatch> matches = matches_tmp.toList();
			double max_dist = 0;
			double min_dist = 100000;
			int rowCount = matches.size();
			for (int i = 0; i < rowCount; i++) {
				double dist = matches.get(i).distance;
				
				//Log.i(TAG, "distance dist===" + dist);
				if (dist < min_dist)
					min_dist = dist;
				if (dist > max_dist)
					max_dist = dist;
			}

			//Log.i(TAG, "rowCount===" + rowCount + "max_dist= " + max_dist
			//		+ " min_dist=" + min_dist);
			List<DMatch> good_matches = new ArrayList<DMatch>();
			double good_dist = 3 * min_dist;

			KeyPoint qkp[] = keypointsScene.toArray();
			KeyPoint okp[] = keypointsObject.toArray();
			 
			for (int i = 0; i < rowCount; i++) {
				if (matches.get(i).distance < good_dist) {
					good_matches.add(matches.get(i));

					//Log.i(TAG, "good_matches......."
					//		+ matches.get(i).trainIdx);
				}
			}
			
			int good_matches_size = good_matches.size();
	        
			for(int i=0; i<good_matches_size; i++){
			  DMatch imatch = good_matches.get(i);
			  //obj.add(refImagesKey.get(imatch.imgIdx).get(imatch.queryIdx).pt);
			 // scene.add(sceneKeyPoints.get(imatch.trainIdx).pt);
			  Core.circle(roi, qkp[imatch.trainIdx].pt, 8,
					  new Scalar(255, 0, 255), 1);
			  
			}

		}

		
		
		src = roi.clone();
		Scalar color=new Scalar(0,255,0);
		Core.rectangle(src, r.tl(), r.br(), color, 3);

		

		
	}
	

	/**
	 * La següent funció permet extreure d'una regió de la imatge
	 * el seu color en especific
	 */
	private Scalar ColorCenterCircle(Mat src, Point Center) 
	{
        int cols = src.cols();
        int rows = src.rows();

        int xOffset = (src.width() - cols) / 2;
        int yOffset = (src.height() - rows) / 2;

        int x = (int)Center.x - xOffset;
        int y = (int)Center.y - yOffset;

       

        if ((x < 0) || (y < 0) || (x > cols) || (y > rows)) return new Scalar(0,0,0);

        Rect touchedRect = new Rect();

        touchedRect.x = (x>8) ? x-8 : 0;
        touchedRect.y = (y>8) ? y-8 : 0;

        touchedRect.width = (x+8 < cols) ? x + 8 - touchedRect.x : cols - touchedRect.x;
        touchedRect.height = (y+8 < rows) ? y + 8 - touchedRect.y : rows - touchedRect.y;

        Mat touchedRegionRgba = src.submat(touchedRect);

        Mat touchedRegionHsv = new Mat();
        Imgproc.cvtColor(touchedRegionRgba, touchedRegionHsv, Imgproc.COLOR_RGB2HSV_FULL);

        Scalar mBlobHsv,mBlobRgba;
        mBlobHsv = new Scalar(255);
        mBlobRgba = new Scalar(255);
        
        // Calculate average color of touched region
        mBlobHsv = Core.sumElems(touchedRegionHsv);
        int pointCount = touchedRect.width*touchedRect.height;
        for (int i = 0; i < mBlobHsv.val.length; i++)
        	mBlobHsv.val[i] /= pointCount;

        mBlobRgba = converScalarH2R(mBlobHsv);

        return mBlobRgba;
	}

	private Scalar converScalarH2R(Scalar hColor) {
        Mat pointMatR = new Mat();
        Mat pointMatH = new Mat(1, 1, CvType.CV_8UC3, hColor);
        Imgproc.cvtColor(pointMatH, pointMatR, Imgproc.COLOR_HSV2RGB_FULL, 4);

        return new Scalar(pointMatR.get(0, 0));
    }

	
	public String getCoinResults()
	{
		String mstr="";
		
		for (int i = 0; i < ListCoins.size(); i++) 
		{

			mstr= mstr + ListCoins.get(i).GetNameCoin() + ", ";

		}

		return mstr;
	}
	
}
