//go:build windows

package usb

import (
	"encoding/binary"
	"fmt"
	"unsafe"

	"golang.org/x/sys/windows"
)

// Interface to the usbshare filter driver (driver/windows).
//
// The structure layouts and IOCTL codes here must match public.h exactly.
// They are marshalled by hand on both sides, so a mismatch corrupts memory
// rather than failing cleanly — change one, change the other.

// GUID_DEVINTERFACE_USBSHARE from public.h.
var guidDevInterfaceUsbShare = windows.GUID{
	Data1: 0x8f3d2a14,
	Data2: 0x6c7b,
	Data3: 0x4e59,
	Data4: [8]byte{0x9a, 0x1d, 0x3f, 0x5b, 0x7c, 0x8e, 0x2d, 0x40},
}

// IOCTL codes, mirroring the USBSHARE_IOCTL macro.
const (
	fileDeviceUsbShare = 0x8000
	methodBuffered     = 0
	fileAnyAccess      = 0
)

func usbShareIOCTL(index uint32) uint32 {
	return (fileDeviceUsbShare << 16) | (fileAnyAccess << 14) | ((0x800 + index) << 2) | methodBuffered
}

var (
	ioctlClaim          = usbShareIOCTL(0)
	ioctlRelease        = usbShareIOCTL(1)
	ioctlGetDescriptors = usbShareIOCTL(2)
	ioctlSubmit         = usbShareIOCTL(3)
	ioctlCancel         = usbShareIOCTL(4)
	ioctlSetInterface   = usbShareIOCTL(5)
	ioctlClearHalt      = usbShareIOCTL(6)
	ioctlReset          = usbShareIOCTL(7)
)

// Transfer types, matching USBSHARE_TRANSFER_* in public.h.
const (
	winTransferControl     = 0
	winTransferIsochronous = 1
	winTransferBulk        = 2
	winTransferInterrupt   = 3
)

// Directions, matching USBSHARE_DIR_*.
const (
	winDirOut = 0
	winDirIn  = 1
)

// winDeviceInfo mirrors USBSHARE_DEVICE_INFO (packed).
type winDeviceInfo struct {
	VendorID           uint16
	ProductID          uint16
	BcdDevice          uint16
	DeviceClass        uint8
	DeviceSubClass     uint8
	DeviceProtocol     uint8
	ConfigurationValue uint8
	NumConfigurations  uint8
	Speed              uint32
	PortNumber         uint32
}

// winTransferHeader mirrors USBSHARE_TRANSFER (packed).
type winTransferHeader struct {
	ID              uint64
	EndpointAddress uint8
	Type            uint8
	Direction       uint8
	Reserved        uint8
	BufferLength    uint32
	Timeout         uint32
	Setup           [8]byte
}

// winTransferResult mirrors USBSHARE_TRANSFER_RESULT (packed).
type winTransferResult struct {
	ID           uint64
	Status       int32
	UsbdStatus   uint32
	ActualLength uint32
}

const (
	winTransferHeaderSize = 8 + 1 + 1 + 1 + 1 + 4 + 4 + 8 // 28
	winTransferResultSize = 8 + 4 + 4 + 4                 // 20
)

// DriverHandle is an open handle to a device claimed through the filter driver.
type DriverHandle struct {
	handle windows.Handle
	info   winDeviceInfo
	nextID uint64
}

// OpenDriverDevice opens the filter driver's interface for a device path and
// claims the device.
//
// Claiming stops the class driver from talking to the device, which is what
// lets us drive it — and it is released automatically if this process dies,
// because the driver ties the claim to the handle.
func OpenDriverDevice(devicePath string) (*DriverHandle, error) {
	pathPtr, err := windows.UTF16PtrFromString(devicePath)
	if err != nil {
		return nil, fmt.Errorf("invalid device path: %w", err)
	}

	handle, err := windows.CreateFile(
		pathPtr,
		windows.GENERIC_READ|windows.GENERIC_WRITE,
		windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE,
		nil,
		windows.OPEN_EXISTING,
		windows.FILE_ATTRIBUTE_NORMAL,
		0,
	)
	if err != nil {
		return nil, fmt.Errorf("opening %s: %w (is the usbshare driver installed?)", devicePath, err)
	}

	h := &DriverHandle{handle: handle}

	if err := h.claim(); err != nil {
		windows.CloseHandle(handle)
		return nil, err
	}

	return h, nil
}

func (h *DriverHandle) claim() error {
	out := make([]byte, unsafe.Sizeof(winDeviceInfo{}))
	var returned uint32

	err := windows.DeviceIoControl(h.handle, ioctlClaim,
		nil, 0,
		&out[0], uint32(len(out)),
		&returned, nil)
	if err != nil {
		return fmt.Errorf("claiming device: %w", err)
	}

	h.info = *(*winDeviceInfo)(unsafe.Pointer(&out[0]))
	return nil
}

// Close releases the device and closes the handle.
func (h *DriverHandle) Close() error {
	var returned uint32
	windows.DeviceIoControl(h.handle, ioctlRelease, nil, 0, nil, 0, &returned, nil)
	return windows.CloseHandle(h.handle)
}

// Info returns the device information reported at claim time.
func (h *DriverHandle) Info() winDeviceInfo { return h.info }

// Descriptors reads the raw descriptor blob: device descriptor followed by
// the configuration descriptors, the same layout Linux usbdevfs returns. It
// is parsed by the same code on both platforms.
func (h *DriverHandle) Descriptors() ([]byte, error) {
	// Ask with a generous buffer first; grow if the driver reports more.
	buf := make([]byte, 4096)
	var returned uint32

	err := windows.DeviceIoControl(h.handle, ioctlGetDescriptors,
		nil, 0, &buf[0], uint32(len(buf)), &returned, nil)

	if err == windows.ERROR_INSUFFICIENT_BUFFER || err == windows.ERROR_MORE_DATA {
		buf = make([]byte, returned)
		err = windows.DeviceIoControl(h.handle, ioctlGetDescriptors,
			nil, 0, &buf[0], uint32(len(buf)), &returned, nil)
	}
	if err != nil {
		return nil, fmt.Errorf("reading descriptors: %w", err)
	}

	return buf[:returned], nil
}

// Transfer performs one USB transfer and blocks until it completes.
//
// For IN transfers data is the buffer to fill; for OUT transfers it holds the
// payload to send. The returned count is how many bytes actually moved, which
// matters for both directions.
func (h *DriverHandle) Transfer(params *TransferParams) (int, error) {
	h.nextID++

	header := winTransferHeader{
		ID:              h.nextID,
		EndpointAddress: params.EndpointAddress,
		Type:            params.Type,
		Direction:       params.Direction,
		BufferLength:    uint32(len(params.Data)),
		Timeout:         params.TimeoutMS,
		Setup:           params.Setup,
	}

	// Input: header followed by the payload for OUT transfers.
	input := make([]byte, winTransferHeaderSize+len(params.Data))
	marshalTransferHeader(input, &header)
	if params.Direction == winDirOut && len(params.Data) > 0 {
		copy(input[winTransferHeaderSize:], params.Data)
	}

	// Output: result header followed by the payload for IN transfers.
	output := make([]byte, winTransferResultSize+len(params.Data))
	var returned uint32

	err := windows.DeviceIoControl(h.handle, ioctlSubmit,
		&input[0], uint32(len(input)),
		&output[0], uint32(len(output)),
		&returned, nil)
	if err != nil {
		return 0, fmt.Errorf("submitting transfer: %w", err)
	}
	if returned < winTransferResultSize {
		return 0, fmt.Errorf("driver returned %d bytes, expected at least %d",
			returned, winTransferResultSize)
	}

	result := unmarshalTransferResult(output)
	if result.Status != 0 {
		return int(result.ActualLength), fmt.Errorf(
			"transfer failed: status 0x%08x, usbd 0x%08x",
			uint32(result.Status), result.UsbdStatus)
	}

	if params.Direction == winDirIn && result.ActualLength > 0 {
		n := int(result.ActualLength)
		if n > len(params.Data) {
			n = len(params.Data)
		}
		copy(params.Data, output[winTransferResultSize:winTransferResultSize+n])
	}

	return int(result.ActualLength), nil
}

// TransferParams describes one transfer.
type TransferParams struct {
	EndpointAddress uint8
	Type            uint8
	Direction       uint8
	Data            []byte
	TimeoutMS       uint32
	Setup           [8]byte
}

// SetInterface selects an alternate setting through the driver, so the USB
// stack re-opens the pipes and reserves bandwidth for isochronous endpoints.
func (h *DriverHandle) SetInterface(iface, alt uint8) error {
	input := []byte{iface, alt}
	var returned uint32

	err := windows.DeviceIoControl(h.handle, ioctlSetInterface,
		&input[0], uint32(len(input)), nil, 0, &returned, nil)
	if err != nil {
		return fmt.Errorf("setting interface %d to alt %d: %w", iface, alt, err)
	}
	return nil
}

// ClearHalt clears a stall condition on an endpoint.
func (h *DriverHandle) ClearHalt(endpoint uint8) error {
	input := []byte{endpoint}
	var returned uint32

	err := windows.DeviceIoControl(h.handle, ioctlClearHalt,
		&input[0], 1, nil, 0, &returned, nil)
	if err != nil {
		return fmt.Errorf("clearing halt on endpoint 0x%02x: %w", endpoint, err)
	}
	return nil
}

// Reset resets the device's port.
func (h *DriverHandle) Reset() error {
	var returned uint32
	err := windows.DeviceIoControl(h.handle, ioctlReset, nil, 0, nil, 0, &returned, nil)
	if err != nil {
		return fmt.Errorf("resetting device: %w", err)
	}
	return nil
}

// marshalTransferHeader writes the header in the driver's packed layout.
// Done field by field rather than by casting a struct: Go inserts padding
// that the packed C structure does not have.
func marshalTransferHeader(buf []byte, h *winTransferHeader) {
	binary.LittleEndian.PutUint64(buf[0:8], h.ID)
	buf[8] = h.EndpointAddress
	buf[9] = h.Type
	buf[10] = h.Direction
	buf[11] = h.Reserved
	binary.LittleEndian.PutUint32(buf[12:16], h.BufferLength)
	binary.LittleEndian.PutUint32(buf[16:20], h.Timeout)
	copy(buf[20:28], h.Setup[:])
}

func unmarshalTransferResult(buf []byte) winTransferResult {
	return winTransferResult{
		ID:           binary.LittleEndian.Uint64(buf[0:8]),
		Status:       int32(binary.LittleEndian.Uint32(buf[8:12])),
		UsbdStatus:   binary.LittleEndian.Uint32(buf[12:16]),
		ActualLength: binary.LittleEndian.Uint32(buf[16:20]),
	}
}
