package modbus

import (
	"encoding/binary"
	"fmt"
)

// ProtocolDataUnit (PDU) is independent of underlying communication layers.
type ProtocolDataUnit struct {
	FuncCode byte
	Data     []byte
}

func EncodeRTUFrame(slaveID byte, pdu ProtocolDataUnit) ([]byte, error) {
	length := len(pdu.Data) + 4
	if length > rtuAduMaxSize {
		return nil, fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'",
			length, rtuAduMaxSize)
	}
	requestAdu := make([]byte, 0, length)
	requestAdu = append(requestAdu, slaveID, pdu.FuncCode)
	requestAdu = append(requestAdu, pdu.Data...)
	checksum := CRC16(requestAdu)
	requestAdu = append(requestAdu, byte(checksum), byte(checksum>>8))
	return requestAdu, nil
}

// DecodeRTUFrame decode extracts slaveID and PDU from RTU frame and verify CRC.
func DecodeRTUFrame(adu []byte) (uint8, []byte, error) {
	if len(adu) < rtuAduMinSize { // Minimum size (including address, funcCode and CRC)
		return 0, nil, fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'",
			len(adu), rtuAduMinSize)
	}
	// Calculate checksum
	crc := CRC16(adu[0 : len(adu)-2])
	expect := binary.LittleEndian.Uint16(adu[len(adu)-2:])
	if crc != expect {
		return 0, nil, fmt.Errorf("modbus: response crc '%x' does not match expected '%x'",
			expect, crc)
	}
	// slaveID & PDU but pass crc
	return adu[0], adu[1 : len(adu)-2], nil
}

// Verify verify confirms valid data(including slaveID,funcCode,response data)
//其中rspPDU = ProtocolDataUnit{pdu[0], pdu[1:]}
func Verify(reqSlaveID, rspSlaveID uint8, reqPDU, rspPDU ProtocolDataUnit) error {
	switch {
	case reqSlaveID != rspSlaveID: // Check slaveID same
		return fmt.Errorf("modbus: response slave id '%v' does not match request '%v'",
			rspSlaveID, reqSlaveID)

	case rspPDU.FuncCode != reqPDU.FuncCode: // Check correct function code returned (exception)
		return responseError(rspPDU)

	case rspPDU.Data == nil || len(rspPDU.Data) == 0: // check Empty response
		return fmt.Errorf("modbus: response data is empty")
	}
	return nil
}

// responseError response error
func responseError(response ProtocolDataUnit) error {
	mbError := &ExceptionError{}
	if response.Data != nil && len(response.Data) > 0 {
		mbError.ExceptionCode = response.Data[0]
	}
	return mbError
}
