/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package cbus

import (
	"context"
	"encoding/binary"
	"fmt"
	"time"

	apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
	apiValues "github.com/apache/plc4x/plc4go/pkg/api/values"
	readWriteModel "github.com/apache/plc4x/plc4go/protocols/cbus/readwrite/model"
	spiModel "github.com/apache/plc4x/plc4go/spi/model"
	"github.com/apache/plc4x/plc4go/spi/utils"
)

//go:generate go tool stringer -type StatusRequestType
//go:generate go tool plc4xLicencer -type=StatusRequestType
type StatusRequestType uint8

const (
	StatusRequestTypeBinaryState StatusRequestType = iota
	StatusRequestTypeLevel
)

type Tag interface {
	apiModel.PlcTag

	GetTagType() TagType
}

// StatusTag can be used to query status using a P-to-MP-StatusRequest command
type StatusTag interface {
	Tag

	GetBridgeAddresses() []readWriteModel.BridgeAddress
	GetStatusRequestType() StatusRequestType
	GetStartingGroupAddressLabel() *byte
	GetApplication() readWriteModel.ApplicationIdContainer
}

func NewStatusTag(bridgeAddresses []readWriteModel.BridgeAddress, statusRequestType StatusRequestType, startingGroupAddressLabel *byte, application readWriteModel.ApplicationIdContainer, numElements uint16) StatusTag {
	return &statusTag{
		bridgeAddresses:           bridgeAddresses,
		tagType:                   STATUS,
		startingGroupAddressLabel: startingGroupAddressLabel,
		statusRequestType:         statusRequestType,
		application:               application,
		numElements:               numElements,
	}
}

type CalTag interface {
	GetBridgeAddresses() []readWriteModel.BridgeAddress
	GetUnitAddress() readWriteModel.UnitAddress
}

// CALRecallTag can be used to get device/network management tags
type CALRecallTag interface {
	Tag
	CalTag

	GetParameter() readWriteModel.Parameter
	GetCount() uint8
}

func NewCALRecallTag(unitAddress readWriteModel.UnitAddress, bridgeAddresses []readWriteModel.BridgeAddress, parameter readWriteModel.Parameter, count uint8, numElements uint16) CALRecallTag {
	return &calRecallTag{
		calTag:      calTag{bridgeAddresses, unitAddress},
		tagType:     CAL_RECALL,
		parameter:   parameter,
		count:       count,
		numElements: numElements,
	}
}

// CALIdentifyTag can be used to get device/network management tags
type CALIdentifyTag interface {
	Tag
	CalTag

	GetAttribute() readWriteModel.Attribute
}

func NewCALIdentifyTag(unitAddress readWriteModel.UnitAddress, bridgeAddresses []readWriteModel.BridgeAddress, attribute readWriteModel.Attribute, numElements uint16) CALIdentifyTag {
	return &calIdentifyTag{
		calTag:      calTag{bridgeAddresses, unitAddress},
		tagType:     CAL_IDENTIFY,
		attribute:   attribute,
		numElements: numElements,
	}
}

// CALGetStatusTag can be used to get device/network management tags
type CALGetStatusTag interface {
	Tag
	CalTag

	GetParameter() readWriteModel.Parameter
	GetCount() uint8
}

func NewCALGetStatusTag(unitAddress readWriteModel.UnitAddress, bridgeAddresses []readWriteModel.BridgeAddress, parameter readWriteModel.Parameter, count uint8, numElements uint16) CALGetStatusTag {
	return &calGetStatusTag{
		calTag:      calTag{bridgeAddresses, unitAddress},
		tagType:     CAL_GETSTATUS,
		parameter:   parameter,
		count:       count,
		numElements: numElements,
	}
}

// SALTag can be used to send SAL commands
type SALTag interface {
	Tag

	GetBridgeAddresses() []readWriteModel.BridgeAddress
	GetApplication() readWriteModel.ApplicationIdContainer
	GetSALCommand() string
}

func NewSALTag(bridgeAddresses []readWriteModel.BridgeAddress, application readWriteModel.ApplicationIdContainer, salCommand string, numElements uint16) SALTag {
	return &salTag{
		bridgeAddresses: bridgeAddresses,
		tagType:         SAL,
		application:     application,
		salCommand:      salCommand,
		numElements:     numElements,
	}
}

type SubscriptionTag interface {
	Tag
	apiModel.PlcSubscriptionTag
}

// SALMonitorTag can be used to monitor sal tags
type SALMonitorTag interface {
	SubscriptionTag

	GetUnitAddress() readWriteModel.UnitAddress
	GetApplication() *readWriteModel.ApplicationIdContainer
}

func NewSALMonitorTag(unitAddress readWriteModel.UnitAddress, application *readWriteModel.ApplicationIdContainer, numElements uint16) SALMonitorTag {
	return &salMonitorTag{
		tagType:     SAL_MONITOR,
		unitAddress: unitAddress,
		application: application,
		numElements: numElements,
	}
}

// MMIMonitorTag can be used to monitor mmi tags
type MMIMonitorTag interface {
	SubscriptionTag

	GetUnitAddress() readWriteModel.UnitAddress
	GetApplication() *readWriteModel.ApplicationIdContainer
}

func NewMMIMonitorTag(unitAddress readWriteModel.UnitAddress, application *readWriteModel.ApplicationIdContainer, numElements uint16) MMIMonitorTag {
	return &mmiMonitorTag{
		tagType:     MMI_STATUS_MONITOR,
		unitAddress: unitAddress,
		application: application,
		numElements: numElements,
	}
}

///////////////////////////////////////
///////////////////////////////////////
//
// Internal section
//

type statusTag struct {
	bridgeAddresses           []readWriteModel.BridgeAddress
	tagType                   TagType
	statusRequestType         StatusRequestType
	startingGroupAddressLabel *byte
	application               readWriteModel.ApplicationIdContainer
	numElements               uint16
}

type calTag struct {
	bridgeAddresses []readWriteModel.BridgeAddress
	unitAddress     readWriteModel.UnitAddress
}

type calRecallTag struct {
	calTag
	tagType     TagType
	parameter   readWriteModel.Parameter
	count       uint8
	numElements uint16
}

type calIdentifyTag struct {
	calTag
	tagType     TagType
	attribute   readWriteModel.Attribute
	numElements uint16
}

type calGetStatusTag struct {
	calTag
	tagType     TagType
	parameter   readWriteModel.Parameter
	count       uint8
	numElements uint16
}

type salTag struct {
	bridgeAddresses []readWriteModel.BridgeAddress
	tagType         TagType
	application     readWriteModel.ApplicationIdContainer
	salCommand      string
	numElements     uint16
}

type salMonitorTag struct {
	tagType     TagType
	unitAddress readWriteModel.UnitAddress
	application *readWriteModel.ApplicationIdContainer
	numElements uint16
}

func (s salMonitorTag) GetPlcSubscriptionType() apiModel.PlcSubscriptionType {
	return apiModel.SubscriptionEvent
}

func (s salMonitorTag) GetDuration() time.Duration {
	return 0
}

type mmiMonitorTag struct {
	tagType     TagType
	unitAddress readWriteModel.UnitAddress
	application *readWriteModel.ApplicationIdContainer
	numElements uint16
}

func (m mmiMonitorTag) GetPlcSubscriptionType() apiModel.PlcSubscriptionType {
	return apiModel.SubscriptionEvent
}

func (m mmiMonitorTag) GetDuration() time.Duration {
	return 0
}

//
// Internal section
//
///////////////////////////////////////
///////////////////////////////////////

func (s statusTag) GetBridgeAddresses() []readWriteModel.BridgeAddress {
	return s.bridgeAddresses
}

func (s statusTag) GetAddressString() string {
	statusRequestType := ""
	switch s.statusRequestType {
	case StatusRequestTypeBinaryState:
		statusRequestType = "binary"
	case StatusRequestTypeLevel:
		statusRequestType = "level"
		if s.startingGroupAddressLabel != nil {
			statusRequestType += fmt.Sprintf("=%#02x", *s.startingGroupAddressLabel)
		}
	default:
		statusRequestType = "invalid"
	}
	return fmt.Sprintf("status/%s/%s", statusRequestType, s.application)
}

func (s statusTag) GetValueType() apiValues.PlcValueType {
	return apiValues.NULL
}

func (s statusTag) GetArrayInfo() []apiModel.ArrayInfo {
	if s.numElements != 1 {
		return []apiModel.ArrayInfo{
			&spiModel.DefaultArrayInfo{
				LowerBound: 0,
				UpperBound: uint32(s.numElements),
			},
		}
	}
	return []apiModel.ArrayInfo{}
}

func (s statusTag) GetTagType() TagType {
	return s.tagType
}

func (s statusTag) GetStatusRequestType() StatusRequestType {
	return s.statusRequestType
}

func (s statusTag) GetStartingGroupAddressLabel() *byte {
	return s.startingGroupAddressLabel
}

func (s statusTag) GetApplication() readWriteModel.ApplicationIdContainer {
	return s.application
}

func (s statusTag) Serialize() ([]byte, error) {
	wb := utils.NewWriteBufferByteBased(utils.WithByteOrderForByteBasedBuffer(binary.BigEndian))
	if err := s.SerializeWithWriteBuffer(context.Background(), wb); err != nil {
		return nil, err
	}
	return wb.GetBytes(), nil
}

func (s statusTag) SerializeWithWriteBuffer(ctx context.Context, wb utils.WriteBuffer) error {
	if err := wb.PushContext(s.tagType.GetName()); err != nil {
		return err
	}

	if len(s.bridgeAddresses) > 0 {
		if err := wb.PushContext("bridgeAddresses"); err != nil {
			return err
		}
		for _, address := range s.bridgeAddresses {
			if err := address.SerializeWithWriteBuffer(ctx, wb); err != nil {
				return err
			}
		}
		if err := wb.PopContext("bridgeAddresses"); err != nil {
			return err
		}
	}

	if err := wb.WriteUint8("statusRequestType", 8, uint8(s.statusRequestType), utils.WithAdditionalStringRepresentation(s.statusRequestType.String())); err != nil {
		return err
	}
	if s.startingGroupAddressLabel != nil {
		if err := wb.WriteUint8("startingGroupAddressLabel", 8, *s.startingGroupAddressLabel); err != nil {
			return err
		}
	}
	if err := wb.WriteUint8("application", 8, uint8(s.application), utils.WithAdditionalStringRepresentation(s.application.String())); err != nil {
		return err
	}

	if err := wb.PopContext(s.tagType.GetName()); err != nil {
		return err
	}
	return nil
}

func (s statusTag) String() string {
	wb := utils.NewWriteBufferBoxBased(utils.WithWriteBufferBoxBasedOmitEmptyBoxes(), utils.WithWriteBufferBoxBasedMergeSingleBoxes())
	if err := wb.WriteSerializable(context.Background(), s); err != nil {
		return err.Error()
	}
	return wb.GetBox().String()
}

func (c calTag) GetBridgeAddresses() []readWriteModel.BridgeAddress {
	return c.bridgeAddresses
}

func (c calTag) GetUnitAddress() readWriteModel.UnitAddress {
	return c.unitAddress
}

func (c calTag) Serialize() ([]byte, error) {
	wb := utils.NewWriteBufferByteBased(utils.WithByteOrderForByteBasedBuffer(binary.BigEndian))
	if err := c.SerializeWithWriteBuffer(context.Background(), wb); err != nil {
		return nil, err
	}
	return wb.GetBytes(), nil
}

func (c calTag) SerializeWithWriteBuffer(ctx context.Context, wb utils.WriteBuffer) error {
	if len(c.bridgeAddresses) > 0 {
		if err := wb.PushContext("bridgeAddresses"); err != nil {
			return err
		}
		for _, address := range c.bridgeAddresses {
			if err := address.SerializeWithWriteBuffer(ctx, wb); err != nil {
				return err
			}
		}
		if err := wb.PopContext("bridgeAddresses"); err != nil {
			return err
		}
	}
	if unitAddress := c.unitAddress; unitAddress != nil {
		return c.unitAddress.SerializeWithWriteBuffer(ctx, wb)
	}
	return nil
}

func (c calTag) String() string {
	wb := utils.NewWriteBufferBoxBased(utils.WithWriteBufferBoxBasedOmitEmptyBoxes(), utils.WithWriteBufferBoxBasedMergeSingleBoxes())
	if err := wb.WriteSerializable(context.Background(), c); err != nil {
		return err.Error()
	}
	return wb.GetBox().String()
}

func (c calRecallTag) GetParameter() readWriteModel.Parameter {
	return c.parameter
}

func (c calRecallTag) GetCount() uint8 {
	return c.count
}

func (c calRecallTag) GetAddressString() string {
	return fmt.Sprintf("cal/%d/recall=%s", c.unitAddress.GetAddress(), c.parameter)
}

func (c calRecallTag) GetValueType() apiValues.PlcValueType {
	return apiValues.Struct
}

func (c calRecallTag) GetArrayInfo() []apiModel.ArrayInfo {
	if c.count != 1 {
		return []apiModel.ArrayInfo{
			&spiModel.DefaultArrayInfo{
				LowerBound: 0,
				UpperBound: uint32(c.count),
			},
		}
	}
	return []apiModel.ArrayInfo{}
}

func (c calRecallTag) GetTagType() TagType {
	return c.tagType
}

func (c calRecallTag) Serialize() ([]byte, error) {
	wb := utils.NewWriteBufferByteBased(utils.WithByteOrderForByteBasedBuffer(binary.BigEndian))
	if err := c.SerializeWithWriteBuffer(context.Background(), wb); err != nil {
		return nil, err
	}
	return wb.GetBytes(), nil
}

func (c calRecallTag) SerializeWithWriteBuffer(ctx context.Context, wb utils.WriteBuffer) error {
	if err := wb.PushContext(c.tagType.GetName()); err != nil {
		return err
	}

	if err := c.calTag.SerializeWithWriteBuffer(ctx, wb); err != nil {
		return err
	}

	if err := c.parameter.SerializeWithWriteBuffer(ctx, wb); err != nil {
		return err
	}

	if err := wb.WriteUint8("count", 8, c.count); err != nil {
		return err
	}

	if err := wb.PopContext(c.tagType.GetName()); err != nil {
		return err
	}
	return nil
}

func (c calRecallTag) String() string {
	wb := utils.NewWriteBufferBoxBased(utils.WithWriteBufferBoxBasedOmitEmptyBoxes(), utils.WithWriteBufferBoxBasedMergeSingleBoxes())
	if err := wb.WriteSerializable(context.Background(), c); err != nil {
		return err.Error()
	}
	return wb.GetBox().String()
}

func (c calIdentifyTag) GetAttribute() readWriteModel.Attribute {
	return c.attribute
}

func (c calIdentifyTag) GetAddressString() string {
	return fmt.Sprintf("cal/%d/identify=%s", c.unitAddress.GetAddress(), c.GetAttribute())
}

func (c calIdentifyTag) GetValueType() apiValues.PlcValueType {
	return apiValues.Struct
}

func (c calIdentifyTag) GetArrayInfo() []apiModel.ArrayInfo {
	if c.numElements != 1 {
		return []apiModel.ArrayInfo{
			&spiModel.DefaultArrayInfo{
				LowerBound: 0,
				UpperBound: uint32(c.numElements),
			},
		}
	}
	return []apiModel.ArrayInfo{}
}

func (c calIdentifyTag) GetTagType() TagType {
	return c.tagType
}

func (c calIdentifyTag) Serialize() ([]byte, error) {
	wb := utils.NewWriteBufferByteBased(utils.WithByteOrderForByteBasedBuffer(binary.BigEndian))
	if err := c.SerializeWithWriteBuffer(context.Background(), wb); err != nil {
		return nil, err
	}
	return wb.GetBytes(), nil
}

func (c calIdentifyTag) SerializeWithWriteBuffer(ctx context.Context, wb utils.WriteBuffer) error {
	if err := wb.PushContext(c.tagType.GetName()); err != nil {
		return err
	}

	if err := c.calTag.SerializeWithWriteBuffer(ctx, wb); err != nil {
		return err
	}

	if err := c.attribute.SerializeWithWriteBuffer(ctx, wb); err != nil {
		return err
	}

	if err := wb.PopContext(c.tagType.GetName()); err != nil {
		return err
	}
	return nil
}

func (c calIdentifyTag) String() string {
	wb := utils.NewWriteBufferBoxBased(utils.WithWriteBufferBoxBasedOmitEmptyBoxes(), utils.WithWriteBufferBoxBasedMergeSingleBoxes())
	if err := wb.WriteSerializable(context.Background(), c); err != nil {
		return err.Error()
	}
	return wb.GetBox().String()
}

func (c calGetStatusTag) GetParameter() readWriteModel.Parameter {
	return c.parameter
}

func (c calGetStatusTag) GetCount() uint8 {
	return c.count
}

func (c calGetStatusTag) GetAddressString() string {
	return fmt.Sprintf("cal/getstatus=%s, %d", c.parameter, c.GetCount())
}

func (c calGetStatusTag) GetValueType() apiValues.PlcValueType {
	return apiValues.Struct
}

func (c calGetStatusTag) GetArrayInfo() []apiModel.ArrayInfo {
	if c.count != 1 {
		return []apiModel.ArrayInfo{
			&spiModel.DefaultArrayInfo{
				LowerBound: 0,
				UpperBound: uint32(c.count),
			},
		}
	}
	return []apiModel.ArrayInfo{}
}

func (c calGetStatusTag) GetTagType() TagType {
	return c.tagType
}

func (c calGetStatusTag) Serialize() ([]byte, error) {
	wb := utils.NewWriteBufferByteBased(utils.WithByteOrderForByteBasedBuffer(binary.BigEndian))
	if err := c.SerializeWithWriteBuffer(context.Background(), wb); err != nil {
		return nil, err
	}
	return wb.GetBytes(), nil
}

func (c calGetStatusTag) SerializeWithWriteBuffer(ctx context.Context, wb utils.WriteBuffer) error {
	if err := wb.PushContext(c.tagType.GetName()); err != nil {
		return err
	}

	if err := c.calTag.SerializeWithWriteBuffer(ctx, wb); err != nil {
		return err
	}

	if err := c.parameter.SerializeWithWriteBuffer(ctx, wb); err != nil {
		return err
	}

	if err := wb.WriteUint8("count", 8, c.count); err != nil {
		return err
	}

	if err := wb.PopContext(c.tagType.GetName()); err != nil {
		return err
	}
	return nil
}

func (c calGetStatusTag) String() string {
	wb := utils.NewWriteBufferBoxBased(utils.WithWriteBufferBoxBasedOmitEmptyBoxes(), utils.WithWriteBufferBoxBasedMergeSingleBoxes())
	if err := wb.WriteSerializable(context.Background(), c); err != nil {
		return err.Error()
	}
	return wb.GetBox().String()
}

func (s salTag) GetBridgeAddresses() []readWriteModel.BridgeAddress {
	return s.bridgeAddresses
}

func (s salTag) GetApplication() readWriteModel.ApplicationIdContainer {
	return s.application
}

func (s salTag) GetSALCommand() string {
	return s.salCommand
}

func (s salTag) GetAddressString() string {
	return fmt.Sprintf("sal/%s/%s", s.application, s.salCommand)
}

func (s salTag) GetValueType() apiValues.PlcValueType {
	return apiValues.Struct
}

func (s salTag) GetArrayInfo() []apiModel.ArrayInfo {
	if s.numElements != 1 {
		return []apiModel.ArrayInfo{
			&spiModel.DefaultArrayInfo{
				LowerBound: 0,
				UpperBound: uint32(s.numElements),
			},
		}
	}
	return []apiModel.ArrayInfo{}
}

func (s salTag) GetTagType() TagType {
	return s.tagType
}

func (s salTag) Serialize() ([]byte, error) {
	wb := utils.NewWriteBufferByteBased(utils.WithByteOrderForByteBasedBuffer(binary.BigEndian))
	if err := s.SerializeWithWriteBuffer(context.Background(), wb); err != nil {
		return nil, err
	}
	return wb.GetBytes(), nil
}

func (s salTag) SerializeWithWriteBuffer(ctx context.Context, wb utils.WriteBuffer) error {
	if err := wb.PushContext(s.tagType.GetName()); err != nil {
		return err
	}

	if len(s.bridgeAddresses) > 0 {
		if err := wb.PushContext("bridgeAddresses"); err != nil {
			return err
		}
		for _, address := range s.bridgeAddresses {
			if err := address.SerializeWithWriteBuffer(ctx, wb); err != nil {
				return err
			}
		}
		if err := wb.PopContext("bridgeAddresses"); err != nil {
			return err
		}
	}

	if err := s.application.SerializeWithWriteBuffer(ctx, wb); err != nil {
		return err
	}

	if err := wb.WriteString("salCommand", uint32(len(s.salCommand)*8), s.salCommand); err != nil {
		return err
	}

	if err := wb.PopContext(s.tagType.GetName()); err != nil {
		return err
	}
	return nil
}

func (s salTag) String() string {
	wb := utils.NewWriteBufferBoxBased(utils.WithWriteBufferBoxBasedOmitEmptyBoxes(), utils.WithWriteBufferBoxBasedMergeSingleBoxes())
	if err := wb.WriteSerializable(context.Background(), s); err != nil {
		return err.Error()
	}
	return wb.GetBox().String()
}

func (s salMonitorTag) GetAddressString() string {
	unitAddress := "*"
	if s.unitAddress != nil {
		unitAddress = fmt.Sprintf("%#02x", s.unitAddress.GetAddress())
	}
	application := "*"
	if s.application != nil {
		application = fmt.Sprintf("%s", *s.application)
	}
	return fmt.Sprintf("salmonitor/%s/%s", unitAddress, application)
}

func (s salMonitorTag) GetValueType() apiValues.PlcValueType {
	return apiValues.Struct
}

func (s salMonitorTag) GetArrayInfo() []apiModel.ArrayInfo {
	if s.numElements != 1 {
		return []apiModel.ArrayInfo{
			&spiModel.DefaultArrayInfo{
				LowerBound: 0,
				UpperBound: uint32(s.numElements),
			},
		}
	}
	return []apiModel.ArrayInfo{}
}

func (s salMonitorTag) GetTagType() TagType {
	return s.tagType
}

func (s salMonitorTag) GetUnitAddress() readWriteModel.UnitAddress {
	return s.unitAddress
}

func (s salMonitorTag) GetApplication() *readWriteModel.ApplicationIdContainer {
	return s.application
}

func (s salMonitorTag) Serialize() ([]byte, error) {
	wb := utils.NewWriteBufferByteBased(utils.WithByteOrderForByteBasedBuffer(binary.BigEndian))
	if err := s.SerializeWithWriteBuffer(context.Background(), wb); err != nil {
		return nil, err
	}
	return wb.GetBytes(), nil
}

func (s salMonitorTag) SerializeWithWriteBuffer(ctx context.Context, wb utils.WriteBuffer) error {
	if err := wb.PushContext(s.tagType.GetName()); err != nil {
		return err
	}

	if unitAddress := s.unitAddress; unitAddress != nil {
		if err := unitAddress.SerializeWithWriteBuffer(ctx, wb); err != nil {
			return err
		}
	}
	if application := s.application; application != nil {
		if err := application.SerializeWithWriteBuffer(ctx, wb); err != nil {
			return err
		}
	}

	if err := wb.PopContext(s.tagType.GetName()); err != nil {
		return err
	}
	return nil
}

func (s salMonitorTag) String() string {
	wb := utils.NewWriteBufferBoxBased(utils.WithWriteBufferBoxBasedOmitEmptyBoxes(), utils.WithWriteBufferBoxBasedMergeSingleBoxes())
	if err := wb.WriteSerializable(context.Background(), s); err != nil {
		return err.Error()
	}
	return wb.GetBox().String()
}

func (m mmiMonitorTag) GetAddressString() string {
	unitAddress := "*"
	if m.unitAddress != nil {
		unitAddress = fmt.Sprintf("%#02x", m.unitAddress.GetAddress())
	}
	application := "*"
	if m.application != nil {
		application = fmt.Sprintf("%s", *m.application)
	}
	return fmt.Sprintf("mmimonitor/%s/%s", unitAddress, application)
}

func (m mmiMonitorTag) GetValueType() apiValues.PlcValueType {
	return apiValues.Struct
}

func (m mmiMonitorTag) GetArrayInfo() []apiModel.ArrayInfo {
	if m.numElements != 1 {
		return []apiModel.ArrayInfo{
			&spiModel.DefaultArrayInfo{
				LowerBound: 0,
				UpperBound: uint32(m.numElements),
			},
		}
	}
	return []apiModel.ArrayInfo{}
}

func (m mmiMonitorTag) GetTagType() TagType {
	return m.tagType
}

func (m mmiMonitorTag) GetUnitAddress() readWriteModel.UnitAddress {
	return m.unitAddress
}

func (m mmiMonitorTag) GetApplication() *readWriteModel.ApplicationIdContainer {
	return m.application
}

func (m mmiMonitorTag) Serialize() ([]byte, error) {
	wb := utils.NewWriteBufferByteBased(utils.WithByteOrderForByteBasedBuffer(binary.BigEndian))
	if err := m.SerializeWithWriteBuffer(context.Background(), wb); err != nil {
		return nil, err
	}
	return wb.GetBytes(), nil
}

func (m mmiMonitorTag) SerializeWithWriteBuffer(ctx context.Context, wb utils.WriteBuffer) error {
	if err := wb.PushContext(m.tagType.GetName()); err != nil {
		return err
	}

	if unitAddress := m.unitAddress; unitAddress != nil {
		if err := unitAddress.SerializeWithWriteBuffer(ctx, wb); err != nil {
			return err
		}
	}
	if application := m.application; application != nil {
		if err := application.SerializeWithWriteBuffer(ctx, wb); err != nil {
			return err
		}
	}

	if err := wb.PopContext(m.tagType.GetName()); err != nil {
		return err
	}
	return nil
}

func (m mmiMonitorTag) String() string {
	wb := utils.NewWriteBufferBoxBased(utils.WithWriteBufferBoxBasedOmitEmptyBoxes(), utils.WithWriteBufferBoxBasedMergeSingleBoxes())
	if err := wb.WriteSerializable(context.Background(), m); err != nil {
		return err.Error()
	}
	return wb.GetBox().String()
}
