# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Implementation of a USB HID mouse.

Two classes are provided by this module. The MouseFeature class implements
the core functionality of a HID mouse and can be included in any HID gadget.
The MouseGadget class implements an example mouse gadget.
"""

import struct

import hid_constants
import hid_descriptors
import hid_gadget
import usb_constants


class MouseFeature(hid_gadget.HidFeature):
  """HID feature implementation for a mouse.

  REPORT_DESC provides an example HID report descriptor for a device including
  this functionality.
  """

  REPORT_DESC = hid_descriptors.ReportDescriptor(
    hid_descriptors.UsagePage(0x01),  # Generic Desktop
    hid_descriptors.Usage(0x02),  # Mouse
    hid_descriptors.Collection(
      hid_constants.CollectionType.APPLICATION,
      hid_descriptors.Usage(0x01),  # Pointer
      hid_descriptors.Collection(
        hid_constants.CollectionType.PHYSICAL,
        hid_descriptors.UsagePage(0x09),  # Buttons
        hid_descriptors.UsageMinimum(1),
        hid_descriptors.UsageMaximum(3),
        hid_descriptors.LogicalMinimum(0, force_length=1),
        hid_descriptors.LogicalMaximum(1),
        hid_descriptors.ReportCount(3),
        hid_descriptors.ReportSize(1),
        hid_descriptors.Input(
          hid_descriptors.Data,
          hid_descriptors.Variable,
          hid_descriptors.Absolute,
        ),
        hid_descriptors.ReportCount(1),
        hid_descriptors.ReportSize(5),
        hid_descriptors.Input(hid_descriptors.Constant),
        hid_descriptors.UsagePage(0x01),  # Generic Desktop
        hid_descriptors.Usage(0x30),  # X
        hid_descriptors.Usage(0x31),  # Y
        hid_descriptors.LogicalMinimum(0x81),  # -127
        hid_descriptors.LogicalMaximum(127),
        hid_descriptors.ReportSize(8),
        hid_descriptors.ReportCount(2),
        hid_descriptors.Input(
          hid_descriptors.Data,
          hid_descriptors.Variable,
          hid_descriptors.Relative,
        ),
      ),
    ),
  )

  def __init__(self):
    super(MouseFeature, self).__init__()
    self._buttons = 0

  def ButtonDown(self, button):
    self._buttons |= button
    if self.IsConnected():
      self.SendReport(self.EncodeInputReport())

  def ButtonUp(self, button):
    self._buttons &= ~button
    if self.IsConnected():
      self.SendReport(self.EncodeInputReport())

  def Move(self, x_displacement, y_displacement):
    if self.IsConnected():
      self.SendReport(self.EncodeInputReport(x_displacement, y_displacement))

  def EncodeInputReport(self, x_displacement=0, y_displacement=0):
    return struct.pack('Bbb', self._buttons, x_displacement, y_displacement)

  def GetInputReport(self):
    """Construct an input report.

    See Device Class Definition for Human Interface Devices (HID) Version 1.11
    Appendix B.2.

    Returns:
      A packed input report.
    """
    return self.EncodeInputReport()


class MouseGadget(hid_gadget.HidGadget):
  """USB gadget implementation of a HID mouse."""

  def __init__(self):
    self._feature = MouseFeature()
    super(MouseGadget, self).__init__(
      report_desc=MouseFeature.REPORT_DESC,
      features={0: self._feature},
      packet_size=8,
      interval_ms=1,
      out_endpoint=False,
      vendor_id=usb_constants.VendorID.GOOGLE,
      product_id=usb_constants.ProductID.GOOGLE_MOUSE_GADGET,
      device_version=0x0100,
    )
    self.AddStringDescriptor(1, 'Google Inc.')
    self.AddStringDescriptor(2, 'Mouse Gadget')

  def ButtonDown(self, button):
    self._feature.ButtonDown(button)

  def ButtonUp(self, button):
    self._feature.ButtonUp(button)

  def Move(self, x_displacement, y_displacement):
    self._feature.Move(x_displacement, y_displacement)


def RegisterHandlers():
  """Registers web request handlers with the application server."""

  from tornado import web

  class WebConfigureHandler(web.RequestHandler):
    def post(self):
      gadget = MouseGadget()
      server.SwitchGadget(gadget)

  class WebClickHandler(web.RequestHandler):
    def post(self):
      BUTTONS = {
        '1': hid_constants.Mouse.BUTTON_1,
        '2': hid_constants.Mouse.BUTTON_2,
        '3': hid_constants.Mouse.BUTTON_3,
      }

      button = BUTTONS[self.get_argument('button')]
      server.gadget.ButtonDown(button)
      server.gadget.ButtonUp(button)

  class WebMoveHandler(web.RequestHandler):
    def post(self):
      x = int(self.get_argument('x'))
      y = int(self.get_argument('y'))
      server.gadget.Move(x, y)

  import server

  server.app.add_handlers(
    '.*$',
    [
      (r'/mouse/configure', WebConfigureHandler),
      (r'/mouse/move', WebMoveHandler),
      (r'/mouse/click', WebClickHandler),
    ],
  )
