package com.documentscanner.views;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.Size;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.media.ExifInterface;
import android.media.MediaActionSound;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.AnimationSet;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import com.documentscanner.BuildConfig;
import com.documentscanner.ImageProcessor;
import com.documentscanner.R;
import com.documentscanner.helpers.CustomOpenCVLoader;
import com.documentscanner.helpers.OpenNoteMessage;
import com.documentscanner.helpers.PreviewFrame;
import com.documentscanner.helpers.ScannedDocument;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.JavaCameraView;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import static com.documentscanner.helpers.Utils.addImageToGallery;
import static com.documentscanner.helpers.Utils.decodeSampledBitmapFromUri;

public class OpenNoteCameraView extends JavaCameraView implements PictureCallback {

    private static final String TAG = "JavaCameraView";
    private String mPictureFileName;
    private Context mContext;
    SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private HUDCanvasView mHud;
    private boolean mBugRotate=false;
    private ImageProcessor mImageProcessor;
    private boolean mFocused;
    private boolean safeToTakePicture;
    private Activity mActivity;
    private boolean mFlashMode = false;
    private SharedPreferences mSharedPref;
    private SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
    private boolean autoMode = true;
    private HandlerThread mImageThread;
    private View mWaitSpinner;
    private PictureCallback pCallback;
    private boolean scanClicked = false;
    private boolean mVisible;

    private boolean documentAnimation = false;
    private int numberOfRectangles = 15;
    private Boolean enableTorch = false;
    private String overlayColor = null;
    private View blinkView = null;
    private View mView = null;
    private boolean manualCapture = false;

    public static OpenNoteCameraView mThis;

    private OnScannerListener listener = null;
    private OnProcessingListener processingListener = null;

    public interface OnScannerListener{
        void onPictureTaken(WritableMap path);
    }

    public interface OnProcessingListener{
        void onProcessingChange(WritableMap path);
    }

    public void setOnScannerListener(OnScannerListener listener){
        this.listener = listener;
    }

    public void removeOnScannerListener(){
        this.listener = null;
    }

    public void setOnProcessingListener(OnProcessingListener processingListener){
        this.processingListener = processingListener;
    }

    public void removeOnProcessingListener(){
        this.processingListener = null;
    }

    public OpenNoteCameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public OpenNoteCameraView(Context context, Integer numCam, Activity activity, FrameLayout frameLayout) {
        super(context, numCam);
        this.mContext = context;
        this.mActivity = activity;
        mSharedPref = PreferenceManager.getDefaultSharedPreferences(context);
        pCallback = this;
        mView = frameLayout;
        
        LayoutInflater lf = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        initOpenCv(context);
    }

    public static OpenNoteCameraView getInstance(){
        return mThis;
    }

    public void setDocumentAnimation(boolean animate){
        this.documentAnimation = animate;
    }
    public void setOverlayColor(String rgbaColor){
        this.overlayColor = rgbaColor;
    }
    public void setDetectionCountBeforeCapture(int numberOfRectangles){
        this.numberOfRectangles = numberOfRectangles;
    }
    public void setEnableTorch(boolean enableTorch){
        this.enableTorch = enableTorch;

        if(mCamera != null){
            Camera.Parameters p = mCamera.getParameters();
            p.setFlashMode(enableTorch ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF);
            mCamera.setParameters(p);
        }
    }

    public void capture(){
        this.requestManualPicture();
    }

    public void focus(){
        this.requestManualFocus();
    }

    public void setManualOnly( boolean manualOnly){
        this.manualCapture = manualOnly;
    }

    public void setBrightness( double brightness ){
        if (mImageProcessor != null) {
            mImageProcessor.setBrightness(brightness);
        }
    }

    public void setContrast( double contrast){
        if (mImageProcessor != null) {
            mImageProcessor.setContrast(contrast);
        }
    }

    public void setRemoveGrayScale(boolean grayscale){
        mImageProcessor.setRemoveGrayScale(grayscale);
    }

    public void initOpenCv(Context context){

        mThis = this;

        mHud = (HUDCanvasView)  mView.findViewById(R.id.hud);
        mWaitSpinner = mView.findViewById(R.id.wait_spinner);
        blinkView = mView.findViewById(R.id.blink_view);
        blinkView.setBackgroundColor(Color.WHITE);
        
        mVisible = true;

        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        Display display = mActivity.getWindowManager().getDefaultDisplay();
        android.graphics.Point size = new android.graphics.Point();
        display.getRealSize(size);

        BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(context) {
            @Override
            public void onManagerConnected(int status) {
                switch (status) {
                    case LoaderCallbackInterface.SUCCESS: {
                        Log.d(TAG, "SUCCESS init openCV: "+status);
                        //System.loadLibrary("ndklibrarysample");
                        enableCameraView();
                    }
                    break;
                    default: {
                        Log.d(TAG, "ERROR init Opencv: "+status);
                        super.onManagerConnected(status);
                    }
                    break;
                }
            }
        };

        if(!OpenCVLoader.initDebug()){
            CustomOpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_1_0, context, mLoaderCallback);
        }else{
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }

        if (mImageThread == null ) {
            mImageThread = new HandlerThread("Worker Thread");
            mImageThread.start();
        }

        if (mImageProcessor == null) {
            mImageProcessor = new ImageProcessor(mImageThread.getLooper(), new Handler(), this, mContext);
        }
        this.setImageProcessorBusy(false);

    }

    public HUDCanvasView getHUD() {
        return mHud;
    }

    private boolean imageProcessorBusy=true;
    private boolean attemptToFocus = false;

    public void setImageProcessorBusy(boolean imageProcessorBusy) {
        this.imageProcessorBusy = imageProcessorBusy;
    }

    public void setAttemptToFocus(boolean attemptToFocus) {
        this.attemptToFocus = attemptToFocus;
    }

    public boolean isFocused() { return this.mFocused; }

    public void turnCameraOn() {
        mSurfaceView = (SurfaceView) mView.findViewById(R.id.surfaceView);
        mSurfaceHolder = this.getHolder();
        mSurfaceHolder.addCallback(this);
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mSurfaceView.setVisibility(SurfaceView.VISIBLE);
    }

    public void enableCameraView() {
        if (mSurfaceView == null) {
            turnCameraOn();
        }
    }

    private int findBestCamera() {
        int cameraId = -1;
        //Search for the back facing camera
        //get the number of cameras
        int numberOfCameras = Camera.getNumberOfCameras();
        //for every camera check
        for (int i = 0; i < numberOfCameras; i++) {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(i, info);
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                cameraId = i;
                break;
            }
            cameraId = i;
        }
        return cameraId;
    }

    public Camera.Size getMaxPreviewResolution() {
        int maxWidth=0;
        Camera.Size curRes=null;

        mCamera.lock();

        for ( Camera.Size r: getResolutionList() ) {
            if (r.width>maxWidth) {
                Log.d(TAG,"supported preview resolution: "+r.width+"x"+r.height);
                maxWidth=r.width;
                curRes=r;
            }
        }

        return curRes;
    }

    public Camera.Size getMaxPictureResolution(float previewRatio) {
        int maxPixels=0;
        int ratioMaxPixels=0;
        Camera.Size currentMaxRes=null;
        Camera.Size ratioCurrentMaxRes=null;
        for ( Camera.Size r: getPictureResolutionList() ) {
            float pictureRatio = (float) r.width / r.height;
            Log.d(TAG,"supported picture resolution: "+r.width+"x"+r.height+" ratio: "+pictureRatio);
            int resolutionPixels = r.width * r.height;

            if (resolutionPixels>ratioMaxPixels && pictureRatio == previewRatio) {
                ratioMaxPixels=resolutionPixels;
                ratioCurrentMaxRes=r;
            }

            if (resolutionPixels>maxPixels) {
                maxPixels=resolutionPixels;
                currentMaxRes=r;
            }
        }

        //boolean matchAspect = mSharedPref.getBoolean("match_aspect", true);

        if (ratioCurrentMaxRes!=null && true) {

            Log.d(TAG,"Max supported picture resolution with preview aspect ratio: "
                    + ratioCurrentMaxRes.width+"x"+ratioCurrentMaxRes.height);
            return ratioCurrentMaxRes;

        }

        return currentMaxRes;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            int cameraId = findBestCamera();
            mCamera = Camera.open(cameraId);
        }
        catch (RuntimeException e) {
            System.err.println(e);
            return;
        }

        Camera.Parameters param;
        param = mCamera.getParameters();

        Camera.Size pSize = getMaxPreviewResolution();
        param.setPreviewSize(pSize.width, pSize.height);
        param.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
        float previewRatio = (float) pSize.width / pSize.height;

        Display display = mActivity.getWindowManager().getDefaultDisplay();
        //Display display =
        android.graphics.Point size = new android.graphics.Point();
        display.getRealSize(size);

        int displayWidth = Math.min(size.y, size.x);
        int displayHeight = Math.max(size.y, size.x);

        float displayRatio =  (float) displayHeight / displayWidth;

        int previewHeight = displayHeight;

        if ( displayRatio > previewRatio ) {
            ViewGroup.LayoutParams surfaceParams = mSurfaceView.getLayoutParams();
            previewHeight = (int) ( (float) size.y/displayRatio*previewRatio);
            surfaceParams.height = previewHeight;
            mSurfaceView.setLayoutParams(surfaceParams);

            mHud.getLayoutParams().height = previewHeight;
        }

        int hotAreaWidth = displayWidth / 4;
        int hotAreaHeight = previewHeight / 2 - hotAreaWidth;


        Camera.Size maxRes = getMaxPictureResolution(previewRatio);
        if ( maxRes != null) {
            param.setPictureSize(maxRes.width, maxRes.height);
            Log.d(TAG,"max supported picture resolution: " + maxRes.width + "x" + maxRes.height);
        }

        PackageManager pm = mActivity.getPackageManager();

        if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)) {
            param.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        } else {
            mFocused = true;
        }
        if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
            param.setFlashMode(enableTorch ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF);
        }
         
        mCamera.setParameters(param);
//        mBugRotate = mSharedPref.getBoolean("bug_rotate", false);

        if (mBugRotate) {
            mCamera.setDisplayOrientation(270);
        } else {
            mCamera.setDisplayOrientation(90);
        }

        if (mImageProcessor != null) {
            mImageProcessor.setBugRotate(mBugRotate);
            mImageProcessor.setNumOfRectangles(numberOfRectangles);
        }

        try {
            mCamera.setAutoFocusMoveCallback(new Camera.AutoFocusMoveCallback() {
                @Override
                public void onAutoFocusMoving(boolean start, Camera camera) {
                    mFocused = !start;
                    Log.d(TAG, "focusMoving: " + mFocused);
                }
            });
        } catch (Exception e) {
            Log.d(TAG, "failed setting AutoFocusMoveCallback");
        }

        // some devices doesn't call the AutoFocusMoveCallback - fake the
        // focus to true at the start
        mFocused = true;

        safeToTakePicture = true;

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        refreshCamera();
    }

    private void refreshCamera() {
        final boolean torchEnabled = this.enableTorch;

        try {
            mCamera.stopPreview();
        }

        catch (Exception e) {
        }

        try {
            mCamera.setPreviewDisplay(mSurfaceHolder);
            mThis.setEnableTorch(torchEnabled);
            mCamera.startPreview();
            mCamera.setPreviewCallback(this);
        }
        catch (Exception e) {
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
        }
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {

        Camera.Size pictureSize = camera.getParameters().getPreviewSize();

        if ( mFocused && ! imageProcessorBusy ) {
            setImageProcessorBusy(true);
            Mat yuv = new Mat(new org.opencv.core.Size(pictureSize.width, pictureSize.height * 1.5), CvType.CV_8UC1);
            yuv.put(0, 0, data);

            Mat mat = new Mat(new org.opencv.core.Size(pictureSize.width, pictureSize.height), CvType.CV_8UC4);
            Imgproc.cvtColor(yuv, mat, Imgproc.COLOR_YUV2RGBA_NV21, 4);

            yuv.release();

            if(!manualCapture){
                sendImageProcessorMessage("previewFrame", new PreviewFrame( mat, autoMode, !(autoMode) ));
            }

        }

    }

    public void invalidateHUD() {
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mHud.invalidate();
            }
        });
    }

    public void sendImageProcessorMessage(String messageText , Object obj ) {
        Log.d(TAG,"sending message to ImageProcessor: "+messageText+" - "+obj.toString());
        Message msg = mImageProcessor.obtainMessage();
        msg.obj = new OpenNoteMessage(messageText, obj );
        mImageProcessor.sendMessage(msg);
    }

    public void blinkScreenAndShutterSound(){
        AudioManager audio = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
        switch( audio.getRingerMode() ){
            case AudioManager.RINGER_MODE_NORMAL:
                MediaActionSound sound = new MediaActionSound();
                sound.play(MediaActionSound.SHUTTER_CLICK);
                break;
            case AudioManager.RINGER_MODE_SILENT:
                break;
            case AudioManager.RINGER_MODE_VIBRATE:
                break;
        }
    }

    public void waitSpinnerVisible() {
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mWaitSpinner.setVisibility(View.VISIBLE);
                WritableMap data = new WritableNativeMap();
                data.putBoolean("processing", true);
                mThis.processingListener.onProcessingChange(data);
            }
        });
    }

    public void waitSpinnerInvisible() {
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
//                blinkView.setVisibility(View.INVISIBLE);
                mWaitSpinner.setVisibility(View.INVISIBLE);
                WritableMap data = new WritableNativeMap();
                data.putBoolean("processing", false);
                mThis.processingListener.onProcessingChange(data);
            }
        });
    }

    public void blinkScreen() {
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                blinkView.bringToFront();
                Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.blink);
                blinkView.startAnimation(animation);
                blinkView.setVisibility(View.INVISIBLE);

            }
        });
    }

    public boolean requestManualFocus(){
      PackageManager pm = mActivity.getPackageManager();
      if(safeToTakePicture){
        safeToTakePicture = false;
        if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)) {
          mCamera.autoFocus(new Camera.AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
              Log.d(TAG,"Manual focus => " + success);
            }
          });
        }
        safeToTakePicture = true;
        return true;
      }
      return false;
    }

    public boolean requestManualPicture(){
        this.blinkScreenAndShutterSound();
        this.waitSpinnerVisible();

        if(safeToTakePicture){

            safeToTakePicture = false;

            try{
                mCamera.takePicture(null,null, pCallback);
            }catch(Exception e){
                this.waitSpinnerInvisible();
            }
            return true;
        }
        return false;
    }

    public boolean requestPicture() {
        PackageManager pm = mActivity.getPackageManager();
        if (safeToTakePicture) {

            safeToTakePicture = false;

            try {
                if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)){
                    mCamera.autoFocus(new Camera.AutoFocusCallback() {
                        @Override
                        public void onAutoFocus(boolean success, Camera camera) {
                            if (success) {
                                mCamera.takePicture(null, null, pCallback);
                                blinkScreen();
                                blinkScreenAndShutterSound();
                            }
                            if (attemptToFocus) {
                                return;
                            } else {
                                attemptToFocus = true;
                            }
                        }
                    });
                }else{
                    mCamera.takePicture(null,null,pCallback);
                    blinkScreen();
                    blinkScreenAndShutterSound();
                }
            }catch(Exception e){
                waitSpinnerInvisible();
            }finally {
                waitSpinnerInvisible();
            }
            return true;
        }
        return false;
    }


    public void saveDocument(ScannedDocument scannedDocument) {

        Mat doc = (scannedDocument.processed != null) ? scannedDocument.processed : scannedDocument.original;

        Intent intent = mActivity.getIntent();
        String fileName;
        boolean isIntent = false;
        Uri fileUri = null;

        if (intent.getAction()!=null && intent.getAction().equals("android.media.action.IMAGE_CAPTURE")) {
            fileUri = ((Uri) intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT));
            Log.d(TAG,"intent uri: " + fileUri.toString());
            try {
                fileName = File.createTempFile("onsFile",".jpg", mContext.getCacheDir()).getPath();
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }
            isIntent = true;
        } else {
            String folderName="documents";
            File folder = new File(Environment.getExternalStorageDirectory().toString()
                    + "/" + folderName );
            if (!folder.exists()) {
                folder.mkdirs();
                Log.d(TAG, "wrote: created folder "+folder.getPath());
            }
            fileName = Environment.getExternalStorageDirectory().toString()
                    + "/" + folderName + "/DOC-"
                    + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date())
                    + ".jpg";
        }
        Mat endDoc = new Mat(Double.valueOf(doc.size().width).intValue(),
                Double.valueOf(doc.size().height).intValue(), CvType.CV_8UC4);

        Core.flip(doc.t(), endDoc, 1);

        Imgcodecs.imwrite(fileName, endDoc);


        endDoc.release();

        WritableMap data = new WritableNativeMap();
        if(this.listener != null){
            data.putString("path",fileName);
            this.listener.onPictureTaken(data);
        }

        if (isIntent) {
            InputStream inputStream = null;
            OutputStream realOutputStream = null;
            try {
                inputStream = new FileInputStream(fileName);
                realOutputStream = mActivity.getContentResolver().openOutputStream(fileUri);
                // Transfer bytes from in to out
                byte [] buffer = new byte[1024];
                int len;
                while( (len = inputStream.read(buffer)) > 0 ) {
                    realOutputStream.write(buffer, 0, len);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return;
            } catch (IOException e) {
                e.printStackTrace();
                return;
            } finally {
                try {
                    inputStream.close();
                    realOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

        Log.d(TAG, "wrote: " + fileName);

        if (isIntent) {
            new File(fileName).delete();
            mActivity.setResult(Activity.RESULT_OK, intent);
            mActivity.finish();
        } else {
            animateDocument(fileName,scannedDocument);
            addImageToGallery(fileName , mContext);
        }

        // Record goal "PictureTaken"
//        ((OpenNoteScannerApplication) getApplication()).getTracker().trackGoal(1);

        refreshCamera();

    }

    private void animateDocument(String filename, ScannedDocument quadrilateral) {

        //if(!documentAnimation) return;

        OpenNoteCameraView.AnimationRunnable runnable = new OpenNoteCameraView.AnimationRunnable(filename,quadrilateral);
        mActivity.runOnUiThread(runnable);
        this.waitSpinnerInvisible();

    }



    class AnimationRunnable implements Runnable {

        private org.opencv.core.Size imageSize;
        private Point[] previewPoints =null;
        public org.opencv.core.Size previewSize = null;
        public String fileName = null;
        public int width;
        public int height;
        private Bitmap bitmap;

        public AnimationRunnable(String filename, ScannedDocument document) {
            this.fileName = filename;
            this.imageSize = document.processed.size();

            if (document.quadrilateral != null) {
                this.previewPoints = document.previewPoints;
                this.previewSize = document.previewSize;
            }
        }

        public double hipotenuse(Point a , Point b) {
            return Math.sqrt( Math.pow(a.x - b.x , 2 ) + Math.pow(a.y - b.y , 2 ));
        }

        @Override
        public void run() {
            final ImageView imageView = (ImageView) mView.findViewById(R.id.scannedAnimation);

            Display display = mActivity.getWindowManager().getDefaultDisplay();
            android.graphics.Point size = new android.graphics.Point();
            display.getRealSize(size);

            int width = Math.min(size.x, size.y);
            int height = Math.max(size.x, size.y);

            // ATENTION: captured images are always in landscape, values should be swapped
            double imageWidth = imageSize.height;
            double imageHeight = imageSize.width;

            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) imageView.getLayoutParams();

            if (previewPoints != null) {
                double documentLeftHeight = hipotenuse(previewPoints[0], previewPoints[1]);
                double documentBottomWidth = hipotenuse(previewPoints[1], previewPoints[2]);
                double documentRightHeight = hipotenuse(previewPoints[2], previewPoints[3]);
                double documentTopWidth = hipotenuse(previewPoints[3], previewPoints[0]);

                double documentWidth = Math.max(documentTopWidth, documentBottomWidth);
                double documentHeight = Math.max(documentLeftHeight, documentRightHeight);

                Log.d(TAG, "device: " + width + "x" + height + " image: " + imageWidth + "x" + imageHeight + " document: " + documentWidth + "x" + documentHeight);


                Log.d(TAG, "previewPoints[0] x=" + previewPoints[0].x + " y=" + previewPoints[0].y);
                Log.d(TAG, "previewPoints[1] x=" + previewPoints[1].x + " y=" + previewPoints[1].y);
                Log.d(TAG, "previewPoints[2] x=" + previewPoints[2].x + " y=" + previewPoints[2].y);
                Log.d(TAG, "previewPoints[3] x=" + previewPoints[3].x + " y=" + previewPoints[3].y);

                // ATENTION: again, swap width and height
                double xRatio = width / previewSize.height;
                double yRatio = height / previewSize.width;

                params.topMargin = (int) (previewPoints[3].x * yRatio);
                params.leftMargin = (int) ( (previewSize.height - previewPoints[3].y ) * xRatio);
                params.width = (int) (documentWidth * xRatio);
                params.height = (int) (documentHeight * yRatio);
            } else {
                params.topMargin = height/4;
                params.leftMargin = width/4;
                params.width = width/2;
                params.height = height/2;
            }

            bitmap = decodeSampledBitmapFromUri(fileName, params.width, params.height);

            imageView.setImageBitmap(bitmap);

            imageView.setVisibility(View.VISIBLE);

            TranslateAnimation translateAnimation = new TranslateAnimation(
                    Animation.ABSOLUTE , 0 , Animation.ABSOLUTE , -params.leftMargin ,
                    Animation.ABSOLUTE , 0 , Animation.ABSOLUTE , height-params.topMargin
            );

            ScaleAnimation scaleAnimation = new ScaleAnimation(1, 0, 1, 0);

            AnimationSet animationSet = new AnimationSet(true);

            animationSet.addAnimation(scaleAnimation);
            animationSet.addAnimation(translateAnimation);

            animationSet.setDuration(600);
            animationSet.setInterpolator(new AccelerateInterpolator());

            animationSet.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    imageView.setVisibility(View.INVISIBLE);
                    imageView.setImageBitmap(null);
                    OpenNoteCameraView.AnimationRunnable.this.bitmap.recycle();
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });


            imageView.startAnimation(animationSet);

        }
    }

    public List<String> getEffectList() {
        return mCamera.getParameters().getSupportedColorEffects();
    }

    public boolean isEffectSupported() {
        return (mCamera.getParameters().getColorEffect() != null);
    }

    public boolean isEffectSupported(String effect) {
        List<String> effectList = getEffectList();
        for(String str: effectList) {
            if(str.trim().contains(effect))
                return true;
        }
        return false;
    }

    public String getEffect() {
        return mCamera.getParameters().getColorEffect();
    }

    public void setEffect(String effect) {
        Camera.Parameters params = mCamera.getParameters();
        params.setColorEffect(effect);
        mCamera.setParameters(params);
    }

    public List<Size> getResolutionList() {
        return mCamera.getParameters().getSupportedPreviewSizes();
    }

    public List<Size> getPictureResolutionList() {
        return mCamera.getParameters().getSupportedPictureSizes();
    }

    public void setMaxPictureResolution() {
        int maxWidth=0;
        Size curRes=null;
        for ( Size r: getPictureResolutionList() ) {
            Log.d(TAG,"supported picture resolution: "+r.width+"x"+r.height);
            if (r.width>maxWidth) {
                maxWidth=r.width;
                curRes=r;
            }
        }

        if (curRes!=null) {
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setPictureSize(curRes.width, curRes.height);
            mCamera.setParameters(parameters);
            Log.d(TAG, "selected picture resolution: " + curRes.width + "x" + curRes.height);
        }

        return;
    }



    public void setMaxPreviewResolution() {
        int maxWidth=0;
        Size curRes=null;

        mCamera.lock();

        for ( Size r: getResolutionList() ) {
            if (r.width>maxWidth) {
                Log.d(TAG,"supported preview resolution: "+r.width+"x"+r.height);
                maxWidth=r.width;
                curRes=r;
            }
        }

        if (curRes!=null) {
            setResolution(curRes);
            Log.d(TAG, "selected preview resolution: " + curRes.width + "x" + curRes.height);
        }

        return;
    }

    public void setResolution(Size resolution) {
        disconnectCamera();
        mMaxHeight = resolution.height;
        mMaxWidth = resolution.width;
        connectCamera(getWidth(), getHeight());
        Log.d(TAG,"resolution: "+resolution.width+" x "+resolution.height);
    }

    public Size getResolution() {
        return mCamera.getParameters().getPreviewSize();
    }


    public void setFlash(boolean stateFlash) {
        /* */
        Camera.Parameters par = mCamera.getParameters();
        par.setFlashMode(stateFlash ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF);
        mCamera.setParameters(par);
        Log.d(TAG,"flash: " + (stateFlash?"on":"off"));
        // */
    }

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

        Camera.Size pictureSize = camera.getParameters().getPictureSize();

        Mat mat = new Mat(new org.opencv.core.Size(pictureSize.width, pictureSize.height), CvType.CV_8U);
        mat.put(0, 0, data);

        setImageProcessorBusy(true);
        sendImageProcessorMessage("pictureTaken", mat);

        safeToTakePicture = true;

    }
}