package com.hboxs.serialport.plc.frame;

import com.hboxs.serialport.plc.Command;
import com.hboxs.serialport.plc.util.AddressUtils;
import com.quyunshuo.androidbaseframemvvm.common.util.ByteUtils;
import com.hboxs.serialport.plc.util.ChecksumUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 命令帧
 */
public abstract class CommandFrame {
    /**
     * 开始字符
     */
    private final byte stx = Command.STX;
    /**
     * 命令
     */
    private byte cmd;
    /**
     * 地址
     */
    private byte[] addr;
    /**
     * 读或写字节数
     */
    private byte[] num = new byte[]{0x00, 0x01};
    /**
     * 写数据
     */
    protected byte[] data;
    /**
     * 结束字节
     */
    private final byte etx = Command.ETX;
    /**
     * 校验和
     */
    private byte[] sum;

    /**
     * 帧数据
     */
    private byte[] frameData;

    /**
     * area
     */
    private char area;

    /**
     * 判断命令标识
     */
    private int id;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    /**
     * 判断命令标识
     */
    private String address;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    /**
     * 构造读帧
     *
     * @param cmd       命令
     * @param address   地址
     * @param numOfByte 长度
     */
    CommandFrame(byte cmd, String address, int numOfByte) {
        this.cmd = cmd;
        setAddress(address,cmd);
        setLength(numOfByte);
        checksum();
    }

    /**
     * 构造写帧
     *
     * @param cmd        命令
     * @param address    地址
     * @param binDataStr 二进制字符串数据
     */
    CommandFrame(byte cmd, String address, String binDataStr) {
        this.cmd = cmd;
        setAddress(address,cmd);
        this.data = getByteData(binDataStr);
        checksum();
    }

    /**
     * 读写M区
     * @param cmd
     * @param address
     */
    CommandFrame(byte cmd, String address) {
        this.cmd = cmd;
        setAddress(address,cmd);
        area = address.charAt(0);
        checksum();
    }

    /**
     * 二进制字符串转字节数组
     *
     * @param binDataStr 二进制字符串
     * @return 字节数组
     */
    private byte[] getByteData(String binDataStr) {
        int length = binDataStr.length();
        // 计算实际长度
        int dataLen = length / 8;
        if (length % 8 != 0) {
            dataLen += 1;
        }
        if (dataLen % 2 == 1) {
            dataLen += 1;
        }
        setLength(dataLen);
        // 处理字符串，二进制转16进制并补0
        StringBuilder builder = new StringBuilder();
        for (int to = length; to > 0; to -= 8) {
            String tmp;
            int from = to - 8;
            if (from < 0) {
                tmp = binDataStr.substring(0, to);
            } else {
                tmp = binDataStr.substring(from, to);
            }
            int i = Integer.parseInt(tmp, 2);
            String tmpHex = Integer.toHexString(i).toUpperCase();
            if (tmpHex.length() == 1) {
                builder.append("0");
            }
            builder.append(tmpHex);
        }
        // 补齐长度
        int offset = dataLen * 2 - builder.length();
        for (int i = 0; i < offset; i++) {
            builder.insert(0, "0");// 需要加在开始
        }
        // 反转字符串处理高位在前低位在后
        //String dataStr = StringUtils.revertDataStr(builder.toString());
        String dataStr = builder.toString();
        char[] dataChar = dataStr.toCharArray();
        byte[] data = new byte[dataChar.length];
        for (int i = 0; i < data.length; i++) {
            data[i] = (byte) dataChar[i];
        }
        return data;

    }

    public byte getStx() {
        return stx;
    }

    public byte getCmd() {
        return cmd;
    }

    public byte[] getAddr() {
        return addr;
    }

    public byte[] getNum() {
        return num;
    }

    public byte[] getData() {
        return data;
    }

    public byte getEtx() {
        return etx;
    }

    public byte[] getSum() {
        return sum;
    }

    /**
     * 设置地址
     *
     * @param address 字符串类型地址
     */
    private void setAddress(String address,byte cmd) {
        this.addr = AddressUtils.getAddress(address,cmd);
    }

    /**
     * 设置读或者写的长度，最大64字节
     *
     * @param dataLen 1 ~ 64
     */
    private void setLength(int dataLen) {
        if (dataLen < 1 || dataLen > 64) {
            throw new IllegalArgumentException("长度不能必须大于0小于等于64");
        }
        // 转16进制
        String hexString = Integer.toHexString(dataLen & 0x00FF).toUpperCase();
        if (hexString.length() == 1) {
            this.num = new byte[]{(byte) '0', (byte) hexString.charAt(0)};
        } else {
            this.num = new byte[]{(byte) hexString.charAt(0), (byte) hexString.charAt(1)};
        }
    }

    private List<Byte> getChecksumData() {
        List<Byte> byteList = new ArrayList<>();
        byteList.add(cmd);

        if ("M".charAt(0)==area&&cmd==Command.CMD_SET||cmd==Command.CMD_RST){
            //m区进行翻转 复位和置位
            byteList.add(addr[2]);
            byteList.add(addr[3]);
            byteList.add(addr[0]);
            byteList.add(addr[1]);
        }

        else {
            byteList.add(addr[0]);
            byteList.add(addr[1]);
            byteList.add(addr[2]);
            byteList.add(addr[3]);
        }

        if (cmd!=Command.CMD_SET&&cmd!=Command.CMD_RST){
            byteList.add(num[0]);
            byteList.add(num[1]);
        }

        if (data != null) {
            for (byte b : data) {
                byteList.add(b);
            }
        }
        byteList.add(etx);

        return byteList;

    }

    /**
     * 计算校验和
     */
    private void checksum() {
        List<Byte> checksumData = getChecksumData();
        this.sum = ChecksumUtils.calculate(checksumData);
        frameData = new byte[checksumData.size() + 3];
        frameData[0] = stx;
        for (int i = 0; i < checksumData.size(); i++) {
            frameData[i + 1] = checksumData.get(i);
        }
        frameData[checksumData.size() + 1] = sum[0];
        frameData[checksumData.size() + 2] = sum[1];
    }

    /**
     * 获取帧数据
     *
     * @return 帧数据
     */
    public byte[] getFrameData() {
        return frameData;
    }

    @Override
    public String toString() {
        return ByteUtils.bytes2HexStr(getFrameData());
    }
}
