
package net.infoxication.reactstarprnt;

import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.net.Uri;
import android.provider.MediaStore;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.Base64;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.starmicronics.stario.PortInfo;
import com.starmicronics.stario.StarIOPort;
import com.starmicronics.stario.StarIOPortException;
import com.starmicronics.stario.StarPrinterStatus;
import com.starmicronics.starioextension.ICommandBuilder;
import com.starmicronics.starioextension.ICommandBuilder.CodePageType;
import com.starmicronics.starioextension.ICommandBuilder.CutPaperAction;
import com.starmicronics.starioextension.IConnectionCallback;
import com.starmicronics.starioextension.StarIoExt;
import com.starmicronics.starioextension.StarIoExt.Emulation;
import com.starmicronics.starioextension.StarIoExtManager;
import com.starmicronics.starioextension.StarIoExtManagerListener;

import java.io.IOException;
import java.lang.NullPointerException;
import java.lang.Exception;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class RNStarPrntModule extends ReactContextBaseJavaModule {

    private final ReactApplicationContext reactContext;
    private StarIoExtManager starIoExtManager;

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

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

    @ReactMethod
    public void portDiscovery(final String strInterface, final Promise promise) {
        new Thread(new Runnable() {
            public void run() {

                WritableArray result = new WritableNativeArray();
                try {

                    if (strInterface.equals("LAN")) {
                        result = getPortDiscovery("LAN");
                    } else if (strInterface.equals("Bluetooth")) {
                        result = getPortDiscovery("Bluetooth");
                    } else if (strInterface.equals("USB")) {
                        result = getPortDiscovery("USB");
                    } else {
                        result = getPortDiscovery("All");
                    }

                } catch (StarIOPortException exception) {
                    promise.reject("PORT_DISCOVERY_ERROR", exception);
                } catch (Exception ex) {
                    promise.reject("PORT_DISCOVERY_ERROR", ex);
                } finally {
                    promise.resolve(result);
                }

            }
        }).start();
    }

    @ReactMethod
    public void checkStatus(final String portName, final String emulation, final Promise promise) {
        new Thread(new Runnable() {
            public void run() {

                String portSettings = getPortSettingsOption(emulation);

                StarIOPort port = null;
                try {

                    port = StarIOPort.getPort(portName, portSettings, 10000, getReactApplicationContext());

                    // A sleep is used to get time for the socket to completely open
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                    }

                    StarPrinterStatus status;
                    Map<String, String> firmwareInformationMap = port.getFirmwareInformation();
                    status = port.retreiveStatus();


                    WritableNativeMap json = new WritableNativeMap();
                    json.putBoolean("offline", status.offline);
                    json.putBoolean("coverOpen", status.coverOpen);
                    json.putBoolean("cutterError", status.cutterError);
                    json.putBoolean("receiptPaperEmpty", status.receiptPaperEmpty);
                    json.putString("ModelName", firmwareInformationMap.get("ModelName"));
                    json.putString("FirmwareVersion", firmwareInformationMap.get("FirmwareVersion"));

                    promise.resolve(json);


                } catch (StarIOPortException e) {
                    promise.reject("CHECK_STATUS_ERROR", e);
                } finally {

                    if (port != null) {
                        try {
                            StarIOPort.releasePort(port);
                        } catch (StarIOPortException e) {
                            promise.reject("CHECK_STATUS_ERROR", e.getMessage());
                        } catch (NullPointerException e2) {
                            promise.reject("CHECK_STATUS_ERROR", e2.getMessage());
                        }
                    }
                }

            }
        }).start();
    }

    @ReactMethod
    public void connect(final String portName, final String emulation, final Boolean hasBarcodeReader, final Promise promise) {
        Context context = getCurrentActivity();

        String portSettings = getPortSettingsOption(emulation);
        if (starIoExtManager != null && starIoExtManager.getPort() != null) {
            starIoExtManager.disconnect((IConnectionCallback) null);
        }
        starIoExtManager = new StarIoExtManager(hasBarcodeReader ? StarIoExtManager.Type.WithBarcodeReader : StarIoExtManager.Type.Standard, portName, portSettings, 10000, context);
        starIoExtManager.setListener(starIoExtManagerListener);

        new Thread(new Runnable() {
            public void run() {

                if (starIoExtManager != null) starIoExtManager.connect(new IConnectionCallback() {
                    @Override
                    public void onConnected(ConnectResult connectResult) {
                        if (connectResult == ConnectResult.Success || connectResult == ConnectResult.AlreadyConnected) {

                            promise.resolve("Printer Connected");

                        } else {
                            promise.reject("CONNECT_ERROR", "Error Connecting to the printer");
                        }
                    }

                    @Override
                    public void onDisconnected() {
                        //Do nothing
                    }
                });

            }
        }).start();
    }

    @ReactMethod
    public void disconnect(final Promise promise) {
        new Thread(new Runnable() {
            public void run() {
                if (starIoExtManager != null && starIoExtManager.getPort() != null) {
                    starIoExtManager.disconnect(new IConnectionCallback() {
                        @Override
                        public void onConnected(ConnectResult connectResult) {
                            // nothing
                        }

                        @Override
                        public void onDisconnected() {
                            //sendEvent("printerOffline", null);
                            starIoExtManager.setListener(null); //remove the listener?
                            promise.resolve("Printer Disconnected");
                        }
                    });
                } else {
                    promise.resolve("No printers connected");
                }

            }
        }).start();
    }

    @ReactMethod
    public void print(final String portName, String emulation, final ReadableArray printCommands, final Promise promise) {

        final String portSettings = getPortSettingsOption(emulation);
        final Emulation _emulation = getEmulation(emulation);
        final Context context = getCurrentActivity();

        new Thread(new Runnable() {
            public void run() {

                ICommandBuilder builder = StarIoExt.createCommandBuilder(_emulation);

                builder.beginDocument();

                appendCommands(builder, printCommands, context);

                builder.endDocument();

                byte[] commands = builder.getCommands();

                if (portName == "null") { // use StarIOExtManager
                    sendCommand(commands, starIoExtManager.getPort(), promise);

                } else {//use StarIOPort
                    sendCommand(context, portName, portSettings, commands, promise);
                }
            }

        }).start();
    }

    private Emulation getEmulation(String emulation) {

        if (emulation.equals("StarPRNT")) return Emulation.StarPRNT;
        else if (emulation.equals("StarPRNTL")) return Emulation.StarPRNTL;
        else if (emulation.equals("StarLine")) return Emulation.StarLine;
        else if (emulation.equals("StarGraphic")) return Emulation.StarGraphic;
        else if (emulation.equals("EscPos")) return Emulation.EscPos;
        else if (emulation.equals("EscPosMobile")) return Emulation.EscPosMobile;
        else if (emulation.equals("StarDotImpact")) return Emulation.StarDotImpact;
        else return Emulation.StarLine;
    }

    ;

    private WritableArray getPortDiscovery(String interfaceName) throws StarIOPortException {
        List<PortInfo> BTPortList;
        List<PortInfo> TCPPortList;
        List<PortInfo> USBPortList;

        final ArrayList<PortInfo> arrayDiscovery = new ArrayList<PortInfo>();

        WritableArray arrayPorts = new WritableNativeArray();


        try {
            if (interfaceName.equals("Bluetooth") || interfaceName.equals("All")) {
                BTPortList = StarIOPort.searchPrinter("BT:");

                for (PortInfo portInfo : BTPortList) {
                    arrayDiscovery.add(portInfo);
                }
            }
        } catch (Exception ex) {
            BTPortList = new ArrayList<>();
        }

        try {
            if (interfaceName.equals("LAN") || interfaceName.equals("All")) {
                TCPPortList = StarIOPort.searchPrinter("TCP:");

                for (PortInfo portInfo : TCPPortList) {
                    arrayDiscovery.add(portInfo);
                }
            }
        } catch (Exception ex) {
            TCPPortList = new ArrayList<>();
        }

        try {
            if (interfaceName.equals("USB") || interfaceName.equals("All")) {
                try {
                    USBPortList = StarIOPort.searchPrinter("USB:", getReactApplicationContext());
                } catch (StarIOPortException e) {
                    USBPortList = new ArrayList<PortInfo>();
                }
                for (PortInfo portInfo : USBPortList) {
                    arrayDiscovery.add(portInfo);
                }
            }
        } catch (Exception ex) {
            USBPortList = new ArrayList<>();
        }

        for (PortInfo discovery : arrayDiscovery) {
            String portName;

            WritableMap port = new WritableNativeMap();
            if (discovery.getPortName().startsWith("BT:"))
                port.putString("portName", "BT:" + discovery.getMacAddress());
            else port.putString("portName", discovery.getPortName());

            if (!discovery.getMacAddress().equals("")) {

                port.putString("macAddress", discovery.getMacAddress());

                if (discovery.getPortName().startsWith("BT:")) {
                    port.putString("modelName", discovery.getPortName());
                } else if (!discovery.getModelName().equals("")) {
                    port.putString("modelName", discovery.getModelName());
                }
            } else if (interfaceName.equals("USB") || interfaceName.equals("All")) {
                if (!discovery.getModelName().equals("")) {
                    port.putString("modelName", discovery.getModelName());
                }
                if (!discovery.getUSBSerialNumber().equals(" SN:")) {
                    port.putString("USBSerialNumber", discovery.getUSBSerialNumber());
                }
            }

            arrayPorts.pushMap(port);
        }

        return arrayPorts;
    }

    private String getPortSettingsOption(String emulation) { // generate the portsettings depending on the emulation type

        String portSettings = "";

        if (emulation.equals("EscPosMobile")) portSettings += "mini";
        else if (emulation.equals("EscPos")) portSettings += "escpos";
        else //StarLine, StarGraphic, StarDotImpact
            if (emulation.equals("StarPRNT") || emulation.equals("StarPRNTL")) {
                portSettings += "Portable";
                portSettings += ";l"; //retry on
            } else portSettings += "";
        return portSettings;
    }

    private boolean sendCommand(byte[] commands, StarIOPort port, Promise promise) {

        try {
            /*
             * using StarIOPort3.1.jar (support USB Port) Android OS Version: upper 2.2
             */
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
            if (port == null) { //Not connected or port closed
                promise.reject("STARIO_PORT_EXCEPTION", "Unable to Open Port, Please Connect to the printer before sending commands");
                return false;
            }

            /*
             * Using Begin / End Checked Block method When sending large amounts of raster data,
             * adjust the value in the timeout in the "StarIOPort.getPort" in order to prevent
             * "timeout" of the "endCheckedBlock method" while a printing.
             *
             * If receipt print is success but timeout error occurs(Show message which is "There
             * was no response of the printer within the timeout period." ), need to change value
             * of timeout more longer in "StarIOPort.getPort" method.
             * (e.g.) 10000 -> 30000
             */
            StarPrinterStatus status;

            status = port.beginCheckedBlock();

            if (status.offline) {
                //sendEvent("printerOffline", null);
                throw new StarIOPortException("A printer is offline");
                //callbackContext.error("The printer is offline");
            }

            port.writePort(commands, 0, commands.length);

            port.setEndCheckedBlockTimeoutMillis(30000);// Change the timeout time of endCheckedBlock method.

            status = port.endCheckedBlock();

            if (status.coverOpen) {
                promise.reject("STARIO_PORT_EXCEPTION", "Cover open");
                //sendEvent("printerCoverOpen", null);
                return false;
            } else if (status.receiptPaperEmpty) {
                promise.reject("STARIO_PORT_EXCEPTION", "Empty paper");
                //sendEvent("printerPaperEmpty", null);
                return false;
            } else if (status.offline) {
                promise.reject("STARIO_PORT_EXCEPTION", "Printer offline");
                //sendEvent("printerOffline", null);
                return false;
            }
            promise.resolve("Success!");

        } catch (StarIOPortException e) {
            //sendEvent("printerImpossible", e.getMessage());
            promise.reject("STARIO_PORT_EXCEPTION", e.getMessage());
            return false;
        } finally {
            return true;
        }
    }

    private boolean sendCommand(Context context, String portName, String portSettings, byte[] commands, Promise promise) {

        StarIOPort port = null;
        try {
            /*
             * using StarIOPort3.1.jar (support USB Port) Android OS Version: upper 2.2
             */
            port = StarIOPort.getPort(portName, portSettings, 10000, context);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }

            /*
             * Using Begin / End Checked Block method When sending large amounts of raster data,
             * adjust the value in the timeout in the "StarIOPort.getPort" in order to prevent
             * "timeout" of the "endCheckedBlock method" while a printing.
             *
             * If receipt print is success but timeout error occurs(Show message which is "There
             * was no response of the printer within the timeout period." ), need to change value
             * of timeout more longer in "StarIOPort.getPort" method.
             * (e.g.) 10000 -> 30000
             */
            StarPrinterStatus status = port.beginCheckedBlock();

            if (status.offline) {
                //throw new StarIOPortException("A printer is offline");
                throw new StarIOPortException("A printer is offline");
            }

            port.writePort(commands, 0, commands.length);


            port.setEndCheckedBlockTimeoutMillis(30000);// Change the timeout time of endCheckedBlock method.
            status = port.endCheckedBlock();

            if (status.coverOpen) {
                promise.reject("STARIO_PORT_EXCEPTION", "Cover open");
                return false;
            } else if (status.receiptPaperEmpty) {
                promise.reject("STARIO_PORT_EXCEPTION", "Empty paper");
                return false;
            } else if (status.offline) {
                promise.reject("STARIO_PORT_EXCEPTION", "Printer offline");
                return false;
            }
            promise.resolve("Success!");

        } catch (StarIOPortException e) {
            promise.reject("STARIO_PORT_EXCEPTION", e.getMessage());
        } finally {
            if (port != null) {
                try {
                    StarIOPort.releasePort(port);
                } catch (StarIOPortException e) {
                }
            }
            return true;
        }
    }

    private void appendCommands(ICommandBuilder builder, ReadableArray printCommands, Context context) {
        Charset encoding = Charset.forName("US-ASCII");
        for (int i = 0; i < printCommands.size(); i++) {
            ReadableMap command = printCommands.getMap(i);
            if (command.hasKey("appendCharacterSpace"))
                builder.appendCharacterSpace(command.getInt("appendCharacterSpace"));
            else if (command.hasKey("appendEncoding"))
                encoding = getEncoding(command.getString("appendEncoding"));
            else if (command.hasKey("appendCodePage"))
                builder.appendCodePage(getCodePageType(command.getString("appendCodePage")));
            else if (command.hasKey("append"))
                builder.append(command.getString("append").getBytes(encoding));
            else if (command.hasKey("appendRaw"))
                builder.append(command.getString("appendRaw").getBytes(encoding));
            else if (command.hasKey("appendEmphasis"))
                builder.appendEmphasis(command.getString("appendEmphasis").getBytes(encoding));
            else if (command.hasKey("enableEmphasis"))
                builder.appendEmphasis(command.getBoolean("enableEmphasis"));
            else if (command.hasKey("appendInvert"))
                builder.appendInvert(command.getString("appendInvert").getBytes(encoding));
            else if (command.hasKey("enableInvert"))
                builder.appendInvert(command.getBoolean("enableInvert"));
            else if (command.hasKey("appendUnderline"))
                builder.appendUnderLine(command.getString("appendUnderline").getBytes(encoding));
            else if (command.hasKey("enableUnderline"))
                builder.appendUnderLine(command.getBoolean("enableUnderline"));
            else if (command.hasKey("appendInternational"))
                builder.appendInternational(getInternational(command.getString("appendInternational")));
            else if (command.hasKey("appendLineFeed"))
                builder.appendLineFeed(command.getInt("appendLineFeed"));
            else if (command.hasKey("appendUnitFeed"))
                builder.appendUnitFeed(command.getInt("appendUnitFeed"));
            else if (command.hasKey("appendLineSpace"))
                builder.appendLineSpace(command.getInt("appendLineSpace"));
            else if (command.hasKey("appendFontStyle"))
                builder.appendFontStyle(getFontStyle(command.getString("appendFontStyle")));
            else if (command.hasKey("appendCutPaper"))
                builder.appendCutPaper(getCutPaperAction(command.getString("appendCutPaper")));
            else if (command.hasKey("openCashDrawer"))
                builder.appendPeripheral(getPeripheralChannel(command.getInt("openCashDrawer")));
            else if (command.hasKey("appendBlackMark"))
                builder.appendBlackMark(getBlackMarkType(command.getString("appendBlackMark")));
            else if (command.hasKey("appendBytes")) {
                ReadableArray bytesArray = command.getArray("appendBytes");
                if (bytesArray != null) {
                    byte[] byteData = new byte[bytesArray.size() + 1];
                    for (int j = 0; j < bytesArray.size(); j++) {
                        byteData[j] = (byte) bytesArray.getInt(j);
                    }
                    builder.append(byteData);
                }
            } else if (command.hasKey("appendRawBytes")) {
                ReadableArray rawBytesArray = command.getArray("appendRawBytes");
                if (rawBytesArray != null) {
                    byte[] rawByteData = new byte[rawBytesArray.size() + 1];
                    for (int j = 0; j < rawBytesArray.size(); j++) {
                        rawByteData[j] = (byte) rawBytesArray.getInt(j);
                    }
                    builder.appendRaw(rawByteData);
                }
            } else if (command.hasKey("appendAbsolutePosition")) {
                if (command.hasKey("data"))
                    builder.appendAbsolutePosition((command.getString("data").getBytes(encoding)), command.getInt("appendAbsolutePosition"));
                else builder.appendAbsolutePosition(command.getInt("appendAbsolutePosition"));
            } else if (command.hasKey("appendAlignment")) {
                if (command.hasKey("data"))
                    builder.appendAlignment((command.getString("data").getBytes(encoding)), getAlignment(command.getString("appendAlignment")));
                else builder.appendAlignment(getAlignment(command.getString("appendAlignment")));
            } else if (command.hasKey("appendHorizontalTabPosition")) {
                ReadableArray tabPositionsArray = command.getArray("appendHorizontalTabPosition");
                if (tabPositionsArray != null) {
                    int[] tabPositions = new int[tabPositionsArray.size()];
                    for (int j = 0; j < tabPositionsArray.size(); j++) {
                        tabPositions[j] = tabPositionsArray.getInt(j);
                    }
                    builder.appendHorizontalTabPosition(tabPositions);
                }
            } else if (command.hasKey("appendLogo")) {
                ICommandBuilder.LogoSize logoSize = (command.hasKey("logoSize") ? getLogoSize(command.getString("logoSize")) : getLogoSize("Normal"));
                builder.appendLogo(logoSize, command.getInt("appendLogo"));
            } else if (command.hasKey("appendBarcode")) {
                ICommandBuilder.BarcodeSymbology barcodeSymbology = (command.hasKey("BarcodeSymbology") ? getBarcodeSymbology(command.getString("BarcodeSymbology")) : getBarcodeSymbology("Code128"));
                ICommandBuilder.BarcodeWidth barcodeWidth = (command.hasKey("BarcodeWidth") ? getBarcodeWidth(command.getString("BarcodeWidth")) : getBarcodeWidth("Mode2"));
                int height = (command.hasKey("height") ? command.getInt("height") : 40);
                Boolean hri = (command.hasKey("hri") ? command.getBoolean("hri") : true);
                if (command.hasKey("absolutePosition")) {
                    int position = command.getInt("absolutePosition");
                    builder.appendBarcodeWithAbsolutePosition(command.getString("appendBarcode").getBytes(encoding), barcodeSymbology, barcodeWidth, height, hri, position);
                } else if (command.hasKey("alignment")) {
                    ICommandBuilder.AlignmentPosition alignmentPosition = getAlignment(command.getString("alignment"));
                    builder.appendBarcodeWithAlignment(command.getString("appendBarcode").getBytes(encoding), barcodeSymbology, barcodeWidth, height, hri, alignmentPosition);
                } else
                    builder.appendBarcode(command.getString("appendBarcode").getBytes(encoding), barcodeSymbology, barcodeWidth, height, hri);
            } else if (command.hasKey("appendMultiple")) {
                int width = (command.hasKey("width") ? command.getInt("width") : 1);
                int height = (command.hasKey("height") ? command.getInt("height") : 1);
                builder.appendMultiple(command.getString("appendMultiple").getBytes(encoding), width, height);
            } else if (command.hasKey("enableMultiple")) {
                int width = (command.hasKey("width") ? command.getInt("width") : 1);
                int height = (command.hasKey("height") ? command.getInt("height") : 1);
                Boolean enableMultiple = command.getBoolean("enableMultiple");
                if (enableMultiple) builder.appendMultiple(width, height);
                else builder.appendMultiple(1, 1); // Reset to default when false sent
            } else if (command.hasKey("appendQrCode")) {
                ICommandBuilder.QrCodeModel qrCodeModel = (command.hasKey("QrCodeModel") ? getQrCodeModel(command.getString("QrCodeModel")) : getQrCodeModel("No2"));
                ICommandBuilder.QrCodeLevel qrCodeLevel = (command.hasKey("QrCodeLevel") ? getQrCodeLevel(command.getString("QrCodeLevel")) : getQrCodeLevel("H"));
                int cell = (command.hasKey("cell") ? command.getInt("cell") : 4);
                if (command.hasKey("absolutePosition")) {
                    int position = command.getInt("absolutePosition");
                    builder.appendQrCodeWithAbsolutePosition(command.getString("appendQrCode").getBytes(encoding), qrCodeModel, qrCodeLevel, cell, position);
                } else if (command.hasKey("alignment")) {
                    ICommandBuilder.AlignmentPosition alignmentPosition = getAlignment(command.getString("alignment"));
                    builder.appendQrCodeWithAlignment(command.getString("appendQrCode").getBytes(encoding), qrCodeModel, qrCodeLevel, cell, alignmentPosition);
                } else
                    builder.appendQrCode(command.getString("appendQrCode").getBytes(encoding), qrCodeModel, qrCodeLevel, cell);
            } else if (command.hasKey("appendBitmap")) {
                ContentResolver contentResolver = context.getContentResolver();
                String uriString = command.getString("appendBitmap");
                boolean diffusion = (command.hasKey("diffusion")) ? command.getBoolean("diffusion") : true;
                int width = (command.hasKey("width")) ? command.getInt("width") : 576;
                boolean bothScale = (command.hasKey("bothScale")) ? command.getBoolean("bothScale") : true;
                ICommandBuilder.BitmapConverterRotation rotation = (command.hasKey("rotation")) ? getConverterRotation(command.getString("rotation")) : getConverterRotation("Normal");
                try {
                    Uri imageUri = Uri.parse(uriString);
                    Bitmap bitmap = MediaStore.Images.Media.getBitmap(contentResolver, imageUri);
                    if (command.hasKey("absolutePosition")) {
                        int position = command.getInt("absolutePosition");
                        builder.appendBitmapWithAbsolutePosition(bitmap, diffusion, width, bothScale, rotation, position);
                    } else if (command.hasKey("alignment")) {
                        ICommandBuilder.AlignmentPosition alignmentPosition = getAlignment(command.getString("alignment"));
                        builder.appendBitmapWithAlignment(bitmap, diffusion, width, bothScale, rotation, alignmentPosition);
                    } else builder.appendBitmap(bitmap, diffusion, width, bothScale, rotation);
                } catch (IOException e) {

                }
            } else if (command.hasKey("appendBitmapBytes")) {
                ReadableArray bytesArray = command.getArray("appendBitmapBytes");
                if (bytesArray != null) {
                    byte[] byteData = new byte[bytesArray.size() + 1];
                    for (int j = 0; j < bytesArray.size(); j++) {
                        byteData[j] = (byte) bytesArray.getInt(j);
                    }

                    Bitmap bitmap = bytesArrayAsBitmap(byteData);
                    builder.appendBitmap(bitmap, false);
                }
            } else if (command.hasKey("appendBase64Bitmap")) {
                String base64Bitmap = command.getString("appendBase64Bitmap");
                builder.appendBitmap(base64ImageAsBitmap(base64Bitmap), false);
            } else if (command.hasKey("appendBitmapText")) {
//                String bitmapText = command.getString("appendBitmapText");
//                Bitmap bitmap = textAsBitmap(bitmapText, 20, Color.BLACK);
//                builder.appendBitmap(bitmap, false);

                int fontSize = (command.hasKey("fontSize")) ? command.getInt("fontSize") : 25;
                boolean diffusion = (command.hasKey("diffusion")) ? command.getBoolean("diffusion") : true;
                int width = (command.hasKey("width")) ? command.getInt("width") : 576;
                boolean bothScale = (command.hasKey("bothScale")) ? command.getBoolean("bothScale") : true;
                String text = command.getString("appendBitmapText");
                Typeface typeface = Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL);
                Bitmap bitmap = createBitmapFromText(text, fontSize, width, typeface);
                ICommandBuilder.BitmapConverterRotation rotation = (command.hasKey("rotation")) ? getConverterRotation(command.getString("rotation")) : getConverterRotation("Normal");
                if(command.hasKey("absolutePosition")){
                    int position =  command.getInt("absolutePosition");
                    builder.appendBitmapWithAbsolutePosition(bitmap, diffusion, width, bothScale, rotation, position);
                } else if(command.hasKey("alignment")){
                    ICommandBuilder.AlignmentPosition alignmentPosition = getAlignment(command.getString("alignment"));
                    builder.appendBitmapWithAlignment(bitmap, diffusion, width, bothScale, rotation, alignmentPosition);
                } else {
                    builder.appendBitmap(bitmap, diffusion, width, bothScale, rotation);
                }
            }
        }
    }

    private Bitmap createBitmapFromText(String printText, int textSize, int printWidth, Typeface typeface) {
        Paint paint = new Paint();
        Bitmap bitmap;
        Canvas canvas;

        paint.setTextSize(textSize);
        paint.setTypeface(typeface);
        paint.getTextBounds(printText, 0, printText.length(), new Rect());

        TextPaint textPaint = new TextPaint(paint);
        android.text.StaticLayout staticLayout = new StaticLayout(printText, textPaint, printWidth, Layout.Alignment.ALIGN_NORMAL, 1, 0, false);

        // Create bitmap
        bitmap = Bitmap.createBitmap(staticLayout.getWidth(), staticLayout.getHeight(), Bitmap.Config.ARGB_8888);

        // Create canvas
        canvas = new Canvas(bitmap);
        canvas.drawColor(Color.WHITE);
        canvas.translate(0, 0);
        staticLayout.draw(canvas);

        return bitmap;
    }

    private Bitmap bytesArrayAsBitmap(byte[] bitmapBytes) {
        return BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
    }

    private Bitmap base64ImageAsBitmap(String imageBase64) {
        byte[] decodedString = Base64.decode(imageBase64, Base64.DEFAULT);
        return BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
    }

    private Bitmap textAsBitmap(String text, float textSize, int textColor) {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setTextSize(textSize);
        paint.setColor(textColor);
        paint.setTextAlign(Paint.Align.LEFT);
        float baseline = -paint.ascent(); // ascent() is negative
        int width = (int) (paint.measureText(text) + 0.5f); // round
        int height = (int) (baseline + paint.descent() + 0.5f);
        Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(image);
        canvas.drawText(text, 0, baseline, paint);
        return image;
    }

    //ICommandBuilder Constant Functions
    private ICommandBuilder.InternationalType getInternational(String international) {
        if (international.equals("UK")) return ICommandBuilder.InternationalType.UK;
        else if (international.equals("USA")) return ICommandBuilder.InternationalType.USA;
        else if (international.equals("France")) return ICommandBuilder.InternationalType.France;
        else if (international.equals("Germany")) return ICommandBuilder.InternationalType.Germany;
        else if (international.equals("Denmark")) return ICommandBuilder.InternationalType.Denmark;
        else if (international.equals("Sweden")) return ICommandBuilder.InternationalType.Sweden;
        else if (international.equals("Italy")) return ICommandBuilder.InternationalType.Italy;
        else if (international.equals("Spain")) return ICommandBuilder.InternationalType.Spain;
        else if (international.equals("Japan")) return ICommandBuilder.InternationalType.Japan;
        else if (international.equals("Norway")) return ICommandBuilder.InternationalType.Norway;
        else if (international.equals("Denmark2"))
            return ICommandBuilder.InternationalType.Denmark2;
        else if (international.equals("Spain2")) return ICommandBuilder.InternationalType.Spain2;
        else if (international.equals("LatinAmerica"))
            return ICommandBuilder.InternationalType.LatinAmerica;
        else if (international.equals("Korea")) return ICommandBuilder.InternationalType.Korea;
        else if (international.equals("Ireland")) return ICommandBuilder.InternationalType.Ireland;
        else if (international.equals("Legal")) return ICommandBuilder.InternationalType.Legal;
        else return ICommandBuilder.InternationalType.USA;
    }

    ;

    private ICommandBuilder.AlignmentPosition getAlignment(String alignment) {
        if (alignment.equals("Left")) return ICommandBuilder.AlignmentPosition.Left;
        else if (alignment.equals("Center")) return ICommandBuilder.AlignmentPosition.Center;
        else if (alignment.equals("Right")) return ICommandBuilder.AlignmentPosition.Right;
        else return ICommandBuilder.AlignmentPosition.Left;
    }

    private ICommandBuilder.BarcodeSymbology getBarcodeSymbology(String barcodeSymbology) {
        if (barcodeSymbology.equals("Code128")) return ICommandBuilder.BarcodeSymbology.Code128;
        else if (barcodeSymbology.equals("Code39")) return ICommandBuilder.BarcodeSymbology.Code39;
        else if (barcodeSymbology.equals("Code93")) return ICommandBuilder.BarcodeSymbology.Code93;
        else if (barcodeSymbology.equals("ITF")) return ICommandBuilder.BarcodeSymbology.ITF;
        else if (barcodeSymbology.equals("JAN8")) return ICommandBuilder.BarcodeSymbology.JAN8;
        else if (barcodeSymbology.equals("JAN13")) return ICommandBuilder.BarcodeSymbology.JAN13;
        else if (barcodeSymbology.equals("NW7")) return ICommandBuilder.BarcodeSymbology.NW7;
        else if (barcodeSymbology.equals("UPCA")) return ICommandBuilder.BarcodeSymbology.UPCA;
        else if (barcodeSymbology.equals("UPCE")) return ICommandBuilder.BarcodeSymbology.UPCE;
        else return ICommandBuilder.BarcodeSymbology.Code128;
    }

    private ICommandBuilder.BarcodeWidth getBarcodeWidth(String barcodeWidth) {
        if (barcodeWidth.equals("Mode1")) return ICommandBuilder.BarcodeWidth.Mode1;
        if (barcodeWidth.equals("Mode2")) return ICommandBuilder.BarcodeWidth.Mode2;
        if (barcodeWidth.equals("Mode3")) return ICommandBuilder.BarcodeWidth.Mode3;
        if (barcodeWidth.equals("Mode4")) return ICommandBuilder.BarcodeWidth.Mode4;
        if (barcodeWidth.equals("Mode5")) return ICommandBuilder.BarcodeWidth.Mode5;
        if (barcodeWidth.equals("Mode6")) return ICommandBuilder.BarcodeWidth.Mode6;
        if (barcodeWidth.equals("Mode7")) return ICommandBuilder.BarcodeWidth.Mode7;
        if (barcodeWidth.equals("Mode8")) return ICommandBuilder.BarcodeWidth.Mode8;
        if (barcodeWidth.equals("Mode9")) return ICommandBuilder.BarcodeWidth.Mode9;
        return ICommandBuilder.BarcodeWidth.Mode2;
    }

    private ICommandBuilder.FontStyleType getFontStyle(String fontStyle) {
        if (fontStyle.equals("A")) return ICommandBuilder.FontStyleType.A;
        if (fontStyle.equals("B")) return ICommandBuilder.FontStyleType.B;
        return ICommandBuilder.FontStyleType.A;
    }

    private ICommandBuilder.LogoSize getLogoSize(String logoSize) {
        if (logoSize.equals("Normal")) return ICommandBuilder.LogoSize.Normal;
        else if (logoSize.equals("DoubleWidth")) return ICommandBuilder.LogoSize.DoubleWidth;
        else if (logoSize.equals("DoubleHeight")) return ICommandBuilder.LogoSize.DoubleHeight;
        else if (logoSize.equals("DoubleWidthDoubleHeight"))
            return ICommandBuilder.LogoSize.DoubleWidthDoubleHeight;
        else return ICommandBuilder.LogoSize.Normal;
    }

    private ICommandBuilder.CutPaperAction getCutPaperAction(String cutPaperAction) {
        if (cutPaperAction.equals("FullCut")) return CutPaperAction.FullCut;
        else if (cutPaperAction.equals("FullCutWithFeed")) return CutPaperAction.FullCutWithFeed;
        else if (cutPaperAction.equals("PartialCut")) return CutPaperAction.PartialCut;
        else if (cutPaperAction.equals("PartialCutWithFeed"))
            return CutPaperAction.PartialCutWithFeed;
        else return CutPaperAction.PartialCutWithFeed;
    }

    private ICommandBuilder.PeripheralChannel getPeripheralChannel(int peripheralChannel) {
        if (peripheralChannel == 1) return ICommandBuilder.PeripheralChannel.No1;
        else if (peripheralChannel == 2) return ICommandBuilder.PeripheralChannel.No2;
        else return ICommandBuilder.PeripheralChannel.No1;
    }

    private ICommandBuilder.QrCodeModel getQrCodeModel(String qrCodeModel) {
        if (qrCodeModel.equals("No1")) return ICommandBuilder.QrCodeModel.No1;
        else if (qrCodeModel.equals("No2")) return ICommandBuilder.QrCodeModel.No2;
        else return ICommandBuilder.QrCodeModel.No1;
    }

    private ICommandBuilder.QrCodeLevel getQrCodeLevel(String qrCodeLevel) {
        if (qrCodeLevel.equals("H")) return ICommandBuilder.QrCodeLevel.H;
        else if (qrCodeLevel.equals("L")) return ICommandBuilder.QrCodeLevel.L;
        else if (qrCodeLevel.equals("M")) return ICommandBuilder.QrCodeLevel.M;
        else if (qrCodeLevel.equals("Q")) return ICommandBuilder.QrCodeLevel.Q;
        else return ICommandBuilder.QrCodeLevel.H;
    }

    private ICommandBuilder.BitmapConverterRotation getConverterRotation(String converterRotation) {
        if (converterRotation.equals("Normal"))
            return ICommandBuilder.BitmapConverterRotation.Normal;
        else if (converterRotation.equals("Left90"))
            return ICommandBuilder.BitmapConverterRotation.Left90;
        else if (converterRotation.equals("Right90"))
            return ICommandBuilder.BitmapConverterRotation.Right90;
        else if (converterRotation.equals("Rotate180"))
            return ICommandBuilder.BitmapConverterRotation.Rotate180;
        else return ICommandBuilder.BitmapConverterRotation.Normal;
    }

    private ICommandBuilder.BlackMarkType getBlackMarkType(String blackMarkType) {
        if (blackMarkType.equals("Valid")) return ICommandBuilder.BlackMarkType.Valid;
        else if (blackMarkType.equals("Invalid")) return ICommandBuilder.BlackMarkType.Invalid;
        else if (blackMarkType.equals("ValidWithDetection"))
            return ICommandBuilder.BlackMarkType.ValidWithDetection;
        else return ICommandBuilder.BlackMarkType.Valid;
    }

    private ICommandBuilder.CodePageType getCodePageType(String codePageType) {
        if (codePageType.equals("CP437")) return CodePageType.CP437;
        else if (codePageType.equals("CP737")) return CodePageType.CP737;
        else if (codePageType.equals("CP772")) return CodePageType.CP772;
        else if (codePageType.equals("CP774")) return CodePageType.CP774;
        else if (codePageType.equals("CP851")) return CodePageType.CP851;
        else if (codePageType.equals("CP852")) return CodePageType.CP852;
        else if (codePageType.equals("CP855")) return CodePageType.CP855;
        else if (codePageType.equals("CP857")) return CodePageType.CP857;
        else if (codePageType.equals("CP858")) return CodePageType.CP858;
        else if (codePageType.equals("CP860")) return CodePageType.CP860;
        else if (codePageType.equals("CP861")) return CodePageType.CP861;
        else if (codePageType.equals("CP862")) return CodePageType.CP862;
        else if (codePageType.equals("CP863")) return CodePageType.CP863;
        else if (codePageType.equals("CP864")) return CodePageType.CP864;
        else if (codePageType.equals("CP865")) return CodePageType.CP866;
        else if (codePageType.equals("CP869")) return CodePageType.CP869;
        else if (codePageType.equals("CP874")) return CodePageType.CP874;
        else if (codePageType.equals("CP928")) return CodePageType.CP928;
        else if (codePageType.equals("CP932")) return CodePageType.CP932;
        else if (codePageType.equals("CP999")) return CodePageType.CP999;
        else if (codePageType.equals("CP1001")) return CodePageType.CP1001;
        else if (codePageType.equals("CP1250")) return CodePageType.CP1250;
        else if (codePageType.equals("CP1251")) return CodePageType.CP1251;
        else if (codePageType.equals("CP1252")) return CodePageType.CP1252;
        else if (codePageType.equals("CP2001")) return CodePageType.CP2001;
        else if (codePageType.equals("CP3001")) return CodePageType.CP3001;
        else if (codePageType.equals("CP3002")) return CodePageType.CP3002;
        else if (codePageType.equals("CP3011")) return CodePageType.CP3011;
        else if (codePageType.equals("CP3012")) return CodePageType.CP3012;
        else if (codePageType.equals("CP3021")) return CodePageType.CP3021;
        else if (codePageType.equals("CP3041")) return CodePageType.CP3041;
        else if (codePageType.equals("CP3840")) return CodePageType.CP3840;
        else if (codePageType.equals("CP3841")) return CodePageType.CP3841;
        else if (codePageType.equals("CP3843")) return CodePageType.CP3843;
        else if (codePageType.equals("CP3845")) return CodePageType.CP3845;
        else if (codePageType.equals("CP3846")) return CodePageType.CP3846;
        else if (codePageType.equals("CP3847")) return CodePageType.CP3847;
        else if (codePageType.equals("CP3848")) return CodePageType.CP3848;
        else if (codePageType.equals("UTF8")) return CodePageType.UTF8;
        else if (codePageType.equals("Blank")) return CodePageType.Blank;
        else return CodePageType.CP998;
    }

    ;

    //Helper functions

    private Charset getEncoding(String encoding) {

        if (encoding.equals("US-ASCII")) return Charset.forName("US-ASCII"); //English
        else if (encoding.equals("Windows-1252")) {
            try {
                return Charset.forName("Windows-1252"); //French, German, Portuguese, Spanish
            } catch (UnsupportedCharsetException e) { //not supported using UTF-8 Instead
                return Charset.forName("UTF-8");
            }
        } else if (encoding.equals("Shift-JIS")) {
            try {
                return Charset.forName("Shift-JIS"); //Japanese
            } catch (UnsupportedCharsetException e) { //not supported using UTF-8 Instead
                return Charset.forName("UTF-8");
            }
        } else if (encoding.equals("Windows-1251")) {
            try {
                return Charset.forName("Windows-1251"); //Russian
            } catch (UnsupportedCharsetException e) { //not supported using UTF-8 Instead
                return Charset.forName("UTF-8");
            }
        } else if (encoding.equals("GB2312")) {
            try {
                return Charset.forName("GB2312"); // Simplified Chinese
            } catch (UnsupportedCharsetException e) { //not supported using UTF-8 Instead
                return Charset.forName("UTF-8");
            }
        } else if (encoding.equals("Big5")) {
            try {
                return Charset.forName("Big5"); // Traditional Chinese
            } catch (UnsupportedCharsetException e) { //not supported using UTF-8 Instead
                return Charset.forName("UTF-8");
            }
        } else if (encoding.equals("UTF-8")) return Charset.forName("UTF-8"); // UTF-8
        else return Charset.forName("US-ASCII");
    }

    ;

    private void sendEvent(String dataType, String info) {
        ReactContext reactContext = getReactApplicationContext();
        String eventName = "starPrntData";
        WritableMap params = new WritableNativeMap();
        params.putString("dataType", dataType);
        if (info != null) params.putString("data", info);
        reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }

    ;


    private StarIoExtManagerListener starIoExtManagerListener = new StarIoExtManagerListener() {
        @Override
        public void onPrinterImpossible() {
            sendEvent("printerImpossible", null);
        }

        @Override
        public void onPrinterOnline() {
            sendEvent("printerOnline", null);
        }

        @Override
        public void onPrinterOffline() {
            sendEvent("printerOffline", null);
        }

        @Override
        public void onPrinterPaperReady() {
            sendEvent("printerPaperReady", null);
        }

        @Override
        public void onPrinterPaperNearEmpty() {
            sendEvent("printerPaperNearEmpty", null);
        }

        @Override
        public void onPrinterPaperEmpty() {
            sendEvent("printerPaperEmpty", null);
        }

        @Override
        public void onPrinterCoverOpen() {
            sendEvent("printerCoverOpen", null);
        }

        @Override
        public void onPrinterCoverClose() {
            sendEvent("printerCoverClose", null);
        }

        //Cash Drawer events
        @Override
        public void onCashDrawerOpen() {
            sendEvent("cashDrawerOpen", null);
        }

        @Override
        public void onCashDrawerClose() {
            sendEvent("cashDrawerClose", null);
        }

        @Override
        public void onBarcodeReaderImpossible() {
            sendEvent("barcodeReaderImpossible", null);
        }

        @Override
        public void onBarcodeReaderConnect() {
            sendEvent("barcodeReaderConnect", null);
        }

        @Override
        public void onBarcodeReaderDisconnect() {
            sendEvent("barcodeReaderDisconnect", null);
        }

        @Override
        public void onBarcodeDataReceive(byte[] data) {
            sendEvent("barcodeDataReceive", new String(data));
        }

    };
}
