package io.scanbot.sdk.reactnative.components.barcodecameraview;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;

import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;

import org.jetbrains.annotations.NotNull;

import java.util.Collections;
import java.util.List;

import io.scanbot.sdk.SdkLicenseError;
import io.scanbot.sdk.barcode.BarcodeDetectorFrameHandler;
import io.scanbot.sdk.barcode.DefaultScanbotBarcodeDetector;
import io.scanbot.sdk.barcode.ScanbotBarcodeDetector;
import io.scanbot.sdk.barcode.entity.BarcodeItem;
import io.scanbot.sdk.barcode.entity.BarcodeScannerAdditionalConfig;
import io.scanbot.sdk.barcode.entity.BarcodeScannerAdditionalConfigBuilder;
import io.scanbot.sdk.barcode.entity.BarcodeScannerConfigBuilder;
import io.scanbot.sdk.barcode.entity.BarcodeScanningResult;
import io.scanbot.sdk.camera.CameraOpenCallback;
import io.scanbot.sdk.camera.FrameHandlerResult;
import io.scanbot.sdk.camera.ScanbotCameraView;
import io.scanbot.sdk.reactnative.JSONUtils;
import io.scanbot.sdk.reactnative.R;
import io.scanbot.sdk.reactnative.components.EventEmitter;
import io.scanbot.sdk.reactnative.components.common.ScanbotComponentFrameLayout;
import io.scanbot.sdk.ui.camera.FinderOverlayView;
import kotlin.NotImplementedError;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;

public class ScanbotBarcodeCameraViewFragment extends Fragment implements BarcodeDetectorFrameHandler.ResultHandler {

    private static final int REQUEST_PERMISSION_CODE = 200;

    private ReactContext callerContext;
    private ViewGroup rootViewGroup;
    private ScanbotComponentFrameLayout cameraLayout;
    private ScanbotCameraView cameraView;
    private FinderOverlayView finderOverlayView;
    private ScanbotBarcodeDetector barcodeDetector;
    private BarcodeDetectorFrameHandler frameHandler;
    private ScanbotBarcodeCameraViewConfiguration configuration = new ScanbotBarcodeCameraViewConfiguration();
    private boolean isInitialized = false;
    private EventEmitter jsEventEmitter;
    private float userDefinedCameraZoomFactor = -1;

    public ScanbotBarcodeCameraViewFragment() {
        super(R.layout.fragment_barcode_scanner);
    }

    public void instantiate(final ReactContext reactContext, final ViewGroup rootViewGroup) {
        this.callerContext = reactContext;
        this.rootViewGroup = rootViewGroup;
        jsEventEmitter = new EventEmitter(reactContext, rootViewGroup);
    }

    @Override
    public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        final Activity activity = getActivity();
        final View rootView = getView();
        assert activity != null;
        assert rootView != null;

        cameraLayout = rootView.findViewById(R.id.barcode_camera_layout);
        cameraView = rootView.findViewById(R.id.barcode_camera);
        finderOverlayView = rootView.findViewById(R.id.barcode_finder_overlay);

        cameraView.post(new Runnable() {
            @Override
            public void run() {
                checkPermissions();
            }
        });

        cameraView.onResume();

        // Setup Frame Handler for Barcode Detection
        barcodeDetector = new DefaultScanbotBarcodeDetector();

        frameHandler = BarcodeDetectorFrameHandler.attach(cameraView, barcodeDetector);
        frameHandler.setDetectionInterval(0);
        frameHandler.addResultHandler(this);

        cameraView.addFrameHandler(frameHandler);

        cameraView.setCameraOpenCallback(new CameraOpenCallback() {
            @Override
            public void onCameraOpened() {
                cameraView.continuousFocus();
            }
        });

        applyConfiguration();

        isInitialized = true;
    }

    private void applyConfiguration() {
        finderOverlayView.setVisibility(configuration.shouldUseFinderView() ? View.VISIBLE : View.INVISIBLE);
        finderOverlayView.setStrokeWidth(configuration.getFinderLineWidth());
        finderOverlayView.setStrokeColor(configuration.getFinderLineColor());
        finderOverlayView.setOverlayColor(colorWithAlpha(
                configuration.getFinderBackgroundColor(),
                configuration.getFinderBackgroundOpacity()));

        int finderMinPadding = configuration.getFinderMinimumPadding();
        finderOverlayView.setFinderMinPadding(finderMinPadding);

        int finderVerticalOffset = configuration.getFinderVerticalOffset();
        finderOverlayView.setFinderInset(finderMinPadding + finderVerticalOffset > 0 ? finderVerticalOffset : 0,
                finderMinPadding,
                finderMinPadding + finderVerticalOffset < 0 ? -1 * finderVerticalOffset : 0,
                finderMinPadding);

        finderOverlayView.setRequiredAspectRatios(Collections.singletonList(configuration.getFinderAspectRatio()));

        cameraView.useFlash(configuration.isFlashEnabled());

        if (configuration.getCameraZoomFactor() != -1) {
            if (configuration.getCameraZoomFactor() != userDefinedCameraZoomFactor || userDefinedCameraZoomFactor == -1) {
                cameraView.setOpticalZoomLevel(configuration.getCameraZoomFactor());
                userDefinedCameraZoomFactor = configuration.getCameraZoomFactor();
            }
        }
        barcodeDetector.modifyConfig(new Function1<BarcodeScannerConfigBuilder, Unit>() {
            @Override
            public Unit invoke(BarcodeScannerConfigBuilder b) {
                if (!configuration.barcodeFormats.isEmpty()) {
                    b.setBarcodeFormats(configuration.barcodeFormats);
                }
                if (!configuration.acceptedDocumentFormats.isEmpty()) {
                    b.setAcceptedDocumentFormats(configuration.acceptedDocumentFormats);
                }
                b.setEngineMode(configuration.engineMode);

                if (configuration.additionalConfiguration != null) {
                    b.modifyAdditionalConfig(new Function1<BarcodeScannerAdditionalConfigBuilder, Unit>() {
                        @Override
                        public Unit invoke(BarcodeScannerAdditionalConfigBuilder ab) {
                            final BarcodeScannerAdditionalConfig config = configuration.additionalConfiguration;
                            ab.setMinimumTextLength(config.getMinimumTextLength());
                            ab.setMaximumTextLength(config.getMaximumTextLength());
                            ab.setMsiPlesseyChecksumAlgorithms(config.getMsiPlesseyChecksumAlgorithms());
                            ab.setStripCheckDigits(config.getStripCheckDigits());
                            ab.setGs1DecodingEnabled(config.getGs1DecodingEnabled());
                            ab.setMinimum1DQuietZoneSize(config.getMinimum1DQuietZoneSize());
                            return Unit.INSTANCE;
                        }
                    });
                }
                return Unit.INSTANCE;
            }
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        if (cameraView != null) {
            cameraView.onResume();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (cameraView != null) {
            cameraView.onResume();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (cameraView != null) {
            cameraView.onPause();
        }
    }

    public void updateWithConfiguration(ScanbotBarcodeCameraViewConfiguration configuration) {
        this.configuration = configuration;

        if (isInitialized) {
            applyConfiguration();
        }
    }

    @ColorInt
    public static int colorWithAlpha(@ColorInt int color, float factor) {
        int alpha = Math.round(Color.alpha(color) * factor);
        int red = Color.red(color);
        int green = Color.green(color);
        int blue = Color.blue(color);
        return Color.argb(alpha, red, green, blue);
    }

    private void sendResult(final List<BarcodeItem> barcodeItems) {

        if (barcodeItems == null || barcodeItems.size() == 0) {
            return;
        }

        final WritableArray barcodes = new WritableNativeArray();

        for (BarcodeItem item : barcodeItems) {
            final WritableMap barcode = new JSONUtils.WritableMapBuilder()
                    .putString("text", item.getText())
                    .putString("type", item.getBarcodeFormat().name())
                    .build();
            barcodes.pushMap(barcode);
        }

        final WritableMap result = new JSONUtils.WritableMapBuilder()
                .putString("status", "OK")
                .putArray("barcodes", barcodes)
                .build();

        final WritableMap data = new JSONUtils.WritableMapBuilder()
                .putMap("result", result)
                .build();

        jsEventEmitter.emitEvent(EventEmitter.NativeEvent.BARCODE_SCANNER_RESULT, data);
    }

    @Override
    public boolean handle(@NotNull FrameHandlerResult<? extends BarcodeScanningResult, ? extends SdkLicenseError> frameHandlerResult) {
        if (frameHandlerResult instanceof FrameHandlerResult.Success) {
            handleSuccess((FrameHandlerResult.Success<BarcodeScanningResult>) frameHandlerResult);
        }
        return false;
    }

    private void handleSuccess(final FrameHandlerResult.Success<BarcodeScanningResult> result) {
        if (result == null || result.getValue() == null) {
            return;
        }

        sendResult(result.getValue().getBarcodeItems());
    }

    private void checkPermissions() {
        if (callerContext == null) {
            return;
        }
        Activity activity = callerContext.getCurrentActivity();

        if (activity != null && ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CODE);
        }
    }
}
