package com.homansystem.iposprinter;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.Toast;
import android.util.Log;

import android.os.Handler;
import android.os.Message;
import android.os.Looper;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.LifecycleEventListener;

import android.annotation.SuppressLint;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;

import com.zj.command.sdk.PrinterCommand;
import com.zj.command.sdk.Command;
import com.zj.command.sdk.PrintPicture;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import com.zj.wfsdk.*;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothAdapter;

import com.caysn.autoreplyprint.caprint.CAPrinterDevice;
import com.caysn.autoreplyprint.caprint.CAPrintCommon;
import com.caysn.autoreplyprint.caprint.CAPrinterConnector;
import com.facebook.react.bridge.LifecycleEventListener;

public class KassenBTPrinter implements MyPrinter {

  private String TAG = "IPOSKASSENBTPRINTER";
  private Context reactContext;
  private PrinterEventListener listener;
  private Promise connPromise = null;
  private BluetoothDevice device = null;
  int nMode = 0;
  public static final int WIDTH_58 = 384;
  public static final int WIDTH_80 = 576;
  private int deviceWidth = WIDTH_58;

  private String mConnectedDeviceName = null;

  private ReactApplicationContext rContext;

  // Message types sent from the BluetoothService Handler
  public static final int MESSAGE_STATE_CHANGE = 1;
  public static final int MESSAGE_READ = 2;
  public static final int MESSAGE_WRITE = 3;
  public static final int MESSAGE_DEVICE_NAME = 4;
  public static final int MESSAGE_TOAST = 5;
  public static final int MESSAGE_CONNECTION_LOST = 6;
  public static final int MESSAGE_UNABLE_CONNECT = 7;

  private Boolean isDebug = false;
  private Boolean hasCutter = true;
  /*******************************************************************************************************/
  // Key names received from the BluetoothService Handler
  public static final String DEVICE_NAME = "device_name";
  public static final String TOAST = "toast";

  public static CAPrinterConnector printerConnector = new CAPrinterConnector();

  public KassenBTPrinter(final Context context, final PrinterEventListener eventListener) {
    reactContext = context;
    listener = eventListener;
    byte[] arr = PrinterCommand.POS_Set_PrtInit();
    listener.onInitializeSuccess("Success Connected");
    printerConnector.registerConnectionStatusChangedEvent(m_connectionStatusChangedEvent);
    ReactApplicationContext reactContext = (ReactApplicationContext) context;
    reactContext.addLifecycleEventListener(lifecycleEventListener);
  }

  public void setTextSize(int size1, int size2) {
    int _size1 = size1 < 1 || size1 > 8 ? 0 : size1 - 1;
    int _size2 = size2 < 1 || size2 > 8 ? 0 : size2 - 1;
    byte[] arr = PrinterCommand.POS_Set_FontSize(_size1, _size2);
      CAPrintCommon.writeBuffer(printerConnector, arr);
  }

  public void setBold(boolean bold) {
    int mybold = bold ? 1 : 0;
    byte[] arr = PrinterCommand.POS_Set_Bold(mybold);
      CAPrintCommon.writeBuffer(printerConnector, arr);
  }

  @Override
  public void setDebug(Boolean isDebug) {
    this.isDebug = isDebug;
  }

  @Override
  public void hasCutter(Boolean value) {
    this.hasCutter = value;
  }

  @Override
  public void connect(String address, Promise promise) {
    if (!this.isDebug) {
      final CAPrinterDevice printerDevice = CAPrinterDevice.fromUidString(address);
      printerConnector.connectPrinterAsync(printerDevice);
      connPromise = promise;
    } else
      promise.resolve("Success");
  }

  private CAPrinterConnector.ConnectionStatusChangedInterface m_connectionStatusChangedEvent = new CAPrinterConnector.ConnectionStatusChangedInterface() {
    @Override
    public void onConnectionStatusChanged() {
      if (printerConnector.isCurrentConnectedPrinter()) {
        connPromise.resolve(null);
      } else if (printerConnector.isCurrentConnectingPrinter()) {
        //CONNECTING
      } else {
        connPromise.reject(new Exception("Failed to connect to printer"));
      }
    }
  };

  public void writeTextWProps(String text, @Nullable ReadableMap property) {
    try {
      boolean bold = property.hasKey("bold") ? property.getBoolean("bold") : false;
      int fontSize = property.hasKey("fontSize") ? property.getInt("fontSize") : 1;
      setBold(bold);
      setTextSize(fontSize, fontSize);
      byte[] arr = text.getBytes("GBK");
      CAPrintCommon.writeBuffer(printerConnector, arr);
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
  }

  @Override
  public void writeText(String text) {
    try {
      setBold(false);
      setTextSize(1, 1);
      byte[] arr = text.getBytes("GBK");
      CAPrintCommon.writeBuffer(printerConnector, arr);
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
  }

  @Override
  public void addTextAlign(int align) {
    byte[] arr = Command.ESC_Align;
    arr[2] = (byte) align;
    CAPrintCommon.writeBuffer(printerConnector, arr);
  }

  @Override
  public void writeFeed(int length) {
    byte[] arr = PrinterCommand.POS_Set_PrtAndFeedPaper(length);
    CAPrintCommon.writeBuffer(printerConnector, arr);
  }

  @Override
  public void writeCut() {
    if (this.hasCutter) {
      byte[] arr = PrinterCommand.POS_Set_Cut(0);
      CAPrintCommon.writeBuffer(printerConnector, arr);
    } else {
      return;
    }
  }

  @Override
  public void writeImage(Bitmap mBitmap, int width) {
    int nMode = 0;
    if (mBitmap != null) {
      byte[] data = PrintPicture.POS_PrintBMP(mBitmap, width, nMode);
      CAPrintCommon.writeBuffer(printerConnector, data);
    }
  }

  @Override
  public void writePulse() {
    byte[] arr = PrinterCommand.POS_Set_Cashbox(0, 80, 80);
    CAPrintCommon.writeBuffer(printerConnector, arr);
  }

  @Override
  public void writeBeep(int numOfBeep, int buzzingTime) {
    byte[] arr = PrinterCommand.POS_Set_Beep(numOfBeep, buzzingTime);
    CAPrintCommon.writeBuffer(printerConnector, arr);
  }

  @Override
  public void printColumn(ReadableArray columnWidths, ReadableArray columnAligns, ReadableArray columnTexts,
      @Nullable ReadableMap options, final Promise promise) {
    if (!printerConnector.isCurrentConnectedPrinter()) {
      promise.reject("Not Connected to printer");
      return;
    }
    boolean bold = options.hasKey("bold") ? options.getBoolean("bold") : false;
    int fontSize = options.hasKey("fontSize") ? options.getInt("fontSize") : 1;
    if (fontSize < 1) {
      fontSize = 1;
    }
    if (fontSize > 8) {
      fontSize = 8;
    }
    if (columnWidths.size() != columnTexts.size() || columnWidths.size() != columnAligns.size()) {
      promise.reject("COLUMN_WIDTH_ALIGNS_AND_TEXTS_NOT_MATCH");
      return;
    }
    int totalLen = 0;
    for (int i = 0; i < columnWidths.size(); i++) {
      totalLen += columnWidths.getInt(i);
    }
    int maxLen = deviceWidth / 8;
    if (totalLen > maxLen) {
      promise.reject("COLUNM_WIDTHS_TOO_LARGE");
      return;
    }
    List<List<String>> table = new ArrayList<List<String>>();
    int padding = 0;
    for (int i = 0; i < columnWidths.size(); i++) {
      int width = (columnWidths.getInt(i) / fontSize) - padding;
      String text = String.copyValueOf(columnTexts.getString(i).toCharArray());
      List<ColumnSplitedString> splited = new ArrayList<ColumnSplitedString>();
      int shorter = 0;
      int counter = 0;
      String temp = "";
      for (int c = 0; c < text.length(); c++) {
        char ch = text.charAt(c);
        int l = isChinese(ch) ? 2 : 1;
        if (l == 2) {
          shorter++;
        }
        temp = temp + ch;
        if (counter + 1 < width) {
          counter = counter + 1;
        } else {
          splited.add(new ColumnSplitedString(shorter, temp));
          temp = "";
          counter = 0;
          shorter = 0;
        }
      }
      if (temp.length() > 0) {
        splited.add(new ColumnSplitedString(shorter, temp));
      }
      int align = columnAligns.getInt(i);
      if (align == 4)
        align = 0;
      List<String> formated = new ArrayList<String>();
      for (ColumnSplitedString s : splited) {
        StringBuilder empty = new StringBuilder();
        for (int w = 0; w < (width + padding - s.getShorter()); w++) {
          empty.append(" ");
        }
        int startIdx = 0;
        String ss = s.getStr();
        if (align == 1 && ss.length() < (width - s.getShorter())) {
          startIdx = (width - s.getShorter() - ss.length()) / 2;
          if (startIdx + ss.length() > width - s.getShorter()) {
            startIdx--;
          }
          if (startIdx < 0) {
            startIdx = 0;
          }
        } else if (align == 2 && ss.length() < (width - s.getShorter())) {
          startIdx = width - s.getShorter() - ss.length();
        }
        empty.replace(startIdx, startIdx + ss.length(), ss);
        formated.add(empty.toString());
      }
      table.add(formated);
    }
    int maxRowCount = 0;
    for (int i = 0; i < table.size(); i++) {
      List<String> row = table.get(i);
      if (row.size() > maxRowCount) {
        maxRowCount = row.size();
      }
    }
    StringBuilder[] rowsToPrint = new StringBuilder[maxRowCount];
    for (int column = 0; column < table.size(); column++) {
      List<String> rows = table.get(column);
      for (int row = 0; row < maxRowCount; row++) {
        if (rowsToPrint[row] == null) {
          rowsToPrint[row] = new StringBuilder();
        }
        if (row < rows.size()) {
          rowsToPrint[row].append(rows.get(row));
        } else {
          int w = columnWidths.getInt(column) / fontSize;
          StringBuilder empty = new StringBuilder();
          for (int i = 0; i < w; i++) {
            empty.append(" ");
          }
          rowsToPrint[row].append(empty.toString());
        }
      }
    }
    for (int i = 0; i < rowsToPrint.length; i++) {
      rowsToPrint[i].append("\n\r");
      try {
        setBold(bold);
        setTextSize(fontSize, fontSize);
        String text = rowsToPrint[i].toString();
        byte[] arr = text.getBytes("GBK");
        CAPrintCommon.writeBuffer(printerConnector, arr);
      } catch (UnsupportedEncodingException e) {
        promise.reject("Error");
      }
    }
    promise.resolve(null);
  }

  @Override
  public void startPrint(Promise promise) {
    promise.resolve(null);
  }

  @Override
  public void disconnect() {
    printerConnector.disconnectPrinter();
  }

  LifecycleEventListener lifecycleEventListener = new LifecycleEventListener() {
    @Override
    public void onHostResume() {

    }

    @Override
    public void onHostPause() {

    }

    @Override
    public void onHostDestroy() {
      printerConnector.unregisterConnectionStatusChangedEvent(m_connectionStatusChangedEvent);
    }
  };

  // private void sendEvent(int eventName, @Nullable WritableMap params) {
  //   ReactApplicationContext fReactContext = (ReactApplicationContext) reactContext;
  //   fReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(Integer.toString(eventName),
  //       params);
  // }

  // @SuppressLint("HandlerLeak")
  // private final Handler mHandler = new Handler() {
  //   @Override
  //   public void handleMessage(Message msg) {
  //     WritableMap params;
  //     switch (msg.what) {
  //       case MESSAGE_STATE_CHANGE:
  //         switch (msg.arg1) {
  //           case BluetoothService.STATE_CONNECTED:
  //             connPromise.resolve(null);
  //             break;
  //           case BluetoothService.STATE_CONNECTING:
  //             break;
  //           case BluetoothService.STATE_LISTEN:
  //           case BluetoothService.STATE_NONE:
  //             break;
  //         }
  //         break;
  //       case MESSAGE_WRITE:

  //         break;
  //       case MESSAGE_READ:

  //         break;
  //       case MESSAGE_DEVICE_NAME:
  //         // save the connected device's name
  //         mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
  //         params = Arguments.createMap();
  //         params.putString(DEVICE_NAME, mConnectedDeviceName);
  //         sendEvent(MESSAGE_DEVICE_NAME, params);
  //         // Toast.makeText(reactContext, "Connected to " + mConnectedDeviceName,
  //         // Toast.LENGTH_SHORT).show();
  //         break;
  //       case MESSAGE_TOAST:
  //         params = Arguments.createMap();
  //         params.putString(TOAST, msg.getData().getString(TOAST));
  //         sendEvent(MESSAGE_TOAST, params);
  //         // Toast.makeText(reactContext, msg.getData().getString(TOAST),
  //         // Toast.LENGTH_SHORT).show();
  //         break;
  //       case MESSAGE_CONNECTION_LOST: // 蓝牙已断开连接
  //         sendEvent(MESSAGE_CONNECTION_LOST, null);
  //         // Toast.makeText(reactContext, "Device connection was lost",
  //         // Toast.LENGTH_SHORT).show();
  //         break;
  //       case MESSAGE_UNABLE_CONNECT: // 无法连接设备
  //         sendEvent(MESSAGE_UNABLE_CONNECT, null);
  //         connPromise.reject(new Exception("Unable to connect device"));
  //         Toast.makeText(reactContext, "Unable to connect device", Toast.LENGTH_SHORT).show();
  //         break;
  //     }
  //   }
  // };

  private static boolean isChinese(char c) {
    Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
    if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
        || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
        || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
        || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
        || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
        || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) {
      return true;
    }
    return false;
  }

  private static class ColumnSplitedString {
    private int shorter;
    private String str;

    public ColumnSplitedString(int shorter, String str) {
      this.shorter = shorter;
      this.str = str;
    }

    public int getShorter() {
      return shorter;
    }

    public String getStr() {
      return str;
    }
  }
}