package cn.muchinfo.rma.netcore.packet;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import cn.muchinfo.rma.netcore.crypto.CRC32;
import cn.muchinfo.rma.netcore.crypto.Packet50Crypto;

/**
 *  5.0 交易报文协议(Len >= 24 byte，小端字节序)
 * Header          byte    1   包头      0xFF
 * Length          UInt32  4   长度
 * FunCode         UInt32  4   功能码（0代表心跳）
 * SessionID       UInt32  4   会话ID
 * Mode            byte    1   内容类型（0：ProtoBuff；1：Json；2：Zip+ProtoBuff）
 * Version         byte    1   版本号
 * SerialNumber    UInt32  4   流水号
 * Content         byte[]  n   数据内容（心跳数据体长度为0）
 * CRC             UInt32  4   检验和    头19个字节检验和
 * End             byte    1   包尾      0x0
 */
public class Packet50 {
    /** 包头 */
    public byte header;
    /** 长度 */
    public int packageLength;
    /** 功能码（0代表心跳） */
    public int funCode;
    /** 会话ID */
    public int sessionID;
    /** 内容类型（0：ProtoBuff；1：Json；2：Zip+ProtoBuff） */
    public byte mode;
    /** 版本号 */
    public byte version;
    /** 流水号 */
    public int serialNumber;
    /** 数据内容（心跳数据体长度为0） */
    public byte[] content;
    /** 检验和 */
    public int crc;
    /** 包尾 */
    public byte end;

    /**
     * 待发送报文的构造函数
     * @param funCode 功能码
     * @param content 报文内容
     */
    public Packet50(int funCode, byte[] content) {
        this.header = (byte)0xFF;
        this.funCode = funCode;
        this.content = content;
    }

    /**
     * 使用接收报文的构造函数
     * @param bytes 报文内容
     */
    public Packet50(byte[] bytes) {
        if (bytes.length >= 24) {
            // 先尝试对数据体进行解密
            byte[] decryptData = null;
            if (bytes.length > 24) {
                int contentLen = bytes.length  - 19 - 5;
                byte[] encryptData = new byte[contentLen];
                System.arraycopy(bytes, 19, encryptData, 0, contentLen);
                decryptData = Packet50Crypto.decrypt(encryptData);
            }

            // 长度
            this.packageLength = ByteBuffer.wrap(bytes, 1, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
            // 功能码
            this.funCode = ByteBuffer.wrap(bytes, 5, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
            // 流水号
            this.serialNumber = ByteBuffer.wrap(bytes, 15, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
            // 内容
            if (null != decryptData) this.content = decryptData;
        }
    }

    /**
     * 获取可发送的包数据流
     * @return 包数据流
     */
    public byte[] getBytes() {
        // 先尝试加密内容
        byte[] encryptData = null;
        if (null != this.content) encryptData = Packet50Crypto.encrypt(this.content);

        this.packageLength = 24;
        // 重新计算整包长度
        if (null != encryptData) this.packageLength = 19 + encryptData.length + 5;

        byte[] rst = new byte[this.packageLength];
        int pos = 0;
        // 报文头
        rst[pos] = this.header;
        pos++;
        // 长度
        byte[] tmpBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(this.packageLength).array(); // 注意：这里要先设置大小端再赋值
        System.arraycopy(tmpBytes, 0, rst, pos, tmpBytes.length);
        pos += 4;
        // 功能码
        tmpBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(this.funCode).array();
        System.arraycopy(tmpBytes, 0, rst, pos, tmpBytes.length);
        pos += 4;
        // 会话ID
        tmpBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(this.sessionID).array();
        System.arraycopy(tmpBytes, 0, rst, pos, tmpBytes.length);
        pos += 4;
        // 内容类型
        rst[pos] = 0x0;
        pos++;
        // 版本号
        rst[pos] = 0x0;
        pos++;
        // 流水号
        tmpBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(this.serialNumber).array();
        System.arraycopy(tmpBytes, 0, rst, pos, tmpBytes.length);
        pos += 4;
        // 报文内容
        if (null != encryptData) {
            System.arraycopy(encryptData, 0, rst, pos, encryptData.length);
        }
        // 校验和
        byte[] crcBytes = new byte[19];
        System.arraycopy(rst, 0, crcBytes, 0, 19);
        int crc = CRC32.getCRC32(crcBytes);
        tmpBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(crc).array();
        System.arraycopy(tmpBytes, 0, rst, rst.length - 5, tmpBytes.length);
        // 包尾
        rst[this.packageLength - 1] = this.end;

        return rst;
    }
}
