package com.app3.pano;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
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.highgui.Highgui;
import org.opencv.imgproc.Imgproc;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.Toast;

public class PanoActivity extends Activity {

	private static final int SELECT_PICTURE = 1;
	private static final int SELECT_PICTURE_TO_MATCH = 2;
	private static final String  TAG = "Example::Pano::Activity";
	private String selectedImagePath;
	private int detectorID=FeatureDetector.FAST;
	private int descriptorID=DescriptorExtractor.ORB;

	Mat sampledImage=null;
	Mat originalImage=null;
	Mat imgToMatch=null;
	private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
		@Override
		public void onManagerConnected(int status) {
			switch (status) {
			case LoaderCallbackInterface.SUCCESS:
			{
				Log.i(TAG, "OpenCV loaded successfully");
				// Load native library after(!) OpenCV initialization
				System.loadLibrary("Pano");
			} break;
			default:
			{
				super.onManagerConnected(status);
			} break;
			}
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_pano);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.pano, menu);
		return true;
	}

	@Override
	public void onResume()
	{
		super.onResume();
		OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_8, this, mLoaderCallback);
	}

	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (resultCode == RESULT_OK) {
			if (requestCode == SELECT_PICTURE) {
				Uri selectedImageUri = data.getData();
				selectedImagePath = getPath(selectedImageUri);
				Log.i(TAG, "selectedImagePath: " + selectedImagePath);
				sampledImage=new Mat();
				loadImage(selectedImagePath,sampledImage);
				displayImage(sampledImage);
			}
			else if (requestCode == SELECT_PICTURE_TO_MATCH) {
				Uri selectedImageUri = data.getData();
				selectedImagePath = getPath(selectedImageUri);
				Log.i(TAG, "selectedImagePath: " + selectedImagePath);
				imgToMatch=new Mat();
				loadImage(selectedImagePath,imgToMatch);
			}
		}
	}
	private void displayImage(Mat image)
	{
		// convert to bitmap:
		Bitmap bitMap = Bitmap.createBitmap(image.cols(), image.rows(),Bitmap.Config.RGB_565);
		Utils.matToBitmap(image, bitMap);

		// find the imageview and draw it!
		ImageView iv = (ImageView) findViewById(R.id.PanoImageView);
		iv.setImageBitmap(bitMap);
	}
	private void loadImage(String path,Mat sampledImage)
	{
		originalImage = Highgui.imread(path);
		Mat rgbImage=new Mat();
		//sampledImage=new Mat();

		Imgproc.cvtColor(originalImage, rgbImage, Imgproc.COLOR_BGR2RGB);

		Display display = getWindowManager().getDefaultDisplay();
		Point size = new Point();
		display.getSize(size);

		int width = size.x;
		int height = size.y;

		double downSampleRatio=calculateSubSampleSize(rgbImage,width,height);

		Imgproc.resize(rgbImage, sampledImage, new Size(),downSampleRatio,downSampleRatio,Imgproc.INTER_AREA);

		try {
			ExifInterface exif = new ExifInterface(selectedImagePath);
			int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);

			switch (orientation )
			{
			case ExifInterface.ORIENTATION_ROTATE_90:
				//get the mirrored image
				sampledImage=sampledImage.t();
				//flip on the y-axis
				Core.flip(sampledImage, sampledImage, 1);
				break;
			case ExifInterface.ORIENTATION_ROTATE_270:
				//get up side down image
				sampledImage=sampledImage.t();
				//Flip on the x-axis
				Core.flip(sampledImage, sampledImage, 0);
				break;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle action bar item clicks here. The action bar will
		// automatically handle clicks on the Home/Up button, so long
		// as you specify a parent activity in AndroidManifest.xml.
		int id = item.getItemId();
		if (id == R.id.action_openGallary) {
			Intent intent = new Intent();
			intent.setType("image/*");
			intent.setAction(Intent.ACTION_GET_CONTENT);
			startActivityForResult(Intent.createChooser(intent,
					"Select Picture"), SELECT_PICTURE);
			return true;
		}
		else if (id == R.id.action_selectImgToMatch) {
			Intent intent = new Intent();
			intent.setType("image/*");
			intent.setAction(Intent.ACTION_GET_CONTENT);
			startActivityForResult(Intent.createChooser(intent,
					"Select Picture"), SELECT_PICTURE_TO_MATCH);
			return true;
		}
		else if(id==R.id.action_harris)
		{
			if(sampledImage==null)
			{
				Context context = getApplicationContext();
				CharSequence text = "You need to load an image first!";
				int duration = Toast.LENGTH_SHORT;

				Toast toast = Toast.makeText(context, text, duration);
				toast.show();
				return true;
			}

			Mat greyImage=new Mat();
			MatOfKeyPoint keyPoints=new MatOfKeyPoint();
			Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);

			FeatureDetector detector=FeatureDetector.create(FeatureDetector.HARRIS);
			detector.detect(greyImage, keyPoints);
			Features2d.drawKeypoints(greyImage, keyPoints, greyImage);  
			displayImage(greyImage);
		}
		else if(id==R.id.action_nativeHarris)
		{
			if(sampledImage==null)
			{
				Context context = getApplicationContext();
				CharSequence text = "You need to load an image first!";
				int duration = Toast.LENGTH_SHORT;

				Toast toast = Toast.makeText(context, text, duration);
				toast.show();
				return true;
			}

			Mat greyImage=new Mat();
			Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
			FindHarrisCorners(greyImage.getNativeObjAddr(),sampledImage.getNativeObjAddr());

			displayImage(sampledImage);	
		}
		else if(id==R.id.action_fast)
		{
			if(sampledImage==null)
			{
				Context context = getApplicationContext();
				CharSequence text = "You need to load an image first!";
				int duration = Toast.LENGTH_SHORT;

				Toast toast = Toast.makeText(context, text, duration);
				toast.show();
				return true;
			}

			Mat greyImage=new Mat();
			Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
			MatOfKeyPoint keyPoints=new MatOfKeyPoint();
			FeatureDetector detector=FeatureDetector.create(FeatureDetector.FAST);
			detector.detect(greyImage, keyPoints);
			Features2d.drawKeypoints(greyImage, keyPoints, greyImage);  
			displayImage(greyImage);
		}
		else if(id==R.id.action_nativefast)
		{
			if(sampledImage==null)
			{
				Context context = getApplicationContext();
				CharSequence text = "You need to load an image first!";
				int duration = Toast.LENGTH_SHORT;

				Toast toast = Toast.makeText(context, text, duration);
				toast.show();
				return true;
			}

			Mat greyImage=new Mat();
			Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
			FindFastFeatures(greyImage.getNativeObjAddr(),sampledImage.getNativeObjAddr());

			displayImage(sampledImage);	
		}
		else if(id==R.id.action_orb)
		{
			if(sampledImage==null)
			{
				Context context = getApplicationContext();
				CharSequence text = "You need to load an image first!";
				int duration = Toast.LENGTH_SHORT;

				Toast toast = Toast.makeText(context, text, duration);
				toast.show();
				return true;
			}

			Mat greyImage=new Mat();
			Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
			MatOfKeyPoint keyPoints=new MatOfKeyPoint();

			FeatureDetector detector=FeatureDetector.create(FeatureDetector.ORB);
			detector.detect(greyImage, keyPoints);
			Features2d.drawKeypoints(greyImage, keyPoints, greyImage);  
			displayImage(greyImage);
		}
		else if(id==R.id.action_nativeorb)
		{
			if(sampledImage==null)
			{
				Context context = getApplicationContext();
				CharSequence text = "You need to load an image first!";
				int duration = Toast.LENGTH_SHORT;

				Toast toast = Toast.makeText(context, text, duration);
				toast.show();
				return true;
			}

			Mat copy=sampledImage.clone();
			FindORBFeatures(copy.getNativeObjAddr(),100);
			displayImage(copy);	
		}
		else if(id==R.id.harris_check)
		{
			detectorID=FeatureDetector.HARRIS;
			item.setChecked(true);
		}
		else if(id==R.id.fast_check)
		{
			detectorID=FeatureDetector.FAST;
			item.setChecked(true);
		}
		else if(id==R.id.orbD_check)
		{
			detectorID=FeatureDetector.ORB;
			item.setChecked(true);
		}
		else if(id==R.id.BRIEF_check)
		{
			descriptorID=DescriptorExtractor.BRIEF;
			item.setChecked(true);
		}
		else if(id==R.id.ORB_check)
		{
			descriptorID=DescriptorExtractor.ORB;
			item.setChecked(true);
		}
		else if(id==R.id.BRESK_check)
		{
			descriptorID=DescriptorExtractor.BRISK;
			item.setChecked(true);
		}
		else if(id==R.id.FREAK_check)
		{
			descriptorID=DescriptorExtractor.FREAK;
			item.setChecked(true);
		}
		else if(id==R.id.action_match)
		{
			if(sampledImage==null || imgToMatch==null)
			{
				Context context = getApplicationContext();
				CharSequence text = "You need to load an object and a scene to match!";
				int duration = Toast.LENGTH_SHORT;

				Toast toast = Toast.makeText(context, text, duration);
				toast.show();
				return true;
			}
			
			int maximumNuberOfMatches=10;
			Mat greyImage=new Mat();
			Mat greyImageToMatch=new Mat();

			Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
			Imgproc.cvtColor(imgToMatch, greyImageToMatch, Imgproc.COLOR_RGB2GRAY);

			MatOfKeyPoint keyPoints=new MatOfKeyPoint();
			MatOfKeyPoint keyPointsToMatch=new MatOfKeyPoint();
			FeatureDetector detector=FeatureDetector.create(detectorID);
			detector.detect(greyImage, keyPoints);
			detector.detect(greyImageToMatch, keyPointsToMatch);

			DescriptorExtractor dExtractor=DescriptorExtractor.create(descriptorID);
			Mat descriptors=new Mat();
			Mat descriptorsToMatch=new Mat();
			dExtractor.compute(greyImage, keyPoints, descriptors);
			dExtractor.compute(greyImageToMatch, keyPointsToMatch, descriptorsToMatch);

			DescriptorMatcher matcher=DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
			MatOfDMatch matches=new MatOfDMatch();
			matcher.match(descriptorsToMatch,descriptors,matches);
			
			ArrayList<DMatch> goodMatches=new ArrayList<DMatch>();
			List<DMatch> allMatches=matches.toList();

			double minDist = 100;
			for( int i = 0; i < descriptorsToMatch.rows(); i++ )
			{ 
				double dist = allMatches.get(i).distance;
				if( dist < minDist ) minDist = dist;
			}

			for( int i = 0; i < descriptorsToMatch.rows()&&goodMatches.size()<maximumNuberOfMatches; i++ )
			{ 
				if( allMatches.get(i).distance <= 2*minDist)
				{ 		
					goodMatches.add(allMatches.get(i)); 
				}
			}

			MatOfDMatch goodEnough=new MatOfDMatch();
			goodEnough.fromList(goodMatches);
			Mat finalImg=new Mat();
			Features2d.drawMatches(greyImageToMatch, keyPointsToMatch, greyImage, keyPoints, goodEnough, finalImg,Scalar.all(-1),Scalar.all(-1),new MatOfByte(),Features2d.DRAW_RICH_KEYPOINTS+Features2d.NOT_DRAW_SINGLE_POINTS);
			displayImage(finalImg);

		}
		else if(id==R.id.action_native_match)
		{
			if(detectorID==FeatureDetector.HARRIS)
			{
				Context context = getApplicationContext();
				CharSequence text = "Not a valida option for native matching";
				int duration = Toast.LENGTH_SHORT;

				Toast toast = Toast.makeText(context, text, duration);
				toast.show();
				return true;
			}
			if(sampledImage==null || imgToMatch==null)
			{
				Context context = getApplicationContext();
				CharSequence text = "You need to load an object and a scence to match!";
				int duration = Toast.LENGTH_SHORT;

				Toast toast = Toast.makeText(context, text, duration);
				toast.show();
				return true;
			}
			Mat finalImg=new Mat();
			FindMatches(imgToMatch.getNativeObjAddr(),sampledImage.getNativeObjAddr(),detectorID,descriptorID,finalImg.getNativeObjAddr());
			displayImage(finalImg);
		}

		return super.onOptionsItemSelected(item);
	}

	/**
	 * helper to retrieve the path of an image URI
	 */
	private String getPath(Uri uri) {
		// just some safety built in 
		if( uri == null ) {
			return null;
		}
		// try to retrieve the image from the media store first
		// this will only work for images selected from gallery
		String[] projection = { MediaStore.Images.Media.DATA };
		Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
		if( cursor != null ){
			int column_index = cursor
					.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
			cursor.moveToFirst();
			return cursor.getString(column_index);
		}
		return uri.getPath();
	}

	private static double calculateSubSampleSize(Mat srcImage,int reqWidth, int reqHeight) {
		// Raw height and width of image
		final int height = srcImage.height();
		final int width = srcImage.width();
		double inSampleSize = 1;

		if (height > reqHeight || width > reqWidth) {

			// Calculate ratios of height and width to requested height and width
			final double heightRatio = (double) reqHeight / (double) height;
			final double widthRatio = (double) reqWidth / (double) width;

			// Choose the smallest ratio as inSampleSize value, this will guarantee
			// a final image with both dimensions larger than or equal to the
			// requested height and width.
			inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
		}

		return inSampleSize;
	}
	public native void FindFastFeatures(long matAddrGr, long matAddrRgba);

	public native void FindHarrisCorners(long matAddrGr, long matAddrRgba);

	public native void FindORBFeatures(long matAddrRgba, int featuresNumber);
	
	public native void FindMatches(long objectAddress, long sceneAddress,int detectorID, int descriptorID,long matchingResult);
}
