package com.junmp.v2.print.RFID;

import cn.hutool.core.util.ObjectUtil;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;


/**
 * <pre>
 *
 * 描述：
 * 版本：1.0.0
 * 日期：2024/9/12 上午9:18
 * 作者：xuzc@junmp.com.cn
 * <br>修改记录
 * <br>修改日期 修改人 修改内容
 *
 * </pre>
 */
public class EpcConvert
{
    private static final String strKeyWords = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";    // 无源标签6位字段对应表

    public static String stringToHex16String(String _str) {
        byte[] buffer = _str.getBytes(StandardCharsets.UTF_8);
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < buffer.length; i++) {
            result.append(String.format("%02X ", buffer[i]));
        }
        return result.toString().trim();
    }

    public static String hex16ByteToHex16String(byte[] _hex16Byte) {
        StringBuilder result = new StringBuilder();
        if (_hex16Byte != null) {
            for (int i = 0; i < _hex16Byte.length; i++) {
                result.append(String.format("%02X ", _hex16Byte[i]));
            }
        }
        return result.toString().trim();
    }

    public static byte[] toHexByte(String hexString) {
        hexString = hexString.replace(" ", "");
        if ((hexString.length() % 2) != 0) {
            hexString += "0";
        }
        byte[] buffer = new byte[hexString.length() / 2];
        for (int i = 0; i < buffer.length; i++) {
            buffer[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16);
        }
        return buffer;
    }

    public static String generateEpc(AnalyzingEpc epc) {
        byte[] buf = new byte[40];
        int pos = 0;
        setData(buf, pos, 8, epc.getHeader());
        pos += 8;
        setData(buf, pos, 6, epc.getIssuerId());
        pos += 6;
        setData(buf, pos, 54, organizationCodeTo6Bin(epc.getOrganizationCode().toUpperCase()));
        pos += 54;
        setData(buf, pos, 4, epc.getWzdmLen());
        pos += 4;
        int wzlen = ((epc.getWzdmLen() - 1) % 8) * 8;
        setData(buf, pos, wzlen, epc.getWzdm());     // 物资代码暂时定义最长为64bits
        pos += wzlen;
        setData(buf, pos, 8, epc.getHxdm());     // 物资代码暂时定义最长为64bits
        pos += 8;
        setData(buf, pos, 4, epc.getSerialLen());
        pos += 4;

        setData(buf, pos, 8, epc.getVer());
        pos += 8;
        setData(buf, pos, 24, epc.getProductionDate());
        pos += 24;
        setData(buf, pos, 6, epc.getExpiryDate());
        pos += 6;
        setData(buf, pos, 2, epc.getExpiryDateUnit());
        pos += 2;
        setData(buf, pos, 48, epc.getTimeSpan());
        pos += 48;
        setData(buf, pos, 12, epc.getNoInBox());
        pos += 12;
        setData(buf, pos, 4, epc.getMachineNum());
        pos += 4;
        setData(buf, pos, 4, epc.getEType());
        pos += 4;
        setData(buf, pos, 4, epc.getEProperty());
        pos += 4;

        // 双字节对齐， 剩余保留区
        if (pos % 16 != 0) {
            pos += 16 - pos % 16;
        }

        // 数据体长度
        int len = pos / 8;
        int crc = crc16(buf, len);
        setData(buf, pos, 16, crc);

        byte[] result = new byte[len + 2];
        System.arraycopy(buf, 0, result, 0, len + 2);

        return hex16ByteToHex16String(result).replace(" ", "");
    }

    /**
     * 生成2.0EPC
     *
     * @param epc EPC 对象
     * @return 生成的 EPC 字节数组
     */
    public static byte[] epc2Gen(AnalyzingEpc epc) {
        byte[] buf = new byte[40];
        int pos = 0;
        setData(buf, pos, 8, epc.getHeader());
        pos += 8;
        setData(buf, pos, 6, epc.getIssuerId());
        pos += 6;
        setData(buf, pos, 54, organizationCodeTo6Bin(epc.getOrganizationCode().toUpperCase()));
        pos += 54;
        setData(buf, pos, 4, epc.getWzdmLen());
        pos += 4;
        int wzlen = ((epc.getWzdmLen() - 1) % 8) * 8;
        setData(buf, pos, wzlen, epc.getWzdm());
        pos += wzlen;
        setData(buf, pos, 8, epc.getHxdm());
        pos += 8;
        setData(buf, pos, 4, epc.getSerialLen());
        pos += 4;

        /*----------------2.0版本字段-------------*/
        setData(buf, pos, 8, epc.getVer());
        pos += 8;
        setData(buf, pos, 24, epc.getProductionDate());
        pos += 24;
        setData(buf, pos, 6, epc.getExpiryDate());
        pos += 6;
        setData(buf, pos, 2, epc.getExpiryDateUnit());
        pos += 2;
        setData(buf, pos, 48, epc.getTimeSpan());
        pos += 48;
        setData(buf, pos, 12, epc.getNoInBox());
        pos += 12;
        setData(buf, pos, 4, epc.getMachineNum());
        pos += 4;

        /*----------------警用装备3.0字段-------------*/
        if (ObjectUtil.isNotNull(epc.getEType()) ){
            setData(buf, pos, 4, epc.getEType());
            pos += 4;
        }

        // 双字节对齐， 剩余保留区
        if (pos % 16 != 0) {
            pos += 16 - pos % 16;
        }

        // 数据体长度
        int len = pos / 8;
        int crc = crc16(buf, len);
        setData(buf, pos, 16, crc);

        byte[] rtn = new byte[len + 2];
        System.arraycopy(buf, 0, rtn, 0, len + 2);

        return rtn;
    }

    /**
     * 生成3.0EPC
     *
     * @param epc EPC 对象
     * @return 生成的 EPC 字节数组
     */
    public static byte[] epc3Gen(AnalyzingEpc epc) {
        byte[] buf = new byte[40];
        int pos = 0;
        setData(buf, pos, 8, epc.getHeader());
        pos += 8;
        setData(buf, pos, 6, epc.getIssuerId());
        pos += 6;
        setData(buf, pos, 54, organizationCodeTo6Bin(epc.getOrganizationCode().toUpperCase()));
        pos += 54;
        setData(buf, pos, 4, epc.getWzdmLen());
        pos += 4;
        int wzlen = ((epc.getWzdmLen() - 1) % 8) * 8;
        setData(buf, pos, wzlen, epc.getWzdm());
        pos += wzlen;
        setData(buf, pos, 8, epc.getHxdm());
        pos += 8;
        setData(buf, pos, 4, epc.getSerialLen());
        pos += 4;

        /*----------------2.0版本字段-------------*/
        setData(buf, pos, 8, epc.getVer());
        pos += 8;
        setData(buf, pos, 24, epc.getProductionDate());
        pos += 24;

        setData(buf, pos, 6, epc.getExpiryDate());
        pos += 6;
        setData(buf, pos, 2, epc.getExpiryDateUnit());
        pos += 2;

        setData(buf, pos, 48, epc.getTimeSpan());
        pos += 48;
        setData(buf, pos, 12, epc.getNoInBox());
        pos += 12;

        /*----------------警用装备3.0字段-------------*/
        setData(buf, pos, 4, epc.getEProperty());
        pos += 4;

        setData(buf, pos, 4, epc.getEType());
        pos += 4;

        // 双字节对齐， 剩余保留区
        if (pos % 16 != 0) {
            pos += 16 - pos % 16;
        }

        // 数据体长度
        int len = pos / 8;
        int crc = crc16(buf, len);
        setData(buf, pos, 16, crc);

        byte[] rtn = new byte[len + 2];
        System.arraycopy(buf, 0, rtn, 0, len + 2);

        return rtn;
    }

    /**
     * 生成EPC
     *
     * @param epc EPC 对象
     * @return 生成的 EPC 字节数组
     */
    public static byte[] epcGen(AnalyzingEpc epc) {
        byte[] buf = new byte[40];
        int pos = 0;
        setData(buf, pos, 8, epc.getHeader());
        pos += 8;
        setData(buf, pos, 6, epc.getIssuerId());
        pos += 6;
        setData(buf, pos, 54, organizationCodeTo6Bin(epc.getOrganizationCode().toUpperCase()));
        pos += 54;
        setData(buf, pos, 4, epc.getWzdmLen());
        pos += 4;
        int wzlen = ((epc.getWzdmLen() - 1) % 8) * 8;
        setData(buf, pos, wzlen, epc.getWzdm());
        pos += wzlen;
        setData(buf, pos, 8, epc.getHxdm());
        pos += 8;
        setData(buf, pos, 4, epc.getSerialLen());
        pos += 4;
        setData(buf, pos, 8, epc.getVer());
        pos += 8;
        setData(buf, pos, 8, epc.getTagType());
        pos += 8;
        setData(buf, pos, 4, epc.getPackingType());
        pos += 4;
        setData(buf, pos, 24, epc.getProductionDate());
        pos += 24;
        setData(buf, pos, 6, epc.getExpiryDate());
        pos += 6;
        setData(buf, pos, 2, epc.getExpiryDateUnit());
        pos += 2;
        setData(buf, pos, 8, epc.getSubPackageNum());
        pos += 8;
        setData(buf, pos, 12, epc.getWzCount());
        pos += 12;
        setData(buf, pos, 20, epc.getBoxNo());
        pos += 20;
        setData(buf, pos, 12, epc.getNoInBox());
        pos += 12;

        // 双字节对齐， 剩余保留区
        if (pos % 16 != 0) {
            pos += 16 - pos % 16;
        }

        // 数据体长度
        int len = pos / 8;
        int crc = crc16(buf, len);
        setData(buf, pos, 16, crc);

        byte[] rtn = new byte[len + 2];
        System.arraycopy(buf, 0, rtn, 0, len + 2);

        return rtn;
    }

    /**
     * 解析EPC
     *
     * @param epc EPC 字节数组
     * @return 解析后的 EPC 对象
     */
    public static AnalyzingEpc epcAnlysing(byte[] epc) {
        // CRC 校验
        int crc_chk = crc16(epc, epc.length - 2);
        int crc_src = (epc[epc.length - 2] & 0xFF) << 8 | (epc[epc.length - 1] & 0xFF);
        if (crc_src != crc_chk) {
            throw new RuntimeException("CRC校验失败");
        }

        // 数据解析
        AnalyzingEpc rtn = new AnalyzingEpc();
        int pos = 0;
        rtn.setHeader((byte) getData(epc, pos, 8));
        pos += 8;
        rtn.setIssuerId((byte) getData(epc, pos, 6));
        pos += 6;
        long oc = getData(epc, pos, 54);rtn.setOrganizationCode(organizationCodeToStr(oc));
        pos += 54;
        rtn.setWzdmLen((byte) (getData(epc, pos, 4)));
        pos += 4;
        int wzlen = ((rtn.getWzdmLen() - 1) % 8) * 8;
        rtn.setWzdm(getData(epc, pos, wzlen));
        pos += wzlen;
        rtn.setHxdm(getData(epc, pos, 8));
        pos += 8;
        rtn.setSerialLen((byte) getData(epc, pos, 4));
        pos += 4;
        rtn.setVer((byte) getData(epc, pos, 8));
        pos += 8;

        //2.0版本解析
        if (rtn.getVer() == 0x02) {
            rtn.setProductionDate((int) getData(epc, pos, 24));
            pos += 24;

            rtn.setExpiryDate((byte) getData(epc, pos, 6));
            pos += 6;
            rtn.setExpiryDateUnit((byte) getData(epc, pos, 2));
            pos += 2;

            rtn.setTimeSpan((long) getData(epc, pos, 48));
            pos += 48;
            rtn.setNoInBox((int) getData(epc, pos, 12));
            pos += 12;
            rtn.setMachineNum((byte) getData(epc, pos, 4));
            pos += 4;
            rtn.setEType((byte) getData(epc, pos, 4));
            pos += 4;

            rtn.setEProperty((byte) 0);
        } else if (rtn.getVer() == 0x03) {
            rtn.setProductionDate((int) getData(epc, pos, 24));
            pos += 24;

            rtn.setExpiryDate((byte) getData(epc, pos, 6));
            pos += 6;
            rtn.setExpiryDateUnit((byte) getData(epc, pos, 2));
            pos += 2;

            rtn.setTimeSpan((long) getData(epc, pos, 48));
            pos += 48;
            rtn.setNoInBox((int) getData(epc, pos, 12));
            pos += 12;
            rtn.setEProperty((byte) getData(epc, pos, 4));
            pos += 4;
            rtn.setEType((byte) getData(epc, pos, 4));
            pos += 4;
        } else {
            rtn.setTagType((byte) getData(epc, pos, 8));
            pos += 8;
            rtn.setPackingType((byte) getData(epc, pos, 4));
            pos += 4;
            rtn.setProductionDate((int) getData(epc, pos, 24));
            pos += 24;
            rtn.setExpiryDate((byte) getData(epc, pos, 6));
            pos += 6;
            rtn.setExpiryDateUnit((byte) getData(epc, pos, 2));
            pos += 2;
            rtn.setSubPackageNum((byte) getData(epc, pos, 8));
            pos += 8;
            rtn.setWzCount((int) getData(epc, pos, 12));
            pos += 12;
            rtn.setBoxNo((int) getData(epc, pos, 20));
            pos += 20;
            rtn.setNoInBox((int) getData(epc, pos, 12));
            pos += 12;
        }

        return rtn;
    }

    // 非单标签还原(jyzb3.0)
    public static String restoreEpc(String info) {
        byte[] epc = toHexByte(info);

        int crc_chk = crc16(epc, epc.length - 2);
        int crc_src = (epc[epc.length - 2] & 0xFF) << 8 | (epc[epc.length - 1] & 0xFF);
        if (crc_src != crc_chk) {
            throw new RuntimeException("CRC校验失败");
        }

        // 数据解析
        AnalyzingEpc rtn = new AnalyzingEpc();
        int pos = 0;
        rtn.setHeader((byte) getData(epc, pos, 8));
        pos += 8;
        rtn.setIssuerId((byte) getData(epc, pos, 6));
        pos += 6;
        long oc = getData(epc, pos, 54);
        rtn.setOrganizationCode(organizationCodeToStr(oc));
        pos += 54;
        rtn.setWzdmLen((byte) (getData(epc, pos, 4)));
        pos += 4;
        int wzlen = ((rtn.getWzdmLen() - 1) % 8) * 8;
        rtn.setWzdm(getData(epc, pos, wzlen));
        pos += wzlen;
        rtn.setHxdm(getData(epc, pos, 8));
        pos += 8;
        rtn.setSerialLen((byte) getData(epc, pos, 4));
        pos += 4;
        rtn.setVer((byte) getData(epc, pos, 8));
        pos += 8;

        rtn.setProductionDate((int) getData(epc, pos, 24));
        pos += 24;

        rtn.setExpiryDate((byte) getData(epc, pos, 6));
        pos += 6;
        rtn.setExpiryDateUnit((byte) getData(epc, pos, 2));
        pos += 2;

        rtn.setTimeSpan((long) getData(epc, pos, 48));
        pos += 48;
        rtn.setNoInBox((int) getData(epc, pos, 12));
        pos += 12;
        rtn.setEProperty((byte) getData(epc, pos, 4));
        pos += 4;
        rtn.setEType((byte) getData(epc, pos, 4));
        pos += 4;

        rtn.setEType((byte) 1);

        String baseEpc = hex16ByteToHex16String(epc3Gen(rtn)).replace(" ", "");
        return baseEpc;
    }

    // 补充生成多标签(jyzb3.0)
    public static List<String> supplementEpc(String info, int etype) {
        byte[] epc = toHexByte(info);

        int crc_chk = crc16(epc, epc.length - 2);
        int crc_src = (epc[epc.length - 2] & 0xFF) << 8 | (epc[epc.length - 1] & 0xFF);
        if (crc_src != crc_chk) {
            throw new RuntimeException("CRC校验失败");
        }

        // 数据解析
        AnalyzingEpc rtn = new AnalyzingEpc();
        int pos = 0;
        rtn.setHeader((byte) getData(epc, pos, 8));
        pos += 8;
        rtn.setIssuerId((byte) getData(epc, pos, 6));
        pos += 6;
        long oc = getData(epc, pos, 54);
        rtn.setOrganizationCode(organizationCodeToStr(oc));
        pos += 54;
        rtn.setWzdmLen((byte) (getData(epc, pos, 4)));
        pos += 4;
        int wzlen = ((rtn.getWzdmLen() - 1) % 8) * 8;
        rtn.setWzdm(getData(epc, pos, wzlen));
        pos += wzlen;
        rtn.setHxdm(getData(epc, pos, 8));
        pos += 8;
        rtn.setSerialLen((byte) getData(epc, pos, 4));
        pos += 4;
        rtn.setVer((byte) getData(epc, pos, 8));
        pos += 8;

        rtn.setProductionDate((int) getData(epc, pos, 24));
        pos += 24;

        rtn.setExpiryDate((byte) getData(epc, pos, 6));
        pos += 6;
        rtn.setExpiryDateUnit((byte) getData(epc, pos, 2));
        pos += 2;

        rtn.setTimeSpan((long) getData(epc, pos, 48));
        pos += 48;
        rtn.setNoInBox((int) getData(epc, pos, 12));
        pos += 12;
        rtn.setEProperty((byte) getData(epc, pos, 4));
        pos += 4;
        rtn.setEType((byte) getData(epc, pos, 4));
        pos += 4;

        List<String> epcList = new ArrayList<>();
        for (int i = 2; i <= etype; i++) {
            rtn.setEType((byte) i);
            epcList.add(hex16ByteToHex16String(epc3Gen(rtn)).replace(" ", ""));
        }

        return epcList;
    }


    private static void setData(byte[] data, int pos, int len, long value) {
        int p = pos;
        while (--len >= 0) {
            int bitPos = 7 - p % 8;
            data[p++ / 8] |= (byte) (((value >> len) & 1) << bitPos);
        }
    }

    private static long getData(byte[] data, int pos, int len) {
        long rtn = 0;
        int p = pos;
        while (len-- > 0) {
            int bitPos = 7 - p % 8;
            rtn <<= 1;
            rtn |= (byte) ((data[p++ / 8] >> bitPos) & 1);
        }

        return rtn;
    }

    private static String organizationCodeToStr(long icode) {
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < 9; i++) {
            byte v = (byte) ((icode >> (i * 6)) & 0x3f);
            code.insert(0, strKeyWords.charAt(v));
        }

        return code.toString();
    }

    private static long organizationCodeTo6Bin(String code) {
        long iCode = 0;
        if (code.length() == 9) {
            for (int i = 0; i < code.length(); i++) {
                int index = strKeyWords.indexOf(code.charAt(i));
                if (index == -1) {
                    throw new IllegalArgumentException("输入字符非法!");
                }
                iCode <<= 6;
                iCode |= index;
            }
        } else {
            throw new IllegalArgumentException("输入长度非法!");
        }

        return iCode;
    }

    public static int crc16(byte[] data, int length) {
        int crc = 0x8791;
        for (int i = 0; i < length; ++i) {
            int temp = 0;
            int a = ((crc >> 8) ^ (0xff & data[i])) << 8;
            for (int j = 0; j < 8; ++j) {
                if (((temp ^ a) & 0x8000) != 0) {
                    temp = (temp << 1) ^ 0x1021;
                } else {
                    temp <<= 1;
                }
                a <<= 1;
            }
            crc = (crc << 8) ^ temp;
        }
        return crc;
    }
}
