package com.reactnative.eposprint;

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

import com.epson.eposprint.Builder;
import com.epson.eposprint.EposException;
import com.epson.eposprint.Print;
import com.epson.eposprint.StatusChangeEventListener;
import com.epson.eposprint.BatteryStatusChangeEventListener;

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

import java.util.HashMap;
import java.util.Map;

public class EposPrintModule extends ReactContextBaseJavaModule implements StatusChangeEventListener, BatteryStatusChangeEventListener {
    
    private final ReactApplicationContext reactContext;
    private Print printer;
    private Builder builder;
    private String printerModel;
    private int printerLanguage;

    public EposPrintModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
        this.printer = null;
        this.builder = null;
    }

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

    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        
        // Connection types
        constants.put("DEVTYPE_TCP", Print.DEVTYPE_TCP);
        constants.put("DEVTYPE_BLUETOOTH", Print.DEVTYPE_BLUETOOTH);
        constants.put("DEVTYPE_USB", Print.DEVTYPE_USB);
        
        // Language constants
        constants.put("LANG_EN", Builder.LANG_EN);
        constants.put("LANG_JA", Builder.LANG_JA);
        constants.put("LANG_ZH_CN", Builder.LANG_ZH_CN);
        constants.put("LANG_ZH_TW", Builder.LANG_ZH_TW);
        constants.put("LANG_KO", Builder.LANG_KO);
        constants.put("LANG_TH", Builder.LANG_TH);
        constants.put("LANG_VI", Builder.LANG_VI);
        
        // Alignment constants
        constants.put("ALIGN_LEFT", Builder.ALIGN_LEFT);
        constants.put("ALIGN_CENTER", Builder.ALIGN_CENTER);
        constants.put("ALIGN_RIGHT", Builder.ALIGN_RIGHT);
        
        // Font constants
        constants.put("FONT_A", Builder.FONT_A);
        constants.put("FONT_B", Builder.FONT_B);
        constants.put("FONT_C", Builder.FONT_C);
        
        // Cut constants
        constants.put("CUT_FEED", Builder.CUT_FEED);
        constants.put("CUT_NO_FEED", Builder.CUT_NO_FEED);
        
        // Barcode constants
        constants.put("BARCODE_UPC_A", Builder.BARCODE_UPC_A);
        constants.put("BARCODE_UPC_E", Builder.BARCODE_UPC_E);
        constants.put("BARCODE_EAN13", Builder.BARCODE_EAN13);
        constants.put("BARCODE_JAN13", Builder.BARCODE_JAN13);
        constants.put("BARCODE_EAN8", Builder.BARCODE_EAN8);
        constants.put("BARCODE_JAN8", Builder.BARCODE_JAN8);
        constants.put("BARCODE_CODE39", Builder.BARCODE_CODE39);
        constants.put("BARCODE_ITF", Builder.BARCODE_ITF);
        constants.put("BARCODE_CODABAR", Builder.BARCODE_CODABAR);
        constants.put("BARCODE_CODE93", Builder.BARCODE_CODE93);
        constants.put("BARCODE_CODE128", Builder.BARCODE_CODE128);
        constants.put("BARCODE_GS1_128", Builder.BARCODE_GS1_128);
        constants.put("BARCODE_GS1_DATABAR_OMNIDIRECTIONAL", Builder.BARCODE_GS1_DATABAR_OMNIDIRECTIONAL);
        constants.put("BARCODE_GS1_DATABAR_TRUNCATED", Builder.BARCODE_GS1_DATABAR_TRUNCATED);
        constants.put("BARCODE_GS1_DATABAR_LIMITED", Builder.BARCODE_GS1_DATABAR_LIMITED);
        constants.put("BARCODE_GS1_DATABAR_EXPANDED", Builder.BARCODE_GS1_DATABAR_EXPANDED);
        
        // HRI position constants
        constants.put("HRI_NONE", Builder.HRI_NONE);
        constants.put("HRI_ABOVE", Builder.HRI_ABOVE);
        constants.put("HRI_BELOW", Builder.HRI_BELOW);
        constants.put("HRI_BOTH", Builder.HRI_BOTH);
        
        // Symbol constants
        constants.put("SYMBOL_PDF417_STANDARD", Builder.SYMBOL_PDF417_STANDARD);
        constants.put("SYMBOL_PDF417_TRUNCATED", Builder.SYMBOL_PDF417_TRUNCATED);
        constants.put("SYMBOL_QRCODE_MODEL_1", Builder.SYMBOL_QRCODE_MODEL_1);
        constants.put("SYMBOL_QRCODE_MODEL_2", Builder.SYMBOL_QRCODE_MODEL_2);
        constants.put("SYMBOL_QRCODE_MICRO", Builder.SYMBOL_QRCODE_MICRO);
        constants.put("SYMBOL_MAXICODE_MODE_2", Builder.SYMBOL_MAXICODE_MODE_2);
        constants.put("SYMBOL_MAXICODE_MODE_3", Builder.SYMBOL_MAXICODE_MODE_3);
        constants.put("SYMBOL_MAXICODE_MODE_4", Builder.SYMBOL_MAXICODE_MODE_4);
        constants.put("SYMBOL_MAXICODE_MODE_5", Builder.SYMBOL_MAXICODE_MODE_5);
        constants.put("SYMBOL_MAXICODE_MODE_6", Builder.SYMBOL_MAXICODE_MODE_6);
        constants.put("SYMBOL_GS1_DATABAR_STACKED", Builder.SYMBOL_GS1_DATABAR_STACKED);
        constants.put("SYMBOL_GS1_DATABAR_STACKED_OMNIDIRECTIONAL", Builder.SYMBOL_GS1_DATABAR_STACKED_OMNIDIRECTIONAL);
        constants.put("SYMBOL_GS1_DATABAR_EXPANDED_STACKED", Builder.SYMBOL_GS1_DATABAR_EXPANDED_STACKED);
        
        // Level constants
        constants.put("LEVEL_0", Builder.LEVEL_0);
        constants.put("LEVEL_1", Builder.LEVEL_1);
        constants.put("LEVEL_2", Builder.LEVEL_2);
        constants.put("LEVEL_3", Builder.LEVEL_3);
        constants.put("LEVEL_4", Builder.LEVEL_4);
        constants.put("LEVEL_5", Builder.LEVEL_5);
        constants.put("LEVEL_6", Builder.LEVEL_6);
        constants.put("LEVEL_7", Builder.LEVEL_7);
        constants.put("LEVEL_8", Builder.LEVEL_8);
        constants.put("LEVEL_L", Builder.LEVEL_L);
        constants.put("LEVEL_M", Builder.LEVEL_M);
        constants.put("LEVEL_Q", Builder.LEVEL_Q);
        constants.put("LEVEL_H", Builder.LEVEL_H);
        
        // Line Style constants
        constants.put("LINE_THIN", Builder.LINE_THIN);
        constants.put("LINE_MEDIUM", Builder.LINE_MEDIUM);
        constants.put("LINE_THICK", Builder.LINE_THICK);
        constants.put("LINE_THIN_DOUBLE", Builder.LINE_THIN_DOUBLE);
        constants.put("LINE_MEDIUM_DOUBLE", Builder.LINE_MEDIUM_DOUBLE);
        constants.put("LINE_THICK_DOUBLE", Builder.LINE_THICK_DOUBLE);
        
        // Direction constants
        constants.put("DIRECTION_LEFT_TO_RIGHT", Builder.DIRECTION_LEFT_TO_RIGHT);
        constants.put("DIRECTION_BOTTOM_TO_TOP", Builder.DIRECTION_BOTTOM_TO_TOP);
        constants.put("DIRECTION_RIGHT_TO_LEFT", Builder.DIRECTION_RIGHT_TO_LEFT);
        constants.put("DIRECTION_TOP_TO_BOTTOM", Builder.DIRECTION_TOP_TO_BOTTOM);
        
        // Boolean constants
        constants.put("TRUE", Print.TRUE);
        constants.put("FALSE", Print.FALSE);
        
        return constants;
    }
    
    private void sendEvent(String eventName, WritableMap params) {
        reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }
    
    @ReactMethod
    public void initializePrinter(Promise promise) {
        try {
            printer = new Print(reactContext.getApplicationContext());
            printer.setStatusChangeEventCallback(this);
            printer.setBatteryStatusChangeEventCallback(this);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("INIT_ERROR", "Failed to initialize printer: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void openPrinter(ReadableMap config, Promise promise) {
        try {
            if (printer == null) {
                promise.reject("NOT_INITIALIZED", "Printer not initialized");
                return;
            }
            
            int deviceType = config.hasKey("deviceType") ? config.getInt("deviceType") : Print.DEVTYPE_TCP;
            String target = config.hasKey("target") ? config.getString("target") : "";
            boolean isStatusMonitor = config.hasKey("isStatusMonitor") && config.getBoolean("isStatusMonitor");
            int interval = config.hasKey("interval") ? config.getInt("interval") : 3000;
            
            printer.openPrinter(deviceType, target, isStatusMonitor ? Print.TRUE : Print.FALSE, interval);
            
            this.printerModel = config.hasKey("printerModel") ? config.getString("printerModel") : "TM-m30";
            this.printerLanguage = config.hasKey("printerLanguage") ? config.getInt("printerLanguage") : Builder.LANG_EN;
            
            promise.resolve(true);
        } catch (EposException e) {
            promise.reject("OPEN_ERROR", "Failed to open printer: Error code: " + e.getErrorStatus());
        } catch (Exception e) {
            promise.reject("OPEN_ERROR", "Failed to open printer: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void closePrinter(Promise promise) {
        try {
            if (printer != null) {
                printer.closePrinter();
                printer = null;
            }
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("CLOSE_ERROR", "Failed to close printer: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void initializeBuilder(Promise promise) {
        try {
            if (printer == null) {
                promise.reject("NOT_INITIALIZED", "Printer not initialized");
                return;
            }
            
            builder = new Builder(printerModel, printerLanguage, reactContext.getApplicationContext());
            promise.resolve(true);
        } catch (EposException e) {
            promise.reject("BUILDER_ERROR", "Failed to initialize builder: Error code: " + e.getErrorStatus());
        } catch (Exception e) {
            promise.reject("BUILDER_ERROR", "Failed to initialize builder: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void clearBuilder(Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.clearCommandBuffer();
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("CLEAR_ERROR", "Failed to clear builder: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addText(String text, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addText(text);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("TEXT_ERROR", "Failed to add text: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addTextAlign(int align, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addTextAlign(align);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("ALIGN_ERROR", "Failed to set text alignment: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addTextSize(int width, int height, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addTextSize(width, height);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("SIZE_ERROR", "Failed to set text size: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addFeed(Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addFeed();
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("FEED_ERROR", "Failed to add feed: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addFeedLine(int line, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addFeedLine(line);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("FEED_ERROR", "Failed to add feed line: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addCut(int cutType, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addCut(cutType);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("CUT_ERROR", "Failed to add cut: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addBarcode(String data, int symbology, int height, int width, int hri, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addBarcode(data, symbology, height, width, hri);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("BARCODE_ERROR", "Failed to add barcode: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addSymbol(String data, int type, int level, int width, int height, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addSymbol(data, type, level, width, height);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("SYMBOL_ERROR", "Failed to add symbol: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addImage(String base64Image, int x, int y, int width, int height, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            byte[] decodedString = Base64.decode(base64Image, Base64.DEFAULT);
            Bitmap bitmap = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
            
            if (bitmap == null) {
                promise.reject("IMAGE_ERROR", "Failed to decode image");
                return;
            }
            
            builder.addImage(bitmap, x, y, width, height, Builder.COLOR_1);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("IMAGE_ERROR", "Failed to add image: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addTextFont(int font, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addTextFont(font);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("FONT_ERROR", "Failed to set text font: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void addTextSmooth(boolean smooth, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addTextSmooth(smooth ? Builder.TRUE : Builder.FALSE);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("SMOOTH_ERROR", "Failed to set text smooth: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void beginPageMode(int x, int y, int width, int height, int direction, Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addPageBegin();
            builder.addPageArea(x, y, width, height);
            builder.addPageDirection(direction);
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("PAGE_ERROR", "Failed to begin page mode: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void endPageMode(Promise promise) {
        try {
            if (builder == null) {
                promise.reject("NOT_INITIALIZED", "Builder not initialized");
                return;
            }
            
            builder.addPageEnd();
            promise.resolve(true);
        } catch (Exception e) {
            promise.reject("PAGE_ERROR", "Failed to end page mode: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void sendData(Promise promise) {
        try {
            if (printer == null || builder == null) {
                promise.reject("NOT_INITIALIZED", "Printer or builder not initialized");
                return;
            }
            
            int[] status = new int[1];
            int[] battery = new int[1];
            
            printer.sendData(builder, 10000, status, battery);
            
            WritableMap statusMap = Arguments.createMap();
            statusMap.putInt("status", status[0]);
            statusMap.putInt("battery", battery[0]);
            
            promise.resolve(statusMap);
        } catch (EposException e) {
            WritableMap errorMap = Arguments.createMap();
            errorMap.putInt("errorStatus", e.getErrorStatus());
            errorMap.putInt("printerStatus", e.getPrinterStatus());
            errorMap.putInt("batteryStatus", e.getBatteryStatus());
            
            promise.reject("SEND_ERROR", "Failed to send data", errorMap);
        } catch (Exception e) {
            promise.reject("SEND_ERROR", "Failed to send data: " + e.getMessage());
        }
    }
    
    @ReactMethod
    public void getPrinterStatus(Promise promise) {
        try {
            if (printer == null) {
                promise.reject("NOT_INITIALIZED", "Printer not initialized");
                return;
            }
            
            // Create a minimal builder
            Builder statusBuilder = new Builder(printerModel, printerLanguage, reactContext.getApplicationContext());
            
            int[] status = new int[1];
            int[] battery = new int[1];
            
            printer.sendData(statusBuilder, 10000, status, battery);
            
            WritableMap statusMap = Arguments.createMap();
            statusMap.putInt("status", status[0]);
            statusMap.putInt("battery", battery[0]);
            
            // Clear the builder
            statusBuilder.clearCommandBuffer();
            
            promise.resolve(statusMap);
        } catch (EposException e) {
            WritableMap errorMap = Arguments.createMap();
            errorMap.putInt("errorStatus", e.getErrorStatus());
            errorMap.putInt("printerStatus", e.getPrinterStatus());
            errorMap.putInt("batteryStatus", e.getBatteryStatus());
            
            promise.reject("STATUS_ERROR", "Failed to get printer status", errorMap);
        } catch (Exception e) {
            promise.reject("STATUS_ERROR", "Failed to get printer status: " + e.getMessage());
        }
    }
    
    @Override
    public void onStatusChangeEvent(String deviceName, int status) {
        WritableMap params = Arguments.createMap();
        params.putString("deviceName", deviceName);
        params.putInt("status", status);
        
        sendEvent("onStatusChange", params);
    }
    
    @Override
    public void onBatteryStatusChangeEvent(String deviceName, int battery) {
        WritableMap params = Arguments.createMap();
        params.putString("deviceName", deviceName);
        params.putInt("battery", battery);
        
        sendEvent("onBatteryStatusChange", params);
    }
} 