# MicroPython USB Mouse module
#
# MIT license; Copyright (c) 2023-2024 Angus Gratton
from micropython import const
import struct
import machine

from usb.device.hid import HIDInterface

_INTERFACE_PROTOCOL_MOUSE = const(0x02)


class MouseInterface(HIDInterface):
    # A basic three button USB mouse HID interface
    def __init__(self, interface_str="MicroPython Mouse"):
        super().__init__(
            _MOUSE_REPORT_DESC,
            protocol=_INTERFACE_PROTOCOL_MOUSE,
            interface_str=interface_str,
        )
        self._l = False  # Left button
        self._m = False  # Middle button
        self._r = False  # Right button
        self._buf = bytearray(3)

    def send_report(self, dx=0, dy=0):
        b = 0
        if self._l:
            b |= 1 << 0
        if self._r:
            b |= 1 << 1
        if self._m:
            b |= 1 << 2

        # Wait for any pending report to be sent to the host
        # before updating contents of _buf.
        #
        # This loop can be removed if you don't care about possibly missing a
        # transient report, the final report buffer contents will always be the
        # last one sent to the host (it just might lose one of the ones in the
        # middle).
        while self.busy():
            machine.idle()

        struct.pack_into("Bbb", self._buf, 0, b, dx, dy)

        return super().send_report(self._buf)

    def click_left(self, down=True):
        self._l = down
        return self.send_report()

    def click_middle(self, down=True):
        self._m = down
        return self.send_report()

    def click_right(self, down=True):
        self._r = down
        return self.send_report()

    def move_by(self, dx, dy):
        if not -127 <= dx <= 127:
            raise ValueError("dx")
        if not -127 <= dy <= 127:
            raise ValueError("dy")
        return self.send_report(dx, dy)


# Basic 3-button mouse HID Report Descriptor.
# This is based on Appendix E.10 of the HID v1.11 document.
# fmt: off
_MOUSE_REPORT_DESC = (
    b'\x05\x01'  # Usage Page (Generic Desktop)
        b'\x09\x02'  # Usage (Mouse)
    b'\xA1\x01'  # Collection (Application)
        b'\x09\x01'  # Usage (Pointer)
        b'\xA1\x00'  # Collection (Physical)
            b'\x05\x09'  # Usage Page (Buttons)
                b'\x19\x01'  # Usage Minimum (01),
                b'\x29\x03'  # Usage Maximum (03),
                b'\x15\x00'  # Logical Minimum (0),
                b'\x25\x01'  # Logical Maximum (1),
                b'\x95\x03'  # Report Count (3),
                b'\x75\x01'  # Report Size (1),
                b'\x81\x02'  # Input (Data, Variable, Absolute), ;3 button bits
                b'\x95\x01'  # Report Count (1),
                b'\x75\x05'  # Report Size (5),
                b'\x81\x01'  # Input (Constant), ;5 bit padding
            b'\x05\x01'  # Usage Page (Generic Desktop),
                b'\x09\x30'  # Usage (X),
                b'\x09\x31'  # Usage (Y),
                b'\x15\x81'  # Logical Minimum (-127),
                b'\x25\x7F'  # Logical Maximum (127),
                b'\x75\x08'  # Report Size (8),
                b'\x95\x02'  # Report Count (2),
                b'\x81\x06'  # Input (Data, Variable, Relative), ;2 position bytes (X & Y)
        b'\xC0'  # End Collection
    b'\xC0'  # End Collection
)
# fmt: on
