package com.cognex.cmbsdk_flutter;

import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;

import com.cognex.cmbcrossplatform.CCPBridge;
import com.cognex.cmbcrossplatform.enums.ImageSourceType;
import com.cognex.cmbcrossplatform.interfaces.APIResponseListener;
import com.cognex.cmbcrossplatform.interfaces.PermissionBridgeListener;
import com.cognex.cmbcrossplatform.interfaces.ReaderDeviceBridgeListener;

import org.json.JSONObject;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry;

/**
 * CmbsdkFlutterPlugin
 */
public class CmbsdkFlutterPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
    private static final String TAG = CmbsdkFlutterPlugin.class.getSimpleName();

    /// The MethodChannel that will the communication between Flutter and native
    /// Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine
    /// and unregister it
    /// when the Flutter Engine is detached from the Activity
    private MethodChannel channel;

    private EventChannel availabilityChannel;
    private EventChannel connectionStateChannel;
    private EventChannel resultsChannel;
    private EventChannel scanningStateChannel;

    private EventChannel.EventSink availabilityChannelEventSink;
    private EventChannel.EventSink connectionStateChannelEventSink;
    private EventChannel.EventSink resultsChannelEventSink;
    private EventChannel.EventSink scanningStateChannelEventSink;

    private ActivityPluginBinding activityPluginBinding;

    private CCPBridge ccpBridge;
    private final ReaderDeviceBridgeListener readerDeviceBridgeListener = new ReaderDeviceBridgeListener() {
        @Override
        public void connectionStateChanged(int connectionState) {
            if (connectionStateChannelEventSink != null)
                connectionStateChannelEventSink.success(connectionState);
        }

        @Override
        public void readResultReceived(JSONObject result) {
            if (resultsChannelEventSink != null)
                resultsChannelEventSink.success(result.toString());
        }

        @Override
        public void availabilityChanged(int availability) {
            if (availabilityChannelEventSink != null)
                availabilityChannelEventSink.success(availability);
        }

        @Override
        public void scanningStateChanged(boolean state) {
            if (scanningStateChannelEventSink != null)
                scanningStateChannelEventSink.success(state);
        }
    };

    private final PermissionBridgeListener permissionBridgeListener = new PermissionBridgeListener() {
        @Override
        public void requestPermission(String[] permissions, int requestCode) {
            if (activityPluginBinding != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                activityPluginBinding.getActivity().requestPermissions(permissions, requestCode);
            }
        }
    };

    private final PluginRegistry.RequestPermissionsResultListener permissionsResultListener = new PluginRegistry.RequestPermissionsResultListener() {
        @Override
        public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
            if (requestCode == CCPBridge.CAMERA_REQUEST_PERMISSION_CODE) {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    ccpBridge.connect(null);
                }
            }

            return false;
        }
    };

    @Override
    public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {
        channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "cmbsdk_flutter");
        channel.setMethodCallHandler(this);

        availabilityChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "availabilityChannel");
        availabilityChannel.setStreamHandler(new EventChannel.StreamHandler() {
            @Override
            public void onListen(Object arguments, EventChannel.EventSink events) {
                Log.d(TAG, "availabilityChannelEventSink is initialized");
                availabilityChannelEventSink = events;
            }

            @Override
            public void onCancel(Object arguments) {

            }
        });

        connectionStateChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "connectionStateChannel");
        connectionStateChannel.setStreamHandler(new EventChannel.StreamHandler() {
            @Override
            public void onListen(Object arguments, EventChannel.EventSink events) {
                Log.d(TAG, "connectionStateChannelEventSink is initialized");
                connectionStateChannelEventSink = events;
            }

            @Override
            public void onCancel(Object arguments) {

            }
        });

        resultsChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "resultsChannel");
        resultsChannel.setStreamHandler(new EventChannel.StreamHandler() {
            @Override
            public void onListen(Object arguments, EventChannel.EventSink events) {
                Log.d(TAG, "resultsChannelEventSink is initialized");
                resultsChannelEventSink = events;
            }

            @Override
            public void onCancel(Object arguments) {

            }
        });

        scanningStateChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "scanningStateChannel");
        scanningStateChannel.setStreamHandler(new EventChannel.StreamHandler() {
            @Override
            public void onListen(Object arguments, EventChannel.EventSink events) {
                Log.d(TAG, "scanningStateChannelEventSink is initialized");
                scanningStateChannelEventSink = events;
            }

            @Override
            public void onCancel(Object arguments) {

            }
        });
    }

    @Override
    public void onDetachedFromEngine(FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);

        availabilityChannelEventSink = null;
        availabilityChannel.setStreamHandler(null);

        connectionStateChannelEventSink = null;
        connectionStateChannel.setStreamHandler(null);

        resultsChannelEventSink = null;
        resultsChannel.setStreamHandler(null);

        scanningStateChannelEventSink = null;
        scanningStateChannel.setStreamHandler(null);
    }

    @Override
    public void onAttachedToActivity(ActivityPluginBinding binding) {
        Log.i(TAG, TAG + " is attached to activity");

        ccpBridge = new CCPBridge(binding.getActivity(), readerDeviceBridgeListener, permissionBridgeListener);
        activityPluginBinding = binding;
        activityPluginBinding.addRequestPermissionsResultListener(permissionsResultListener);
        activityPluginBinding.getActivity().getApplication()
                .registerActivityLifecycleCallbacks(ccpBridge.lifecycleCallbacks);
    }

    @Override
    public void onDetachedFromActivity() {
        Log.i(TAG, TAG + " is detached from activity");

        activityPluginBinding.removeRequestPermissionsResultListener(permissionsResultListener);
        activityPluginBinding.getActivity().getApplication()
                .unregisterActivityLifecycleCallbacks(ccpBridge.lifecycleCallbacks);
        activityPluginBinding = null;
        ccpBridge = null;
    }

    @Override
    public void onDetachedFromActivityForConfigChanges() {
    }

    @Override
    public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
    }

    @SuppressWarnings("ConstantConditions")
    @Override
    public void onMethodCall(MethodCall call, Result result) {
        switch (call.method) {
            case "loadScanner":
                loadScanner(result, (int) call.arguments());
                return;
            case "connect":
                connect(result);
                return;
            case "disconnect":
                disconnect(result);
                return;
            case "startScanning":
                startScanning(result);
                return;
            case "stopScanning":
                stopScanning(result);
                return;
            case "setSymbologyEnabled":
                setSymbologyEnabled(result, (int) call.argument("symbology"),
                        (boolean) call.argument("enable"));
                return;
            case "isSymbologyEnabled":
                isSymbologyEnabled(result, (int) call.arguments());
                return;
            case "setCameraMode":
                setCameraMode((int) call.arguments());
                return;
            case "setPreviewOptions":
                setPreviewOptions((int) call.arguments());
                return;
            case "setPreviewOverlayMode":
                setPreviewOverlayMode((int) call.arguments());
                return;
            case "setPreviewContainerPositionAndSize":
                setPreviewContainerPositionAndSize((double) call.argument("x"), (double) call.argument("y"),
                        (double) call.argument("width"), (double) call.argument("height"));
                return;
            case "setLightsOn":
                setLightsOn(result, (boolean) call.arguments());
                return;
            case "isLightsOn":
                isLightsOn(result);
                return;
            case "enableImage":
                enableImage(result, (boolean) call.arguments());
                return;
            case "enableImageGraphics":
                enableImageGraphics(result, (boolean) call.arguments());
                return;
            case "sendCommand":
                sendCommand(result, (String) call.arguments());
                return;
            case "resetConfig":
                resetConfig(result);
                return;
            case "registerSDK":
                registerSDK((String) call.arguments());
                return;
            case "getAvailability":
                getAvailability(result);
                return;
            case "getConnectionState":
                getConnectionState(result);
                return;
            case "beep":
                beep(result);
                return;
            case "getDeviceBatteryLevel":
                getDeviceBatteryLevel(result);
                return;
            case "getSdkVersion":
                getSdkVersion(result);
                return;
            case "setPreviewContainerFullScreen":
                setPreviewContainerFullScreen(result);
                return;
            case "setStopScannerOnRotate":
                setStopScannerOnRotate((boolean) call.arguments());
                return;
            case "enableCameraFlag":
            case "disableCameraFlag":
                editCameraFlag(result, (int) call.argument("codeMask"),
                        (int) call.argument("flag"), "enableCameraFlag".equalsIgnoreCase(call.method));
                return;
            case "setCameraParam":
                setCameraParam(result, (int) call.argument("param"),
                        (int) call.argument("value"), (int) call.argument("codeMask"));
                return;
            case "setParser":
                setParser(result, (int) call.arguments());
                return;
            case "setReadStringEncoding":
                setReadStringEncoding(result, (int) call.arguments());
                return;
            case "getCameraExposureCompensationRange":
                getCameraExposureCompensationRange(result);
                return;
            case "setCameraExposureCompensation":
                setCameraExposureCompensation(result, (double) call.arguments());
                return;
            case "loadCameraConfig":
                loadCameraConfig(result);
                return;
            case "showToast":
                showToast((String) call.arguments());
                return;
            case "hideToast":
                hideToast();
                return;
            case "scanImageFromUri":
                scanImage(result, ImageSourceType.URI, (String) call.arguments());
                return;
            case "scanImageFromBase64":
                scanImage(result, ImageSourceType.BASE64, (String) call.arguments());
                return;
            case "setPairedBluetoothDevice":
                setPairedBluetoothDevice((String) call.arguments());
                return;
            default:
                result.notImplemented();
        }
    }

    private APIResponseListener createResponseListener(final Result apResult) {
        return new APIResponseListener() {
            @Override
            public void successWithObject(Object result) {
                // float[] is not supported
                // JSONObject is not supported
                if (result instanceof float[]) {
                    final float[] floatResult = (float[]) result;
                    final double[] doubleResult = new double[floatResult.length];
                    for (int i = 0; i < floatResult.length; i++)
                        doubleResult[i] = floatResult[i];

                    apResult.success(doubleResult);
                } else if (result instanceof JSONObject)
                    apResult.success(result.toString());
                else {
                    // String, boolean or int result
                    apResult.success(result);
                }
            }

            @Override
            public void error(String errorCode, String errorMessage) {
                apResult.error(errorCode, errorMessage, null);
            }
        };
    }

    private void loadScanner(final Result result, int deviceType) {
        ccpBridge.loadScanner(createResponseListener(result), deviceType);
    }

    private void connect(final Result result) {
        ccpBridge.connect(createResponseListener(result));
    }

    private void disconnect(final Result result) {
        ccpBridge.disconnect(createResponseListener(result));
    }

    private void startScanning(final Result result) {
        ccpBridge.startScanning(createResponseListener(result));
    }

    private void stopScanning(final Result result) {
        ccpBridge.stopScanning(createResponseListener(result));
    }

    private void setSymbologyEnabled(final Result result, int symbology, boolean enable) {
        ccpBridge.setSymbologyEnabled(createResponseListener(result), symbology, enable);
    }

    private void isSymbologyEnabled(final Result result, int symbology) {
        ccpBridge.isSymbologyEnabled(createResponseListener(result), symbology);
    }

    private void setCameraMode(int cameraMode) {
        ccpBridge.setCameraMode(cameraMode);
    }

    private void setPreviewOptions(int previewOptions) {
        ccpBridge.setPreviewOptions(previewOptions);
    }

    private void setPreviewOverlayMode(int overlayMode) {
        ccpBridge.setPreviewOverlayMode(overlayMode);
    }

    private void setPreviewContainerPositionAndSize(double x, double y, double width, double height) {
        ccpBridge.setPreviewContainerPositionAndSize(x, y, width, height);
    }

    private void setLightsOn(final Result result, boolean on) {
        ccpBridge.setLightsOn(createResponseListener(result), on);
    }

    private void isLightsOn(final Result result) {
        ccpBridge.isLightsOn(createResponseListener(result));
    }

    private void enableImage(final Result result, boolean enable) {
        ccpBridge.enableImage(createResponseListener(result), enable);
    }

    private void enableImageGraphics(final Result result, boolean enable) {
        ccpBridge.enableImageGraphics(createResponseListener(result), enable);
    }

    private void sendCommand(final Result result, String commandString) {
        ccpBridge.sendCommand(createResponseListener(result), commandString);
    }

    private void resetConfig(final Result result) {
        ccpBridge.resetConfig(createResponseListener(result));
    }

    private void registerSDK(String key) {
        ccpBridge.registerSDK(key);
    }

    private void getAvailability(final Result result) {
        ccpBridge.getAvailability(createResponseListener(result));
    }

    private void getConnectionState(final Result result) {
        ccpBridge.getConnectionState(createResponseListener(result));
    }

    private void beep(final Result result) {
        ccpBridge.beep(createResponseListener(result));
    }

    private void getDeviceBatteryLevel(final Result result) {
        ccpBridge.getDeviceBatteryLevel(createResponseListener(result));
    }

    private void getSdkVersion(final Result result) {
        ccpBridge.getSdkVersion(createResponseListener(result));
    }

    private void setPreviewContainerFullScreen(final Result result) {
        ccpBridge.setPreviewContainerFullScreen(createResponseListener(result));
    }

    private void setStopScannerOnRotate(boolean stop) {
        ccpBridge.setStopScannerOnRotate(stop);
    }

    private void editCameraFlag(final Result result, int mask, int flag, boolean enable) {
        ccpBridge.editCameraFlag(createResponseListener(result), mask, flag, enable);
    }

    private void setCameraParam(final Result result, int param, int value, int mask) {
        ccpBridge.setCameraParam(createResponseListener(result), param, value, mask);
    }

    private void setParser(final Result result, int parser) {
        ccpBridge.setParser(createResponseListener(result), parser);
    }

    private void setReadStringEncoding(final Result result, int encoding) {
        ccpBridge.setReadStringEncoding(createResponseListener(result), encoding);
    }

    private void getCameraExposureCompensationRange(final Result result) {
        ccpBridge.getCameraExposureCompensationRange(createResponseListener(result));
    }

    private void setCameraExposureCompensation(final Result result, double exposureCompensation) {
        ccpBridge.setCameraExposureCompensation(createResponseListener(result), (float) exposureCompensation);
    }

    private void loadCameraConfig(final Result result) {
        ccpBridge.loadCameraConfig(createResponseListener(result));
    }

    private void showToast(String message) {
        ccpBridge.showToast(message);
    }

    private void hideToast() {
        ccpBridge.hideToast(true);
    }

    private void scanImage(final Result result, ImageSourceType sourceType, String source) {
        ccpBridge.scanImage(createResponseListener(result), sourceType, source);
    }

    private void setPairedBluetoothDevice(String btDevice) {
        ccpBridge.setPairedBTDeviceMacAddress(btDevice);
    }
}
