package modbus

import (
	"fmt"
	"strconv"
	"testing"
	"time"

	mb "github.com/grid-x/modbus"
	"github.com/stretchr/testify/require"
	"github.com/tbrandon/mbserver"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/metric"
	"github.com/influxdata/telegraf/testutil"
)

func TestRegister(t *testing.T) {
	modbus := Modbus{
		Name:              "TestRetryFailExhausted",
		Controller:        "tcp://localhost:1502",
		ConfigurationType: "register",
		Log:               testutil.Logger{},
	}
	modbus.SlaveID = 1
	modbus.Coils = []fieldDefinition{
		{
			Name:    "coil",
			Address: []uint16{0},
		},
	}
	modbus.DiscreteInputs = []fieldDefinition{
		{
			Name:    "discrete",
			Address: []uint16{0},
		},
	}
	modbus.HoldingRegisters = []fieldDefinition{
		{
			Name:      "holding",
			Address:   []uint16{0},
			DataType:  "INT16",
			ByteOrder: "AB",
			Scale:     1.0,
		},
	}
	modbus.InputRegisters = []fieldDefinition{
		{
			Name:      "input",
			Address:   []uint16{0},
			DataType:  "INT16",
			ByteOrder: "AB",
			Scale:     1.0,
		},
	}

	require.NoError(t, modbus.Init())
	require.NotEmpty(t, modbus.requests)
	require.NotNil(t, modbus.requests[1])
	require.Len(t, modbus.requests[1].coil, len(modbus.Coils))
	require.Len(t, modbus.requests[1].discrete, len(modbus.DiscreteInputs))
	require.Len(t, modbus.requests[1].holding, len(modbus.HoldingRegisters))
	require.Len(t, modbus.requests[1].input, len(modbus.InputRegisters))
}

func TestRegisterCoils(t *testing.T) {
	var coilTests = []struct {
		name     string
		address  uint16
		dtype    string
		quantity uint16
		write    []byte
		read     interface{}
	}{
		{
			name:     "coil0_turn_off",
			address:  0,
			quantity: 1,
			write:    []byte{0x00},
			read:     uint16(0),
		},
		{
			name:     "coil0_turn_on",
			address:  0,
			quantity: 1,
			write:    []byte{0x01},
			read:     uint16(1),
		},
		{
			name:     "coil1_turn_on",
			address:  1,
			quantity: 1,
			write:    []byte{0x01},
			read:     uint16(1),
		},
		{
			name:     "coil2_turn_on",
			address:  2,
			quantity: 1,
			write:    []byte{0x01},
			read:     uint16(1),
		},
		{
			name:     "coil3_turn_on",
			address:  3,
			quantity: 1,
			write:    []byte{0x01},
			read:     uint16(1),
		},
		{
			name:     "coil1_turn_off",
			address:  1,
			quantity: 1,
			write:    []byte{0x00},
			read:     uint16(0),
		},
		{
			name:     "coil2_turn_off",
			address:  2,
			quantity: 1,
			write:    []byte{0x00},
			read:     uint16(0),
		},
		{
			name:     "coil3_turn_off",
			address:  3,
			quantity: 1,
			write:    []byte{0x00},
			read:     uint16(0),
		},
		{
			name:     "coil4_turn_off",
			address:  4,
			quantity: 1,
			write:    []byte{0x00},
			read:     uint16(0),
		},
		{
			name:     "coil4_turn_on",
			address:  4,
			quantity: 1,
			write:    []byte{0x01},
			read:     uint16(1),
		},
		{
			name:     "coil4_turn_off_bool",
			address:  4,
			quantity: 1,
			dtype:    "BOOL",
			write:    []byte{0x00},
			read:     false,
		},
		{
			name:     "coil4_turn_on_bool",
			address:  4,
			quantity: 1,
			dtype:    "BOOL",
			write:    []byte{0x01},
			read:     true,
		},
	}

	serv := mbserver.NewServer()
	require.NoError(t, serv.ListenTCP("localhost:1502"))
	defer serv.Close()

	handler := mb.NewTCPClientHandler("localhost:1502")
	require.NoError(t, handler.Connect())
	defer handler.Close()
	client := mb.NewClient(handler)

	for _, ct := range coilTests {
		t.Run(ct.name, func(t *testing.T) {
			_, err := client.WriteMultipleCoils(ct.address, ct.quantity, ct.write)
			require.NoError(t, err)

			modbus := Modbus{
				Name:       "TestCoils",
				Controller: "tcp://localhost:1502",
				Log:        testutil.Logger{},
			}
			modbus.SlaveID = 1
			modbus.Coils = []fieldDefinition{
				{
					Name:     ct.name,
					Address:  []uint16{ct.address},
					DataType: ct.dtype,
				},
			}

			expected := []telegraf.Metric{
				metric.New(
					"modbus",
					map[string]string{
						"type":     cCoils,
						"slave_id": strconv.Itoa(int(modbus.SlaveID)),
						"name":     modbus.Name,
					},
					map[string]interface{}{ct.name: ct.read},
					time.Unix(0, 0),
				),
			}

			var acc testutil.Accumulator
			require.NoError(t, modbus.Init())
			require.NotEmpty(t, modbus.requests)
			require.NoError(t, modbus.Gather(&acc))
			acc.Wait(len(expected))

			testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
		})
	}
}

func TestRegisterHoldingRegisters(t *testing.T) {
	var holdingRegisterTests = []struct {
		name      string
		address   []uint16
		quantity  uint16
		bit       uint8
		byteOrder string
		dataType  string
		scale     float64
		write     []byte
		read      interface{}
	}{
		{
			name:      "register5_bit3",
			address:   []uint16{5},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "BIT",
			bit:       3,
			write:     []byte{0x18, 0x0d},
			read:      uint8(1),
		},
		{
			name:      "register5_bit14",
			address:   []uint16{5},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "BIT",
			bit:       14,
			write:     []byte{0x18, 0x0d},
			read:      uint8(0),
		},
		{
			name:      "register0_ab_float",
			address:   []uint16{0},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "FIXED",
			scale:     0.1,
			write:     []byte{0xFF, 0xD6},
			read:      float64(-4.2),
		},
		{
			name:      "register1_ba_ufloat",
			address:   []uint16{1},
			quantity:  1,
			byteOrder: "BA",
			dataType:  "UFIXED",
			scale:     0.1,
			write:     []byte{0xD8, 0xFF},
			read:      float64(6549.6),
		},
		{
			name:      "register4_register5_abcd_float",
			address:   []uint16{4, 5},
			quantity:  2,
			byteOrder: "ABCD",
			dataType:  "FIXED",
			scale:     0.1,
			write:     []byte{0xFF, 0xFF, 0xFF, 0xD6},
			read:      float64(-4.2),
		},
		{
			name:      "register5_register6_dcba_ufloat",
			address:   []uint16{5, 6},
			quantity:  2,
			byteOrder: "DCBA",
			dataType:  "UFIXED",
			scale:     0.001,
			write:     []byte{0xD8, 0xFF, 0xFF, 0xFF},
			read:      float64(4294967.256),
		},
		{
			name:      "register5_to_register8_abcdefgh_float",
			address:   []uint16{5, 6, 7, 8},
			quantity:  4,
			byteOrder: "ABCDEFGH",
			dataType:  "FIXED",
			scale:     0.000001,
			write:     []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD6},
			read:      float64(-0.000042),
		},
		{
			name:      "register6_to_register9_hgfedcba_ufloat",
			address:   []uint16{6, 7, 8, 9},
			quantity:  4,
			byteOrder: "HGFEDCBA",
			dataType:  "UFIXED",
			scale:     0.000000001,
			write:     []byte{0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
			read:      float64(18441921395.520346504),
		},
		{
			name:      "register20_uint16",
			address:   []uint16{10},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "UINT8L",
			scale:     1,
			write:     []byte{0x18, 0x0D},
			read:      uint8(13),
		},
		{
			name:      "register20_uint16-scale_.1",
			address:   []uint16{10},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "UINT8L",
			scale:     .1,
			write:     []byte{0x18, 0x0D},
			read:      uint8(1),
		},
		{
			name:      "register20_uint16_scale_10",
			address:   []uint16{10},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "UINT8L",
			scale:     10,
			write:     []byte{0x18, 0x0D},
			read:      uint8(130),
		},
		{
			name:      "register11_uint8H",
			address:   []uint16{11},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "UINT8H",
			scale:     1,
			write:     []byte{0x18, 0x0D},
			read:      uint8(24),
		},
		{
			name:      "register11_uint8L-scale_.1",
			address:   []uint16{11},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "UINT8H",
			scale:     .1,
			write:     []byte{0x18, 0x0D},
			read:      uint8(2),
		},
		{
			name:      "register11_uint8L_scale_10",
			address:   []uint16{11},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "UINT8H",
			scale:     10,
			write:     []byte{0x18, 0x0D},
			read:      uint8(240),
		},
		{
			name:      "register12_int8L",
			address:   []uint16{12},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "INT8L",
			scale:     1,
			write:     []byte{0x98, 0x8D},
			read:      int8(-115),
		},
		{
			name:      "register12_int8L-scale_.1",
			address:   []uint16{12},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "INT8L",
			scale:     .1,
			write:     []byte{0x98, 0x8D},
			read:      int8(-11),
		},
		{
			name:      "register12_int8L_scale_10",
			address:   []uint16{12},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "INT8L",
			scale:     10,
			write:     []byte{0x98, 0xF8},
			read:      int8(-80),
		},
		{
			name:      "register13_int8H",
			address:   []uint16{13},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "INT8H",
			scale:     1,
			write:     []byte{0x98, 0x8D},
			read:      int8(-104),
		},
		{
			name:      "register13_int8H-scale_.1",
			address:   []uint16{13},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "INT8H",
			scale:     .1,
			write:     []byte{0x98, 0x8D},
			read:      int8(-10),
		},
		{
			name:      "register13_int8H_scale_10",
			address:   []uint16{13},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "INT8H",
			scale:     10,
			write:     []byte{0xFD, 0x8D},
			read:      int8(-30),
		},
		{
			name:      "register15_ab_uint16",
			address:   []uint16{15},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "UINT16",
			scale:     1,
			write:     []byte{0xAB, 0xCD},
			read:      uint16(43981),
		},
		{
			name:      "register15_ab_uint16-scale_.1",
			address:   []uint16{15},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "UINT16",
			scale:     .1,
			write:     []byte{0xAB, 0xCD},
			read:      uint16(4398),
		},
		{
			name:      "register15_ab_uint16_scale_10",
			address:   []uint16{15},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "UINT16",
			scale:     10,
			write:     []byte{0x00, 0x2A},
			read:      uint16(420),
		},
		{
			name:      "register20_ba_uint16",
			address:   []uint16{20},
			quantity:  1,
			byteOrder: "BA",
			dataType:  "UINT16",
			scale:     1,
			write:     []byte{0xAB, 0xCD},
			read:      uint16(52651),
		},
		{
			name:      "register30_ab_int16",
			address:   []uint16{20},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "INT16",
			scale:     1,
			write:     []byte{0xAB, 0xCD},
			read:      int16(-21555),
		},
		{
			name:      "register40_ba_int16",
			address:   []uint16{40},
			quantity:  1,
			byteOrder: "BA",
			dataType:  "INT16",
			scale:     1,
			write:     []byte{0xAB, 0xCD},
			read:      int16(-12885),
		},
		{
			name:      "register50_register51_abcd_int32_scaled",
			address:   []uint16{50, 51},
			quantity:  2,
			byteOrder: "ABCD",
			dataType:  "INT32",
			scale:     10,
			write:     []byte{0x00, 0x00, 0xAB, 0xCD},
			read:      int32(439810),
		},
		{
			name:      "register50_register51_abcd_int32",
			address:   []uint16{50, 51},
			quantity:  2,
			byteOrder: "ABCD",
			dataType:  "INT32",
			scale:     1,
			write:     []byte{0xAA, 0xBB, 0xCC, 0xDD},
			read:      int32(-1430532899),
		},
		{
			name:      "register60_register61_dcba_int32",
			address:   []uint16{60, 61},
			quantity:  2,
			byteOrder: "DCBA",
			dataType:  "INT32",
			scale:     1,
			write:     []byte{0xAA, 0xBB, 0xCC, 0xDD},
			read:      int32(-573785174),
		},
		{
			name:      "register70_register71_badc_int32",
			address:   []uint16{70, 71},
			quantity:  2,
			byteOrder: "BADC",
			dataType:  "INT32",
			scale:     1,
			write:     []byte{0xAA, 0xBB, 0xCC, 0xDD},
			read:      int32(-1146430004),
		},
		{
			name:      "register80_register81_cdab_int32",
			address:   []uint16{80, 81},
			quantity:  2,
			byteOrder: "CDAB",
			dataType:  "INT32",
			scale:     1,
			write:     []byte{0xAA, 0xBB, 0xCC, 0xDD},
			read:      int32(-857888069),
		},
		{
			name:      "register90_register91_abcd_uint32",
			address:   []uint16{90, 91},
			quantity:  2,
			byteOrder: "ABCD",
			dataType:  "UINT32",
			scale:     1,
			write:     []byte{0xAA, 0xBB, 0xCC, 0xDD},
			read:      uint32(2864434397),
		},
		{
			name:      "register100_register101_dcba_uint32",
			address:   []uint16{100, 101},
			quantity:  2,
			byteOrder: "DCBA",
			dataType:  "UINT32",
			scale:     1,
			write:     []byte{0xAA, 0xBB, 0xCC, 0xDD},
			read:      uint32(3721182122),
		},
		{
			name:      "register110_register111_badc_uint32",
			address:   []uint16{110, 111},
			quantity:  2,
			byteOrder: "BADC",
			dataType:  "UINT32",
			scale:     1,
			write:     []byte{0xAA, 0xBB, 0xCC, 0xDD},
			read:      uint32(3148537292),
		},
		{
			name:      "register120_register121_cdab_uint32",
			address:   []uint16{120, 121},
			quantity:  2,
			byteOrder: "CDAB",
			dataType:  "UINT32",
			scale:     1,
			write:     []byte{0xAA, 0xBB, 0xCC, 0xDD},
			read:      uint32(3437079227),
		},
		{
			name:      "register130_register131_abcd_float32_ieee",
			address:   []uint16{130, 131},
			quantity:  2,
			byteOrder: "ABCD",
			dataType:  "FLOAT32-IEEE",
			scale:     1,
			write:     []byte{0xAA, 0xBB, 0xCC, 0xDD},
			read:      float32(-3.3360025e-13),
		},
		{
			name:      "register130_register131_abcd_float32_ieee_scaled",
			address:   []uint16{130, 131},
			quantity:  2,
			byteOrder: "ABCD",
			dataType:  "FLOAT32-IEEE",
			scale:     10,
			write:     []byte{0xAA, 0xBB, 0xCC, 0xDD},
			read:      float32(-3.3360025e-12),
		},
		{
			name:      "register140_to_register143_abcdefgh_int64_scaled",
			address:   []uint16{140, 141, 142, 143},
			quantity:  4,
			byteOrder: "ABCDEFGH",
			dataType:  "INT64",
			scale:     10,
			write:     []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xAB, 0xCD},
			read:      int64(10995116717570),
		},
		{
			name:      "register140_to_register143_abcdefgh_int64",
			address:   []uint16{140, 141, 142, 143},
			quantity:  4,
			byteOrder: "ABCDEFGH",
			dataType:  "INT64",
			scale:     1,
			write:     []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xAB, 0xCD},
			read:      int64(1099511671757),
		},
		{
			name:      "register150_to_register153_hgfedcba_int64",
			address:   []uint16{150, 151, 152, 153},
			quantity:  4,
			byteOrder: "HGFEDCBA",
			dataType:  "INT64",
			scale:     1,
			write:     []byte{0x84, 0xF6, 0x45, 0xF9, 0xBC, 0xFE, 0xFF, 0xFF},
			read:      int64(-1387387292028),
		},
		{
			name:      "register160_to_register163_badcfehg_int64",
			address:   []uint16{160, 161, 162, 163},
			quantity:  4,
			byteOrder: "BADCFEHG",
			dataType:  "INT64",
			scale:     1,
			write:     []byte{0xFF, 0xFF, 0xBC, 0xFE, 0x45, 0xF9, 0x84, 0xF6},
			read:      int64(-1387387292028),
		},
		{
			name:      "register170_to_register173_ghefcdab_int64",
			address:   []uint16{170, 171, 172, 173},
			quantity:  4,
			byteOrder: "GHEFCDAB",
			dataType:  "INT64",
			scale:     1,
			write:     []byte{0xF6, 0x84, 0xF9, 0x45, 0xFE, 0xBC, 0xFF, 0xFF},
			read:      int64(-1387387292028),
		},
		{
			name:      "register180_to_register183_abcdefgh_uint64_scaled",
			address:   []uint16{180, 181, 182, 183},
			quantity:  4,
			byteOrder: "ABCDEFGH",
			dataType:  "UINT64",
			scale:     10,
			write:     []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xAB, 0xCD},
			read:      uint64(10995116717570),
		},
		{
			name:      "register180_to_register183_abcdefgh_uint64",
			address:   []uint16{180, 181, 182, 183},
			quantity:  4,
			byteOrder: "ABCDEFGH",
			dataType:  "UINT64",
			scale:     1,
			write:     []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xAB, 0xCD},
			read:      uint64(1099511671757),
		},
		{
			name:      "register190_to_register193_hgfedcba_uint64",
			address:   []uint16{190, 191, 192, 193},
			quantity:  4,
			byteOrder: "HGFEDCBA",
			dataType:  "UINT64",
			scale:     1,
			write:     []byte{0x84, 0xF6, 0x45, 0xF9, 0xBC, 0xFE, 0xFF, 0xFF},
			read:      uint64(18446742686322259968),
		},
		{
			name:      "register200_to_register203_badcfehg_uint64",
			address:   []uint16{200, 201, 202, 203},
			quantity:  4,
			byteOrder: "BADCFEHG",
			dataType:  "UINT64",
			scale:     1,
			write:     []byte{0xFF, 0xFF, 0xBC, 0xFE, 0x45, 0xF9, 0x84, 0xF6},
			read:      uint64(18446742686322259968),
		},
		{
			name:      "register210_to_register213_ghefcdab_uint64",
			address:   []uint16{210, 211, 212, 213},
			quantity:  4,
			byteOrder: "GHEFCDAB",
			dataType:  "UINT64",
			scale:     1,
			write:     []byte{0xF6, 0x84, 0xF9, 0x45, 0xFE, 0xBC, 0xFF, 0xFF},
			read:      uint64(18446742686322259968),
		},
		{
			name:      "register214_to_register217_abcdefgh_float64_ieee",
			address:   []uint16{214, 215, 216, 217},
			quantity:  4,
			byteOrder: "ABCDEFGH",
			dataType:  "FLOAT64-IEEE",
			scale:     1,
			write:     []byte{0xBF, 0x9C, 0x6A, 0x40, 0xC3, 0x47, 0x8F, 0x55},
			read:      float64(-0.02774907295123737),
		},
		{
			name:      "register214_to_register217_abcdefgh_float64_ieee_scaled",
			address:   []uint16{214, 215, 216, 217},
			quantity:  4,
			byteOrder: "ABCDEFGH",
			dataType:  "FLOAT64-IEEE",
			scale:     0.1,
			write:     []byte{0xBF, 0x9C, 0x6A, 0x40, 0xC3, 0x47, 0x8F, 0x55},
			read:      float64(-0.002774907295123737),
		},
		{
			name:      "register218_to_register221_abcdefgh_float64_ieee_pos",
			address:   []uint16{218, 219, 220, 221},
			quantity:  4,
			byteOrder: "ABCDEFGH",
			dataType:  "FLOAT64-IEEE",
			scale:     1,
			write:     []byte{0x3F, 0x9C, 0x6A, 0x40, 0xC3, 0x47, 0x8F, 0x55},
			read:      float64(0.02774907295123737),
		},
		{
			name:      "register222_to_register225_hgfecdba_float64_ieee",
			address:   []uint16{222, 223, 224, 225},
			quantity:  4,
			byteOrder: "HGFEDCBA",
			dataType:  "FLOAT64-IEEE",
			scale:     1,
			write:     []byte{0x55, 0x8F, 0x47, 0xC3, 0x40, 0x6A, 0x9C, 0xBF},
			read:      float64(-0.02774907295123737),
		},
		{
			name:      "register226_to_register229_badcfehg_float64_ieee",
			address:   []uint16{226, 227, 228, 229},
			quantity:  4,
			byteOrder: "BADCFEHG",
			dataType:  "FLOAT64-IEEE",
			scale:     1,
			write:     []byte{0x9C, 0xBF, 0x40, 0x6A, 0x47, 0xC3, 0x55, 0x8F},
			read:      float64(-0.02774907295123737),
		},
		{
			name:      "register230_to_register233_ghefcdab_float64_ieee",
			address:   []uint16{230, 231, 232, 233},
			quantity:  4,
			byteOrder: "GHEFCDAB",
			dataType:  "FLOAT64-IEEE",
			scale:     1,
			write:     []byte{0x8F, 0x55, 0xC3, 0x47, 0x6A, 0x40, 0xBF, 0x9C},
			read:      float64(-0.02774907295123737),
		},
		{
			name:      "register240_abcd_float16",
			address:   []uint16{240},
			quantity:  1,
			byteOrder: "AB",
			dataType:  "FLOAT16-IEEE",
			scale:     1,
			write:     []byte{0xb8, 0x14},
			read:      float64(-0.509765625),
		},
		{
			name:      "register240_dcba_float16",
			address:   []uint16{240},
			quantity:  1,
			byteOrder: "BA",
			dataType:  "FLOAT16-IEEE",
			scale:     1,
			write:     []byte{0x14, 0xb8},
			read:      float64(-0.509765625),
		},
		{
			name:      "register250_abcd_string",
			address:   []uint16{250, 251, 252, 253, 254, 255, 256},
			quantity:  7,
			byteOrder: "AB",
			dataType:  "STRING",
			write:     []byte{0x4d, 0x6f, 0x64, 0x62, 0x75, 0x73, 0x20, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x00},
			read:      "Modbus String",
		},
		{
			name:      "register250_dcba_string",
			address:   []uint16{250, 251, 252, 253, 254, 255, 256},
			quantity:  7,
			byteOrder: "BA",
			dataType:  "STRING",
			write:     []byte{0x6f, 0x4d, 0x62, 0x64, 0x73, 0x75, 0x53, 0x20, 0x72, 0x74, 0x6e, 0x69, 0x00, 0x67},
			read:      "Modbus String",
		},
	}

	serv := mbserver.NewServer()
	require.NoError(t, serv.ListenTCP("localhost:1502"))
	defer serv.Close()

	handler := mb.NewTCPClientHandler("localhost:1502")
	require.NoError(t, handler.Connect())
	defer handler.Close()
	client := mb.NewClient(handler)

	for _, hrt := range holdingRegisterTests {
		t.Run(hrt.name, func(t *testing.T) {
			_, err := client.WriteMultipleRegisters(hrt.address[0], hrt.quantity, hrt.write)
			require.NoError(t, err)

			modbus := Modbus{
				Name:       "TestHoldingRegisters",
				Controller: "tcp://localhost:1502",
				Log:        testutil.Logger{},
			}
			modbus.SlaveID = 1
			modbus.HoldingRegisters = []fieldDefinition{
				{
					Name:      hrt.name,
					ByteOrder: hrt.byteOrder,
					DataType:  hrt.dataType,
					Scale:     hrt.scale,
					Address:   hrt.address,
					Bit:       hrt.bit,
				},
			}

			expected := []telegraf.Metric{
				metric.New(
					"modbus",
					map[string]string{
						"type":     cHoldingRegisters,
						"slave_id": strconv.Itoa(int(modbus.SlaveID)),
						"name":     modbus.Name,
					},
					map[string]interface{}{hrt.name: hrt.read},
					time.Unix(0, 0),
				),
			}

			var acc testutil.Accumulator
			require.NoError(t, modbus.Init())
			require.NotEmpty(t, modbus.requests)
			require.NoError(t, modbus.Gather(&acc))
			acc.Wait(len(expected))

			testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
		})
	}
}

func TestRegisterReadMultipleCoilWithHole(t *testing.T) {
	serv := mbserver.NewServer()
	require.NoError(t, serv.ListenTCP("localhost:1502"))
	defer serv.Close()

	handler := mb.NewTCPClientHandler("localhost:1502")
	require.NoError(t, handler.Connect())
	defer handler.Close()
	client := mb.NewClient(handler)

	fcs := make([]fieldDefinition, 0, 26)
	expectedFields := make(map[string]interface{})
	writeValue := uint16(0)
	readValue := uint16(0)
	for i := 0; i < 14; i++ {
		fc := fieldDefinition{}
		fc.Name = fmt.Sprintf("coil-%v", i)
		fc.Address = []uint16{uint16(i)}
		fcs = append(fcs, fc)

		_, err := client.WriteSingleCoil(fc.Address[0], writeValue)
		require.NoError(t, err)

		expectedFields[fc.Name] = readValue
		writeValue = 65280 - writeValue
		readValue = 1 - readValue
	}
	for i := 15; i < 18; i++ {
		fc := fieldDefinition{}
		fc.Name = fmt.Sprintf("coil-%v", i)
		fc.Address = []uint16{uint16(i)}
		fcs = append(fcs, fc)

		_, err := client.WriteSingleCoil(fc.Address[0], writeValue)
		require.NoError(t, err)

		expectedFields[fc.Name] = readValue
		writeValue = 65280 - writeValue
		readValue = 1 - readValue
	}
	for i := 24; i < 33; i++ {
		fc := fieldDefinition{}
		fc.Name = fmt.Sprintf("coil-%v", i)
		fc.Address = []uint16{uint16(i)}
		fcs = append(fcs, fc)

		_, err := client.WriteSingleCoil(fc.Address[0], writeValue)
		require.NoError(t, err)

		expectedFields[fc.Name] = readValue
		writeValue = 65280 - writeValue
		readValue = 1 - readValue
	}
	require.Len(t, expectedFields, len(fcs))

	modbus := Modbus{
		Name:       "TestReadMultipleCoilWithHole",
		Controller: "tcp://localhost:1502",
		Log:        testutil.Logger{Name: "modbus:MultipleCoilWithHole"},
	}
	modbus.SlaveID = 1
	modbus.Coils = fcs

	expected := []telegraf.Metric{
		metric.New(
			"modbus",
			map[string]string{
				"type":     cCoils,
				"slave_id": strconv.Itoa(int(modbus.SlaveID)),
				"name":     modbus.Name,
			},
			expectedFields,
			time.Unix(0, 0),
		),
	}

	var acc testutil.Accumulator
	require.NoError(t, modbus.Init())
	require.NotEmpty(t, modbus.requests)
	require.NoError(t, modbus.Gather(&acc))
	acc.Wait(len(expected))

	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestRegisterReadMultipleCoilLimit(t *testing.T) {
	serv := mbserver.NewServer()
	require.NoError(t, serv.ListenTCP("localhost:1502"))
	defer serv.Close()

	handler := mb.NewTCPClientHandler("localhost:1502")
	require.NoError(t, handler.Connect())
	defer handler.Close()
	client := mb.NewClient(handler)

	fcs := make([]fieldDefinition, 0, 4000)
	expectedFields := make(map[string]interface{})
	writeValue := uint16(0)
	readValue := uint16(0)
	for i := 0; i < 4000; i++ {
		fc := fieldDefinition{}
		fc.Name = fmt.Sprintf("coil-%v", i)
		fc.Address = []uint16{uint16(i)}
		fcs = append(fcs, fc)

		_, err := client.WriteSingleCoil(fc.Address[0], writeValue)
		require.NoError(t, err)

		expectedFields[fc.Name] = readValue
		writeValue = 65280 - writeValue
		readValue = 1 - readValue
	}
	require.Len(t, expectedFields, len(fcs))

	modbus := Modbus{
		Name:       "TestReadCoils",
		Controller: "tcp://localhost:1502",
		Log:        testutil.Logger{},
	}
	modbus.SlaveID = 1
	modbus.Coils = fcs

	expected := []telegraf.Metric{
		metric.New(
			"modbus",
			map[string]string{
				"type":     cCoils,
				"slave_id": strconv.Itoa(int(modbus.SlaveID)),
				"name":     modbus.Name,
			},
			expectedFields,
			time.Unix(0, 0),
		),
	}

	var acc testutil.Accumulator
	require.NoError(t, modbus.Init())
	require.NotEmpty(t, modbus.requests)
	require.NoError(t, modbus.Gather(&acc))
	acc.Wait(len(expected))

	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestRegisterReadMultipleHoldingRegisterWithHole(t *testing.T) {
	serv := mbserver.NewServer()
	require.NoError(t, serv.ListenTCP("localhost:1502"))
	defer serv.Close()

	handler := mb.NewTCPClientHandler("localhost:1502")
	require.NoError(t, handler.Connect())
	defer handler.Close()
	client := mb.NewClient(handler)

	fcs := make([]fieldDefinition, 0, 20)
	expectedFields := make(map[string]interface{})
	for i := 0; i < 10; i++ {
		fc := fieldDefinition{
			Name:      fmt.Sprintf("HoldingRegister-%v", i),
			ByteOrder: "AB",
			DataType:  "INT16",
			Scale:     1.0,
			Address:   []uint16{uint16(i)},
		}
		fcs = append(fcs, fc)

		_, err := client.WriteSingleRegister(fc.Address[0], uint16(i))
		require.NoError(t, err)

		expectedFields[fc.Name] = int64(i)
	}
	for i := 20; i < 30; i++ {
		fc := fieldDefinition{
			Name:      fmt.Sprintf("HoldingRegister-%v", i),
			ByteOrder: "AB",
			DataType:  "INT16",
			Scale:     1.0,
			Address:   []uint16{uint16(i)},
		}
		fcs = append(fcs, fc)

		_, err := client.WriteSingleRegister(fc.Address[0], uint16(i))
		require.NoError(t, err)

		expectedFields[fc.Name] = int64(i)
	}
	require.Len(t, expectedFields, len(fcs))

	modbus := Modbus{
		Name:       "TestHoldingRegister",
		Controller: "tcp://localhost:1502",
		Log:        testutil.Logger{},
	}
	modbus.SlaveID = 1
	modbus.HoldingRegisters = fcs

	expected := []telegraf.Metric{
		metric.New(
			"modbus",
			map[string]string{
				"type":     cHoldingRegisters,
				"slave_id": strconv.Itoa(int(modbus.SlaveID)),
				"name":     modbus.Name,
			},
			expectedFields,
			time.Unix(0, 0),
		),
	}

	var acc testutil.Accumulator
	require.NoError(t, modbus.Init())
	require.NotEmpty(t, modbus.requests)
	require.NoError(t, modbus.Gather(&acc))
	acc.Wait(len(expected))

	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestRegisterReadMultipleHoldingRegisterLimit(t *testing.T) {
	serv := mbserver.NewServer()
	require.NoError(t, serv.ListenTCP("localhost:1502"))
	defer serv.Close()

	handler := mb.NewTCPClientHandler("localhost:1502")
	require.NoError(t, handler.Connect())
	defer handler.Close()
	client := mb.NewClient(handler)

	fcs := make([]fieldDefinition, 0, 401)
	expectedFields := make(map[string]interface{})
	for i := 0; i <= 400; i++ {
		fc := fieldDefinition{}
		fc.Name = fmt.Sprintf("HoldingRegister-%v", i)
		fc.ByteOrder = "AB"
		fc.DataType = "INT16"
		fc.Scale = 1.0
		fc.Address = []uint16{uint16(i)}
		fcs = append(fcs, fc)

		_, err := client.WriteSingleRegister(fc.Address[0], uint16(i))
		require.NoError(t, err)

		expectedFields[fc.Name] = int64(i)
	}

	modbus := Modbus{
		Name:       "TestHoldingRegister",
		Controller: "tcp://localhost:1502",
		Log:        testutil.Logger{},
	}
	modbus.SlaveID = 1
	modbus.HoldingRegisters = fcs

	expected := []telegraf.Metric{
		metric.New(
			"modbus",
			map[string]string{
				"type":     cHoldingRegisters,
				"slave_id": strconv.Itoa(int(modbus.SlaveID)),
				"name":     modbus.Name,
			},
			expectedFields,
			time.Unix(0, 0),
		),
	}

	var acc testutil.Accumulator
	require.NoError(t, modbus.Init())
	require.NotEmpty(t, modbus.requests)
	require.NoError(t, modbus.Gather(&acc))
	acc.Wait(len(expected))

	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestRegisterHighAddresses(t *testing.T) {
	// Test case for issue https://github.com/influxdata/telegraf/issues/15138

	// Setup a server
	serv := mbserver.NewServer()
	require.NoError(t, serv.ListenTCP("localhost:1502"))
	defer serv.Close()

	handler := mb.NewTCPClientHandler("localhost:1502")
	require.NoError(t, handler.Connect())
	defer handler.Close()
	client := mb.NewClient(handler)

	// Write the register values
	data := []byte{
		0x4d, 0x6f, 0x64, 0x62, 0x75, 0x73, 0x20, 0x53,
		0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x48, 0x65,
		0x6c, 0x6c, 0x6f, 0x00,
	}
	_, err := client.WriteMultipleRegisters(65524, 10, data)
	require.NoError(t, err)
	_, err = client.WriteMultipleRegisters(65534, 1, []byte{0x10, 0x92})
	require.NoError(t, err)

	modbus := Modbus{
		Name:       "Issue-15138",
		Controller: "tcp://localhost:1502",
		Log:        testutil.Logger{},
	}
	modbus.SlaveID = 1
	modbus.HoldingRegisters = []fieldDefinition{
		{
			Name:      "DeviceName",
			ByteOrder: "AB",
			DataType:  "STRING",
			Address:   []uint16{65524, 65525, 65526, 65527, 65528, 65529, 65530, 65531, 65532, 65533},
		},
		{
			Name:      "DeviceConnectionStatus",
			ByteOrder: "AB",
			DataType:  "UINT16",
			Address:   []uint16{65534},
			Scale:     1,
		},
	}

	expected := []telegraf.Metric{
		metric.New(
			"modbus",
			map[string]string{
				"type":     cHoldingRegisters,
				"slave_id": strconv.Itoa(int(modbus.SlaveID)),
				"name":     modbus.Name,
			},
			map[string]interface{}{
				"DeviceName":             "Modbus String Hello",
				"DeviceConnectionStatus": uint16(4242),
			},
			time.Unix(0, 0),
		),
	}

	var acc testutil.Accumulator
	require.NoError(t, modbus.Init())
	require.NotEmpty(t, modbus.requests)
	require.Len(t, modbus.requests[1].holding, 1)
	require.NoError(t, modbus.Gather(&acc))
	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestRegisterMaxRegistersWorkaround(t *testing.T) {
	plugin := &Modbus{
		Name:                  "Test",
		Controller:            "tcp://localhost:1502",
		ConfigurationType:     "register",
		configurationOriginal: configurationOriginal{SlaveID: 1},
		Log:                   &testutil.Logger{},
		Workarounds: workarounds{
			MaxBitRegistersPerRequest:  6,
			MaxWordRegistersPerRequest: 8,
		},
	}
	plugin.Coils = make([]fieldDefinition, 0, 10)
	for i := range uint16(10) {
		plugin.Coils = append(plugin.Coils,
			fieldDefinition{
				Measurement: "test",
				Name:        fmt.Sprintf("field-coil-%d", i),
				Address:     []uint16{i},
			},
		)
	}
	plugin.DiscreteInputs = make([]fieldDefinition, 0, 10)
	for i := range uint16(10) {
		plugin.DiscreteInputs = append(plugin.DiscreteInputs,
			fieldDefinition{
				Measurement: "test",
				Name:        fmt.Sprintf("field-discrete-%d", i),
				Address:     []uint16{i},
			},
		)
	}
	plugin.HoldingRegisters = make([]fieldDefinition, 0, 10)
	for i := range uint16(10) {
		plugin.HoldingRegisters = append(plugin.HoldingRegisters,
			fieldDefinition{
				Measurement: "test",
				Name:        fmt.Sprintf("field-holding-%d", i),
				Address:     []uint16{4 * i, 4*i + 1, 4*i + 2, 4*i + 3},
				DataType:    "UINT64",
				ByteOrder:   "ABCDEFGH",
				Scale:       1.0,
			},
		)
	}
	plugin.InputRegisters = make([]fieldDefinition, 0, 10)
	for i := range uint16(10) {
		plugin.InputRegisters = append(plugin.InputRegisters,
			fieldDefinition{
				Measurement: "test",
				Name:        fmt.Sprintf("field-input-%d", i),
				Address:     []uint16{4 * i, 4*i + 1, 4*i + 2, 4*i + 3},
				DataType:    "UINT64",
				ByteOrder:   "ABCDEFGH",
				Scale:       1.0,
			},
		)
	}
	require.NoError(t, plugin.Init())

	require.Len(t, plugin.requests, 1)
	require.Contains(t, plugin.requests, byte(1))
	require.Len(t, plugin.requests[1].coil, 2, "coil")
	require.Len(t, plugin.requests[1].discrete, 2, "discrete")
	require.Len(t, plugin.requests[1].holding, 5, "holding")
	require.Len(t, plugin.requests[1].input, 5, "input")
}
