package com.regula.documentreader;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.Toast;
import android.util.Base64;

import com.regula.documentreader.api.DocumentReader;
import com.regula.documentreader.api.completions.IDocumentReaderCompletion;
import com.regula.documentreader.api.completions.IDocumentReaderInitCompletion;
import com.regula.documentreader.api.completions.IDocumentReaderPrepareCompletion;
import com.regula.documentreader.api.enums.DocReaderAction;
import com.regula.documentreader.api.params.ImageInputParam;
import com.regula.documentreader.api.results.DocumentReaderResults;

import static com.regula.documentreader.JSONConstructor.resultsToJsonObject;
import static com.regula.documentreader.JSONConstructor.generateDocumentReaderScenario;

import com.regula.documentreader.api.results.DocumentReaderScenario;

import org.json.JSONArray;

import java.util.ArrayList;

@SuppressWarnings({"unused", "ConstantConditions", "WeakerAccess", "AccessStaticViaInstance", "NullableProblems"})
public class RNRegulaDocumentReaderModule extends ReactContextBaseJavaModule {

    int timer = -2000000000;
    private static ReactApplicationContext reactContext;
    private final String prepareDatabaseProgressChangeEvent = "prepareDatabaseProgressChangeEvent";

    public RNRegulaDocumentReaderModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
    }

    @Override
    public String getName() {
        return "RNRegulaDocumentReader";
    }

    public static ReactApplicationContext getContext() {
        return reactContext;
    }

    @ReactMethod
    public void selectedScenario(Callback callback) {
        ArrayList<String> scenarioNames = new ArrayList<>();
        for (int i = 0; i < DocumentReader.Instance().availableScenarios.size(); i++)
            scenarioNames.add(DocumentReader.Instance().availableScenarios.get(i).name);
        callback.invoke(generateDocumentReaderScenario(DocumentReader.Instance().availableScenarios.get(scenarioNames.indexOf(DocumentReader.Instance().processParams().scenario))));
    }

    @ReactMethod
    public void getDocumentReaderIsReady(Callback callback) {
        callback.invoke(DocumentReader.Instance().getDocumentReaderIsReady());
    }

    @ReactMethod
    public void getDocumentReaderStatus(Callback callback) {
        callback.invoke(DocumentReader.Instance().getDocumentReaderStatus());
    }

    @ReactMethod
    public void getCanRFID(Callback callback) {
        callback.invoke(DocumentReader.Instance().getCanRFID());
    }

    @ReactMethod
    public void initializeReader(ReadableMap opts, final Callback callback) {
        if (!DocumentReader.Instance().getDocumentReaderIsReady()) {
            final byte[] license = Base64.decode(opts.getString("licenseKey"), Base64.NO_WRAP);
            DocumentReader.Instance().initializeReader(reactContext, license, getInitCompletion(callback));
        } else
            callback.invoke("already initialized");
    }

    @ReactMethod
    public void startNewSession(Callback callback) {
        DocumentReader.Instance().startNewSession();
        callback.invoke();
    }

    @ReactMethod
    public void startNewPage(Callback callback) {
        DocumentReader.Instance().startNewPage();
        callback.invoke();
    }

    @ReactMethod
    public void recognizeImageWithImageInputParams(String base64Image, final ReadableMap params, final Callback callback) {
        if (DocumentReader.Instance().getDocumentReaderIsReady()) {
            byte[] decodedString = Base64.decode(base64Image, Base64.DEFAULT);
            Bitmap image = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
            DocumentReader.Instance().recognizeImage(image, new ImageInputParam(params.getInt("width"), params.getInt("height"), params.getInt("type")), getCompletion(callback));
        } else
            callback.invoke("document reader not ready");
    }

    @ReactMethod
    public void recognizeImageWithOpts(final ReadableMap opts, String base64Image, final Callback callback) {
        RegulaConfig.setConfig(DocumentReader.Instance(), opts);
        recognizeImage(base64Image, callback);
    }

    @ReactMethod
    public void recognizeImage(String base64Image, final Callback callback) {
        if (DocumentReader.Instance().getDocumentReaderIsReady()) {
            byte[] decodedString = Base64.decode(base64Image, Base64.DEFAULT);
            Bitmap image = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
            DocumentReader.Instance().recognizeImage(image, getCompletion(callback));
        } else
            callback.invoke("document reader not ready");
    }

    @ReactMethod
    public void recognizeImageFrame(String base64Image, final ReadableMap opts, final Callback callback) {
        if (DocumentReader.Instance().getDocumentReaderIsReady()) {
            byte[] decodedString = Base64.decode(base64Image, Base64.DEFAULT);
            Bitmap image = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
            DocumentReader.Instance().recognizeImageFrame(image, new ImageInputParam(opts.getInt("width"), opts.getInt("height"), opts.getInt("type")), getCompletion(callback));
        } else
            callback.invoke("document reader not ready");
    }

    @ReactMethod
    public void recognizeVideoFrame(String byteString, final ReadableMap opts, final Callback callback) {
        if (DocumentReader.Instance().getDocumentReaderIsReady())
            DocumentReader.Instance().recognizeVideoFrame(byteString.getBytes(), new ImageInputParam(opts.getInt("width"), opts.getInt("height"), opts.getInt("type")), getCompletion(callback));
        else
            callback.invoke("document reader not ready");
    }

    @ReactMethod
    public void showScannerWithCameraID(int cameraID, final Callback callback) {
        if (DocumentReader.Instance().getDocumentReaderIsReady())
            DocumentReader.Instance().showScanner(reactContext.getCurrentActivity(), cameraID, getCompletion(callback));
        else
            callback.invoke("document reader not ready");
    }

    @ReactMethod
    public void showScanner(final Callback callback) {
        showScannerWithCameraID(-1, callback);
    }

    @ReactMethod
    public void showScannerWithCameraIDAndOpts(int cameraID, final ReadableMap opts, final Callback callback) {
        if (DocumentReader.Instance().getDocumentReaderIsReady()) {
            RegulaConfig.setConfig(DocumentReader.Instance(), opts);
            DocumentReader.Instance().showScanner(reactContext, cameraID, getCompletion(callback));
        } else
            callback.invoke("document reader not ready");

    }

    @ReactMethod
    public void stopScanner(Callback callback) {
        DocumentReader.Instance().stopScanner(reactContext);
        callback.invoke();
    }

    @ReactMethod
    public void startRFIDReader(Callback callback) {
        DocumentReader.Instance().startRFIDReader(reactContext, getCompletion(callback));
    }

    @ReactMethod
    public void stopRFIDReader(Callback callback) {
        DocumentReader.Instance().stopRFIDReader(reactContext);
        callback.invoke();
    }

    @ReactMethod
    public void prepareDatabase(String dbID, final Callback callback) {
        DocumentReader.Instance().prepareDatabase(reactContext, dbID, getPrepareCompletion(callback));
    }

    @ReactMethod
    public void runAutoUpdate(String dbID, Callback callback) {
        DocumentReader.Instance().runAutoUpdate(reactContext, dbID, getPrepareCompletion(callback));
    }

    @ReactMethod
    public void setRfidScenario(final ReadableMap opts, final Callback callback) {
        RegulaConfig.setRfidScenario(opts);
        callback.invoke();
    }

    @ReactMethod
    public void getRfidScenario(final Callback callback) {
        callback.invoke(DocumentReader.Instance().rfidScenario().toJson());
    }

    @ReactMethod
    public void getAPIVersion(Callback callback) {
        callback.invoke(DocumentReader.Instance().version.api);
    }

    @ReactMethod
    public void getCoreVersion(Callback callback) {
        callback.invoke(DocumentReader.Instance().version.core);
    }

    @ReactMethod
    public void getCoreMode(Callback callback) {
        callback.invoke(DocumentReader.Instance().version.coreMode);
    }

    @ReactMethod
    public void getDatabaseID(Callback callback) {
        callback.invoke(DocumentReader.Instance().version.database.databaseID);
    }

    @ReactMethod
    public void getDatabaseVersion(Callback callback) {
        callback.invoke(DocumentReader.Instance().version.database.version);
    }

    @ReactMethod
    public void getDatabaseDate(Callback callback) {
        callback.invoke(DocumentReader.Instance().version.database.date);
    }

    @ReactMethod
    public void getDatabaseDescription(Callback callback) {
        callback.invoke(DocumentReader.Instance().version.database.databaseDescription);
    }

    @ReactMethod
    public void getDatabaseCountriesNumber(Callback callback) {
        callback.invoke(DocumentReader.Instance().version.database.countriesNumber);
    }

    @ReactMethod
    public void getDatabaseDocumentsNumber(Callback callback) {
        callback.invoke(DocumentReader.Instance().version.database.documentsNumber);
    }

    @ReactMethod
    public void getAvailableScenarios(Callback callback) {
        JSONArray scenarios = new JSONArray();
        for (DocumentReaderScenario scenario : DocumentReader.Instance().availableScenarios)
            scenarios.put(generateDocumentReaderScenario(scenario));
        callback.invoke(scenarios.toString());
    }

    @ReactMethod
    public void getSessionLogFolder(Callback callback) {
        callback.invoke(DocumentReader.Instance().processParams().sessionLogFolder);
    }

    private void showDialog(String msg) {
        Toast.makeText(reactContext, msg, Toast.LENGTH_LONG).show();
    }

    @ReactMethod
    public void setConfig(final ReadableMap opts, final Callback callback) {
        if (DocumentReader.Instance().getDocumentReaderIsReady()) {
            RegulaConfig.setConfig(DocumentReader.Instance(), opts);
            callback.invoke();
        } else
            callback.invoke("document reader not ready");
    }

    @ReactMethod
    public void setCameraSessionIsPaused(boolean ignored, final Callback callback) {
        callback.invoke("setCameraSessionIsPaused() is an ios-only method");
    }

    @ReactMethod
    public void getConfig(final Callback callback) {
        if (DocumentReader.Instance().getDocumentReaderIsReady())
            callback.invoke(RegulaConfig.getConfig(DocumentReader.Instance()).toString());
        else
            callback.invoke("document reader not ready");
    }

    @ReactMethod
    public void deinitializeReader(final Callback callback) {
        DocumentReader.Instance().deinitializeReader();
        callback.invoke();
    }

    @ReactMethod
    public void getCanUseAuthenticator(final Callback callback) {
        DocumentReader.Instance().getCanUseAuthenticator();
        callback.invoke();
    }

    @ReactMethod
    public void getLicenseExpiryDate(final Callback callback) {
        callback.invoke(DocumentReader.Instance().license().getExpiryDate().toString());
    }

    @ReactMethod
    public void getLicenseCountryFilter(final Callback callback) {
        JSONArray list = new JSONArray();
        if (DocumentReader.Instance().license().getCountryFilter() == null)
            callback.invoke("null");
        else {
            for (String item : DocumentReader.Instance().licenseResult().license().getCountryFilter())
                list.put(item);
            callback.invoke(list.toString());
        }
    }

    private IDocumentReaderCompletion getCompletion(final Callback callback) {
        return new IDocumentReaderCompletion() {
            @Override
            public void onCompleted(int action, DocumentReaderResults results, String error) {
                DocumentReader reader = DocumentReader.Instance();
                switch (action) {
                    case DocReaderAction.COMPLETE:
                        callback.invoke("Success:" + resultsToJsonObject(results).toString());
                        break;
                    case DocReaderAction.CANCEL:
                        callback.invoke("Canceled by user");
                        break;
                    case DocReaderAction.ERROR:
                        callback.invoke("Error:" + error);
                        break;
                    case DocReaderAction.MORE_PAGES_AVAILABLE:
                        break;
                    case DocReaderAction.NOTIFICATION:
                        break;
                    case DocReaderAction.PROCESS:
                        break;
                    case DocReaderAction.PROCESS_WHITE_UV_IMAGES:
                        break;
                }
            }
        };
    }

    private IDocumentReaderPrepareCompletion getPrepareCompletion(final Callback callback) {
        return new IDocumentReaderPrepareCompletion() {
            @Override
            public void onPrepareProgressChanged(int progress) {
                if ((int) (System.currentTimeMillis()) > timer + 10) {
                    // without a timer events are being sent too fast what results in blocking UI
                    // and skipping frames
                    WritableMap map = Arguments.createMap();
                    map.putString("msg", "Downloading database: " + progress + "%");
                    reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(prepareDatabaseProgressChangeEvent, map);
                    timer = (int) (System.currentTimeMillis());
                }
            }

            @Override
            public void onPrepareCompleted(boolean status, String error) {
                if (status)
                    callback.invoke("database prepared");
                else
                    callback.invoke("database preparation failed: " + error);
            }
        };
    }

    private IDocumentReaderInitCompletion getInitCompletion(final Callback callback) {
        return new IDocumentReaderInitCompletion() {
            @Override
            public void onInitCompleted(boolean success, String error) {
                if (success) {
                    DocumentReader.Instance().processParams().scenario = DocumentReader.Instance().availableScenarios.get(0).name;
                    callback.invoke("init completed");
                } else
                    callback.invoke("Init failed:" + error);
            }
        };
    }
}
