package app.captureid.plugins.cidprint;

import android.Manifest;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.util.Log;
import android.util.Size;
import android.view.Window;
import android.view.WindowManager;

import com.getcapacitor.JSObject;
import com.getcapacitor.PermissionState;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.PluginResult;
import com.getcapacitor.annotation.ActivityCallback;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.getcapacitor.annotation.Permission;
import com.getcapacitor.annotation.PermissionCallback;
import com.getcapacitor.JSArray;

import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Iterator;

import androidx.activity.result.ActivityResult;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;

import app.captureid.cidprinterlibrary.CIDPrinter;
import app.captureid.cidprinterlibrary.labels.ReferenceLabel;
import app.captureid.cidprinterlibrary.notifications.InitResult;
import app.captureid.cidprinterlibrary.notifications.PrinterLibraryEvent;
import app.captureid.cidprinterlibrary.notifications.PrinterLibraryListener;
import app.captureid.cidprinterlibrary.printers.PrinterInformation;
import app.captureid.cidprinterlibrary.utils.PrinterUtils;

@RequiresApi(api = Build.VERSION_CODES.S)
@CapacitorPlugin(
  permissions = {
    @Permission(strings = { Manifest.permission.ACCESS_COARSE_LOCATION }, alias = "android.permission.ACCESS_COARSE_LOCATION"),
    @Permission(strings = { Manifest.permission.BLUETOOTH_ADMIN }, alias = "android.permission.BLUETOOTH_ADMIN"),
    @Permission(strings = { Manifest.permission.BLUETOOTH }, alias = "android.permission.BLUETOOTH"),
    @Permission(strings = { Manifest.permission.BLUETOOTH_CONNECT }, alias = "bluetooth_connect"),
    @Permission(strings = { Manifest.permission.BLUETOOTH_SCAN }, alias = "bluetooth_scan"),
    @Permission(strings = { Manifest.permission.BLUETOOTH_ADVERTISE }, alias = "bluetooth_advertise"),
    @Permission(strings = { Manifest.permission.ACCESS_FINE_LOCATION }, alias = "android.permission.ACCESS_FINE_LOCATION"),
    @Permission(strings = { Manifest.permission.INTERNET }, alias = "Manifest.permission.INTERNET"),
    @Permission(strings = { Manifest.permission.WRITE_EXTERNAL_STORAGE }, alias = "Manifest.permission.WRITE_EXTERNAL_STORAGE"),
    @Permission(strings = { Manifest.permission.READ_EXTERNAL_STORAGE }, alias = "Manifest.permission.READ_EXTERNAL_STORAGE"),
    @Permission(strings = { Manifest.permission.READ_CONTACTS }, alias = "android.permission.READ_CONTACTS"),
    @Permission(strings = { Manifest.permission.READ_CALL_LOG }, alias = "android.permission.READ_CALL_LOG")
  }
)

public class CIDPrint extends Plugin {
    private static final String TAG = CIDPrint.class.getSimpleName();

    private static final String LIBRARY_EVENT = "printerLibraryEvent";
    private static final String LIBRARY_NOT_INITIALIZED = "library not initialized";
    private static final String LIBRARY_ALREADY_INITIALIZED = "library already initialized";

    private boolean isDebug = false;
    private CIDPrinter _printerlibrary;
    // list to handle different callback methods
    private HashMap<String, PluginCall> _callbacks = new HashMap<String, PluginCall>();
    private AppCompatActivity _activity;
    private int _enableBluetoothRequestID;
    // initialization state of the printer library
    private boolean _isInitialized = false;
    private final Object _permissionLock = new Object();

    private String _activateLicenseCallbackID = "";
    private String _enableBluetoothCallbackID = "";

    private static final class TimeoutOptions {
        final int gap;
        final int timeout;
        final int status;
        final int information;
        final int formfeed;
        final int detect;

        private TimeoutOptions(int gap, int timeout, int status, int information, int formfeed, int detect) {
            this.gap = gap;
            this.timeout = timeout;
            this.status = status;
            this.information = information;
            this.formfeed = formfeed;
            this.detect = detect;
        }

        static TimeoutOptions fromCall(PluginCall call) {
            return new TimeoutOptions(
                call.getInt("gap", 0),
                call.getInt("timeout", 15000),
                call.getInt("status", 500),
                call.getInt("information", 1000),
                call.getInt("formfeed", 200),
                call.getInt("detect", 400)
            );
        }
    }

    public CIDPrint() {
        super();
    }

    // provides the ability to Log messages form the release library when the building app works in debug mode
    private void LOG(String message) {
        if(isDebug) {
            Log.i(TAG, message);
        }
    }

    @PluginMethod
    public void showNavigationBar(PluginCall call) {
        boolean bshow = call.getBoolean("show");
        try{
            final Window window = getActivity().getWindow();
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat.getInsetsController(window, window.getDecorView());
                    if(bshow) {
                        windowInsetsControllerCompat.show(WindowInsetsCompat.Type.navigationBars());
                    } else {
                        windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.navigationBars());
                    }

                    window.clearFlags(WindowManager.LayoutParams. FLAG_FULLSCREEN);
                    window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
                    notifyListeners("onHide", new JSObject());
                    call.resolve();
                }
            });
        } catch (Exception e){
            Log.d(TAG, "showNavigationBar: " + e.toString());
            call.reject(e.toString());
        }
    }

    /**
     * activate License has to be called once after initialization and before any other method call
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void activateLicense(PluginCall call) {
        // library initialization is required
        if(_isInitialized) {
            getBridge().saveCall(call);
            _activateLicenseCallbackID = call.getCallbackId();
            call.setKeepAlive(true);
            PrinterLibraryEvent evt = _printerlibrary.activateLicense(call.getString("licenseKey"), call.getString("customerID"));
            call.resolve(fromJSONObject(evt.toJSONObject()));
        } else {
            call.reject("Library not initialized");
        }
    }

    /**
     * called to release used resources should be called for a clean shutdown
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void closeCIDPrinterLib(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.closeSharedlibrary();
            _isInitialized = false;
        }
    }

    /**
     * establish a connection to the printer with the given bluetooth mac address
     * or the latest connected printer when mc is set to null
     * parameters:
     *      mac = bluetooth mac address of the printer or null
     *      autoreconnect = try to reconnect printer when device resumes from sleep mode
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void connectToPreferredPrinter(PluginCall call) {
        if(_isInitialized) {
            // store the PluginCall instance to notify the caller in a later step
            addCallback("connectToPreferredPrinter", call);
            _printerlibrary.connectToPreferredPrinter(
                    call.getString("mac"),
                    call.getBoolean("autoreconnect") == null? false:call.getBoolean("autoreconnect"),
                    call.getInt("timeout", 15000)
            );
        }
    }

    @PluginMethod
    public void setLTSSensitivity(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.setLTSSensitivity(
                    call.getInt("sensitivity", 9));
        }
    }

    @PluginMethod
    public void setTimeouts(PluginCall call) {
        if(_isInitialized) {
            TimeoutOptions options = TimeoutOptions.fromCall(call);
            _printerlibrary.setTimeouts(
                    options.gap,
                    options.timeout,
                    options.status,
                    options.information,
                    options.formfeed,
                    options.detect);
        }
    }

    /**
     * connect to a network printer with the given address or hostname and port
     * parameters:
     *      address = ipaddress or hostname of the printer
     *      port = the configured port of the printer to communicate with (default 9100)
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void connectToPrinter(PluginCall call) {
        if(_isInitialized) {
            boolean reconnect = call.getBoolean("autoreconnect") == null?false:call.getBoolean("autoreconnect");
            _printerlibrary.connectToPrinter(
                    call.getString("address"),
                    reconnect,
                    call.getInt("timeout", 15000)
            );
        }
    }

    /**
     * converts a label file into the connected printers command language
     * parameters:
     *      label = name of the labelfile (needs to be placed in the assets folder)
     * @param call
     */
    @PluginMethod
    public void convert(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.convert(call.getString("label"));
        }
    }

    /**
     * provides debug output and helps to debug the library without sending the data to a printer
     * parameters:
     *      label = labelfilename (needs to be placed in the assets folder)
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void debugPrint(PluginCall call) {
        if(_isInitialized) _printerlibrary.debugPrint(call.getString("label"));
    }

    /**
     * close connection and disconnect from printer
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void disconnectFromPrinter(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.disconnectPrinter();
        }
    }

    /**
     * run the discover process to find bluetooth printers
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void discoverDevices(PluginCall call) {
        if(_isInitialized) {
            addCallback("discoverDevices", call);
            int timeout = call.getInt("timeout")==null?0:call.getInt("timeout");
            _printerlibrary.discoverDevices(timeout);
        }
    }

    /**
     * enable/disable the bluetooth adapter
     * parameters:
     *      enable = true(enable) or false(disable)
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void enableBluetoothAdapter(PluginCall call) {
        getBridge().saveCall(call);
        _enableBluetoothCallbackID = call.getCallbackId();
        call.setKeepAlive(true);

        if(_isInitialized) call.resolve(fromJSONObject(_printerlibrary.enableBluetoothAdapter(call.getBoolean("enable")).toJSONObject()));
    }

    /**
     * method to activate/deactivate bluetooth communication abilities in the library (required to be
     * called once before calling any other bluetooth method from the library)
     * parameters:
     *      enable = true(enable) false(disable)
     *
     * note:
     *      when called to enable bluetooth capabilities the method tries to enable the bluetooth adapter if not already enabled
     *      on call to disable bluetooth printing the bluetooth adapter will not be disabled.
     *      to enable/disable the bluetooth adapter use the method enableBluetoothAdapter.
     *
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void enableBluetoothPrinting (PluginCall call) {
        if(_isInitialized) {
            getBridge().saveCall(call);
            _enableBluetoothCallbackID = call.getCallbackId();
            call.setKeepAlive(true);
            addCallback("enableBluetoothPrinting", call);
            _printerlibrary.enableBluetoothPrinting(call.getBoolean("enable"));
        }
    }

    /**
     * enable/disable the dispensing mode of the printer (not all printer models are supported)
     * parameters:
     *      enable = ture(enable) false(disable)
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void enableDispendingMode(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.enableDispensingMode(call.getBoolean("enable"));
        }
    }

    /**
     * enable/disable the possibilities to work with network printers
     * parameters:
     *      enable = true(enable) false(disable)
     * @param call
     */
    @PluginMethod
    public void enableNetworkPrinting (PluginCall call) {
        if(_isInitialized) _printerlibrary.enableNetworkPrinting(call.getBoolean("enable"));
    }

    /**
     * reports if a printer actually was connected before device goes to suspend mode
     * can be used when device resumes and the app need to do some work in that case
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void getLatestConnectState(PluginCall call) {
        if(_isInitialized) {
            PrinterLibraryEvent evt = _printerlibrary.getLatestConnectState();
            call.successCallback(new PluginResult(fromJSONObject(evt.toJSONObject())));
        }
    }

    /**
     * retrieve the media size settings from the printer
     * return:
     *      JSONObject with the width and height of the media size set in printer configuration
     * @param call Capacitor PluginCall instance
     *
     */
    @PluginMethod
    public void getMediaSize(PluginCall call) {
        if(_isInitialized) {
            Size sz =_printerlibrary.getMediaSize();
            if(sz == null) call.reject("failed to retrieve media size");
            else {
                JSObject obj = new JSObject();
                obj.put("width", sz.getWidth());
                obj.put("height", sz.getHeight());
                call.resolve(obj);
            }
        }
    }

    /**
     * notifies the app about the devices actually paired with the printer
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void getPairedDevices(PluginCall call) {
        if(_isInitialized) {
            addCallback("getPairedDevices", call);
            _printerlibrary.getPairedDevices();
        }
    }

    /**
     * retreive information about the printer
     * the provided data depends on printer model
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void getPrinterInformation(PluginCall call) {
        if(_isInitialized) {
            PrinterInformation info = _printerlibrary.getPrinterInformation();
            try {
                call.successCallback(new PluginResult(JSObject.fromJSONObject(info.toJSONObject())));
            } catch (JSONException e) {
                Log.e(TAG, "getPrinterInformation error: " + e.getMessage());
                call.errorCallback(e.getMessage());
            } catch(NullPointerException ex) {
                Log.e(TAG, "getPrinterInformation error: " + ex.getMessage());
                call.errorCallback(ex.getMessage());
            }
        }
    }

    /**
     * returns the library version number as string
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void getPrinterLibraryVersion(PluginCall call) {
        call.resolve(new JSObject().put("version", CIDPrinter.getVersion()));
    }

    @PluginMethod
    public void getPrinterStatus(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.getStatus();
        }
    }

    /**
     * initialize the printer library
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void initCIDPrinterLib(PluginCall call) {
        if(_isInitialized) {
            call.reject(LIBRARY_ALREADY_INITIALIZED);
            return;
        }
        // request the library instance. we are working with a single instance
        _printerlibrary = CIDPrinter.getSharedLibrary(_activity);
        // test for a valid instance
        if(_printerlibrary != null) {
            _printerlibrary.enableDebugToFile(false);
            _isInitialized = true;
            // set state and add our global listener to receive notifications from the library
            // this is done before any other call that we doesn't miss any notification messages
            _printerlibrary.addListener(_library_listener);
            // now run initialization routine
            PrinterLibraryEvent evt = _printerlibrary.initialize();
            // check if the running app is debug mode to enable information messages from the library
            isDebug = (this.getActivity().getApplicationContext().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) == ApplicationInfo.FLAG_DEBUGGABLE;
            _printerlibrary.setDebug(isDebug);
            // check for permissions
            if(hasRequiredPermissions(CIDPrinter.getRequiredPermissions())) {
                // permssion are set. no further action required
                // we use the event data returned from initialize method
                // and set the type to notification
                // send the result to the app and exit method
                call.successCallback(new PluginResult(fromJSONObject(evt.toJSONObject())));
                return;
            }
            // permssion not set. send the result form initialization and add the list of the required permissions
            evt = new PrinterLibraryEvent(this, PrinterLibraryEvent.EventType.NOTIFY, PrinterLibraryEvent.LibraryActionType.INITIALIZE, new InitResult(CIDPrinter.getRequiredPermissions()));
            call.successCallback(new PluginResult(fromJSONObject(evt.toJSONObject())));
//                    .success(fromJSONObject(evt.getJSONObject()));
        } else {
            call.reject("library initialization failed");
        }
    }

    @PluginMethod
    public void requestAllPermissions(PluginCall call) {
        JSArray permissions = call.getArray("permissions");
        if(_isInitialized) {
            requestAllPermissions(call, "permissionCallback");
            synchronized (_permissionLock) {
                try {
                    _permissionLock.wait();
                } catch (InterruptedException ex) {
                    Log.e(TAG, "Permission request interrupted");
                    call.reject("Permission request interrupted");
                    return;
                }
//                call.resolve(new JSObject().put("result", true));
                return;
            }
        }
        call.reject(LIBRARY_NOT_INITIALIZED);
    }

    /**
     * intergrated method to request the permissions required for this library. to avoid typo mistakes
     * you can use the strings returned from the call to initCIDPrinterLib .
     * paramters:
     *      permission = string containing the requested permission in android notation (i.e. android.permission.BLUETOOTH)
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void requestPermission(PluginCall call) {
        if(_isInitialized) {
            // use the integrated capacitor plugin method to request the permission
            requestPermissionForAlias(call.getString("permission"), call, "permissionCallback");
            // add a lock to wait for the result
            synchronized (_permissionLock) {
                try {
                    _permissionLock.wait();
                } catch (InterruptedException e) {
                    Log.e(TAG, "Permission request interrupted");
                    call.reject("Permission request interrupted");
                }
                return;
            }
        }
        call.reject(LIBRARY_NOT_INITIALIZED);
    }

    /**
     * method to enable/disable the ability to log the raw data sent to printer to a file
     * @param enable
     */
    @PluginMethod
    public void enableDebugToFile(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.enableDebugToFile(call.getBoolean("enable"));
        }
    }

    /**
     * method to receive the result of the user interaction while permission request
     * @param call Capacitor PluginCall instance
     */
    @PermissionCallback
    private void permissionCallback(PluginCall call) {
        JSArray map = call.getArray("permissions");
        try {
            for(Object perm: map.toList()) {
                // check if permission has not been granted
                if(getPermissionState(perm.toString()) != PermissionState.GRANTED) {
                    call.reject("permission denied: " + perm);
                    return;
                }
            }
        } catch (JSONException e) {
            call.reject("permission exception: " + e.getMessage());
        }
        call.resolve(new JSObject().put("result", true));
        synchronized (_permissionLock) {
            _permissionLock.notify();
        }
    }

    @PluginMethod
    public void isLibraryInitialized(PluginCall call) {
        call.resolve(new JSObject().put("result", _isInitialized));
    }

    /**
     * return the state of the bluetooth adapter
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void isBluetoothAdapterEnabled(PluginCall call) {
        if(_isInitialized) {
            call.resolve(new JSObject().put("result", _printerlibrary.isBluetoothAdapterEnabled()));
        }
    }

    @PluginMethod
    public void enableClipping(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.enableClipping(call.getBoolean("enable"));
        }
    }

    /**
     * send calibration request to the printer
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void enableCalibration(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.enableCalibration(call.getBoolean("enable"));
        }
    }

    /**
     * send a formfeed request to the printer
     * @param call
     */
    @PluginMethod
    public void sendFormFeed(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.sendFormFeed();
        }
    }

    /**
     * print out the given data to the connected printer
     * parameters:
     *      data = string representing the data that should be printed
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void printData(PluginCall call) {
        if(_isInitialized) {
            addCallback("printData", call);
            _printerlibrary.print(call.getString("data").getBytes(), false);
        }
    }

    /**
     * print out the content of the given label file
     * parameters:
     *      label = name of the labelfile that should be printed (the file has to be available in the assets folder)
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void printLabel(PluginCall call) {
        if(_isInitialized) {
            addCallback("printData", call);
            _printerlibrary.print(call.getString("label"));
        }
    }

    /**
     * prints out the given label and replaces the variable placeholders with the values in the list
     * parameters:
     *      label = label file template provided in the assets folder
     *      data = list with the values that should be places into the template
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void printLabelWithData(PluginCall call) {
        try {
            if (_isInitialized) {
                addCallback("printLabelWithData", call);
                _printerlibrary.print(call.getString("label"), call.getArray("data").toList().toArray(new String[call.getArray("data").toList().size()]));
            }
        } catch(JSONException ex) {
            Log.e(TAG, "printLabelWithData: " + ex.getMessage());
        } catch(ClassCastException ex) {
            Log.e(TAG, "printLabelWithData: " + ex.getMessage());
        } catch(Exception ex) {
            Log.e(TAG, "printLabelWithData: " + ex.getMessage());
        }
    }

    /**
     * prints the given label template and replaces the variable content with the values provided by the Json object
     * parameters:
     *      label = name of the label file provided in the assets folder
     *      data = json object with the data to be used in the label template
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void printLabelWithObject(PluginCall call) {
        try {
            if (_isInitialized) {
                LOG("start print command received");
                addCallback("printLabelWithObject", call);
                LOG("callback registered");
                _printerlibrary.print(call.getString("label"), call.getData(). getJSONObject("data").getJSONObject("variables"), call.getInt("quantity", 1));
                LOG("print command sent to library");
            }
        } catch(JSONException ex) {
            Log.e(TAG, "printLabelWithData: " + ex.getMessage());
        } catch(ClassCastException ex) {
            Log.e(TAG, "printLabelWithData: " + ex.getMessage());
        } catch(Exception ex) {
            Log.e(TAG, "printLabelWithData: " + ex.getMessage());
        }
    }

    /**
     * print the label data content without converting it. used to print data to the connected printer
     * in its native printer command language
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void printNativeData(PluginCall call) {
        if(_isInitialized) {
            addCallback("printNativeData", call);
            _printerlibrary.print(call.getString("data").getBytes(), true);
        }
    }

    /**
     * print out a receipt or journal with variable length
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void printReceiptWithObject(PluginCall call) {
        try {
            if (_isInitialized) {
                addCallback("printReceiptWithObject", call);
                _printerlibrary.printReceipt(call.getString("label"), call.getData().getJSONObject("data"));
            }
        } catch(JSONException ex) {
            Log.e(TAG, "printReceiptWithObject: " + ex.getMessage());
        } catch(ClassCastException ex) {
            Log.e(TAG, "printReceiptWithObject: " + ex.getMessage());
        } catch(Exception ex) {
            Log.e(TAG, "printReceiptWithObject: " + ex.getMessage());
        }
    }

    /**
     * print a dotmatrix to help positioning elements on the label
     * for internal use only
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void printReferenceLabel(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.printReferenceLabel(ReferenceLabel.ReferenceLabelType.values()[call.getInt("labeltype", 0)]);
        }
    }

    /**
     * enable/disable autoreconnect after device resume
     * parameters:
     *      autoreconnect = true(enable) false(disable)
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void setAutoReconnect(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.setAutoReconnect(call.getBoolean("autoreconnect"));
        }
    }

    /**
     * set the library and printer configuration with the values given by the json object data
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void setConfiguration(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.setConfiguration(call.getData());
        }
    }

    /**
     * set the media width and height
     * parameters:
     *      width = the width of the label
     *      height = the height of the label
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void setupMediaSize(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.setupMediaSize(call.getInt("width"), call.getInt("height"));
        }
    }

    /**
     * upload a file to the printer(i.e. font file)
     * prmeters:
     *      file = filename to be uploaded to the printer (must be provided in the assets folder)
     *      filetype = type of the file to be uploaded
     * @param call Capacitor PluginCall instance
     */
    @PluginMethod
    public void uploadFile(PluginCall call) {
        if(_isInitialized) {
            _printerlibrary.uploadFile(call.getString("file"), PrinterUtils.PrinterFileType.values()[call.getInt("filetype")]);
        }
    }

    private PrinterLibraryListener _library_listener = new PrinterLibraryListener() {
        @Override
        public void onPrinterLibraryEvent(PrinterLibraryEvent event) {
            switch(event.getEventType()) {
                case SUCCESS:
                    handleSuccess(event);
                    break;
                case FAILED:
                    handleError(event);
                    break;
                case NOTIFY:
                    handleNotification(event);
                    break;
            }
        }

        @Override
        public void requestForStartIntent(Intent intent, int requestID) {
            _enableBluetoothRequestID = requestID;
            PluginCall call = getBridge().getSavedCall(_enableBluetoothCallbackID);
            startActivityForResult(call, intent, "enableBTCallback");
//            startActivityForResult(savedLastCall, intent, requestID);
        }
    };

    @ActivityCallback
    private void enableBTCallback(PluginCall call, ActivityResult result) {
        if (call == null) {
            return;
        }
        if(_isInitialized) {
            _printerlibrary.handleOnActivityResult(CIDPrinter.REQUEST_ENABLE_BT, result.getResultCode(), null);
        }
    }

    private void handleSuccess(PrinterLibraryEvent evt) {
        JSObject obj = null;
        obj = fromJSONObject(evt.toJSONObject());
        try {
            switch(evt.getAction()) {
                case LICENSE_ACTIVATION:
                    PluginCall call = getBridge().getSavedCall(_activateLicenseCallbackID);
                    call.successCallback(new PluginResult(obj));
                    getBridge().releaseCall(call);
                    break;
                default:
              }
            notifyListeners(LIBRARY_EVENT, obj);
        //            switch (evt.getAction()) {
//                case DISCOVER:
//                    notifyListeners(LIBRARY_EVENT, obj);
////                    _callbacks.get("discoverDevices").successCallback(result);
//                    break;
//                case CONNECT:
////                    _callbacks.get("connectToPreferredPrinter").successCallback(result);
//                    break;
//                case PRINT:
//                    notifyListeners(LIBRARY_EVENT, obj);
//                    break;
//            }
        } catch(Exception ex) {
            Log.e(TAG, "handleSuccess: " + ex.getMessage());
        }
    }

    private void handleError(PrinterLibraryEvent evt) {
        JSObject obj = new JSObject();
        obj = fromJSONObject((evt.toJSONObject()));
        notifyListeners(LIBRARY_EVENT, obj);
    }

    private void handleNotification(PrinterLibraryEvent evt) {
        JSObject obj = new JSObject();
        obj = fromJSONObject(evt.toJSONObject());
        notifyListeners(LIBRARY_EVENT, obj);
    }

    private void addCallback(String functionName, PluginCall callback) {
        try {
            if (_callbacks.containsKey(functionName)) {
                if (_callbacks.get(functionName).isReleased()) {
                    _callbacks.remove(functionName);
                    _callbacks.put(functionName, callback);
                }
            } else {
                _callbacks.put(functionName, callback);
            }
        } catch(NullPointerException ex) {
            Log.d(TAG, "addCallback throws error: " + ex.getMessage());
        }
    }

    private boolean hasRequiredPermissions(String[] permissions) {
        for(String permission: permissions) {
            if(getPermissionState(permission) == PermissionState.DENIED ||
                getPermissionState(permission) == PermissionState.PROMPT) {
                return false;
            }
        }
        return true;
    }

    private JSObject fromJSONObject(JSONObject jobj) {
        Iterator<String> keys = jobj.keys();
        while(keys.hasNext()) {
            String key = keys.next();
            try {
                if(jobj.get(key) instanceof JSONObject) {
                    jobj.put(key, fromJSONObject((JSONObject) jobj.get(key)));
                }
            } catch (JSONException e) {
                Log.d(TAG, "fromJSONObject encounters an error in " + key + " error: " + e.getMessage());
            }
        }
        try {
            return  JSObject.fromJSONObject(jobj);
        } catch (JSONException e) {
            Log.d(TAG, "fromJSONObject encounters an error: " + e.getMessage());
        }
        return new JSObject();
    }

    @Override
    protected void handleOnResume() {
        super.handleOnResume();
        _activity = getActivity();
        if(_isInitialized) {
            _printerlibrary.onResume(false);
        }
    }

    @Override
    protected void handleOnRestart() {
        super.handleOnRestart();
        if(_isInitialized) {
            _printerlibrary.onResume(true);
        }
    }

    @Override
    protected void handleOnPause() {
        if(_isInitialized) {
            _printerlibrary.onPause();
        }
        super.handleOnPause();
    }

    @Override
    protected void handleOnDestroy() {
        if(_isInitialized) {
            _printerlibrary.onDestroy();
        }
        super.handleOnDestroy();
    }
}
