package modbus

import (
	"encoding/binary"
	"errors"
	"fmt"
)

const (
	Protocol = 0x00
)

const (
	ADUBaseSize = 7 + 1           // 报文头部 + 功能码
	ADURespSize = ADUBaseSize + 1 // ADUBaseSize + 寄存器数量
)

const (
	ProtocolName = "modbus"
)

const (
	Code3 = 0x03 // Holding Registers (4x)
	Code4 = 0x04 // Input Registers (3x)

	Code6  = 0x06
	Code16 = 0x10
)

// PDU Modbus TCP Request/Response PDU structure
type PDU struct {
	FunctionCode byte
	Data         []byte
}

// ADU Modbus TCP ADU structure
type ADU struct {
	TransactionID uint16
	ProtocolID    uint16
	Length        uint16
	UnitID        byte
	PDU           PDU
}

// NewADU Create a Modbus TCP ADU
func NewADU(transactionID, protocolID uint16, unitID byte, pdu PDU) ADU {
	return ADU{
		TransactionID: transactionID,
		ProtocolID:    protocolID,
		Length:        uint16(len(pdu.Data) + 2), // UnitID + FunctionCode
		UnitID:        unitID,
		PDU:           pdu,
	}
}

// ParseADU Parse a Modbus TCP ADU from bytes
func ParseADU(data []byte) (ADU, error) {
	if len(data) < ADURespSize {
		return ADU{}, errors.New("data too short to be a valid Modbus TCP ADU")
	}

	adu := ADU{
		TransactionID: binary.BigEndian.Uint16(data[0:2]),
		ProtocolID:    binary.BigEndian.Uint16(data[2:4]),
		Length:        binary.BigEndian.Uint16(data[4:6]),
		UnitID:        data[6],
	}

	pdu := PDU{
		FunctionCode: data[7],
		Data:         data[9:], // idx 8 is DataLength
	}
	adu.PDU = pdu

	return adu, nil
}

// NewPDUReadRegisters Create a Modbus PDU for function code 3 or 4
func NewPDUReadRegisters(functionCode byte, startAddress, quantity uint16) PDU {
	data := make([]byte, 4)
	binary.BigEndian.PutUint16(data[0:2], startAddress)
	binary.BigEndian.PutUint16(data[2:4], quantity)
	return PDU{
		FunctionCode: functionCode,
		Data:         data,
	}
}

// NewPDUWriteSingleRegister Create a Modbus PDU for function code 6
func NewPDUWriteSingleRegister(address, value uint16) PDU {
	data := make([]byte, 4)
	binary.BigEndian.PutUint16(data[0:2], address)
	binary.BigEndian.PutUint16(data[2:4], value)
	return PDU{
		FunctionCode: Code6,
		Data:         data,
	}
}

func NewPDUWriterSingleRegisterFromBytes(address uint16, value []byte) (PDU, error) {
	if len(value) != RegisterSize {
		return PDU{}, errors.New("quantity of values does not match provided values")
	}
	data := make([]byte, 4)
	binary.BigEndian.PutUint16(data[0:2], address)
	copy(data[2:4], value)
	return PDU{
		FunctionCode: Code6,
		Data:         data,
	}, nil
}

// NewPDUWriteMultipleRegisters Create a Modbus PDU for function code 16
func NewPDUWriteMultipleRegisters(startAddress, quantity uint16, values []uint16) (PDU, error) {
	if len(values) != int(quantity) {
		return PDU{}, errors.New("quantity of values does not match provided values")
	}
	if quantity > MaxWriteRegsQuantity {
		return PDU{}, errors.New("quantity is out of MaxWriteRegisters")
	}

	data := make([]byte, 5+2*len(values))
	binary.BigEndian.PutUint16(data[0:2], startAddress)
	binary.BigEndian.PutUint16(data[2:4], quantity)
	data[4] = byte(RegisterSize * quantity)

	for i, value := range values {
		binary.BigEndian.PutUint16(data[5+2*i:], value)
	}

	return PDU{
		FunctionCode: Code16,
		Data:         data,
	}, nil
}

func NewPDUWriterMultipleRegistersFromBytes(startAddress, quantity uint16, buf []byte) (PDU, error) {
	if len(buf)/RegisterSize != int(quantity) {
		return PDU{}, errors.New("quantity of values does not match provided values")
	}
	if quantity > MaxWriteRegsQuantity {
		return PDU{}, errors.New("quantity is out of MaxWriteRegisters")
	}

	data := make([]byte, 5+len(buf))
	binary.BigEndian.PutUint16(data[0:2], startAddress)
	binary.BigEndian.PutUint16(data[2:4], quantity)
	data[4] = byte(RegisterSize * quantity)

	copy(data[5:], buf)

	return PDU{
		FunctionCode: Code16,
		Data:         data,
	}, nil
}

// Serialize the Modbus ADU to bytes
func (adu *ADU) Serialize() []byte {
	data := make([]byte, ADUBaseSize+len(adu.PDU.Data))
	binary.BigEndian.PutUint16(data[0:2], adu.TransactionID)
	binary.BigEndian.PutUint16(data[2:4], adu.ProtocolID)
	binary.BigEndian.PutUint16(data[4:6], adu.Length)
	data[6] = adu.UnitID
	data[7] = adu.PDU.FunctionCode
	copy(data[8:], adu.PDU.Data)
	return data
}

func CheckADU(req, ret ADU) error {
	if req.TransactionID != ret.TransactionID {
		return fmt.Errorf("req.TransactionID != ret.TransactionID: %d->%d", req.TransactionID, ret.TransactionID)
	}
	if req.ProtocolID != ret.ProtocolID {
		return fmt.Errorf("req.ProtocolID != ret.ProtocolID: %d->%d", req.ProtocolID, ret.ProtocolID)
	}
	if req.UnitID != ret.UnitID {
		return fmt.Errorf("req.UnitID != ret.UnitID: %d->%d", req.UnitID, ret.UnitID)
	}
	if req.PDU.FunctionCode != ret.PDU.FunctionCode {
		return fmt.Errorf("req.PDU.FunctionCode != ret.PDU.FunctionCode: %d->%d", req.PDU.FunctionCode, ret.PDU.FunctionCode)
	}
	return nil
}

// Example usage
func main() {
	// Create a Modbus PDU for reading registers (Function code 3)
	pdu := NewPDUReadRegisters(3, 0x0000, 10)
	adu := NewADU(1, Protocol, 1, pdu)
	serializedADU := adu.Serialize()

	fmt.Printf("Serialized ADU: %x\n", serializedADU)

	// Parse the serialized ADU back to ModbusADU structure
	parsedADU, err := ParseADU(serializedADU)
	if err != nil {
		fmt.Printf("Error parsing ADU: %v\n", err)
	} else {
		fmt.Printf("Parsed ADU: %+v\n", parsedADU)
	}
}
