package com.tasapp.capacitor;

import com.getcapacitor.Bridge;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.PermissionState;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.ActivityCallback;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.getcapacitor.annotation.Permission;
import com.getcapacitor.annotation.PermissionCallback;
import com.tasapp.android.CameraActivity;
import com.tasapp.android.CropActivity;
import com.tasapp.android.RecordActivity;

import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.RECORD_AUDIO;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;

import androidx.activity.result.ActivityResult;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@CapacitorPlugin(
        name = "CameraCapturePlugin",
        permissions = {
                @Permission(strings = { CAMERA }, alias = CameraCapturePlugin.CAMERA_PERMISSION_ALIAS),
                @Permission(strings = { RECORD_AUDIO }, alias = CameraCapturePlugin.AUDIO_PERMISSION_ALIAS),
        }
)
public class CameraCapturePlugin extends Plugin {
    static final String CAMERA_PERMISSION_ALIAS = "camera";
    static final String AUDIO_PERMISSION_ALIAS = "audio";

    private static final ArrayList<String> uris = new ArrayList<>();
    private static JSArray dataResult;

    @PluginMethod
    public void capture(PluginCall call) {
        if (PermissionState.GRANTED.equals(getPermissionState(CAMERA_PERMISSION_ALIAS))) {
            startCameraX(call);
        } else {
            requestPermissionForAlias(CAMERA_PERMISSION_ALIAS, call, "handleCameraPermissionResult");
        }
    }

    @PluginMethod
    public void captureVideo(PluginCall call) {
        Map<String, PermissionState> permissionStates = getPermissionStates();
        if (PermissionState.GRANTED.equals(permissionStates.get(CAMERA_PERMISSION_ALIAS)) && PermissionState.GRANTED.equals(permissionStates.get(AUDIO_PERMISSION_ALIAS))) {
            startRecord(call);
        } else {
            requestPermissionForAliases(new String[] { CAMERA_PERMISSION_ALIAS, AUDIO_PERMISSION_ALIAS }, call, "handleRecordPermissionResult");
        }
    }

    private void startRecord(PluginCall call) {
        Intent intent = new Intent(getActivity(), RecordActivity.class);
        startActivityForResult(call, intent, "videoRecordResult");
    }

    @ActivityCallback
    private void videoRecordResult(PluginCall call, ActivityResult result) {
        final int code = result.getResultCode();
        if (code == Activity.RESULT_OK) {
            JSObject resultObj = new JSObject();
            final Intent dataIntent = result.getData();
            final Bundle bundle;
            if (dataIntent != null) {
                bundle = dataIntent.getExtras();
                final String data = bundle.getString("result");
                resultObj.put("data", bridge.getLocalUrl() + Bridge.CAPACITOR_FILE_START + data);
                call.resolve(resultObj);
            } else {
                call.reject("Canceled");
            }
        } else if (code == Activity.RESULT_CANCELED) {
            final Intent dataIntent = result.getData();
            String msg = "Canceled";
            if (dataIntent != null) {
                msg = dataIntent.getExtras().getString("error");
            }
            call.reject(msg);
        } else {
            call.reject("Canceled");
        }
    }

    @ActivityCallback
    private void simpleCropResult(PluginCall call, ActivityResult result) {
        final int code = result.getResultCode();
        if (code == Activity.RESULT_OK) {
            JSObject resultObj = new JSObject();
            final Intent dataIntent = result.getData();
            final Bundle bundle;
            if (dataIntent != null) {
                bundle = dataIntent.getExtras();
                final String data = bundle.getString("result");
                resultObj.put("data", bridge.getLocalUrl() + Bridge.CAPACITOR_FILE_START + data);
                final String dataThumb = bundle.getString("thumb");
                resultObj.put("thumb", bridge.getLocalUrl() + Bridge.CAPACITOR_FILE_START + dataThumb);
                JSObject cordsObj = new JSObject();
                final int coordsX = bundle.getInt("coordsX");
                cordsObj.put("x", coordsX);
                final int coordsY = bundle.getInt("coordsY");
                cordsObj.put("y", coordsY);
                resultObj.put("coords", cordsObj);
                JSObject dimensionsObj = new JSObject();
                final int dimensionsX = bundle.getInt("dimensionsX");
                dimensionsObj.put("width", dimensionsX);
                final int dimensionsY = bundle.getInt("dimensionsY");
                dimensionsObj.put("height", dimensionsY);
                resultObj.put("dimensions", dimensionsObj);
                call.resolve(resultObj);
            } else {
                call.reject("Canceled");
            }
        } else if (code == Activity.RESULT_CANCELED) {
            final Intent dataIntent = result.getData();
            String msg = "Canceled";
            if (dataIntent != null) {
                msg = dataIntent.getExtras().getString("error");
            }
            call.reject(msg);
        } else {
            call.reject("Canceled");
        }
    }

    @ActivityCallback
    private void pickSimpleImageResult(PluginCall call, ActivityResult result) {
        Intent resultIntent = result.getData();
        if (result.getResultCode() == Activity.RESULT_OK && resultIntent != null) {
            Uri uri = resultIntent.getData();
            if (uri == null) {
                call.reject("Canceled");
                return;
            }
            final int aspectRatioX = call.getInt("aspectRatioX", 1);
            final int aspectRatioY = call.getInt("aspectRatioY", 1);
            final boolean rounded = call.getBoolean("rounded", false);
            final String msg = call.getString("message", "No Message");
            Intent intent = new Intent(getActivity(), CropActivity.class);
            intent.putExtra("aspectRatioX", aspectRatioX);
            intent.putExtra("aspectRatioY", aspectRatioY);
            intent.putExtra("rounded", rounded);
            intent.putExtra("message", msg);
            intent.putExtra("file", uri.toString());
            intent.putExtra("moveFile", true);
            startActivityForResult(call, intent, "simpleCropResult");
        } else {
            call.reject("Canceled");
        }
    }

    @PluginMethod
    public void pickSimpleImage(PluginCall call) {
        call.setKeepAlive(true);
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(call, Intent.createChooser(intent, "Select Picture"), "pickSimpleImageResult");
    }

    @PluginMethod
    public void pickSimpleVideo(PluginCall call) {
        call.setKeepAlive(true);
        Intent intent = new Intent();
        intent.setType("video/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(call, intent, "pickSimpleVideoResult");
    }

    @PluginMethod
    public void pickMultipleVideo(PluginCall call) {
        call.setKeepAlive(true);
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
        intent.putExtra("multi-pick", true);
        intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] { "video/*" });
        intent.setType("video/*");
        startActivityForResult(call, intent, "pickMultipleVideoResult");
    }

    @ActivityCallback
    private void pickSimpleVideoResult(PluginCall call, ActivityResult result) {
        Intent resultIntent = result.getData();
        if (result.getResultCode() == Activity.RESULT_OK && resultIntent != null) {
            Uri uri = resultIntent.getData();
            if (uri == null) {
                call.reject("Canceled");
                return;
            }
            try {
                InputStream iStream = getActivity().getContentResolver().openInputStream(uri);
                ByteArrayOutputStream bitmapOutputStream = getByteArrayOutputStream(iStream);
                File newUFile = getTempImage(uri, bitmapOutputStream);
                JSObject jsResult = new JSObject();
                jsResult.put("url", bridge.getLocalUrl() + Bridge.CAPACITOR_FILE_START + newUFile.getAbsolutePath());
                call.resolve(jsResult);
            } catch (Exception e) {
                e.printStackTrace();
                call.reject(e.getMessage());
            }
        } else {
            call.reject("Canceled");
        }
    }

    @ActivityCallback
    private void pickMultipleVideoResult(PluginCall call, ActivityResult result) {
        int resultCode = result.getResultCode();
        switch (resultCode) {
            case Activity.RESULT_OK:
                Intent data = result.getData();
                if (data != null) {
                    List<String> videoUris = getUrisFromIntent(data);
                    if (videoUris.size() == 0) {
                        call.reject("Canceled");
                    } else {
                        JSObject jsResult = new JSObject();
                        JSArray urls = new JSArray();
                        for (String uriPath: videoUris) {
                            try {
                                Uri uri = Uri.parse(uriPath);
                                InputStream iStream = getActivity().getContentResolver().openInputStream(uri);
                                ByteArrayOutputStream bitmapOutputStream = getByteArrayOutputStream(iStream);
                                File newUFile = getTempImage(uri, bitmapOutputStream);
                                urls.put(bridge.getLocalUrl() + Bridge.CAPACITOR_FILE_START + newUFile.getAbsolutePath());
                            } catch (Exception e) {
                                e.printStackTrace();
                                call.reject(e.getMessage());
                            }
                        }
                        jsResult.put("urls", urls);
                        call.resolve(jsResult);
                    }
                } else {
                    call.reject("Canceled");
                }
                break;
            case Activity.RESULT_CANCELED:
                call.reject("Canceled");
                break;
            default:
                call.reject("Unknown Error");
        }
    }

    @SuppressWarnings("deprecation")
    private ArrayList<Parcelable> getLegacyParcelableArrayList(Bundle bundle, String key) {
        return bundle.getParcelableArrayList(key);
    }

    @ActivityCallback
    private void multipleCropResult(PluginCall call, ActivityResult result) {
        try {
            final int code = result.getResultCode();
            final Intent dataIntent = result.getData();
            if (code == Activity.RESULT_OK && dataIntent != null) {
                JSObject resultObj = new JSObject();
                final Bundle bundle = dataIntent.getExtras();
                final String data = bundle.getString("result");
                resultObj.put("data", bridge.getLocalUrl() + Bridge.CAPACITOR_FILE_START + data);
                final String dataThumb = bundle.getString("thumb");
                resultObj.put("thumb", bridge.getLocalUrl() + Bridge.CAPACITOR_FILE_START + dataThumb);
                JSObject cordsObj = new JSObject();
                final int coordsX = bundle.getInt("coordsX");
                cordsObj.put("x", coordsX);
                final int coordsY = bundle.getInt("coordsY");
                cordsObj.put("y", coordsY);
                resultObj.put("coords", cordsObj);
                JSObject dimensionsObj = new JSObject();
                final int dimensionsX = bundle.getInt("dimensionsX");
                dimensionsObj.put("width", dimensionsX);
                final int dimensionsY = bundle.getInt("dimensionsY");
                dimensionsObj.put("height", dimensionsY);
                resultObj.put("dimensions", dimensionsObj);
                dataResult.put(resultObj);
            }
            processMultiple(call);
        } catch (NullPointerException err) {
            err.printStackTrace();
            call.reject(err.getMessage());
        }

    }

    private void processMultiple(PluginCall call) {
        if (uris.size() == 0) {
            JSObject resultObj = new JSObject();
            resultObj.put("data", dataResult);
            call.resolve(resultObj);
        } else {
            final int aspectRatioX = call.getInt("aspectRatioX", 1);
            final int aspectRatioY = call.getInt("aspectRatioY", 1);
            final boolean rounded = call.getBoolean("rounded", false);
            final String msg = call.getString("message", "No Message");
            String item = uris.remove(uris.size() - 1);
            Intent intent = new Intent(getActivity(), CropActivity.class);
            intent.putExtra("aspectRatioX", aspectRatioX);
            intent.putExtra("aspectRatioY", aspectRatioY);
            intent.putExtra("rounded", rounded);
            intent.putExtra("message", msg);
            intent.putExtra("file", item);
            intent.putExtra("moveFile", true);
            startActivityForResult(call, intent, "multipleCropResult");
        }
    }

    private List<String> getUrisFromIntent(Intent data) {
        ArrayList<String> tmpUris = new ArrayList<>();
        if (data.getClipData() != null) {
            int count = data.getClipData().getItemCount();
            for (int i = 0; i < count; i++) {
                Uri fileUri = data.getClipData().getItemAt(i).getUri();
                tmpUris.add(fileUri.toString());
            }
        } else if (data.getData() != null) {
            Uri fileUri = data.getData();
            tmpUris.add(fileUri.toString());
        } else if (data.getExtras() != null) {
            Bundle bundle = data.getExtras();
            if (bundle.keySet().contains("selectedItems")) {
                ArrayList<Parcelable> fileUris;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    fileUris = bundle.getParcelableArrayList("selectedItems", Parcelable.class);
                } else {
                    fileUris = getLegacyParcelableArrayList(bundle, "selectedItems");
                }
                if (fileUris != null) {
                    for (Parcelable fileUri : fileUris) {
                        if (fileUri instanceof Uri) {
                            Uri imageUri = (Uri) fileUri;
                            tmpUris.add(imageUri.toString());
                        }
                    }
                }
            }
        }
        return tmpUris;
    }

    @ActivityCallback
    private void pickMultipleImageResult(PluginCall call, ActivityResult result) {
        uris.clear();
        dataResult = new JSArray();
        Intent data = result.getData();
        if (data != null) {
            List<String> tmpIris = getUrisFromIntent(data);
            uris.addAll(tmpIris);
            if (uris.size() == 0) {
                call.reject("Canceled");
            } else {
                processMultiple(call);
            }
        } else {
            call.reject("Canceled");
        }
    }

    @PluginMethod
    public void pickMultipleImage(PluginCall call) {
        call.setKeepAlive(true);
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
        intent.putExtra("multi-pick", true);
        intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] { "image/*" });
        intent.setType("image/*");
        startActivityForResult(call, intent, "pickMultipleImageResult");
    }

    private void startCameraX(PluginCall call) {
        call.setKeepAlive(true);
        final int aspectRatioX = call.getInt("aspectRatioX", 1);
        final int aspectRatioY = call.getInt("aspectRatioY", 1);
        final boolean rounded = call.getBoolean("rounded", false);
        final String msg = call.getString("message", "No Message");
        Intent intent = new Intent(getActivity(), CameraActivity.class);
        intent.putExtra("aspectRatioX", aspectRatioX);
        intent.putExtra("aspectRatioY", aspectRatioY);
        intent.putExtra("rounded", rounded);
        intent.putExtra("message", msg);
        startActivityForResult(call, intent, "cameraXResult");
    }

    @ActivityCallback
    private void cameraXResult(PluginCall call, ActivityResult result) {
        final int code = result.getResultCode();
        if (code == Activity.RESULT_OK) {
            final Intent dataIntent = result.getData();
            Bundle bundle = dataIntent.getExtras();
            final int aspectRatioX = call.getInt("aspectRatioX", 1);
            final int aspectRatioY = call.getInt("aspectRatioY", 1);
            final boolean rounded = call.getBoolean("rounded", false);
            final String msg = call.getString("message", "No Message");
            final String filePath = bundle.getString("result");
            final Uri uri = Uri.parse(filePath);
            final boolean orientation = bundle.getBoolean("orientation");
            Intent intent = new Intent(getActivity(), CropActivity.class);
            intent.putExtra("aspectRatioX", aspectRatioX);
            intent.putExtra("aspectRatioY", aspectRatioY);
            intent.putExtra("rounded", rounded);
            intent.putExtra("message", msg);
            intent.putExtra("file", uri.toString());
            intent.putExtra("moveFile", false);
            intent.putExtra("orientation", orientation);
            startActivityForResult(call, intent, "simpleCropResult");
        } else if(code == Activity.RESULT_CANCELED) {
            final Intent dataIntent = result.getData();
            String msg = "Canceled";
            if (dataIntent != null) {
                msg = dataIntent.getExtras().getString("error");
            }
            call.reject(msg);
        } else {
            call.reject("Canceled");
        }
    }

    @PermissionCallback
    private void handleCameraPermissionResult(PluginCall call) {
        if (PermissionState.GRANTED.equals(getPermissionState(CAMERA_PERMISSION_ALIAS))) {
            startCameraX(call);
        } else {
            call.reject("Permission failed: user denied access to camera.");
        }
    }

    @PermissionCallback
    private void handleRecordPermissionResult(PluginCall call) {
        Map<String, PermissionState> permissionStates = getPermissionStates();
        if (PermissionState.GRANTED.equals(permissionStates.get(CAMERA_PERMISSION_ALIAS)) && PermissionState.GRANTED.equals(permissionStates.get(AUDIO_PERMISSION_ALIAS))) {
            startRecord(call);
        } else {
            call.reject("Permission failed: user denied access to camera.");
        }
    }

    private File getTempImage(Uri u, ByteArrayOutputStream bitmapOutputStream) {
        ByteArrayInputStream bis = null;
        File newFile = null;
        try {
            bis = new ByteArrayInputStream(bitmapOutputStream.toByteArray());
            newFile = saveFile(u, bis);
        } catch (IOException ex) {} finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return newFile;
    }

    private void writeFile(File outFile, InputStream is) throws IOException {
        FileOutputStream fos = new FileOutputStream(outFile);
        byte[] buffer = new byte[1024];
        int len;
        while ((len = is.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
        fos.close();
    }

    private File getTempFile(Uri uri) {
        String filename = Uri.parse(Uri.decode(uri.toString())).getLastPathSegment();
        File cacheDir = getContext().getCacheDir();
        return new File(cacheDir, filename);
    }

    private File saveFile(Uri uri, InputStream is) throws IOException {
        File outFile = null;
        if (uri.getScheme().equals("content")) {
            outFile = getTempFile(uri);
        } else {
            outFile = new File(uri.getPath());
        }
        try {
            writeFile(outFile, is);
        } catch (FileNotFoundException ex) {
            outFile = getTempFile(uri);
            writeFile(outFile, is);
        }
        return outFile;
    }

    private ByteArrayOutputStream getByteArrayOutputStream(InputStream inputStream) throws IOException {
        ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
        int bufferSize = 1024;
        byte[] buffer = new byte[bufferSize];

        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            byteBuffer.write(buffer, 0, len);
        }
        return byteBuffer;
    }
}
