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 com.facebook.react.bridge.ReactApplicationContext;

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.*;

public class IPPrinter implements MyPrinter {

  private String TAG = "IPOSIPPRINTER";
  private WifiCommunication wfComm = null;
  private Context reactContext;
  private PrinterEventListener listener;
  private static final int WFPRINTER_REVMSG = 0x06;
  private Promise connPromise = null;
  revMsgThread revThred = null;
  int connFlag = 0;
  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;
  private Boolean isDebug = false;

  public static final int MESSAGE_DISCONNECTED = 0;
  public static final int MESSAGE_FAILED = 1;
  public static final int MESSAGE_NOPAPER = 2;

  public IPPrinter(final Context context, final PrinterEventListener eventListener) {
    reactContext = context;
    listener = eventListener;
    wfComm = new WifiCommunication(mHandler);
    buffer = new ByteArrayOutputStream();
    buffer.reset();
    byte[] arr = PrinterCommand.POS_Set_PrtInit();
    buffer.write(arr, 0, arr.length);
    listener.onInitializeSuccess("Success Connected");
  }

  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 setDebug(Boolean isDebug){
    this.isDebug = isDebug;
  }

  @Override
  public void connect(String ipAddress, Promise promise){
    this.connPromise = promise;
    Log.d("IP Printer",String.format("Connecting to printer with params. Debug: %b with IP Address: %s",this.isDebug, ipAddress));
    if(this.isDebug) promise.resolve("Success");
    else this.wfComm.initSocket(ipAddress,9100);
  }

  @Override 
  public void hasCutter(Boolean value) {
    return;
  }

  @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, 80, 80);
    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();
      wfComm.sndByte(buf);
      buffer = null;
    }else{
      Log.d("IPPrinter","TRY TO CONVERT");
      try{
        String buf = buffer.toString("GBK");
        Log.d("IPPrinter", buf);
      }catch(UnsupportedEncodingException e){
        Log.e("IPPrinter", e.getMessage());
      }
      buffer = null;
    }
    promise.resolve(null);
  }

  @Override
  public void disconnect() {
    wfComm.close();
  }

  @Override
  public void writeBeep(int numOfBeep, int buzzingTime) {
    byte[] arr = PrinterCommand.POS_Set_Beep(numOfBeep, buzzingTime);
    buffer.write(arr, 0, arr.length);
  }

  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(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
      case WifiCommunication.WFPRINTER_CONNECTED:
        connFlag = 0;
        // Toast.makeText(reactContext, "Connect the WIFI-printer successful",
        // Toast.LENGTH_SHORT).show();
        revThred = new revMsgThread();
        revThred.start();
        connPromise.resolve("Success");
        break;
      case WifiCommunication.WFPRINTER_DISCONNECTED:
        // Toast.makeText(reactContext, "Disconnect the WIFI-printer successful",
        // Toast.LENGTH_SHORT).show();
        sendEvent(MESSAGE_DISCONNECTED, null);
        break;
      case WifiCommunication.SEND_FAILED:
        connFlag = 0;
        Toast.makeText(reactContext, "Send Data Failed,please reconnect", Toast.LENGTH_SHORT).show();
        sendEvent(MESSAGE_FAILED, null);
        break;
      case WifiCommunication.WFPRINTER_CONNECTEDERR:
        connFlag = 0;
        Toast.makeText(reactContext, "Connect the WIFI-printer error", Toast.LENGTH_SHORT).show();
        connPromise.reject("Failed to connect to printer");
        break;
      case WFPRINTER_REVMSG:
        byte revData = (byte) Integer.parseInt(msg.obj.toString());
        if (((revData >> 6) & 0x01) == 0x01)
          Toast.makeText(reactContext, "The printer has no paper", Toast.LENGTH_SHORT).show();
        sendEvent(MESSAGE_NOPAPER, null);
        break;
      default:
        break;
      }
    }
  };

  class revMsgThread extends Thread {
    @Override
    public void run() {
      try {
        Message msg = new Message();
        int revData;
        while (true) {
          revData = wfComm.revByte();
          if (revData != -1) {
            msg = mHandler.obtainMessage(WFPRINTER_REVMSG);
            msg.obj = revData;
            mHandler.sendMessage(msg);
          }
          Thread.sleep(20);
        }
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  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;
    }
  }
}