package packet

import (
	"bytes"
	"compress/zlib"
	"errors"
	"fmt"
	"hash/crc32"
	"io"
	"log"
	"mtp20access/utils"
	"net"
)

/************协议格式************************************

WTAS协议是交易系统与管理客户端/会员系统之间的通信协议，由报文头 + 报文体两部分组成
标识	            类型	字节数	内容	说明
HeadTag	        byte	1	    0xFF	头标记
Length	        uint	4		包总长度，包括头长度
FunCode	        uint	4		功能号（为0代表心跳，心跳数据体长度为0）
SessionId	    uint	4		会话ID，由交易接入(代理)维护
Mode	        byte	1		内容类型，0:ProtoBuff，1:Json，2:Zip + ProtBuff，…
Version	        byte	1		版本号
SerialNumber	uint	4		通讯报文序号
数据体				            业务结构体，ProtoBuf. 数据体格式： 头部 4 节字 + 内容 + 8 节字 掩码, 解密的时候只解内容部分
CRC	            byte	4		报文校验和
FootTag	        byte	1	0x00	尾标记

数据体前,包头长度=19, 空包长度=24
#define MAXKEY    "B0FB83E39A5EBFAABE471362A58393FF"
#define TRANSKEY  "F7A72DE7D6264530F01BA49BC73EB873"
*******************************************************/

// MiPacket 协议包结构体
type MiPacket struct {
	Length       uint32 // 包总长度
	FunCode      uint32 // 功能能
	SessionId    uint32 // 数据包的sid
	Mode         uint32 // 业务数据体的格式 0:ProtoBuff，1:Json，2:Zip + ProtoBuff
	SerialNumber uint32 // 通信流水号
	Data         []byte // 业务数据体
	CRC          uint32 // CRC
	OriMsg       []byte // 原始包数据
}

// EnPack 打包,根据现有的数据内容重新打包,无任何数据时打出来的是心跳包
// 这里不对数据体进行加密, 如需加密，请先加密再设置进来
func (p *MiPacket) EnPack() []byte {
	p.Length = uint32(len(p.Data)) + 24 // 空包长度24

	buf := make([]byte, 0)                                     // 缓冲区
	buf = append(buf, byte(0xFF))                              // HeadTag 0xFF
	buf = append(buf, utils.UintToBytes(p.Length)...)          // Length
	buf = append(buf, utils.UintToBytes(p.FunCode)...)         // FunCode
	buf = append(buf, utils.UintToBytes(p.SessionId)...)       // SessionId
	buf = append(buf, byte(0))                                 // Mode
	buf = append(buf, byte(0))                                 // Version
	buf = append(buf, utils.UintToBytes(p.SerialNumber)...)    // SerialNumber
	buf = append(buf, p.Data...)                               // 数据体 body
	p.CRC = crc32.Update(58861227, crc32.IEEETable, buf[0:19]) //
	buf = append(buf, utils.UintToBytes(p.CRC)...)             // CRC
	buf = append(buf, byte(0))                                 // FootTag

	return buf
}

// UnPackHead 解包头
// @buf length must be >=19
func (p *MiPacket) UnPackHead(buf []byte) error {
	if len(buf) < 19 {
		return errors.New("header len error")
	}

	if buf[0] != 0xFF {
		return errors.New("header flag error")
	}

	p.Length = utils.BytesToUint32(buf[1:5])         // Length
	p.FunCode = utils.BytesToUint32(buf[5:9])        // FunCode
	p.SessionId = utils.BytesToUint32(buf[9:13])     // SessionId
	p.SerialNumber = utils.BytesToUint32(buf[15:19]) // SerialNumber
	p.Mode = uint32(buf[13])                         // Mode

	if p.Length > 1024*10000 || p.Length < 24 {
		fmt.Println("packet too big or len error, len:", p.Length)
		return fmt.Errorf("invalid len, must in[24,1024000]")
	}

	return nil
}

// HeaderLen 包头长度, HeaderTag~SerialNumber 的长度
// 用于tcp接收数据时第一部分的长度
func (p *MiPacket) HeaderLen() uint32 {
	return 19
}

// BodyLen 除包头外的长度, 含FootTag
// 用于tcp接收数据时第二部分的长度
func (p *MiPacket) BodyLen() uint32 {
	return p.Length - 19
}

// FuncCode 功能号
func (p *MiPacket) FuncCode() int {
	return int(p.FunCode)
}

// UnPack 从指定的缓冲区解包
// 注意：不解密业务数据体, 可用 DecodeData 方法进行解密
// @buf 完整的包数据内容
func (p *MiPacket) UnPack(buf []byte) error {
	err := p.UnPackHead(buf[:19])
	if err != nil {
		return err
	}
	if len(buf) != int(p.Length) {
		err := fmt.Errorf("packet length err")
		log.Println(err)
		return err
	}
	//长度24为心跳包, 非心跳包时保存数据体内容
	//包头前面19字节+4个字节数据体加密长度, 所以从23开始
	//包尾5个字节,但是加密的数据体后8个字节掩码不需要, 所以是 p.length-13
	if len(buf) != 24 {
		p.Data = buf[23 : p.Length-13]
	}
	p.OriMsg = buf[:]
	return nil
}

// DecodeData 解密业务数据体内容
// @mode 数据体类型 0:ProtoBuff，1:Json，2:Zip + ProtoBuff
// @msg  业务数据体
func (p *MiPacket) DecodeData(mode uint32, msg []byte) ([]byte, error) {
	if mode == 2 {
		b := bytes.NewReader(msg)
		r, err := zlib.NewReader(b)
		if err != nil {
			return nil, err
		}
		var out bytes.Buffer
		_, _ = io.Copy(&out, r)
		_ = r.Close()
		msg = out.Bytes()
	}

	return Decrypt(msg, AESKey, true)
}

// EncodeData 加密码业务数据体内容
func (p *MiPacket) EncodeData(mode uint32, msg []byte) ([]byte, error) {
	buf := msg
	if mode == 2 {
		var b bytes.Buffer
		w := zlib.NewWriter(&b)
		_, err := w.Write(msg)
		if err != nil {
			return nil, err
		}
		_ = w.Close()
		buf = b.Bytes()
	}
	return Encrypt(buf, AESKey, true)
}

// SetData 设置业务数据, 不含通信包头、CRC等字段
func (p *MiPacket) SetData(buf []byte) {
	p.Data = buf[:]
}

// SetOriMsg 设置原始包数据
func (p *MiPacket) SetOriMsg(arg ...[]byte) {
	if p.OriMsg == nil {
		p.OriMsg = make([]byte, 0)
	}
	for i := range arg {
		p.OriMsg = append(p.OriMsg, arg[i]...)
	}
}

// GetOriMsg 获取解包的原始数据
func (p *MiPacket) GetOriMsg() []byte {
	return p.OriMsg
}

// RebuildForNewSid 设置新的通信流水号(sessionId)且重新打包
func (p *MiPacket) RebuildForNewSid(sessionId uint32) {
	// 从某个位置开始, 替换一段内容
	f := func(buf []byte, pos int, newBuf []byte) {
		for i := 0; i < len(newBuf); i++ {
			buf[pos] = newBuf[i]
			pos++
		}
	}
	p.SessionId = sessionId
	s := utils.UintToBytes(p.SessionId)
	f(p.OriMsg, 9, s)
	p.CRC = crc32.Update(58861227, crc32.IEEETable, p.OriMsg[0:19])
	crc := utils.UintToBytes(p.CRC)
	f(p.OriMsg, len(p.OriMsg)-5, crc)
}

// ReadMessage 从指定tcp链接读取一个协议包
// @返回值 []byte 未解包的原始数据包, 如果需获取业务数据内容,
// 请调用UnPack方法后取成员变量 Data 的内容
func (p *MiPacket) ReadMessage(conn *net.Conn) ([]byte, error) {
	p.OriMsg = make([]byte, 0)
	if conn == nil {
		return p.OriMsg, fmt.Errorf("conn is nil")
	}

	headerBuf := make([]byte, p.HeaderLen())
	nRead, err := io.ReadFull(*conn, headerBuf)
	if err != nil || nRead != len(headerBuf) {
		return p.OriMsg, fmt.Errorf("read header data error, maybe conn closed:%v", err)
	}

	err = p.UnPackHead(headerBuf)
	if err != nil {
		return p.OriMsg, err
	}

	dataBuf := make([]byte, p.BodyLen())
	nRead, err = io.ReadFull(*conn, dataBuf)
	if err != nil || nRead != len(dataBuf) {
		return p.OriMsg, fmt.Errorf("read data error, maybe conn closed:%v", err)
	}

	p.SetOriMsg(headerBuf, dataBuf)
	return p.OriMsg, nil
}

// HeaderInfo 头部信息, 功能号、sid、流水号、长度等
func (p *MiPacket) HeaderInfo() string {
	return fmt.Sprintf("funcode[%d] sid[%d] serial[%d] iLen:%d",
		p.FunCode, p.SessionId, p.SerialNumber, p.Length)
}

// BuildPacket 创建包
// @bCrypto 是否对数据进行加密
func BuildPacket(funCode, sessionId, serialNum uint32, msg []byte, bCrypto bool) ([]byte, error) {
	p := new(MiPacket)
	p.FunCode = funCode
	p.SessionId = sessionId
	p.SerialNumber = serialNum
	if bCrypto && len(msg) > 0 {
		buf, _ := Encrypt(msg, AESKey, true)
		p.Data = buf
		return p.EnPack(), nil
	}

	p.Data = msg
	return p.EnPack(), nil
}
