package com.aluxoft.earrecognition;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v7.app.ActionBarActivity;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
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 java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Callable;

import bolts.Continuation;
import bolts.Task;

public class MainActivity extends ActionBarActivity {


    ProgressDialog loading;

    private static final int REQUEST_IMAGE_CAPTURE = 1;
    private String mCurrentPhotoPath;
    private String mCurrentPhotoPath1 = null;
    private String mCurrentPhotoPath2 = null;
    private String mCurrentPhotoPath3 = null;
    private int previewWidth = 150;
    private int previewHeight = 150;
    private ImageView previewImage1;
    private ImageView previewImage2;
    private ImageView previewImage3;
    private ImageView previewImage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        System.loadLibrary("opencv_java");
        System.loadLibrary("nonfree");

        setContentView(R.layout.activity_main);
        previewImage1 = (ImageView)findViewById(R.id.imageView);
        previewImage2 = (ImageView)findViewById(R.id.imageView2);
        previewImage3 = (ImageView)findViewById(R.id.imageView3);
        final Button captureFirstEarButton = (Button)findViewById(R.id.button);
        captureFirstEarButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                previewImage = previewImage1;
                takePhoto();
                mCurrentPhotoPath1 = mCurrentPhotoPath;
            }
        });


        final Button captureSecondEarButton = (Button)findViewById(R.id.button2);
        captureSecondEarButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                previewImage = previewImage2;
                takePhoto();
                mCurrentPhotoPath2 = mCurrentPhotoPath;
            }
        });

        final Button captureEarToIdentifyButton = (Button) findViewById(R.id.toidentify);
        captureEarToIdentifyButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                previewImage = previewImage3;
                takePhoto();
                mCurrentPhotoPath3 = mCurrentPhotoPath;
            }
        });

        final Button processButton = (Button) findViewById(R.id.button3);
        processButton.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                // do opencv magic here...
                if (mCurrentPhotoPath1 != null && mCurrentPhotoPath2 != null && mCurrentPhotoPath3 != null) {
                    Task.callInBackground(new Callable<Void>() {
                        @Override
                        public Void call() throws Exception {
                            doOpenCvMagic();
                            return null;
                        }
                    }).continueWith(new Continuation<Void, Void>() {
                        @Override
                        public Void then(Task<Void> task) throws Exception {
                            return null;
                        }
                    });

                }
            }
        });

    }

    public void takePhoto() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
            if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                File photoFile = null;
                try {
                    photoFile = createImageFile();
                } catch (IOException ex) {
                    // Error occurred while creating the File
                }
                // Continue only if the File was successfully created
                if (photoFile != null) {
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
                    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
                }
            }
        }
    }

    @SuppressLint("NewApi")
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == REQUEST_IMAGE_CAPTURE) {
            if (resultCode == RESULT_OK) {
                if (mCurrentPhotoPath != null) {
                    // adds image to device gallery.
                    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                    File f = new File(mCurrentPhotoPath);
                    Uri contentUri = Uri.fromFile(f);
                    mediaScanIntent.setData(contentUri);
                    this.sendBroadcast(mediaScanIntent);
                    String path = f.getAbsolutePath();
                    Log.e("", path);
                    Bitmap galleryImage = getBitMapForPreview(path,previewWidth,previewHeight);
                    previewImage.setImageBitmap(galleryImage);
                }
            }
        }
    }

    public void doOpenCvMagic() {

        Task.forResult("").continueWith(new Continuation<String, Void>() {
            @Override
            public Void then(Task<String> task) throws Exception {
                loading = ProgressDialog.show(MainActivity.this, "Matching", "Wait while identifying the image...");
                loading.setCancelable(false);
                loading.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                return null;
            }
        }, Task.UI_THREAD_EXECUTOR);

        Mat img1 = Highgui.imread(mCurrentPhotoPath1, Highgui.CV_LOAD_IMAGE_GRAYSCALE);
        resizeMat(img1);

        Mat img2 = Highgui.imread(mCurrentPhotoPath2, Highgui.CV_LOAD_IMAGE_GRAYSCALE);
        resizeMat(img2);

        Mat img3 = Highgui.imread(mCurrentPhotoPath3, Highgui.CV_LOAD_IMAGE_GRAYSCALE);
        resizeMat(img3);

        FeatureDetector detector = FeatureDetector.create(FeatureDetector.SIFT);

        MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
        MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
        MatOfKeyPoint keypoints3 = new MatOfKeyPoint();

        detector.detect(img1, keypoints1);
        detector.detect(img2, keypoints2);
        detector.detect(img3, keypoints3);

        Mat descriptors1 = new Mat();
        Mat descriptors2 = new Mat();
        Mat descriptors3 = new Mat();

        DescriptorExtractor extractor = DescriptorExtractor.create(DescriptorExtractor.SIFT);
        extractor.compute(img1, keypoints1, descriptors1);
        extractor.compute(img2, keypoints2, descriptors2);
        extractor.compute(img3, keypoints3, descriptors3);

        DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE);

        final Mat outImg1 = new Mat();
        final Mat outImg2 = new Mat();
        final Mat outImg3 = new Mat();

        double distance13 = distanceFromData(matcher, keypoints3, descriptors3, keypoints1, descriptors1);
        double distance23 = distanceFromData(matcher, keypoints3, descriptors3, keypoints2, descriptors2);

        String message = "";
        System.out.println(distance13);
        System.out.println(distance23);
        if (distance13 < distance23) {
            message = "It's more likely to be the subject 1";
        } else if (distance23 < distance13) {
            message = "It's more likely to be the subject 2";
        } else {
            message = "Both subjects are equally likely";
        }

        Features2d.drawKeypoints(img1, keypoints1, outImg1);
        Features2d.drawKeypoints(img2, keypoints2, outImg2);
        Features2d.drawKeypoints(img3, keypoints3, outImg3);



        Task.forResult(message).continueWith(new Continuation<String, Void>() {
            @Override
            public Void then(Task<String> task) throws Exception {
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("Identification")
                        .setMessage(task.getResult())
                        .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                            }
                        })
                        .setIcon(android.R.drawable.ic_dialog_info)
                        .show();

                Bitmap bitmap = Bitmap.createBitmap(outImg1.cols(), outImg1.rows(), Bitmap.Config.ARGB_8888);
                Utils.matToBitmap(outImg1, bitmap);
                previewImage1.setImageBitmap(bitmap);

                bitmap = Bitmap.createBitmap(outImg2.cols(), outImg2.rows(), Bitmap.Config.ARGB_8888);
                Utils.matToBitmap(outImg2, bitmap);
                previewImage2.setImageBitmap(bitmap);

                bitmap = Bitmap.createBitmap(outImg3.cols(), outImg3.rows(), Bitmap.Config.ARGB_8888);
                Utils.matToBitmap(outImg3, bitmap);
                previewImage3.setImageBitmap(bitmap);
                loading.dismiss();
                return null;
            }
        }, Task.UI_THREAD_EXECUTOR);
    }

    // ------- Helper methods -------

    public void resizeMat(Mat image) {
        final double kMaxWidth = 160;
        final double kMaxHeight = 120;

        if (image.width() <= kMaxWidth && image.height() <= kMaxHeight) {
            return;
        }

        double ratio = (double)image.width() / (double)image.height();
        double width, height;
        if (ratio > 1) {
            width = kMaxWidth;
            height = kMaxWidth * ((double)image.height()/(double)image.width());
        } else {
            height = kMaxHeight;
            width = kMaxHeight* ratio;
        }
        Imgproc.resize(image, image, new Size(width, height));
    }


    public double distanceFromData(DescriptorMatcher matcher, MatOfKeyPoint keypoints1, Mat descriptors1, MatOfKeyPoint keypoints2, Mat descriptors2) {
        MatOfDMatch matches = new MatOfDMatch();
        matcher.match(descriptors1, descriptors2, matches);
        DMatch[] aMatches = matches.toArray();

        double distance = 0;
        for (int i=0;i<aMatches.length;++i) {
            distance += aMatches[i].distance;
        }

        return distance;
    }

    private File createImageFile() throws IOException {
        // Create an image file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(imageFileName, /* prefix */
                ".jpg", /* suffix */
                storageDir /* directory */
        );

        // Save a file: path for use with ACTION_VIEW intents
        mCurrentPhotoPath = image.getAbsolutePath();
        return image;
    }

    private Bitmap getBitMapForPreview(String path, int width, int height) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        // Just used to compute the sample size.
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);
        options.inSampleSize = calculateInSampleSize(options, (int)convertDpToPixel((float)width, this), (int)convertDpToPixel((float)height,this));
        options.inJustDecodeBounds = false;
        Bitmap galleryImage = BitmapFactory.decodeFile(path, options);

        return galleryImage;
    }

    /**
     * This is used to decreases the image memory load, android is inefficient loading images
     * and if you don't reduce the size accordingly to the resolution of the widget, it will have
     * memory errors, it has to compute the pixels using the dpi of the screen and generate them
     * according to the device screen density.
     *
     * @param options
     * @param reqWidth   width of the imageView
     * @param reqHeight height of the imageView
     * @return
     */
    private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

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

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and
            // keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

    /**
     * This method converts dp unit to equivalent pixels, depending on device density.
     *
     * @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
     * @param context Context to get resources and device specific display metrics
     * @return A float value to represent px equivalent to dp depending on device density
     */
    public static float convertDpToPixel(float dp, Context context){
        Resources resources = context.getResources();
        DisplayMetrics metrics = resources.getDisplayMetrics();

        /**
         * android developers equations:
         * px = dp * (dpi / 160)
         * dp = px / (dpi / 160)
         */
        float px = dp * (metrics.densityDpi / 160f);
        return px;
    }

}
