package com.visagetechnologies.androidcameraplugin;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.app.AlertDialog;
import android.view.Surface;
import android.view.SurfaceView;
import android.widget.TextView;
import android.graphics.Matrix;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.TypedValue;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.WindowManager;
import android.widget.Toast;

import java.io.IOException;
import java.util.List;
import java.lang.*;
import java.lang.String;
import java.nio.ByteBuffer;

import com.unity3d.player.Camera2Wrapper;
import com.unity3d.player.UnityPlayer;
import com.visagetechnologies.bsc.ColorModel;
import com.visagetechnologies.bsc.FrameProvider;
import com.visagetechnologies.CamIL.Plane;
import com.visagetechnologies.CamIL.IFormat;
import com.visagetechnologies.CamIL.EFormat;
import com.visagetechnologies.makeupsdk.MakeupEngine;

public class CameraActivity {
    public final String TAG = "JavaCamTrackerView";

    public FrameProvider frameProvider;

    SurfaceTexture tex;

    int imageWidth = 0;
    int imageHeight = 0;

    Camera cam;
    boolean isCamOpen = false;
    int cameraFacing = CameraInfo.CAMERA_FACING_FRONT;

    public CameraActivity(Context context, FrameProvider frameProvider) {
        this.frameProvider = frameProvider;
    }

    /**
     * Calculates supported frame dimensions that are closest (by eucledian distance) to desired width and height
     *
     * @param parameters camera parameters
     * @param width      desired frame width
     * @param height     desired frame height
     */
    private void setPreviewSize(Camera.Parameters parameters, int width, int height) {
        int idx = 0;
        double dist1 = Integer.MAX_VALUE;
        double dist2 = Integer.MAX_VALUE;

        List<Size> sizes = parameters.getSupportedPreviewSizes();

        for (int i = 0; i < sizes.size(); i++) {
            dist2 = Math.abs(
                    Math.sqrt(Math.pow((sizes.get(i).width - width), 2) +
                            Math.pow((sizes.get(i).height - height), 2))
            );

            if (dist2 < dist1) {
                idx = i;
                dist1 = dist2;
            }
        }

        parameters.setPreviewSize(
                sizes.get(idx).width,
                sizes.get(idx).height
        );
    }

    /**
     * @return camera device ID
     */
    private int getCameraId() {
        int numberOfCameras = Camera.getNumberOfCameras();

        for (int cameraIndex = 0; cameraIndex < numberOfCameras; cameraIndex++) {
            CameraInfo info = new CameraInfo();
            Camera.getCameraInfo(cameraIndex, info);

            if (info.facing == this.cameraFacing)
                return cameraIndex;
        }

        return -1;
    }

    /**
     * Start grabbing frames from camera
     */
    public void GrabFromCamera() {

        int camId = getCameraId();
        Log.d(TAG, "camId: " + camId);

        try {
            cam = Camera.open(camId);
        } catch (Exception e) {
            Log.e(TAG, "Unable to open camera");
            return;
        }

        imageWidth = imageWidth <= 0 ? 640 : imageWidth;
        imageHeight = imageHeight <= 0 ? 480 : imageHeight;

        Camera.Parameters parameters = cam.getParameters();
        setPreviewSize(parameters, imageWidth, imageHeight);
        parameters.setPreviewFormat(ImageFormat.NV21);
        cam.setParameters(parameters);

        final Size previewSize = cam.getParameters().getPreviewSize();
        imageWidth = previewSize.width;
        imageHeight = previewSize.height;

        Log.i(TAG, "Current preview size is " + imageWidth + ", " + imageHeight);

        double bytesPerPixel = (ImageFormat.getBitsPerPixel(cam.getParameters().getPreviewFormat()) / 8.0);
        int dataBufferSize = (int) (imageWidth * imageHeight * bytesPerPixel);

        cam.addCallbackBuffer(new byte[dataBufferSize]);

        try {
            tex = new SurfaceTexture(0);
            Log.i(TAG, "Setting preview tex");
            cam.setPreviewTexture(tex);
        } catch (IOException e) {
            e.printStackTrace();
        }

        cam.setPreviewCallbackWithBuffer(new PreviewCallback() {
            public void onPreviewFrame(byte[] data, Camera camera) {
                EFormat eformat = EFormat.YVU420i;
                IFormat format = com.visagetechnologies.BeautySightCore.GetFormat(eformat);
                
                // This rotates the image to portrait, as AddFrame expects properly oriented picture.
                byte[] data_rotated = rotateNV21(data, imageWidth, imageHeight, 270, cameraFacing == CameraInfo.CAMERA_FACING_FRONT);
                ByteBuffer data_buffer = ByteBuffer.wrap(data_rotated);

                // note width/height are fliped as the image is rotated
                // int id = frameProvider.AddFrame(data_buffer, imageHeight, imageWidth, ColorModel.YVU420i); // queue new frame

                com.visagetechnologies.CamIL.Image oldFrame = frameProvider.ReuseInputFrame();
                com.visagetechnologies.CamIL.Image frame = oldFrame;
                com.visagetechnologies.CamIL.Plane yPlane = frame.GetPlane(0);

                // If buffered image does not fit the new frame
                // we have to replace it with new one.
                // Also, it is reasonable to assume if it's the same format
                // we only need to match dimensions of the first plane
                // note, width/height are rotated
                boolean isReusingBuffer =
                        frame.GetFormat().Equals(format) &&
                        yPlane.GetWidth() == imageHeight &&
                        yPlane.GetHeight() == imageWidth;

                if (!isReusingBuffer) {
                    oldFrame.delete();
                    frame = new com.visagetechnologies.CamIL.Image(imageHeight, imageWidth, eformat);
                    yPlane = frame.GetPlane(0);
                }

                int ySize = yPlane.GetSize();
                com.visagetechnologies.CamIL.Plane uvPlane = frame.GetPlane(1);
                int uvSize = uvPlane.GetSize();

                // Y Plane
                ByteBuffer yNativeBuffer = yPlane.GetBuffer();
                yNativeBuffer.put(data_rotated, 0, ySize);

                // UV Plane
                ByteBuffer uvNativeBuffer = uvPlane.GetBuffer();
                uvNativeBuffer.put(data_rotated, ySize, uvSize);

                int id = frameProvider.AddFrame(frame); // queue new frame

                camera.addCallbackBuffer(data); // reuse android camera buffer
            }
        });

        Log.d(TAG, "Starting preview");
        cam.startPreview();
        isCamOpen = true;
    }

    /**
     * Stops camera.
     */
    public void closeCamera() {
        if (cam != null) {
            cam.stopPreview();
            cam.release();
            cam = null;
        }
    }

    public void onPause() {
        Log.d(TAG, "On Pause");
        closeCamera();
    }

    public void onResume() {
        Log.d(TAG, "On Resume: " + isCamOpen);
        if (isCamOpen)
            GrabFromCamera();
    }

    public void onDestroy() {
        Log.d(TAG, "On Destroy");
        closeCamera();
    }

    public static byte[] rotateNV21(byte[] yvu, int width, int height, int rotation, boolean flip) {
        if (rotation == 0) return yvu;
        if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
            throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0");
        }

        final byte[] output = new byte[yvu.length];
        final int frameSize = width * height;
        final boolean swap = rotation % 180 != 0;
        final boolean xflip = flip ^ rotation % 270 != 0;
        final boolean yflip = rotation >= 180;

        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++) {
                // NV21 is layed out in two planes:
                // YYYYYYYY...VUVU...
                final int yIn = j * width + i;
                final int vIn = frameSize + (j >> 1) * width + (i & ~1);
                final int uIn = vIn + 1;

                final int wOut = swap ? height : width;
                final int hOut = swap ? width : height;
                final int iSwapped = swap ? j : i;
                final int jSwapped = swap ? i : j;
                final int iOut = xflip ? wOut - iSwapped - 1 : iSwapped;
                final int jOut = yflip ? hOut - jSwapped - 1 : jSwapped;

                final int yOut = jOut * wOut + iOut;
                final int vOut = frameSize + (jOut >> 1) * wOut + (iOut & ~1);
                final int uOut = vOut + 1;

                // The out YVU420i should be laid out as:
                // YYYYYYYY...VUVU...
                output[yOut] = (byte) (0xff & yvu[yIn]);
                output[vOut] = (byte) (0xff & yvu[vIn]);
                output[uOut] = (byte) (0xff & yvu[uIn]);
            }
        }
        return output;
    }
}
