package modbus

import (
	"bytes"
	"encoding/binary"
	"fmt"

	"golib/v2/gnet"
)

const (
	ProtocolModbus = 0x0000
)

const (
	FuncCode3  uint8 = 0x03 // FuncCode3 功能码 03 读取多个连续保持寄存器
	FuncCode04 uint8 = 0x04 // FuncCode04 功能码 04 读取输入寄存器
	FuncCode06 uint8 = 0x06 // FuncCode06 功能码 06 写入单个保持寄存器
	FuncCode16 uint8 = 0x10 // FuncCode16 功能码 16 写入多个连续保持寄存器
)

const (
	MinTCPReqSize  = 6
	MinTCPRespSize = 9
)

type TCPRequest struct {
	TransactionID uint16 // TransactionID 事务标识符
	ProtocolID    uint16 // ProtocolID 协议标识符, 通常情况下为 ProtocolModbus
	length        uint16 // length 剩余数据长度, 不包含 TransactionID 和 ProtocolID
	UnitID        uint8  // UnitID 单元标识符, 即起设备 ID
	FunctionCode  uint8  // FunctionCode 功能码
	StartNo       uint16 // StartNo 起始地址
	RegisterLen   uint16 // RegisterLen 根据 StartNo 的连续读取或写入的寄存器数量
	dataLength    uint8  // DataLength Data 的数据长度
	Data          []byte // Data 需要写入的数据
}

func (m *TCPRequest) Pack() []byte {
	b := make([]byte, 12)

	gnet.BigEndian.PutUint16(b[0:], m.TransactionID)
	gnet.BigEndian.PutUint16(b[2:], m.ProtocolID)

	b[6] = m.UnitID
	b[7] = m.FunctionCode

	gnet.BigEndian.PutUint16(b[8:], m.StartNo)
	gnet.BigEndian.PutUint16(b[10:], m.RegisterLen)

	if m.FunctionCode == FuncCode16 {
		m.length++                        // 加 1 表示多一个 Data 长度字段
		m.dataLength = uint8(len(m.Data)) // 补充写入数据大小
		b = append(b, m.dataLength)
	}

	if len(m.Data) > 0 {
		b = append(b, m.Data...)
	}

	// 6 表示从 UnitID 至 RegisterLen 固定长度
	m.length = m.length + 6 + uint16(len(m.Data))

	gnet.BigEndian.PutUint16(b[4:6], m.length)
	return b
}

type TCPResponse struct {
	TransactionID uint16 // TransactionID 事务标识符
	ProtocolID    uint16 // ProtocolID 协议标识符, 通常情况下为 0x0000
	Length        uint16 // Length 数据长度, 不包含 TransactionID 和 ProtocolID
	UnitID        uint8  // UnitID 单元标识符, 即起设备 ID
	FunctionCode  uint8  // FunctionCode 功能码
	DataLength    uint8  // DataLength Data 的数据长度
	Data          []byte // Data 返回的数据
}

func (m *TCPResponse) UnpackRequest(b []byte, r *TCPRequest) error {
	if err := m.Unpack(b); err != nil {
		return err
	}
	if r.TransactionID != m.TransactionID {
		return fmt.Errorf("TransactionID: request is not equal to that of the response")
	}
	if r.ProtocolID != m.ProtocolID {
		return fmt.Errorf("ProtocolID: request is not equal to that of the response")
	}
	if r.FunctionCode != m.FunctionCode {
		return fmt.Errorf("FunctionCode: request is not equal to that of the response")
	}
	return nil
}

func (m *TCPResponse) Unpack(b []byte) error {
	if len(b) < MinTCPRespSize {
		return fmt.Errorf("data too short: %d", len(b))
	}
	buf := bytes.NewReader(b)

	if err := binary.Read(buf, gnet.BigEndian, &m.TransactionID); err != nil {
		return err
	}
	if err := binary.Read(buf, gnet.BigEndian, &m.ProtocolID); err != nil {
		return err
	}
	if err := binary.Read(buf, gnet.BigEndian, &m.Length); err != nil {
		return err
	}
	if err := binary.Read(buf, gnet.BigEndian, &m.UnitID); err != nil {
		return err
	}
	if err := binary.Read(buf, gnet.BigEndian, &m.FunctionCode); err != nil {
		return err
	}
	if err := binary.Read(buf, gnet.BigEndian, &m.DataLength); err != nil {
		return err
	}

	m.Data = make([]byte, m.DataLength)
	_, err := buf.Read(m.Data)
	return err
}
