package it.unipr.scarpentim.pasmtftest1;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import it.unipr.scarpentim.pasmtftest1.img.ImageProcessor;
import it.unipr.scarpentim.pasmtftest1.img.ImageSaver;
import it.unipr.scarpentim.pasmtftest1.tensorflow.Classifier;
import it.unipr.scarpentim.pasmtftest1.yolo.YoloV3Classifier;

public class MainActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 {


    private static final int MY_PERMISSIONS_REQUEST_CAMERA = 1;
    private static final int MY_PERMISSIONS_REQUEST_STORAGE = 1;

    private boolean disable = false;
    private Classifier classifier = null;
    private CameraBridgeViewBase mOpenCvCameraView;

    Mat mRgba;
    Mat mRgbaF;
    Mat mRgbaT;

    private static final String TAG = "PASM_yolov3";
    private static final int SELECT_PICTURE = 1;
    private String selectedImagePath;
    private Bitmap myBitmap = null;

    private Menu mOptionsMenu;


    private static final int INPUT_SIZE = 416;
    private static final String INPUT_LAYER_NAME = "yolov3-tiny/net1";
    private static final String OUTPUT_LAYER_NAME = "yolov3-tiny/convolutional10/BiasAdd,yolov3-tiny/convolutional13/BiasAdd";
    private static final int[] TINY_YOLO_BLOCK_SIZE = {32, 16};

    private static final String MODEL_FILE = "ultimate_yolov3-tiny";
    private Mat rgbImage;
    private ImageSaver imageSaver;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        setContentView(R.layout.activity_main);
        validateCameraPermission();
        classifier = initClassifier();
        imageSaver = new ImageSaver();
        imageSaver.createFolderIfNotExist();
        mOpenCvCameraView= findViewById(R.id.cameraView1);
        mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
        mOpenCvCameraView.setCvCameraViewListener(this);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.mymenu, menu);
        mOptionsMenu = menu;
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_openGallery) {
            decreasePreview();
            validateReadStoragePermission();
            Intent intent = new Intent();
            intent.setType("image/*");
            intent.setAction(Intent.ACTION_PICK);
            startActivityForResult(Intent.createChooser(intent,"Select an image"), SELECT_PICTURE);
            return true;
        }else if (id == R.id.action_openCamera) {
            decreasePreview();
            ImageView iv = findViewById(R.id.ivGallery);
            iv.setVisibility(View.GONE);
            myBitmap = null;
            mOpenCvCameraView.enableView();
            mOptionsMenu.getItem(1).setEnabled(false);
        }
        return super.onOptionsItemSelected(item);
    }

    Bitmap smallBitmap = null;

    public void classify(View view) {

        enlargePreview();

        Mat mRgbaTemp = mRgba.clone();
        //mOpenCvCameraView.disableView();
        //mOpenCvCameraView.setVisibility(View.GONE);
        new ComputeTask().execute(mRgbaTemp);
        TextView tv = findViewById(R.id.textView);
        tv.setText("I'm thinking...");
    }

    //region - Metodi da CvCameraViewListener2

    @Override
    public void onCameraViewStarted(int width, int height) {
        mRgba = new Mat(height, width, CvType.CV_8UC4);
        mRgbaF = new Mat(width, height, CvType.CV_8UC4);
        mRgbaT = new Mat(width, height, CvType.CV_8UC4);
        Log.i(TAG, "height : " + height);
        Log.i(TAG, "width : " + width);
        //Log.i(TAG, "mOpenCvCameraView size (w,h):" + mOpenCvCameraView.getWidth() +  " - " + mOpenCvCameraView.getHeight());
    }

    @Override
    public void onCameraViewStopped() {
        mRgba.release();
        mRgbaF.release();
        mRgbaT.release();
    }

    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
        mRgba = inputFrame.rgba();
        return inputFrame.rgba();
    }
    //endregion

    @Override
    protected void onResume() {
        super.onResume();
        // Chiama l'inizializzazione asincrona e passa l'oggetto callback
        // creato in precendeza, e sceglie quale versione di OpenCV caricare.
        // Serve anche a verificare che l'OpenCV manager installato supporti
        // la versione che si sta provando a caricare.
        if (!disable)
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION,this, mLoaderCallback);
    }

    public void hideRecognizedImage(View view) {

        togglePreviewSize();
    }

    private class ComputeTask extends AsyncTask<Mat, Bitmap, Mat>{

        @Override
        protected Mat doInBackground(Mat... mats) {
            Mat mRgbaTemp = mats[0];
            ImageProcessor processor = new ImageProcessor(getApplicationContext(), classifier.getLabels());
            if (myBitmap != null){
                smallBitmap = Bitmap.createScaledBitmap(myBitmap, INPUT_SIZE, INPUT_SIZE, false);
                Display display = getWindowManager().getDefaultDisplay();
                Point size = new Point();
                display.getSize(size);
                int width = size.x;
                int height = size.y;

                float ratio = (float)myBitmap.getWidth() / (float)myBitmap.getHeight();
                Bitmap reducedBitmap = Bitmap.createScaledBitmap(myBitmap, (int) (height * ratio), height, false);

                this.publishProgress(reducedBitmap);
                processor.loadImage(myBitmap, INPUT_SIZE, INPUT_SIZE);
            }else{
                smallBitmap = Bitmap.createBitmap(INPUT_SIZE, INPUT_SIZE, Bitmap.Config.RGB_565);
                Bitmap bigBitmap = Bitmap.createBitmap(mRgbaF.width(), mRgbaF.height(), Bitmap.Config.RGB_565);
                Mat mRgbaFixedSize = new Mat(INPUT_SIZE, INPUT_SIZE, CvType.CV_8UC4);

                Core.transpose(mRgbaTemp, mRgbaT);
                Imgproc.resize(mRgbaT, mRgbaF, mRgbaF.size(), 0,0, 0);
                Core.flip(mRgbaF, mRgbaTemp, 1 );

                Imgproc.resize(mRgbaTemp, mRgbaFixedSize, new Size(INPUT_SIZE, INPUT_SIZE), 0,0, 0);

                Utils.matToBitmap(mRgbaFixedSize, smallBitmap);
                Utils.matToBitmap(mRgbaTemp, bigBitmap);

                this.publishProgress(bigBitmap);
                processor.loadImage(bigBitmap, INPUT_SIZE, INPUT_SIZE);
                //OLD Toast.makeText(getApplicationContext(), "Nessuna immagine caricata", Toast.LENGTH_SHORT).show();
            }

            List<Classifier.Recognition> recognitions = classifier.recognizeImage(smallBitmap);
            Mat mat = processor.drawBoxes(recognitions, 0.2);
            imageSaver.save(mat); // remove for realtime processing!
            return mat;
        }

        @Override
        protected void onPostExecute(Mat result) {
            ImageView ivGallery = findViewById(R.id.ivGallery);
            ivGallery.setVisibility(View.GONE);
            ImageView iv = findViewById(R.id.ivPreview);
            Bitmap bigBitmap = Bitmap.createBitmap(result.width(), result.height(), Bitmap.Config.RGB_565);
            Utils.matToBitmap(result, bigBitmap);
            iv.setImageBitmap(bigBitmap);
            iv.setVisibility(View.VISIBLE);
            TextView tv = findViewById(R.id.textView);
            tv.setText("Done!");

        }

        @Override
        protected void onProgressUpdate(Bitmap... values) {
            Log.i(TAG, "### onProgressUpdate called!!");
            super.onProgressUpdate(values[0]);
            ImageView iv = findViewById(R.id.ivPreview);
            iv.setImageBitmap(values[0]);
            iv.setVisibility(View.VISIBLE);
            Log.i(TAG, "h - w --> " + values[0].getHeight() + " - " + values[0].getWidth());
        }
    }

    private void validateReadStoragePermission() {
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            Log.i(TAG, "Permission is not granted");
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                    MY_PERMISSIONS_REQUEST_STORAGE);
        }
    }

    private void validateCameraPermission() {
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            Log.i(TAG, "Permission for camera is not granted");
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.CAMERA},
                    MY_PERMISSIONS_REQUEST_CAMERA);
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            if (requestCode == SELECT_PICTURE) {
                mOptionsMenu.getItem(1).setEnabled(true);
                disable = true;
                mOpenCvCameraView.disableView();
                Uri selectedImageUri = data.getData();
                selectedImagePath = getPath(selectedImageUri);
                Log.i(TAG, "selectedImagePath: " + selectedImagePath);
                loadImage(selectedImagePath);
                myBitmap = BitmapFactory.decodeFile(selectedImagePath);
                ImageView iv = findViewById(R.id.ivGallery);
                iv.setImageBitmap(myBitmap);
                iv.setVisibility(View.VISIBLE);
            }
        }
    }

    public void loadImage(String path) {
        Mat originalImage = Imgcodecs.imread(path);
        rgbImage = new Mat();
        Imgproc.cvtColor(originalImage, rgbImage, Imgproc.COLOR_BGR2RGB);
    }

    private String getPath(Uri uri) {
        if (uri == null) {
            return null;
        }
        // prova a recuperare l'immagine prima dal Media Store
        // questo però funziona solo per immagini selezionate dalla galleria
        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 Classifier initClassifier() {
        try {
            return YoloV3Classifier.create(
                    super.getAssets(),
                    MODEL_FILE,
                    INPUT_SIZE,
                    INPUT_LAYER_NAME,
                    OUTPUT_LAYER_NAME,
                    TINY_YOLO_BLOCK_SIZE,
                    0);

        } catch (IOException e) {
            throw new RuntimeException("classifier init problem", e);
        }
    }

    // Questo oggetto callback è usato quando inizializzaimo la libreria OpenCV in modo asincrono
    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        // Una volta che OpenCV manager è connesso viene chiamato questo metodo di
        public void onManagerConnected(int status) {
            switch (status) {
                // Una volta che OpenCV manager si è connesso con successo
                // possiamo abilitare l'interazione con la tlc
                case LoaderCallbackInterface.SUCCESS:
                    Log.i(TAG, "OpenCV loaded successfully");
                    mOpenCvCameraView.enableView();

                    break;
                default:
                    super.onManagerConnected(status);
                     break;
            }
        }
    };


    boolean isPreviewLarge = true;

    private void enlargePreview(){
        if (!isPreviewLarge) {
            transformPreview(1);
            isPreviewLarge = true;
        }
    }

    private void decreasePreview(){
        if (isPreviewLarge) {
            transformPreview(-1);
            isPreviewLarge = false;
        }
    }

    private void togglePreviewSize(){
        if (isPreviewLarge) {
            transformPreview(-1);
            isPreviewLarge = false;
        }else {
            transformPreview(1);
            isPreviewLarge = true;
        }
    }

    private void transformPreview(int sign) {

        findViewById(R.id.ivPreview).animate()
                .translationXBy(-550f * sign)
                .translationYBy(-650f * sign)
                .scaleYBy(0.75f * sign)
                .scaleXBy(0.75f * sign);
    }
}