/*
 * Copyright 2018-present Open Networking Foundation

 * Licensed 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

 * http://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 omci

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	me "github.com/opencord/omci-lib-go/v2/generated"
)

type MibDbEntry struct {
	classId  me.ClassID
	entityId EntityID
	params   me.AttributeValueMap
}

type MibDb struct {
	NumberOfCommands uint16
	items            []MibDbEntry
}

type EntityID []byte

func (e EntityID) ToString() string {
	return hex.EncodeToString(e)
}

func (e EntityID) ToUint16() uint16 {
	return binary.BigEndian.Uint16(e)
}

func (e EntityID) ToUint32() uint32 {
	return binary.BigEndian.Uint32(e)
}

func (e EntityID) FromUint16(id uint16) EntityID {
	buff := new(bytes.Buffer)
	err := binary.Write(buff, binary.BigEndian, id)
	if err != nil {
		panic(err)
	}

	return buff.Bytes()
}

func (e EntityID) Equals(i EntityID) bool {
	if res := bytes.Compare(e, i); res == 0 {
		return true
	}
	return false
}

const (
	cardHolderOnuType byte = 0x01 // ONU is a single piece of integrated equipment
	ethernetUnitType  byte = 0x2f // Ethernet BASE-T
	xgsPonUnitType    byte = 0xee // XG-PON10G10
	cardHolderSlotID  byte = 0x01
	tcontSlotId       byte = 0x80 // why is this not the same as the cardHolderSlotID, it does not point to anything
	aniGId            byte = 0x01

	upstreamPriorityQueues   = 8  // Number of queues for each T-CONT
	downstreamPriorityQueues = 16 // Number of queues for each PPTP
	tconts                   = 8  // NOTE will we ever need to configure this?
	// trafficSchedulers        = 8  // NOTE will we ever need to configure this?
)

var (
	cardHolderEntityID  = EntityID{cardHolderOnuType, cardHolderSlotID}
	circuitPackEntityID = cardHolderEntityID // is the same as that of the cardholder ME containing this circuit pack instance
)

func GenerateUniPortEntityId(id uint32) EntityID {
	return EntityID{cardHolderSlotID, byte(id)}
}

// creates a MIB database for a ONU
// CircuitPack and CardHolder are static, everything else can be configured
func GenerateMibDatabase(uniPortCount int) (*MibDb, error) {

	mibDb := MibDb{
		items: []MibDbEntry{},
	}

	// the first element to return is the ONU-Data
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.OnuDataClassID,
		EntityID{0x00, 0x00},
		me.AttributeValueMap{"MibDataSync": 0}, // FIXME this needs to be parametrized before sending the response
	})

	// then we report the CardHolder
	// NOTE we have not report it till now, so leave it commented out
	//mibDb.items = append(mibDb.items, MibDbEntry{
	//	me.CardholderClassID,
	//	cardHolderEntityID,
	//	me.AttributeValueMap{
	//		"ActualPlugInUnitType":   cardHolderOnuType,
	//		"ExpectedPlugInUnitType": ethernetUnitType,
	//	},
	//})

	// circuitPack XG-PON10G10
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.CircuitPackClassID,
		circuitPackEntityID,
		me.AttributeValueMap{
			"Type":          xgsPonUnitType,
			"NumberOfPorts": 1, // NOTE is this the ANI port? must be
			"SerialNumber":  ToOctets("BBSM-Circuit-Pack-ani", 20),
			"Version":       ToOctets("v0.0.1", 20),
		},
	})
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.CircuitPackClassID,
		circuitPackEntityID,
		me.AttributeValueMap{
			"VendorId":            "ONF",
			"AdministrativeState": 0,
			"OperationalState":    0,
			"BridgedOrIpInd":      0,
		},
	})
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.CircuitPackClassID,
		circuitPackEntityID,
		me.AttributeValueMap{
			"EquipmentId":                 ToOctets("BBSM-Circuit-Pack", 20),
			"CardConfiguration":           0,
			"TotalTContBufferNumber":      8,
			"TotalPriorityQueueNumber":    8,
			"TotalTrafficSchedulerNumber": 0,
		},
	})
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.CircuitPackClassID,
		circuitPackEntityID,
		me.AttributeValueMap{
			"PowerShedOverride": uint32(0),
		},
	})

	// ANI-G
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.AniGClassID,
		EntityID{tcontSlotId, aniGId},
		me.AttributeValueMap{
			"Arc":                         0,
			"ArcInterval":                 0,
			"Deprecated":                  0,
			"GemBlockLength":              48,
			"LowerOpticalThreshold":       255,
			"LowerTransmitPowerThreshold": 129,
			"OnuResponseTime":             0,
			"OpticalSignalLevel":          57428,
			"PiggybackDbaReporting":       0,
			"SignalDegradeThreshold":      9,
			"SignalFailThreshold":         5,
			"SrIndication":                1,
			"TotalTcontNumber":            8,
			"TransmitOpticalLevel":        3171,
			"UpperOpticalThreshold":       255,
			"UpperTransmitPowerThreshold": 129,
		},
	})

	// circuitPack Ethernet
	// NOTE the circuit pack is divided in multiple messages as too big to fit in a single one
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.CircuitPackClassID,
		circuitPackEntityID,
		me.AttributeValueMap{
			"Type":          ethernetUnitType,
			"NumberOfPorts": uniPortCount,
			"SerialNumber":  ToOctets("BBSM-Circuit-Pack", 20),
			"Version":       ToOctets("v0.0.1", 20),
		},
	})
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.CircuitPackClassID,
		circuitPackEntityID,
		me.AttributeValueMap{
			"VendorId":            "ONF",
			"AdministrativeState": 0,
			"OperationalState":    0,
			"BridgedOrIpInd":      0,
		},
	})
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.CircuitPackClassID,
		circuitPackEntityID,
		me.AttributeValueMap{
			"EquipmentId":                 ToOctets("BBSM-Circuit-Pack", 20),
			"CardConfiguration":           0,
			"TotalTContBufferNumber":      8,
			"TotalPriorityQueueNumber":    8,
			"TotalTrafficSchedulerNumber": 16,
		},
	})
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.CircuitPackClassID,
		circuitPackEntityID,
		me.AttributeValueMap{
			"PowerShedOverride": uint32(0),
		},
	})

	// PPTP and UNI-Gs
	// NOTE this are dependent on the number of UNI this ONU supports
	// Through an identical ID, the UNI-G ME is implicitly linked to an instance of a PPTP
	for i := 1; i <= uniPortCount; i++ {
		uniEntityId := GenerateUniPortEntityId(uint32(i))

		mibDb.items = append(mibDb.items, MibDbEntry{
			me.PhysicalPathTerminationPointEthernetUniClassID,
			uniEntityId,
			me.AttributeValueMap{
				"ExpectedType":                  0,
				"SensedType":                    ethernetUnitType,
				"AutoDetectionConfiguration":    0,
				"EthernetLoopbackConfiguration": 0,
				"AdministrativeState":           0,
				"OperationalState":              0,
				"ConfigurationInd":              3,
				"MaxFrameSize":                  1518,
				"DteOrDceInd":                   0,
				"PauseTime":                     0,
				"BridgedOrIpInd":                2,
				"Arc":                           0,
				"ArcInterval":                   0,
				"PppoeFilter":                   0,
				"PowerControl":                  0,
			},
		})

		mibDb.items = append(mibDb.items, MibDbEntry{
			me.UniGClassID,
			uniEntityId,
			me.AttributeValueMap{
				"AdministrativeState":         0,
				"Deprecated":                  0,
				"ManagementCapability":        0,
				"NonOmciManagementIdentifier": 0,
				"RelayAgentOptions":           0,
			},
		})

		// Downstream Queues (related to PPTP)
		// 16 priorities queues for each UNI Ports
		// EntityID = cardHolderSlotID + Uni EntityID (0101)
		for j := 1; j <= downstreamPriorityQueues; j++ {
			queueEntityId := EntityID{cardHolderSlotID, byte(j)}

			// we first report the PriorityQueue without any attribute
			mibDb.items = append(mibDb.items, MibDbEntry{
				me.PriorityQueueClassID,
				queueEntityId, //was not reported in the original implementation
				me.AttributeValueMap{},
			})

			// then we report it with the required attributes
			// In the downstream direction, the first byte is the slot number and the second byte is the port number of the queue's destination port.
			relatedPort := append(uniEntityId, 0x00, byte(j))
			mibDb.items = append(mibDb.items, MibDbEntry{
				me.PriorityQueueClassID,
				queueEntityId, //was not reported in the original implementation
				me.AttributeValueMap{
					"QueueConfigurationOption":                            0,
					"MaximumQueueSize":                                    100,
					"AllocatedQueueSize":                                  100,
					"DiscardBlockCounterResetInterval":                    0,
					"ThresholdValueForDiscardedBlocksDueToBufferOverflow": 0,
					"RelatedPort":                                         relatedPort.ToUint32(),
					"TrafficSchedulerPointer":                             0, //it was hardcoded to 0x0108 in the current implementation
					"Weight":                                              1,
					"BackPressureOperation":                               1,
					"BackPressureTime":                                    0,
					"BackPressureOccurQueueThreshold":                     0,
					"BackPressureClearQueueThreshold":                     0,
				},
			})
		}
	}

	// T-CONTS and Traffic Schedulers
	for i := 1; i <= tconts; i++ {
		tcontEntityId := EntityID{tcontSlotId, byte(i)}

		mibDb.items = append(mibDb.items, MibDbEntry{
			me.TContClassID,
			tcontEntityId,
			me.AttributeValueMap{
				"AllocId": 65535,
			},
		})

		tsEntityId := EntityID{cardHolderSlotID, byte(i)}
		mibDb.items = append(mibDb.items, MibDbEntry{
			me.TrafficSchedulerClassID,
			tsEntityId, //was not reported in the original implementation
			me.AttributeValueMap{
				"TContPointer":            tcontEntityId.ToUint16(), // was hardcoded to a non-existing t-cont
				"TrafficSchedulerPointer": 0,
				"Policy":                  02,
				"PriorityWeight":          0,
			},
		})

		for j := 1; j <= upstreamPriorityQueues; j++ {
			queueEntityId := EntityID{tcontSlotId, byte(j)}
			// Upstream Queues (related to traffic schedulers)
			// 8 priorities queues per TCONT
			// EntityID = tcontSlotId + Uni EntityID (8001)

			// we first report the PriorityQueue without any attribute
			mibDb.items = append(mibDb.items, MibDbEntry{
				me.PriorityQueueClassID,
				queueEntityId, //was not reported in the original implementation
				me.AttributeValueMap{},
			})

			// then we report it with the required attributes
			// In the upstream direction, the first 2 bytes are the ME ID of the associated T- CONT, the first byte of which is a slot number, the second byte a T-CONT number.
			relatedPort := append(tcontEntityId, 0x00, byte(j))
			mibDb.items = append(mibDb.items, MibDbEntry{
				me.PriorityQueueClassID,
				queueEntityId, //was not reported in the original implementation
				me.AttributeValueMap{
					"QueueConfigurationOption":                            0,
					"MaximumQueueSize":                                    100,
					"AllocatedQueueSize":                                  100,
					"DiscardBlockCounterResetInterval":                    0,
					"ThresholdValueForDiscardedBlocksDueToBufferOverflow": 0,
					"RelatedPort":                                         relatedPort.ToUint32(),
					"TrafficSchedulerPointer":                             tsEntityId.ToUint16(), //it was hardcoded to 0x0108 in the current implementation
					"Weight":                                              1,
					"BackPressureOperation":                               1,
					"BackPressureTime":                                    0,
					"BackPressureOccurQueueThreshold":                     0,
					"BackPressureClearQueueThreshold":                     0,
				},
			})
		}
	}

	// ONU-2g
	mibDb.items = append(mibDb.items, MibDbEntry{
		me.Onu2GClassID,
		EntityID{0x00, 0x00},
		me.AttributeValueMap{
			"ConnectivityCapability":                      127,
			"CurrentConnectivityMode":                     0,
			"Deprecated":                                  1,
			"PriorityQueueScaleFactor":                    1,
			"QualityOfServiceQosConfigurationFlexibility": 63,
			"Sysuptime":                                   0,
			"TotalGemPortIdNumber":                        8,
			"TotalPriorityQueueNumber":                    64,
			"TotalTrafficSchedulerNumber":                 8,
		},
	})

	mibDb.NumberOfCommands = uint16(len(mibDb.items))

	return &mibDb, nil
}
