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.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;

import android.annotation.SuppressLint;
import javax.annotation.Nullable;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringReader;
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.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import com.zj.usbsdk.UsbController;
import android.hardware.usb.UsbDevice;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothAdapter;
import android.app.Activity;

public class USBPrinter implements MyPrinter {

  private String TAG = "IPOSUSBPRINTER";
  private Context reactContext;
  private PrinterEventListener listener;
  private Promise connPromise = null;
  private ByteArrayOutputStream buffer = null;
  int nMode = 0;
  public static final int WIDTH_58 = 384;
  public static final int WIDTH_80 = 576;
  private int deviceWidth = WIDTH_80;

  static UsbController usbCtrl = null;
  static UsbDevice dev = null;
  private int[][] u_infor;

  private Boolean isDebug = false;

  // Message types sent from the BluetoothService Handler
  public USBPrinter(final Context context, final PrinterEventListener eventListener, Activity mActivity) {
    reactContext = context;
    listener = eventListener;
    usbCtrl = new UsbController(mActivity, mHandler);
    u_infor = new int[21][2];
    u_infor[0][0] = 0x1CBE;
    u_infor[0][1] = 0x0003;
    u_infor[1][0] = 0x1CB0;
    u_infor[1][1] = 0x0003;
    u_infor[2][0] = 0x0483;
    u_infor[2][1] = 0x5740;
    u_infor[3][0] = 0x0493;
    u_infor[3][1] = 0x8760;
    u_infor[4][0] = 0x0416;
    u_infor[4][1] = 0x5011;
    u_infor[5][0] = 0x0416;
    u_infor[5][1] = 0xAABB;
    u_infor[6][0] = 0x1659;
    u_infor[6][1] = 0x8965;
    u_infor[7][0] = 0x0483;
    u_infor[7][1] = 0x5741;
    u_infor[8][0] = 0x04B8;
    u_infor[8][1] = 0x0202;
    u_infor[9][0] = 0x04B8;
    u_infor[9][1] = 0x0E27;
    u_infor[10][0] = 0x4B43;
    u_infor[10][1] = 0x3538;
    u_infor[11][0] = 0x0FE6;
    u_infor[11][1] = 0x811E;
    u_infor[12][0] = 0x04B8;
    u_infor[12][1] = 0x0E11;
    u_infor[13][0] = 0x0033;
    u_infor[13][1] = 0x3107;
    u_infor[14][0] = 0x4B43;
    u_infor[14][1] = 0x3830;
    u_infor[15][0] = 0x0483;
    u_infor[15][1] = 0x070B;
    u_infor[16][0] = 0x0519;
    u_infor[16][1] = 0x000B;
    u_infor[17][0] = 0x1504;
    u_infor[17][1] = 0x002B;
    u_infor[18][0] = 0x04B8;
    u_infor[18][1] = 0x0202;
    u_infor[19][0] = 0x1504;
    u_infor[19][1] = 0x0059;
    u_infor[20][0] = 0x28E9;
    u_infor[20][1] = 0x0289;
    buffer = new ByteArrayOutputStream();
    buffer.reset();
    byte[] arr = PrinterCommand.POS_Set_PrtInit();
    buffer.write(arr, 0, arr.length);
    listener.onInitializeSuccess("Success Initialize");
  }

  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);
    buffer.write(arr, 0, arr.length);
  }

  public void setBold(boolean bold) {
    int mybold = bold ? 1 : 0;
    byte[] arr = PrinterCommand.POS_Set_Bold(mybold);
    buffer.write(arr, 0, arr.length);
  }

  @Override
  public void hasCutter(Boolean value) {
    return;
  }

  @Override
  public void setDebug(Boolean isDebug) {
    this.isDebug = isDebug;
  }

  @Override
  public void connect(String address, Promise promise) {
    // connPromise = promise;
    if (!this.isDebug) {
      usbCtrl.close();
      int i = 0;
      for (i = 0; i < 21; i++) {
        dev = usbCtrl.getDev(u_infor[i][0], u_infor[i][1]);
        if (dev != null)
          break;
      }
      if (dev != null) {
        if (!(usbCtrl.isHasPermission(dev))) {
          usbCtrl.getPermission(dev);
        }
        promise.resolve(null);
      } else {
        promise.reject(new Exception("USB Printer not found"));
      }
    } else
      promise.resolve(null);
  }

  @Override
  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");
      buffer.write(arr, 0, arr.length);
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
  }

  @Override
  public void writeText(String text) {
    try {
      setBold(false);
      setTextSize(1, 1);
      byte[] arr = text.getBytes("GBK");
      buffer.write(arr, 0, arr.length);
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
  }

  @Override
  public void addTextAlign(int align) {
    byte[] arr = Command.ESC_Align;
    arr[2] = (byte) align;
    buffer.write(arr, 0, arr.length);
  }

  @Override
  public void writeFeed(int length) {
    byte[] arr = PrinterCommand.POS_Set_PrtAndFeedPaper(length);
    buffer.write(arr, 0, arr.length);
  }

  @Override
  public void writeCut() {
    byte[] arr = PrinterCommand.POS_Set_Cut(0);
    buffer.write(arr, 0, arr.length);
  }

  @Override
  public void writeImage(Bitmap mBitmap, int width) {
    int nMode = 0;
    if (mBitmap != null) {
      byte[] data = PrintPicture.POS_PrintBMP(mBitmap, width, nMode);
      buffer.write(data, 0, data.length);
    }
  }

  @Override
  public void writePulse() {
    byte[] arr = PrinterCommand.POS_Set_Cashbox(0, 20, 20);
    buffer.write(arr, 0, arr.length);
  }

  @Override
  public void printColumn(ReadableArray columnWidths, ReadableArray columnAligns, ReadableArray columnTexts,
      @Nullable ReadableMap options, final Promise promise) {
    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");
        buffer.write(arr, 0, arr.length);
      } catch (UnsupportedEncodingException e) {
        promise.reject("Error");
      }
    }
    promise.resolve(null);
  }

  @Override
  public void startPrint(Promise promise) {
    if (!this.isDebug) {
      byte[] buf = buffer.toByteArray();
      boolean retry = true;
      int retrial = 0;
      while (retry) {
        retrial++;
        if (retrial > 3)
          retry = false;
        try {
          ByteArrayInputStream bis = new ByteArrayInputStream(buffer.toByteArray());
          int length = 1024;
          int n = 0;
          byte[] tmpBuf = new byte[length];
          while ((n = bis.read(tmpBuf)) > 0) {
            usbCtrl.sendByte(tmpBuf, dev);
            tmpBuf = new byte[length];
          }
          usbCtrl.sendByte(Command.LF, dev);
          retry = false;
        } catch (NullPointerException e) {
          e.printStackTrace();
          try {
            Thread.sleep(1000);
          } catch (InterruptedException f) {
            f.printStackTrace();
          }
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      buffer = null;
      promise.resolve(null);
    } else {
      try {
        String buf = buffer.toString("GBK");
        Log.d("USBPrinter", buf);
      } catch (UnsupportedEncodingException e) {
        Log.e("USBPrinter", e.getMessage());
      }
      promise.resolve(null);
    }
  }

  public void writeBeep(int numOfBeep, int buzzingTime) {
    byte[] arr = PrinterCommand.POS_Set_Beep(numOfBeep, buzzingTime);
    buffer.write(arr, 0, arr.length);
  }

  @Override
  public void disconnect() {
    usbCtrl.close();
  }

  @SuppressLint("HandlerLeak")
  private final Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
        case UsbController.USB_CONNECTED:
          // connPromise.resolve(null);
          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;
    }
  }
}