package descriptor

import (
	"errors"
	"internal/binary"
	"internal/bytealg"
)

var configurationCDCHID = [configurationTypeLen]byte{
	configurationTypeLen,
	TypeConfiguration,
	0x64, 0x00, // adjust length as needed
	0x03, // number of interfaces
	0x01, // configuration value
	0x00, // index to string description
	0xa0, // attributes
	0x32, // maxpower
}

var ConfigurationCDCHID = ConfigurationType{
	data: configurationCDCHID[:],
}

var interfaceHID = [interfaceTypeLen]byte{
	interfaceTypeLen,
	TypeInterface,
	0x02, // InterfaceNumber
	0x00, // AlternateSetting
	0x02, // NumEndpoints
	0x03, // InterfaceClass
	0x00, // InterfaceSubClass
	0x00, // InterfaceProtocol
	0x00, // Interface
}

var InterfaceHID = InterfaceType{
	data: interfaceHID[:],
}

const (
	ClassHIDTypeLen = 9
)

type ClassHIDType struct {
	data []byte
}

func (d ClassHIDType) Bytes() []byte {
	return d.data[:]
}

func (d ClassHIDType) Length(v uint8) {
	d.data[0] = byte(v)
}

func (d ClassHIDType) Type(v uint8) {
	d.data[1] = byte(v)
}

func (d ClassHIDType) HID(v uint16) {
	binary.LittleEndian.PutUint16(d.data[2:4], v)
}

func (d ClassHIDType) CountryCode(v uint8) {
	d.data[4] = byte(v)
}

func (d ClassHIDType) NumDescriptors(v uint8) {
	d.data[5] = byte(v)
}

func (d ClassHIDType) ClassType(v uint8) {
	d.data[6] = byte(v)
}

func (d ClassHIDType) ClassLength(v uint16) {
	binary.LittleEndian.PutUint16(d.data[7:9], v)
}

var errNoClassHIDFound = errors.New("no classHID found")

// FindClassHIDType tries to find the ClassHID class in the descriptor.
func FindClassHIDType(des, class []byte) (ClassHIDType, error) {
	if len(des) < ClassHIDTypeLen || len(class) == 0 {
		return ClassHIDType{}, errNoClassHIDFound
	}

	// search only for ClassHIDType without the ClassLength,
	// in case it has already been set.
	idx := bytealg.Index(des, class[:ClassHIDTypeLen-2])
	if idx == -1 {
		return ClassHIDType{}, errNoClassHIDFound
	}

	return ClassHIDType{data: des[idx : idx+ClassHIDTypeLen]}, nil
}

var classHID = [ClassHIDTypeLen]byte{
	ClassHIDTypeLen,
	TypeClassHID,
	0x11, // HID version L
	0x01, // HID version H
	0x00, // CountryCode
	0x01, // NumDescriptors
	0x22, // ClassType
	0x91, // ClassLength L
	0x00, // ClassLength H
}

var ClassHID = ClassHIDType{
	data: classHID[:],
}

// EP1 IN : CDC Call Management
// EP2 OUT: CDC OUT
// EP2 IN : CDC IN
// EP3 OUT: HID OUT
// EP3 IN : HID IN
var CDCHID = Descriptor{
	Device: DeviceCDC.Bytes(),
	Configuration: Append([][]byte{
		ConfigurationCDCHID.Bytes(),
		InterfaceAssociationCDC.Bytes(),
		InterfaceCDCControl.Bytes(),
		ClassSpecificCDCHeader.Bytes(),
		ClassSpecificCDCACM.Bytes(),
		ClassSpecificCDCUnion.Bytes(),
		ClassSpecificCDCCallManagement.Bytes(),
		EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(),
		InterfaceCDCData.Bytes(),
		EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
		EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
		InterfaceHID.Bytes(),
		ClassHID.Bytes(),
		EndpointIN(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
		EndpointOUT(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
	}),
	HID: map[uint16][]byte{
		2: Append([][]byte{ // Update ClassLength in classHID whenever the array length is modified!
			HIDUsagePageGenericDesktop,
			HIDUsageDesktopKeyboard,
			HIDCollectionApplication,
			HIDReportID(2),

			HIDUsagePageKeyboard,
			HIDUsageMinimum(224),
			HIDUsageMaximum(231),
			HIDLogicalMinimum(0),
			HIDLogicalMaximum(1),
			HIDReportSize(1),
			HIDReportCount(8),
			HIDInputDataVarAbs,
			HIDReportCount(1),
			HIDReportSize(8),
			HIDInputConstVarAbs,
			HIDReportCount(3),
			HIDReportSize(1),
			HIDUsagePageLED,
			HIDUsageMinimum(1),
			HIDUsageMaximum(3),
			HIDOutputDataVarAbs,
			HIDReportCount(5),
			HIDReportSize(1),
			HIDOutputConstVarAbs,
			HIDReportCount(6),
			HIDReportSize(8),
			HIDLogicalMinimum(0),
			HIDLogicalMaximum(255),

			HIDUsagePageKeyboard,
			HIDUsageMinimum(0),
			HIDUsageMaximum(255),
			HIDInputDataAryAbs,
			HIDCollectionEnd,

			HIDUsagePageGenericDesktop,
			HIDUsageDesktopMouse,
			HIDCollectionApplication,
			HIDUsageDesktopPointer,
			HIDCollectionPhysical,
			HIDReportID(1),

			HIDUsagePageButton,
			HIDUsageMinimum(1),
			HIDUsageMaximum(5),
			HIDLogicalMinimum(0),
			HIDLogicalMaximum(1),
			HIDReportCount(5),
			HIDReportSize(1),
			HIDInputDataVarAbs,
			HIDReportCount(1),
			HIDReportSize(3),
			HIDInputConstVarAbs,

			HIDUsagePageGenericDesktop,
			HIDUsageDesktopX,
			HIDUsageDesktopY,
			HIDUsageDesktopWheel,
			HIDLogicalMinimum(-127),
			HIDLogicalMaximum(127),
			HIDReportSize(8),
			HIDReportCount(3),
			HIDInputDataVarRel,
			HIDCollectionEnd,
			HIDCollectionEnd,

			HIDUsagePageConsumer,
			HIDUsageConsumerControl,
			HIDCollectionApplication,
			HIDReportID(3),
			HIDLogicalMinimum(0),
			HIDLogicalMaximum(8191),
			HIDUsageMinimum(0),
			HIDUsageMaximum(0x1FFF),
			HIDReportSize(16),
			HIDReportCount(1),
			HIDInputDataAryAbs,
			HIDCollectionEnd,
		}),
	},
}
