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 android.app.Activity;

import com.homansystem.innerprinter.PrinterManager;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class HarvardPrinter implements MyPrinter, PrinterManager.PrinterManagerListener {

  private String TAG = "HARVARDPRINTER";
  private Context reactContext;
  private PrinterEventListener listener;
  private PrinterManager mPrinterManager;
  private Activity mActivity;
  private Promise connPromise = null;
  final Map<String, Integer> printAttrMap = new HashMap<String, Integer>();
  final Map<String, Integer> printColumnStrMap = new HashMap<String, Integer>();
  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 int fontSize = 24;
  public int typeFace = 0;
  public int align = 1;

  public HarvardPrinter(final Context context, final PrinterEventListener eventListener, Activity mActivity) {
    reactContext = context;
    listener = eventListener;
    this.mActivity = mActivity;
    ReactApplicationContext fReactContext = (ReactApplicationContext) reactContext;
    mPrinterManager = new PrinterManager(mActivity, this, fReactContext);
    mPrinterManager.onPrinterStart();
  }

  public void setTextSize(int size, int size2) {
    fontSize = size;
  }

  public void setBold(boolean bold) {
    int mybold = bold ? 1 : 0;
    typeFace = mybold;
  }

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

  @Override
  public void connect(String address, Promise promise) {
    Log.d(TAG,
        String.format("Connecting to printer with params. Debug: %b", this.isDebug));
    promise.resolve("Success");
  }

  @Override
  public void hasCutter(Boolean value) {
    return;
  }

  @Override
  public void writeTextWProps(String text, @Nullable ReadableMap property) {
    String patternStr = "----------------";
    Pattern pattern = Pattern.compile(patternStr);
    Matcher matcher = pattern.matcher(text);
    boolean matchFound  = matcher.matches();
    String printText;
    if (matchFound) {
      printText = text.substring(1).replaceAll("\r","").replaceFirst("\n","");
    } else {
      printText = text.replaceAll("\r","").replaceFirst("\n","");
    }
    int mfontSize = property.hasKey("fontSize") ? property.getInt("fontSize") * 24 : 24;
    boolean bold = property.hasKey("bold") ? property.getBoolean("bold") : false;
    int myBold = bold ? 1 : 0;
    printAttrMap.put(mPrinterManager.KEY_ALIGN, align);
    printAttrMap.put(mPrinterManager.KEY_LINESPACE, 0);
    printAttrMap.put(mPrinterManager.KEY_TEXTSIZE, mfontSize);
    printAttrMap.put(mPrinterManager.KEY_TYPEFACE, myBold);
    printAttrMap.put(mPrinterManager.KEY_MARGINLEFT, 0);
    printAttrMap.put(mPrinterManager.KEY_MARGINRIGHT, 0);
    mPrinterManager.printTextWithAttributes(printText, printAttrMap);
    // ThreadPoolManager.getInstance().executeTask(new Runnable() {
    // @Override
    // public void run() {
    // try {
    // } catch (Exception e) {
    // e.printStackTrace();
    // }
    // }
    // });
  }

  @Override
  public void writeText(String text) {
    String patternStr = "----------------";
    Pattern pattern = Pattern.compile(patternStr);
    Matcher matcher = pattern.matcher(text);
    boolean matchFound  = matcher.matches();
    String printText;
    if (matchFound) {
      printText = text.substring(1).replaceAll("\r","").replaceFirst("\n","");
    } else {
      printText = text.replaceAll("\r","").replaceFirst("\n","");
    }
    setTextSize(24, 0);
    printAttrMap.put(mPrinterManager.KEY_ALIGN, align);
    printAttrMap.put(mPrinterManager.KEY_LINESPACE, 0);
    printAttrMap.put(mPrinterManager.KEY_TEXTSIZE, fontSize);
    printAttrMap.put(mPrinterManager.KEY_TYPEFACE, typeFace);
    printAttrMap.put(mPrinterManager.KEY_MARGINLEFT, 0);
    printAttrMap.put(mPrinterManager.KEY_MARGINRIGHT, 0);
    mPrinterManager.printTextWithAttributes(printText, printAttrMap);
    // ThreadPoolManager.getInstance().executeTask(new Runnable() {
    // @Override
    // public void run() {
    // try {
    // } catch (Exception e) {
    // e.printStackTrace();
    // }
    // }
    // });
  }

  @Override
  public void addTextAlign(int align) {
    int mAlign = align;
    if (align == 0)
      mAlign = 1;
    if (align == 1)
      mAlign = 0;
    if (align == 2)
      mAlign = 2;
    this.align = mAlign;
  }

  @Override
  public void writeFeed(int length) {
    int jumlahRow = (int) Math.ceil(length / 24);
    for (int i = 0; i < jumlahRow - 1; i++) {
      mPrinterManager.printText("\n");
    }
    // ThreadPoolManager.getInstance().executeTask(new Runnable() {
    // @Override
    // public void run() {
    // try {
    // } catch (Exception e) {
    // e.printStackTrace();
    // }
    // }
    // });
  }

  @Override
  public void writeCut() {
    return;
  }

  @Override
  public void writeImage(Bitmap mBitmap, int width) {
    int nMode = 0;
    if (mBitmap != null) {
      final Map<String, Integer> map = new HashMap<String, Integer>();
      map.put(PrinterManager.KEY_ALIGN, align);
      map.put(PrinterManager.KEY_MARGINLEFT, 0);
      map.put(PrinterManager.KEY_MARGINRIGHT, 0);
      mPrinterManager.printBitmap(mBitmap, map);
      // ThreadPoolManager.getInstance().executeTask(new Runnable() {
      // @Override
      // public void run() {
      // try {
      // } catch (Exception e) {
      // e.printStackTrace();
      // }
      // }
      // });
    }
  }

  @Override
  public void writePulse() {
    return;
  }

  @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();
        Log.d(TAG, "column text: " + text);
        mPrinterManager.printText(text);
        // byte[] arr = text.getBytes("GBK");
        // buffer.write(arr, 0, arr.length);
      } catch (Exception e) {
        promise.reject("Error");
      }
    }
    promise.resolve(null);
  }

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

  @Override
  public void disconnect() {
    // try {
    // mPrinterManager.onPrinterStop();
    // } catch (Exception e) {
    // e.printStackTrace();
    // }
  }

  @Override
  public void writeBeep(int numOfBeep, int buzzingTime) {
    return;
  }

  @Override
  public void onServiceConnected() {
    mPrinterManager.printerInit();
    listener.onInitializeSuccess("Success Connected");
    Log.d(TAG, "Printer Terhubung");
    // updateLayout(true);
  }

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

  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;
    }
  }
}