package modbus

import (
	"encoding/binary"
	"fmt"
)

// proto address limit
const (
	AddressBroadCast = 0
	AddressMin       = 1
	addressMax       = 247
)

// AddressMax proto address max limit
// you can change with SetSpecialAddressMax,
// when your device have address upon addressMax
var AddressMax byte = addressMax

const (
	PduMinSize = 1   // funcCode(1)
	PduMaxSize = 253 // funcCode(1) + data(252)

	rtuAduMinSize = 4   // address(1) + funcCode(1) + crc(2)
	rtuAduMaxSize = 256 // address(1) + PDU(253) + crc(2)

	asciiAduMinSize       = 3
	asciiAduMaxSize       = 256
	asciiCharacterMaxSize = 513

	tcpProtocolIdentifier = 0x0000
	// Modbus Application Protocol
	tcpHeaderMbapSize = 7 // MBAP header
	tcpAduMinSize     = 8 // MBAP + funcCode
	tcpAduMaxSize     = 260
)

// proto register limit
const (
	// ReadBitsQuantityMin Bits
	ReadBitsQuantityMin  = 1    // 0x0001
	ReadBitsQuantityMax  = 2000 // 0x07d0
	WriteBitsQuantityMin = 1    // 1
	WriteBitsQuantityMax = 1968 // 0x07b0
	// ReadRegQuantityMin 16 Bits
	ReadRegQuantityMin             = 1   // 1
	ReadRegQuantityMax             = 125 // 0x007d
	WriteRegQuantityMin            = 1   // 1
	WriteRegQuantityMax            = 123 // 0x007b
	ReadWriteOnReadRegQuantityMin  = 1   // 1
	ReadWriteOnReadRegQuantityMax  = 125 // 0x007d
	ReadWriteOnWriteRegQuantityMin = 1   // 1
	ReadWriteOnWriteRegQuantityMax = 121 // 0x0079
)

// Function Code
const (
	// FuncCodeReadDiscreteInputs Bit access
	FuncCodeReadDiscreteInputs = 2  //读离散量输入
	FuncCodeReadCoils          = 1  //读线圈
	FuncCodeWriteSingleCoil    = 5  //写单个线圈
	FuncCodeWriteMultipleCoils = 15 // 写多个线圈

	// FuncCodeReadInputRegisters 16-bit access
	FuncCodeReadInputRegisters         = 4  //读输入寄存器
	FuncCodeReadHoldingRegisters       = 3  //读保持寄存器
	FuncCodeWriteSingleRegister        = 6  //写单个寄存器
	FuncCodeWriteMultipleRegisters     = 16 //写多个寄存器
	FuncCodeReadWriteMultipleRegisters = 23
	FuncCodeMaskWriteRegister          = 22
	FuncCodeReadFIFOQueue              = 24
	FuncCodeOtherReportSlaveID         = 17
	// FuncCodeDiagReadException          = 7
	// FuncCodeDiagDiagnostic             = 8
	// FuncCodeDiagGetComEventCnt         = 11
	// FuncCodeDiagGetComEventLog         = 12
)

// Exception Code
const (
	ExceptionCodeIllegalFunction                    = 1
	ExceptionCodeIllegalDataAddress                 = 2
	ExceptionCodeIllegalDataValue                   = 3
	ExceptionCodeServerDeviceFailure                = 4
	ExceptionCodeAcknowledge                        = 5
	ExceptionCodeServerDeviceBusy                   = 6
	ExceptionCodeNegativeAcknowledge                = 7
	ExceptionCodeMemoryParityError                  = 8
	ExceptionCodeGatewayPathUnavailable             = 10
	ExceptionCodeGatewayTargetDeviceFailedToRespond = 11
)

// ExceptionError implements error interface.
type ExceptionError struct {
	ExceptionCode byte
}

// Error converts known modbus exception code to error message.
func (e *ExceptionError) Error() string {
	var name string
	switch e.ExceptionCode {
	case ExceptionCodeIllegalFunction:
		name = "illegal function"
	case ExceptionCodeIllegalDataAddress:
		name = "illegal data address"
	case ExceptionCodeIllegalDataValue:
		name = "illegal data value"
	case ExceptionCodeServerDeviceFailure:
		name = "server device failure"
	case ExceptionCodeAcknowledge:
		name = "acknowledge"
	case ExceptionCodeServerDeviceBusy:
		name = "server device busy"
	case ExceptionCodeNegativeAcknowledge:
		name = "Negative Acknowledge"
	case ExceptionCodeMemoryParityError:
		name = "memory parity error"
	case ExceptionCodeGatewayPathUnavailable:
		name = "gateway path unavailable"
	case ExceptionCodeGatewayTargetDeviceFailedToRespond:
		name = "gateway target device failed to respond"
	default:
		name = "unknown"
	}
	return fmt.Sprintf("modbus: exception '%v' (%s)", e.ExceptionCode, name)
}

// CalculateResponseLength modbus RTU 响应长度
func CalculateResponseLength(adu []byte) int {
	length := rtuAduMinSize
	switch adu[1] {
	case FuncCodeReadDiscreteInputs,
		FuncCodeReadCoils:
		count := int(binary.BigEndian.Uint16(adu[4:]))
		length += 1 + count/8
		if count%8 != 0 {
			length++
		}
	case FuncCodeReadInputRegisters,
		FuncCodeReadHoldingRegisters,
		FuncCodeReadWriteMultipleRegisters:
		count := int(binary.BigEndian.Uint16(adu[4:]))
		length += 1 + count*2
	case FuncCodeWriteSingleCoil,
		FuncCodeWriteMultipleCoils,
		FuncCodeWriteSingleRegister,
		FuncCodeWriteMultipleRegisters:
		length += 4
	case FuncCodeMaskWriteRegister:
		length += 6
	case FuncCodeReadFIFOQueue:
		// undetermined
	default:
	}
	return length
}
