package com.joe.print.mvp.print.service;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.Nullable;

import com.epson.epos2.printer.Printer;
import com.epson.epos2.printer.PrinterStatusInfo;
import com.epson.epos2.printer.ReceiveListener;
import com.gingersoft.gsa.cloud.common.bean.BaseResult;
import com.gingersoft.gsa.cloud.common.constans.HttpsConstans;
import com.gingersoft.gsa.cloud.common.constans.PrintConstans;
import com.gingersoft.gsa.cloud.common.core.restaurant.RestaurantInfoManager;
import com.gingersoft.gsa.cloud.common.core.user.UserContext;
import com.gingersoft.gsa.cloud.common.logan.LoganManager;
import com.gingersoft.gsa.cloud.common.printer.AidlUtil;
import com.gingersoft.gsa.cloud.common.printer.plugins.PrinterFlowProxy;
import com.gingersoft.gsa.cloud.common.utils.JsonUtils;
import com.gingersoft.gsa.cloud.common.utils.gson.GsonUtils;
import com.gingersoft.gsa.cloud.common.utils.log.LogUtil;
import com.gingersoft.gsa.cloud.common.utils.okhttpUtils.OkHttp3Utils;
import com.gingersoft.gsa.cloud.common.utils.other.TextUtil;
import com.gingersoft.gsa.cloud.common.utils.threadPool.ThreadPoolManager;
import com.gingersoft.gsa.cloud.common.utils.time.TimeUtils;
import com.gingersoft.gsa.cloud.common.utils.toast.ToastUtils;
import com.gingersoft.gsa.cloud.database.bean.PrintCurrencyBean;
import com.gingersoft.gsa.cloud.database.bean.PrinterDeviceBean;
import com.gingersoft.gsa.cloud.database.utils.PrinterDeviceDaoUtils;
import com.gingersoft.gsa.cloud.print.PrintExecutor;
import com.gingersoft.gsa.cloud.print.PrintSocketHolder;
import com.gingersoft.gsa.cloud.print.PrinterWriter58mm;
import com.gingersoft.gsa.cloud.print.bean.PrjBean;
import com.gingersoft.gsa.cloud.print.bean.UpdateBean;
import com.hyweb.n5.lib.constant.PrinterConstant;
import com.hyweb.n5.lib.util.PrinterUtil;
import com.hyweb.n5.server.aidl.IOnPrintCallback;
import com.joe.print.mvp.print.EpsonPrint;
import com.joe.print.mvp.print.PrintPrjKitchen;
import com.joe.print.mvp.print.common.PrinterFinderCallback;
import com.joe.print.mvp.print.common.SendResultCode;
import com.joe.print.mvp.print.maker.PrjPrintMaker;
import com.joe.print.mvp.print.usb.EscCommand;
import com.joe.print.mvp.print.usb.UsbPrint;
import com.joe.print.mvp.print.usb.UsbPrinter;
import com.joe.print.mvp.print.usb.UsbPrinterFinder;
import com.joe.print.mvp.print.utils.MyPrintUtils;
import com.sunmi.peripheral.printer.InnerResultCallbcak;
import com.xuexiang.rxutil2.rxjava.RxJavaUtils;

import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import jcifs.smb.SmbFile;
import jcifs.smb.SmbFileOutputStream;
import okhttp3.MediaType;
import okhttp3.RequestBody;

import static com.gingersoft.gsa.cloud.database.bean.PrinterDeviceBean.PRINT_IP;
import static com.gingersoft.gsa.cloud.database.bean.PrinterDeviceBean.PRINT_LOCAL;
import static com.gingersoft.gsa.cloud.database.bean.PrinterDeviceBean.PRINT_PRJ_PC;
import static com.gingersoft.gsa.cloud.database.bean.PrinterDeviceBean.PRINT_USB;


/**
 * 在用戶登錄成功後，打開打印service，每隔3~5秒請求一次。請求到了數據就進行打印邏輯
 */
public class PrjService extends Service implements ReceiveListener {

    private Disposable pollDisposable;
    private Disposable wakeDisposable;
    private Context mContext;
    private String TAG = "Prj";

    @Override
    public void onCreate() {
        super.onCreate();
        LoganManager.w_printer(TAG, "PrjService onCreate");
        mContext = this;
        initUsbPrint();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mContext = this;
        LoganManager.w_printer(TAG, "PrjService onStartCommand");
        //開始請求
        startGetPrjInfo();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        printerFinder.unregisterReceiver();
        PrintSocketHolder.getInstance().closeSocket();
        //關閉針式連接
        EpsonPrint.getInstance().disconnectPrinter();
        EpsonPrint.getInstance().finalizeObject();
        LoganManager.w_printer(TAG, "PrjService onDestroy");
    }

    private UsbPrinterFinder printerFinder;
    private List<UsbPrinter> mUsbPrinters;

    public void initUsbPrint() {
        printerFinder = new UsbPrinterFinder(mContext, new PrinterFinderCallback<UsbPrinter>() {
            @Override
            public void onStart() {

            }

            @Override
            public void onFound(UsbPrinter usbPrinter) {

            }

            @Override
            public void onFinished(List<UsbPrinter> usbPrinters) {
                mUsbPrinters = usbPrinters;
            }
        });
        //開啟監聽USB連接
        printerFinder.startFinder();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 開啟輪詢查詢prj數據
     */
    private void startGetPrjInfo() {
        cancel(wakeDisposable);
        cancel(pollDisposable);
        if (!UserContext.newInstance().isLogin()) {
            LoganManager.w_printer(TAG, "PrjService 用户未登录!");
            return;
        }
        pollDisposable = RxJavaUtils.polling(10, 30, TimeUnit.SECONDS)
                .subscribe(aLong -> {
                    LoganManager.w_printer(TAG, "獲取Prj數據-->");
                    //輪詢時，關閉打印機連接，避免一直佔用打印機socket連接
                    EpsonPrint.getInstance().disconnectPrinter();
                    EpsonPrint.getInstance().finalizeObject();
                    //獲取prj數據
                    getPrjInfo();
                });
    }

    /**
     * 請求prj數據
     */
    private void getPrjInfo() {
        OkHttp3Utils.get(HttpsConstans.ROOT_SERVER_ADDRESS_FORMAL + "printerRecording/get?restaurantId=" + RestaurantInfoManager.newInstance().getRestaurantId())
                .subscribeOn(Schedulers.io())//切换到io线程進行網絡請求
                .observeOn(Schedulers.io())//切換到io线程處理請求結果
                .subscribe(new Observer<String>() {

                    @Override
                    public void onSubscribe(Disposable d) {
                        LoganManager.w_printer(TAG, "獲取Prj數據 Disposable");
                    }

                    @Override
                    public void onNext(String prjInfo) {
                        LoganManager.w_printer(TAG, "獲取Prj數據 onNext: " + prjInfo);
                        //請求到數據，停止輪詢，開始打印，在打印完之後再重新開始輪詢
                        newPrint(prjInfo);
                        //開啟另一個定時，三十秒之後自動請求，避免上面的打印成功或失敗時沒有回調。
                        cancel(wakeDisposable);
                        wakeDisposable = RxJavaUtils.delay(30, TimeUnit.SECONDS)
                                .subscribe(aLong -> {
                                    LoganManager.w_printer(TAG, "輪詢獲取Prj數據-->");
                                    startGetPrjInfo();
                                });
                    }

                    @Override
                    public void onError(Throwable e) {
                        LoganManager.w_printer(TAG, "獲取Prj數據 onError：" + e.getMessage());
                    }

                    @Override
                    public void onComplete() {
                        LoganManager.w_printer(TAG, "獲取Prj數據 onComplete");
                    }
                });
    }

    /**
     * 取消輪詢
     */
    private void cancel(Disposable disposable) {
        if (disposable != null) {
            disposable.dispose();
        }
    }

    private String prjJson = "{\"success\":true,\"sysTime\":1614581072400,\"data\":{\"K3\":[{\"id\":44419,\"printerDeviceId\":149,\"status\":2,\"orderNo\":\"233212281076117397\",\"orderTime\":2021,\"sender\":\"\",\"sendTime\":\"盡快送達 預計 15:49-16:04\",\"person\":0,\"number\":1,\"orderDetailsTime\":\"Mar 1, 2021 2:43:38 PM\",\"orderDetailsId\":12895,\"productName\":\"鬥獸場大什扒\",\"productName2\":\"\",\"productName3\":\"\",\"parentId\":0,\"type\":3,\"createTime\":1614581044514,\"productId\":27921,\"requests\":1,\"actualPrinterDeviceId\":149,\"flyPrinterDeviceId\":150,\"takeFoodCode\":\"1074\",\"billNo\":\"\",\"orderType\":2,\"userName\":\"wyh\",\"isFirstSendOrder\":1,\"quantity\":49,\"memberName\":\"\"}],\"K4\":[{\"id\":44420,\"printerDeviceId\":150,\"status\":2,\"orderNo\":\"233212281076117397\",\"orderTime\":2021,\"sender\":\"\",\"sendTime\":\"盡快送達 預計 15:49-16:04\",\"person\":0,\"number\":1,\"orderDetailsTime\":\"Mar 1, 2021 2:43:38 PM\",\"orderDetailsId\":12896,\"productName\":\"芝士焗龍蝦\",\"productName2\":\"\",\"productName3\":\"\",\"parentId\":0,\"type\":3,\"createTime\":1614581044521,\"productId\":27926,\"requests\":1,\"actualPrinterDeviceId\":150,\"takeFoodCode\":\"1074\",\"billNo\":\"\",\"orderType\":2,\"userName\":\"wyh\",\"isFirstSendOrder\":1,\"quantity\":50,\"memberName\":\"\"}]}}\n";

    private void newPrint(String json) {
        Map<String, List<PrjBean>> listMap = new HashMap<>();
        int totalPrj = 0;
        BaseResult prjBean = JsonUtils.parseObject(json, BaseResult.class);
        if (prjBean == null || prjBean.getData() == null) {
            LoganManager.w_printer(TAG, "newPrint  prjBean == null || prjBean.getData() == null ");
            return;
        }
        //有數據，取消輪詢，等待打印結束。
        cancel(pollDisposable);
        //第一步：解析PRJ數據，格式為 Map<廚房位置，需要打印的數據>
        try {
            JSONObject jsonObject = new JSONObject(GsonUtils.GsonString(prjBean.getData()));
            //通过迭代器获取这段json当中所有的key值
            Iterator keys = jsonObject.keys();
            //然后通过一个循环取出所有的key值
            while (keys.hasNext()) {
                String key = String.valueOf(keys.next());
                //最后就可以通过刚刚得到的key值去解析后面的json了
                JSONArray dataJson = (JSONArray) jsonObject.get(key);
                //解析出prj集合
                List<PrjBean> datas = JsonUtils.parseArray(dataJson.toString(), PrjBean.class);
                if (datas != null && datas.size() > 0) {
                    //打印位置和需要打印的數據
                    listMap.put(key, datas);
                    //計算prj總數
                    totalPrj = calculationPrjQuantity(datas);
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
            LoganManager.w_printer(TAG, "newPrint  JSONException: " + e.getMessage());
        }
        //設置每張prj的頁數
        setPrjIndex(listMap, totalPrj);
        //配置對應打印機
        foreachPrint(listMap, getPrinterDevices());
    }

    /**
     * 計算出prj總數
     *
     * @param prjBeans prj數據
     */
    private int calculationPrjQuantity(List<PrjBean> prjBeans) {
        int totalPrj = 0;
        //計算本次打印的prj總張數有沒有不需要切紙的
        boolean isHasNoCut = false;
        for (PrjBean bean : prjBeans) {
            if (bean.getStatus() == 2) {
                // 需要切紙，prj總數就+1
                totalPrj++;
            } else if (bean.getStatus() != PrjBean.PRJ_STATUS_FOLLOW_PARENT) {
                // 不切紙，並且不跟主項，在遍歷結束後，需要總數+1
                isHasNoCut = true;
            }
        }
        if (isHasNoCut) {
            // 有不需要切紙的食物，prj總數+1
            // +1的原因是，如果全是不切紙的，在遍歷時是不加1的，總數就為0，所以在遍歷結束後+1
            totalPrj++;
        }
        return totalPrj;
    }

    /**
     * 設置prj的頁數
     *
     * @param listMap  key為打印位置
     * @param totalPrj prj總數
     * @return 排好頁數的prj數據
     */
    private Map<String, List<PrjBean>> setPrjIndex(Map<String, List<PrjBean>> listMap, int totalPrj) {
        int currentIndex = 0;
        for (Map.Entry<String, List<PrjBean>> prjMap : listMap.entrySet()) {
            //上一個對象是否切紙
            boolean lastIsCute = false;
            for (PrjBean bean : prjMap.getValue()) {
                bean.setTotalPrj(totalPrj);
                if (bean.getStatus() == PrjBean.PRJ_STATUS_CUT_PAPER) {
                    //要切紙，紙張數+1
                    currentIndex++;
                    lastIsCute = true;
                } else if (bean.getStatus() != PrjBean.PRJ_STATUS_FOLLOW_PARENT) {
                    // 不跟主項的
                    if (currentIndex == 0) {
                        currentIndex = 1;
                    } else if (lastIsCute) {
                        //如果上一張切紙，那這一張下標就要+1
                        currentIndex++;
                    }
                    lastIsCute = false;
                }
                bean.setCurrentIndex(currentIndex);
            }
            currentIndex++;
        }
        return listMap;
    }

    private void foreachPrint(Map<String, List<PrjBean>> listMap, List<PrinterDeviceBean> printerDeviceBeans) {
        //雙重遍歷可以以後優化
        //第二步：遍歷 Map<廚房位置，需要打印的數據>，通過廚房位置找到對應的打印機，並且通過數據拿到對應的通用配置
        for (Map.Entry<String, List<PrjBean>> prjMap : listMap.entrySet()) {
            boolean isFindDevice = false;
            for (PrinterDeviceBean deviceBean : printerDeviceBeans) {
                //遍歷得到當前打印機，如果沒找到打印機，就不打印
                if (prjMap.getKey().toLowerCase().equals(deviceBean.getName().toLowerCase())) {
                    isFindDevice = true;
                    //如果PRJ數據的廚房位置和打印機名稱相同，則就是這台打印機打印
                    //通過需要打印的數據，得到通用配置
                    PrintCurrencyBean printCurrencyBean = getPrintCurrencyBean(prjMap.getValue());
                    //將通用配置設置給了對應的打印機對象後，得到新的打印機對象
                    PrinterDeviceBean configPrinterDeviceBean = MyPrintUtils.configPrinterProperties(printCurrencyBean, deviceBean);
                    // 判斷打印機的類型，調用不同的打印方式，
                    // 這裡已經拿到這台打印機需要打印的所有數據，為prjMp.getValue()，
                    // 生成對應的打印數據，除了針式打印機，其他都生成bitmap
                    generatePrintData(prjMap.getKey(), prjMap.getValue(), configPrinterDeviceBean);
                    break;
                }
            }
            if (!isFindDevice) {
                //用戶沒有這個打印機，打印失敗，返回後台原因
                updatePrjState(UpdateBean.FAIL_PRINT_BY_NOT_FIND_DEVICE, getPrintIdsByPrjData(prjMap.getValue()));
            }
        }
    }

    /**
     * 獲取打印設備
     *
     * @return 打印機列表
     */
    private List<PrinterDeviceBean> getPrinterDevices() {
        //讀取打印機和通用配置，可以優化
        //獲取所有打印機
        PrinterDeviceDaoUtils printerDeviceDaoUtils = new PrinterDeviceDaoUtils(this);
        LoganManager.w_printer(TAG, "获取打印設備信息");
        return printerDeviceDaoUtils.queryAllPrinterDeviceBean();
    }

    /**
     * 獲取通用打印配置
     *
     * @param beans prj數據
     * @return 通用配置
     */
    private PrintCurrencyBean getPrintCurrencyBean(List<PrjBean> beans) {
        PrintCurrencyBean printCurrencyBean = null;
        if (beans != null && beans.size() > 0) {
            if (beans.get(0).getOrderType() == 1 || beans.get(0).getOrderType() == 3) {
                //堂食，skyorder
                printCurrencyBean = MyPrintUtils.getPrintCurrencyBeanByType(mContext, 1);
            } else {
                //外賣
                printCurrencyBean = MyPrintUtils.getPrintCurrencyBeanByType(mContext, 2);
            }
        }
        return printCurrencyBean;
    }

    /**
     * 生成用於打印的prj的Bitmap
     */
    private void generatePrintData(String key, List<PrjBean> beans, PrinterDeviceBean printerDeviceBean) {

        int orderType = 1;
        if (beans != null && beans.size() > 0) {
            orderType = beans.get(0).getOrderType();
        } else {
            PrinterFlowProxy.newInstance().dispatchPrintError(new Exception("prj解析數據為空：" + GsonUtils.GsonString(beans)), UpdateBean.FAIL_PRINT_BY_NOT_PRINT_INFO);
            return;
        }
        PrinterFlowProxy.newInstance().dispatchPrinterDataBefore(orderType, PrintConstans.PRINT_KITCHEN, GsonUtils.GsonString(beans), GsonUtils.GsonString(printerDeviceBean));

        if (isPinPrinter(printerDeviceBean) && printerDeviceBean.getPrinterDeviceType() == PRINT_IP) {
            //針式打印
            stylusPrinting(key, beans, printerDeviceBean);
        } else if (printerDeviceBean.getPrinterDeviceType() == PRINT_PRJ_PC) {
            //prj模式，生成文件到共享電腦上
            prjToPc(beans, printerDeviceBean);
        } else if (printerDeviceBean.getPrinterDeviceType() == PRINT_LOCAL && PrintConstans.PRINT_MODEL_WISEPOS.contains(Build.MODEL)) {
            //本機打印並且是BBPOS

        } else {
            List<Map<String, Bitmap>> bitmapMaps = generatePrintMaps(key, beans, printerDeviceBean);
            switch (printerDeviceBean.getPrinterDeviceType()) {
                case PRINT_IP:
                    LoganManager.w_printer(TAG, "開始IP打印-->");
                    //IP打印
                    ipPrint(printerDeviceBean, bitmapMaps);
                    break;
                case PRINT_LOCAL:
                    //本地N5或Sunmi打印
                    locationPrint(bitmapMaps);
                    break;
                case PRINT_USB:
                    LoganManager.w_printer(TAG, "開始USB打印-->");
                    //USB打印
                    usbPrint(bitmapMaps);
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * PRJ模式，生成PRJ文件到共享的電腦上
     *
     * @param beans             prj數據
     * @param printerDeviceBean 打印設備
     */
    private void prjToPc(List<PrjBean> beans, PrinterDeviceBean printerDeviceBean) {
        //PRJ模式，生成PRJ文件到共享的電腦上
        ThreadPoolManager.getInstence().putExecutableTasks(() -> {
            List<PrjBean> noCutList = new ArrayList<>();
            List<PrjBean> cutList = new ArrayList<>();
            for (PrjBean bean : beans) {
                if (bean.getStatus() == 2) {
                    //需要切紙
                    cutList.add(bean);
                } else {
                    //不需要切紙
                    noCutList.add(bean);
                }
            }

            String rootPath = "smb://" + printerDeviceBean.getPrintPath() + "/";
            try {
                SmbFile smbFile = new SmbFile(rootPath);
                smbFile.connect();
                if (smbFile.isDirectory()) {
                    for (int i = 0; i < cutList.size(); i++) {
                        String newFilePath = rootPath + System.currentTimeMillis() + ".prj";
                        SmbFile createFile = new SmbFile(newFilePath);
                        createFile.createNewFile();
                        if (createFile.exists()) {
                            PrintStream ps = new PrintStream(new SmbFileOutputStream(createFile));

                            // 往文件里写入字符串
                            String prjInfo = "[printType]ptKitchen\n" +
                                    "[table]4\n" +
                                    "[date]落單時間:11-13 17:48\n" +
                                    "[waiter]n5 收銀員\n" +
                                    "[KP]K2烤爐\n" +
                                    "[PAX]人數:2\n" +
                                    "[line]\n" +
                                    "[food_1]1 包子(主項)\n" +
                                    "[food_1]1 包子(主項)\n" +
                                    "[table2]4\n";
                            ps.println(prjInfo);
                        }
                    }
                    if (noCutList.size() > 0) {
                        String newFilePath = rootPath + System.currentTimeMillis() + ".prj";
                        SmbFile createFile = new SmbFile(newFilePath);
                        createFile.createNewFile();
                        if (createFile.exists()) {
                            PrintStream ps = new PrintStream(new SmbFileOutputStream(createFile));
                            PrjBean bean = noCutList.get(0);

                            String tableName = "";
                            if (TextUtil.isEmptyOrNullOrUndefined(bean.getTableName())) {
                                if (bean.getOrderType() == 7) {
                                    tableName = "自取";
                                } else {
                                    tableName = "外賣";
                                }
                            } else {
                                tableName = bean.getTableName();
                            }
                            // 往文件里写入字符串
//                                    String prjInfo = "[printType]ptKitchen\n" +
//                                            "[table]4\n" +
//                                            "[date]落單時間:11-13 17:48\n" +
//                                            "[waiter]n5 收銀員\n" +
//                                            "[KP]K2烤爐\n" +
//                                            "[PAX]人數:2\n" +
//                                            "[line]\n" +
//                                            "[food_1]1 包子(主項)\n" +
//                                            "[food_1]1 包子(主項)\n" +
//                                            "[table2]4\n";
                            String prjInfo = "[printType]ptKitchen\n" +
                                    "[table]" + tableName + "\n" +
                                    "[date]落單時間:11-13 17:48\n" +
                                    "[waiter]n5 收銀員\n" +
                                    "[KP]K2烤爐\n" +
                                    "[PAX]人數:2\n" +
                                    "[line]\n" +
                                    "[food_1]1 包子(主項)\n" +
                                    "[food_1]1 包子(主項)\n" +
                                    "[table2]" + tableName + "\n";
                            ps.println(prjInfo);
                        }
                    }

                } else {
                    ToastUtils.show(this, "PRJ輸出路徑必須為文件夾");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 針式打印機
     *
     * @param key               廚房位置
     * @param beans             prj數據
     * @param printerDeviceBean 打印機
     */
    private void stylusPrinting(String key, List<PrjBean> beans, PrinterDeviceBean printerDeviceBean) {
        //針式打印機並且打印機類型為IP打印，生成獨特的格式
        List<Map<String, Bitmap>> bitmapMaps = generatePrintMaps(key, beans, printerDeviceBean);
        //將打印的圖片保存到手機中
        hookPrinterBitmap(bitmapMaps, beans);
        boolean initResult = EpsonPrint.getInstance().initializeObject(this, this, this::updatePrjState);
        if (!initResult) {
            //初始化打印機失敗
            LogUtil.e("eee", "prj初始化打印失敗" + getPrintIds(bitmapMaps));
            PrinterFlowProxy.newInstance().dispatchConnectionError(new ConnectException("初始化針式打印機失敗"));
            updatePrjState(UpdateBean.FAIL_EPSON_INIT, getPrintIds(bitmapMaps));
            return;
        }
        LogUtil.d("eee", "針式打印機本次PRJ數量：" + bitmapMaps.size());
        PrinterFlowProxy.newInstance().dispatchConnectionBefore("針式打印機" + bitmapMaps.size(), printerDeviceBean.getIp(), printerDeviceBean.getPort(), 0, 0);
        for (int i = 0; i < bitmapMaps.size(); i++) {
            for (Map.Entry<String, Bitmap> mapEntry : bitmapMaps.get(i).entrySet()) {
                EpsonPrint.getInstance().putPrintData(printerDeviceBean.getIp(), mapEntry.getValue(), mapEntry.getKey());
            }
        }
    }

    /**
     * ip設備打印
     */
    public void ipPrint(PrinterDeviceBean printerDeviceBean, List<Map<String, Bitmap>> bitmapMaps) {
        for (int i = 0; i < bitmapMaps.size(); i++) {
//            if (executor == null) {
//                executor = new PrintExecutor().setOnPrjPrintResultListener((errorCode, ids) -> {
//                    switch (errorCode) {
//                        case PrintSocketHolder.ERROR_0:
//                            //更新狀態
//                            updatePrjSuccess(ids);
//                            break;
//                        case PrintSocketHolder.ERROR_2:
//                            updatePrjFailure(ids);
//                            break;
//                    }
//                });
//            }
//            PrjPrintMaker maker = new PrjPrintMaker(bitmapMaps.get(i), printerDeviceBean.getIp(), printerDeviceBean.getPort());
//            executor.doPrinterRequestAsync(maker);
            PrintExecutor.getInstance().setOnPrjPrintResultListener(this::updatePrjState);
            PrjPrintMaker maker = new PrjPrintMaker(bitmapMaps.get(i), printerDeviceBean.getIp(), printerDeviceBean.getPort());
            PrintExecutor.getInstance().doPrinterRequestAsync(maker);
        }
    }

    /**
     * usb打印
     */
    public void usbPrint(List<Map<String, Bitmap>> bitmapMaps) {
        for (int i = 0; i < bitmapMaps.size(); i++) {
            for (Map.Entry<String, Bitmap> bitmapMap : bitmapMaps.get(i).entrySet()) {
                final String key = bitmapMap.getKey();
                UsbPrint usbPrint = new UsbPrint(mContext, (code, printId) -> {
                    //打印結果
                    if (code == SendResultCode.SEND_SUCCESS) {
                        PrinterFlowProxy.newInstance().dispatchPrintSuccess();
                        updatePrjSuccess(key);
                    } else if (code == SendResultCode.SEND_FAILED) {
                        PrinterFlowProxy.newInstance().dispatchPrintError(new Exception("發送打印數據失敗"), code);
                        updatePrjFailure(key);
                    }
                });
                if (mUsbPrinters != null && mUsbPrinters.size() > 0) {
                    EscCommand esc = new EscCommand();
                    ArrayList<byte[]> bytes = new ArrayList<>();
                    try {
                        bytes.addAll(new PrinterWriter58mm().getImageByte(bitmapMap.getValue()));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    bytes.add(esc.getPrintAndFeedLines((byte) 8));
                    bytes.add(esc.getCutPaper());
                    bytes.add(esc.getCleanCache());
                    usbPrint.sendPrintCommand(mUsbPrinters.get(0), bytes);
                } else {
                    //打印失敗
                    updatePrjFailure(key);
                }
            }
        }
    }

    /**
     * 本機打印
     */
    public void locationPrint(List<Map<String, Bitmap>> bitmapMaps) {
        String model = Build.MODEL;
        if (PrintConstans.PRINT_MODEL_V2.contains(model)) {
            //商米打印
            LoganManager.w_printer(TAG, "開始商米打印-->");
            sunmiPrint(bitmapMaps);
        } else if (PrintConstans.PRINT_MODEL_N5.contains(model)) {
            //N5打印
            LoganManager.w_printer(TAG, "開始N5打印-->");
            n5Print(bitmapMaps);
        } else if (PrintConstans.PRINT_MODEL_WISEPOS.contains(model)) {
            //BBPOS，生成data
            ToastUtils.show(this, "BBPOS不支持打印廚房單");
        } else {
            updatePrjState(UpdateBean.FAIL_LOCAL_PRINT_NOT_DEVICE, getPrintIds(bitmapMaps));
        }
    }

    /**
     * 獲取本次打印的prj id
     *
     * @return prj的id集合
     */
    private String getPrintIdsByPrjData(List<PrjBean> prjBeans) {
        StringBuilder stringBuilder = new StringBuilder();
        for (PrjBean prjBean : prjBeans) {
            stringBuilder.append(prjBean.getId());
            if (stringBuilder.toString().lastIndexOf(",") != stringBuilder.length() - 1) {
                //如果最後一位不是逗號，才添加
                stringBuilder.append(",");
            }
        }
        return stringBuilder.toString();
    }

    /**
     * 獲取本次打印的prj id
     *
     * @param bitmapMaps
     * @return
     */
    private String getPrintIds(List<Map<String, Bitmap>> bitmapMaps) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < bitmapMaps.size(); i++) {
            for (Map.Entry<String, Bitmap> mapEntry : bitmapMaps.get(i).entrySet()) {
                stringBuilder.append(mapEntry.getKey()).append(",");
            }
        }
        if (stringBuilder.length() <= 0) {
            return "";
        }
        //最後一位是逗號，去掉
        return stringBuilder.substring(0, stringBuilder.length() - 1);
    }

    /**
     * 商米打印
     */
    private void sunmiPrint(List<Map<String, Bitmap>> bitmapMaps) {
        //商米打印
        for (int i = 0; i < bitmapMaps.size(); i++) {
            for (Map.Entry<String, Bitmap> mapEntry : bitmapMaps.get(i).entrySet()) {
                final String ids = mapEntry.getKey();
                AidlUtil.getInstance().printBitmap(mapEntry.getValue(), new InnerResultCallbcak() {
                    @Override
                    public void onRunResult(boolean isSuccess) {
                        LoganManager.w_printer(TAG, "商米onRunResult: " + isSuccess);
                        //返回接⼝执⾏的情况(并⾮真实打印):成功或失败
                        if (isSuccess) {
                            updatePrjSuccess(ids);
                        } else {
                            updatePrjFailure(ids);
                        }
                    }

                    @Override
                    public void onReturnString(String result) {
                        //部分接⼝会异步返回查询数据
                        LoganManager.w_printer(TAG, "商米onReturnString: " + result);
                    }

                    @Override
                    public void onRaiseException(int code, String msg) {
                        //接⼝执⾏失败时，返回的异常状态
                        PrinterFlowProxy.newInstance().dispatchPrintError(new Exception(msg), code);
                        LoganManager.w_printer(TAG, "商米onRaiseException: " + code + msg);
                        switch (code) {
                            case 3:
                                //通訊異常
                                updatePrjState(UpdateBean.FAIL_PRINT_BY_NOT_CONNECT, ids);
                                break;
                            case 4:
                                //缺紙
                                updatePrjState(UpdateBean.FAIL_NOT_PAPER, ids);
                                break;
                            case 5:
                                //過熱
                                updatePrjState(UpdateBean.FAIL_SUNMI_PRINT_HOT, ids);
                                break;
                            case 6:
                                //蓋子未合上
                                updatePrjState(UpdateBean.FAIL_LID_NOT_CLOSED_SUNMI, ids);
                                break;
                            case 7:
                                //切刀異常
                                updatePrjState(UpdateBean.FAIL_CUT_ABNORMAL_SUNMI, ids);
                                break;
                            case 9:
                                //黑標異常
                                updatePrjState(UpdateBean.FAIL_SUNMI_NO_BLACK_MARK_DETECTED, ids);
                                break;
                            default:
                                updatePrjState(UpdateBean.FAIL_PRINT_BY_NOT_FIND_DEVICE_SUNMI, ids);
                                break;
                        }
                    }

                    @Override
                    public void onPrintResult(int code, String msg) {
                        //事务模式下真实的打印结果返回
                        LoganManager.w_printer(TAG, "商米onPrintResult: " + msg);
                    }
                });
            }
        }
    }

    /**
     * n5打印
     */
    private void n5Print(List<Map<String, Bitmap>> bitmapMaps) {
        for (int i = 0; i < bitmapMaps.size(); i++) {
            for (Map.Entry<String, Bitmap> mapEntry : bitmapMaps.get(i).entrySet()) {
                final String ids = mapEntry.getKey();
                try {
                    PrinterUtil.appendImage(mapEntry.getValue(), PrinterConstant.ALIGN_CENTER);
                    PrinterUtil.appendPrnStr("\n", 24, PrinterConstant.ALIGN_CENTER, false);
                    PrinterUtil.appendPrnStr("\n", 24, PrinterConstant.ALIGN_CENTER, false);
                    PrinterUtil.startPrint(true, new IOnPrintCallback.Stub() {
                        @Override
                        public void onPrintResult(int i) {
                            LoganManager.w_printer(TAG, "N5 onPrintResult: " + i);
                            if (i == 0) {
                                //打印成功
                                updatePrjSuccess(ids);
                            } else {
                                //打印失敗
                                PrinterFlowProxy.newInstance().dispatchPrintError(new Exception(UpdateBean.getStateByCode(i)), i);
                                updatePrjState(i, ids);
                            }
                        }

                        @Override
                        public IBinder asBinder() {
                            LoganManager.w_printer(TAG, "N5 asBinder");
                            return this;
                        }
                    });
                } catch (RemoteException e) {
                    e.printStackTrace();
                    PrinterFlowProxy.newInstance().dispatchPrintError(e, i);
                    LoganManager.w_printer(TAG, "N5 RemoteException :" + e.getLocalizedMessage());
                    updatePrjFailure(ids);
                }
            }
        }
    }

    /**
     * 生成打印需要的Map集合，Map的key是這張PRJ的所有PRJ記錄的id，然後會同時打印多張，所以是一個List集合
     *
     * @param key   打印位置
     * @param beans 打印的食品和其他的一些信息
     */
    private List<Map<String, Bitmap>> generatePrintMaps(String key, List<PrjBean> beans, PrinterDeviceBean printerDeviceBean) {
        //當前所有食品都是同一個打印位置，例如：K1
        PrintPrjKitchen printPrjKitchen = new PrintPrjKitchen();
        //這個Map的key是這張PRJ的所有PRJ記錄的id，然後會同時打印多張，所以是一個List集合
        List<Map<String, Bitmap>> bitmapMaps = new ArrayList<>();
        List<PrjBean> notCutData = new ArrayList<>();
        List<PrjBean> cutPrjData = new ArrayList<>();

        int prjSize = beans.size();
        for (int i = 0; i < prjSize; i++) {
            PrjBean bean = beans.get(i);
            //如果是跟隨主項，那就放入不切紙，因為要切紙的，都已經全部遍歷完了
            //如果是要切紙的
            if (bean.getStatus() == PrjBean.PRJ_STATUS_CUT_PAPER) {
                //裝進切紙集合
                cutPrjData.add(bean);
                //遍歷後面的prj，一直到不是他子項為止
                i = getFollow(i, beans, cutPrjData, notCutData, key, printerDeviceBean, printPrjKitchen, bitmapMaps);
            } else {
                //不切紙的，都放在這裡
                notCutData.add(bean);
            }
        }
        //將所有不切紙的轉為打印數據
        PrjDataTransToPrintData(key, printerDeviceBean, printPrjKitchen, bitmapMaps, notCutData);
        hookPrinterBitmap(bitmapMaps, beans);
        return bitmapMaps;
    }

    /**
     * 將prjData數據轉為打印數據
     *
     * @param key               打印位置
     * @param printerDeviceBean 打印設備信息
     * @param printPrjKitchen   打印類
     * @param bitmapMaps        裝本次打印數據的map，key為打印數據的所有prj的id
     * @param prjData           prj數據
     */
    private void PrjDataTransToPrintData(String key, PrinterDeviceBean printerDeviceBean, PrintPrjKitchen printPrjKitchen, List<Map<String, Bitmap>> bitmapMaps, List<PrjBean> prjData) {
        if (prjData.size() > 0) {
            Map<String, Bitmap> map = new HashMap<>();
            map.put(getPrjIds(prjData), printPrjKitchen.getKitChenPrintBitmap(mContext, key, prjData, printerDeviceBean));
            bitmapMaps.add(map);
            prjData.clear();
        }
    }

    /**
     * @param i                 當前食品下標
     * @param beans             所有prj數據集合
     * @param cutPrjData        切紙的集合
     * @param notCutData        不切紙的集合
     * @param key               打印位置
     * @param printerDeviceBean 打印設備
     * @param printPrjKitchen   打印類
     * @param bitmapMaps        裝打印數據的map
     * @return 返回本次遍歷到的食品位置
     */
    public int getFollow(int i, List<PrjBean> beans, List<PrjBean> cutPrjData, List<PrjBean> notCutData, String key, PrinterDeviceBean printerDeviceBean, PrintPrjKitchen printPrjKitchen, List<Map<String, Bitmap>> bitmapMaps) {
        //遍歷後面所有的prj
        for (int j = i + 1; j < beans.size(); j++) {
            PrjBean itemPrjBean = beans.get(j);
            if (itemPrjBean.getType() == PrjBean.FOOD_TYPE) {
                //如果是食品，可能是套餐
                if (itemPrjBean.getParentId() != 0) {
                    //用來判斷是不是這個食品的細項，如果遍歷完所有的食品，還沒找到他的父類，說明就不是當前要切紙對象的細項
                    boolean isThisFoodItem = false;
                    //這裡之所以需要遍歷切紙集合，因為當前食品可能是三級，他的父類id不一定是第一級那個bean的id，可能是二級食品的子類
                    for (int k = 0; k < cutPrjData.size(); k++) {
                        if (itemPrjBean.getParentId() == cutPrjData.get(k).getOrderDetailsId()) {
                            isThisFoodItem = true;
                            //是這個食品的細項，判斷是不是跟隨主項
                            if (itemPrjBean.getStatus() == PrjBean.PRJ_STATUS_FOLLOW_PARENT) {
                                //跟隨主項
                                cutPrjData.add(itemPrjBean);
                            } else if (itemPrjBean.getStatus() == PrjBean.PRJ_STATUS_CUT_PAPER) {
                                //不跟隨主項，要切紙
                                //要切紙的，又要判斷下一個是不是當前細項的細項，因為有可能下一個細項也是要跟隨主項的
                                List<PrjBean> itemCutPrjData = new ArrayList<>();
                                itemCutPrjData.add(itemPrjBean);
                                //遍歷之後所有的食品，一直到不是當前子項的子項為止
                                j = getFollow(j, beans, itemCutPrjData, notCutData, key, printerDeviceBean, printPrjKitchen, bitmapMaps);
                            } else {
                                //不跟隨主項，不切紙
                                //下一個食品可能是這個不切紙食品的細項
                                //遍歷之後的食品，直到不是這個食品的細項為止
                                j = getNotCutFoodItem(j, beans, itemPrjBean, notCutData, key, printerDeviceBean, printPrjKitchen, bitmapMaps);
                            }
                            break;
                        }
                    }
                    if (!isThisFoodItem) {
                        //不是這個食品的細項，說明這個食品的所有細項都已經遍歷完了
                        //將這個食品的數據轉為打印數據
                        PrjDataTransToPrintData(key, printerDeviceBean, printPrjKitchen, bitmapMaps, cutPrjData);
                        //跳出循環
                        return j - 1;
                    }
                } else {
                    //如果是食品，並且沒有父類，說明之前的食品所有細項都已經遍歷完了
                    //將這個食品的數據轉為打印數據
                    PrjDataTransToPrintData(key, printerDeviceBean, printPrjKitchen, bitmapMaps, cutPrjData);
                    //就跳出循環
                    return j - 1;
                }
            } else {
                boolean isThisFoodItem = false;
                //如果是細項，先判斷是不是這個食品的細項，再判斷是不是跟隨主項
                //這裡之所以需要遍歷切紙集合，因為當前細項可能是三級細項，他的父類id不一定是第一級那個bean的id，可能是二級細項的子類
                for (int k = 0; k < cutPrjData.size(); k++) {
                    if (itemPrjBean.getParentId() == cutPrjData.get(k).getOrderDetailsId()) {
                        isThisFoodItem = true;
                        //是這個食品的細項，判斷是不是跟隨主項
                        if (itemPrjBean.getStatus() == PrjBean.PRJ_STATUS_FOLLOW_PARENT) {
                            //跟隨主項
                            cutPrjData.add(itemPrjBean);
                        } else if (itemPrjBean.getStatus() == PrjBean.PRJ_STATUS_CUT_PAPER) {
                            //不跟隨主項，要切紙
                            //要切紙的，又要判斷下一個是不是當前細項的細項，因為有可能下一個細項也是要跟隨主項的
                            List<PrjBean> itemCutPrjData = new ArrayList<>();
                            itemCutPrjData.add(itemPrjBean);
                            //遍歷之後所有的食品，一直到不是當前子項的子項為止
                            j = getFollow(j, beans, itemCutPrjData, notCutData, key, printerDeviceBean, printPrjKitchen, bitmapMaps);
                        } else {
                            //不跟隨主項，不切紙
                            //下一個食品可能是這個不切紙食品的細項
                            //遍歷之後的食品，直到不是這個食品的細項為止
                            j = getNotCutFoodItem(j, beans, itemPrjBean, notCutData, key, printerDeviceBean, printPrjKitchen, bitmapMaps);
                        }
                        break;
                    }
                }
                if (!isThisFoodItem) {
                    //不是這個食品的細項，說明這個食品的所有細項都已經遍歷完了
                    //將這個食品的數據轉為打印數據
                    PrjDataTransToPrintData(key, printerDeviceBean, printPrjKitchen, bitmapMaps, cutPrjData);
                    //跳出循環
                    return j - 1;
                }
            }
        }
        PrjDataTransToPrintData(key, printerDeviceBean, printPrjKitchen, bitmapMaps, cutPrjData);
        return beans.size();
    }

    /**
     * 獲取不切紙的後續的食品細項
     *
     * @param j
     * @param beans
     * @param notCutData
     * @param key
     * @param printerDeviceBean
     * @param printPrjKitchen
     * @param bitmapMaps
     * @return
     */
    private int getNotCutFoodItem(int j, List<PrjBean> beans, PrjBean parentPrjBean, List<PrjBean> notCutData, String key, PrinterDeviceBean printerDeviceBean, PrintPrjKitchen printPrjKitchen, List<Map<String, Bitmap>> bitmapMaps) {
        //遍歷不切紙的後續的prj
        //當前食品的後續的不切紙的細項
        List<PrjBean> currentNotCutPrjBeans = new ArrayList<>();
        currentNotCutPrjBeans.add(parentPrjBean);
        for (int i = j + 1; i < beans.size(); i++) {
            PrjBean itemPrjBean = beans.get(i);
            boolean isThisFoodItem = false;
            //如果這個食品是不是食品的子類
            //這裡之所以遍歷，是因為可能存在，一級-二級-三級，如果只用一級去和三級判斷父類關係，三級判斷通不過
            for (int k = 0; k < currentNotCutPrjBeans.size(); k++) {
                if (itemPrjBean.getParentId() == currentNotCutPrjBeans.get(k).getOrderDetailsId()) {
                    isThisFoodItem = true;
                    //判斷是否切紙
                    if (itemPrjBean.getStatus() == PrjBean.PRJ_STATUS_CUT_PAPER) {
                        //要切紙的，又要判斷下一個是不是當前細項的細項，因為有可能下一個細項也是要跟隨主項的
                        List<PrjBean> itemCutPrjData = new ArrayList<>();
                        itemCutPrjData.add(itemPrjBean);
                        //遍歷之後所有的食品，一直到不是當前子項的子項為止
                        i = getFollow(i, beans, itemCutPrjData, notCutData, key, printerDeviceBean, printPrjKitchen, bitmapMaps);
                    } else {
                        //不切紙或
                        //跟隨主項，就加入不切紙的集合中
                        currentNotCutPrjBeans.add(itemPrjBean);
                    }
                    break;
                }
            }
            if (!isThisFoodItem) {
                //不是這個食品的細項
                notCutData.addAll(currentNotCutPrjBeans);
                return i - 1;
            }
        }
        notCutData.addAll(currentNotCutPrjBeans);
        return beans.size();
    }


    @NotNull
    private String getPrjIds(List<PrjBean> noCut) {
        StringBuilder stringBuffer = new StringBuilder();
        for (PrjBean noCutData : noCut) {
            stringBuffer.append(noCutData.getId());
            stringBuffer.append(",");
        }
        if (stringBuffer.length() <= 0) {
            return "";
        }
        //最後一位是逗號，去掉
        return stringBuffer.substring(0, stringBuffer.length() - 1);
    }

    private void hookPrinterBitmap
            (List<Map<String, Bitmap>> bitmapMaps, List<PrjBean> beans) {
        if (bitmapMaps.size() > 0) {
            String prjName = String.valueOf(System.currentTimeMillis());
            int orderType = 1;
            if (beans.size() > 0) {
                PrjBean prjBean = beans.get(0);
                if (prjBean != null) {
                    if (!TextUtils.isEmpty(prjBean.getBillNo())) {
                        prjName = prjBean.getBillNo();
                    } else {
                        prjName = prjBean.getOrderNo();
                    }
                    orderType = prjBean.getOrderType();
                }
            }
            List<Bitmap> bitmapList = new ArrayList<>();
            for (int i = 0; i < bitmapMaps.size(); i++) {
                Set<Map.Entry<String, Bitmap>> bitmapSet = bitmapMaps.get(i).entrySet();
                Iterator<Map.Entry<String, Bitmap>> bitmapIterator = bitmapSet.iterator();
                while (bitmapIterator.hasNext()) {
                    bitmapList.add(bitmapIterator.next().getValue());
                }
            }
            PrinterFlowProxy.newInstance().dispatchPrinterBitmapBefore(orderType, PrintConstans.PRINT_KITCHEN, prjName, bitmapList);
        }
    }

    /**
     * 是否為針式打印機
     *
     * @param printerDeviceBean 打印機實體類
     * @return true是
     */
    public static boolean isPinPrinter(PrinterDeviceBean printerDeviceBean) {
        return (printerDeviceBean != null && printerDeviceBean.getPrinterName() != null && printerDeviceBean.getPrinterName().toLowerCase().contains("EPSON".toLowerCase()))
                && (printerDeviceBean.getModel() != null && printerDeviceBean.getModel().toLowerCase().contains("TM-U220B".toLowerCase()));
    }

    private void updatePrjSuccess(String ids) {
        updatePrjState(UpdateBean.ALREADY_PRINT, ids);
    }

    /**
     * 打印失敗，將打印狀態改為未打印，並不是改為打印失敗，失敗是由後台判斷打印失敗次數超過最大限制之後改為打印失敗的
     *
     * @param ids prj的id集合
     */
    private void updatePrjFailure(String ids) {
        updatePrjState(UpdateBean.NO_PRINT, ids);
    }

    /**
     * @param printState 打印狀態 1未打印 2打印中 3已打印
     */
    private void updatePrjState(int printState, String ids) {
        Long time = null;
        if (printState == 3) {
            time = TimeUtils.getCurrentTimeInLong();
        }

        List<UpdateBean> updateBeans = new ArrayList<>();
        String[] idArrays = ids.split(",");
        for (String id : idArrays) {
            updateBeans.add(new UpdateBean(id, printState, time));
        }
        String json = JsonUtils.toJson(updateBeans);
        Log.e(TAG, "修改打印狀態：" + json);
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), json);

        //打印過後，直接再讀數據，不用管是否已更新狀態。
        startGetPrjInfo();
        OkHttp3Utils.post(HttpsConstans.ROOT_SERVER_ADDRESS_FORMAL + "printerRecording/update", requestBody)
                .subscribeOn(Schedulers.io())//切换到io线程進行網絡請求
                .subscribe(new Observer<String>() {

                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(String s) {
                        LoganManager.w_printer(TAG, "修改Prj打印狀態---end----: " + json + s);
                    }

                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    @Override
    public void onPtrReceive(Printer printer, int i, PrinterStatusInfo printerStatusInfo, String
            s) {
        LogUtil.e("eee", "prj針式打印結果" + i);
        //針式打印回調
        if (i == 0) {
            PrinterFlowProxy.newInstance().dispatchPrintSuccess();
            //打印成功
            updatePrjSuccess(s);
        } else {
            PrinterFlowProxy.newInstance().dispatchPrintError(new Exception("打印失敗"), i);
            //打印失敗
            updatePrjFailure(s);
        }
        startGetPrjInfo();
    }
}
