package com.digitalvotingpass.camera;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.camera2.CameraCharacteristics;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.widget.ImageView;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Created by wkmeijer on 12-6-17.
 */

public class CameraFragmentUtil {
    public final static String TAG = "CameraFragmentUtil";

    /**
     * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
     * is at least as large as the respective texture view size, and that is at most as large as the
     * respective max size, and whose aspect ratio matches with the specified value. If such size
     * doesn't exist, choose the largest one that is at most as large as the respective max size,
     * and whose aspect ratio matches with the specified value.
     *
     * @param choices           The list of sizes that the camera supports for the intended output
     *                          class
     * @param textureViewWidth  The width of the texture view relative to sensor coordinate
     * @param textureViewHeight The height of the texture view relative to sensor coordinate
     * @param maxWidth          The maximum width that can be chosen
     * @param maxHeight         The maximum height that can be chosen
     * @param aspectRatio       The aspect ratio
     * @return The optimal {@code Size}, or an arbitrary one if none were big enough
     */
    public static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
                                          int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {

        // Collect the supported resolutions that are at least as big as the preview Surface
        List<Size> bigEnough = new ArrayList<>();
        // Collect the supported resolutions that are smaller than the preview Surface
        List<Size> notBigEnough = new ArrayList<>();
        int w = aspectRatio.getWidth();
        int h = aspectRatio.getHeight();
        for (Size option : choices) {
            if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
                    option.getHeight() == option.getWidth() * h / w) {
                if (option.getWidth() >= textureViewWidth &&
                        option.getHeight() >= textureViewHeight) {
                    bigEnough.add(option);
                } else {
                    notBigEnough.add(option);
                }
            }
        }

        // Pick the smallest of those big enough. If there is no one big enough, pick the
        // largest of those not big enough.
        if (bigEnough.size() > 0) {
            return Collections.min(bigEnough, new CompareSizesByArea());
        } else if (notBigEnough.size() > 0) {
            return Collections.max(notBigEnough, new CompareSizesByArea());
        } else {
            Log.e(TAG, "Couldn't find any suitable preview size");
            return choices[0];
        }
    }

    /**
     * Rotate the bitmap a given amount of degrees. This is used to get the correct bitmap when
     * the device is in landscape mode.
     * @param bitmap
     * @param degrees
     * @return a rotated bitmap
     */
    public static Bitmap rotateBitmap(Bitmap bitmap, int degrees) {
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();
        // Setting pre rotate
        Matrix mtx = new Matrix();
        mtx.preRotate(degrees);
        // Rotating Bitmap
        Bitmap rotated = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
        return Bitmap.createScaledBitmap(rotated, bitmap.getWidth(), bitmap.getHeight(), true);
    }

    /**
     * Resize a bitmap and return the resized one.
     * @param bm - Initial bitmap
     * @param newWidth
     * @param newHeight
     * @return a resized bitmap
     */
    public static Bitmap getResizedBitmap(Bitmap bm, int newWidth, int newHeight) {
        int width = bm.getWidth();
        int height = bm.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // "RECREATE" THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(
                bm, 0, 0, width, height, matrix, false);
        bm.recycle();
        return resizedBitmap;
    }

    /**
     * Crop the bitmap to only the part of the scansegment. The bitmap should only contain the part
     * that displays the MRZ of a travel document.
     * @param bitmap - The bitmap created from the camerapreview
     * @param scanSegment - Scansegment, the segment that should be scanned with OCR
     * @return
     */
    public static Bitmap cropBitmap(Bitmap bitmap, ImageView scanSegment) {
        int startX = (int) scanSegment.getX();
        int startY = (int) scanSegment.getY();
        int width = scanSegment.getWidth();
        int length = scanSegment.getHeight();
        return Bitmap.createBitmap(bitmap, startX, startY, width, length);
    }

    /**
     * Get the scan rectangle.
     * @return The rectangle.
     */
    public static Rect getScanRect(ImageView scanSegment) {
        int startX = (int) scanSegment.getX();
        int startY = (int) scanSegment.getY();
        int width = scanSegment.getWidth();
        int length = scanSegment.getHeight();
        return new Rect(startX, startY, startX + width, startY + length);
    }

    /**
     * Compares two {@code Size}s based on their areas.
     */
    static class CompareSizesByArea implements Comparator<Size> {

        @Override
        public int compare(Size lhs, Size rhs) {
            // We cast here to ensure the multiplications won't overflow
            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                    (long) rhs.getWidth() * rhs.getHeight());
        }

    }

    /**
     * Find out if we need to swap dimensions to get the preview size relative to sensor coordinate.
     * @param activity - The associated activity from which the camera is loaded.
     * @param characteristics - CameraCharacteristics corresponding to the current started cameradevice
     * @return swappedDimensions - A boolean value indicating if the the dimensions need to be swapped.
     */
    public static boolean needSwappedDimensions(Activity activity, CameraCharacteristics characteristics) {
        int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        //noinspection ConstantConditions
        int mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        boolean swappedDimensions = false;
        switch (displayRotation) {
            case Surface.ROTATION_0:
            case Surface.ROTATION_90:
                if (mSensorOrientation == 90 || mSensorOrientation == 270) {
                    swappedDimensions = true;
                }
                break;
            case Surface.ROTATION_180:
            case Surface.ROTATION_270:
                if (mSensorOrientation == 0 || mSensorOrientation == 180) {
                    swappedDimensions = true;
                }
                break;
            default:
                Log.e(TAG, "Display rotation is invalid: " + displayRotation);
        }
        return swappedDimensions;
    }
}