# The MIT License (MIT)
#
# Copyright (c) 2018 Dan Halbert for Adafruit Industries
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import struct

"""
HID specific descriptors
========================

* Author(s): Dan Halbert
"""

from collections import namedtuple

from adafruit_usb_descriptor import hid

# Information about each kind of device
# report_length does not include report ID in first byte, if present when sent.
DeviceData = namedtuple('DeviceData', ('report_length', 'usage_page', 'usage'))
HID_DEVICE_DATA = {
    "KEYBOARD" : DeviceData(report_length=8, usage_page=0x01, usage=0x06),    # Generic Desktop, Keyboard
    "MOUSE" : DeviceData(report_length=4, usage_page=0x01, usage=0x02),       # Generic Desktop, Mouse
    "CONSUMER" : DeviceData(report_length=2, usage_page=0x0C, usage=0x01),    # Consumer, Consumer Control
    "SYS_CONTROL" : DeviceData(report_length=1, usage_page=0x01, usage=0x80), # Generic Desktop, Sys Control
    "GAMEPAD" : DeviceData(report_length=6, usage_page=0x01, usage=0x05),     # Generic Desktop, Game Pad
    "DIGITIZER" : DeviceData(report_length=5, usage_page=0x0D, usage=0x02),   # Digitizers, Pen
    "XAC_COMPATIBLE_GAMEPAD" : DeviceData(report_length=3, usage_page=0x01, usage=0x05), # Generic Desktop, Game Pad
    "RAW" : DeviceData(report_length=64, usage_page=0xFFAF, usage=0xAF),      # Vendor 0xFFAF "Adafruit", 0xAF
    }

def keyboard_hid_descriptor(report_id):
    data = HID_DEVICE_DATA["KEYBOARD"]
    return hid.ReportDescriptor(
        description="KEYBOARD",
        report_descriptor=bytes(
            # Regular keyboard
            (0x05, data.usage_page,      # Usage Page (Generic Desktop)
             0x09, data.usage,           # Usage (Keyboard)
             0xA1, 0x01,                 # Collection (Application)
            ) +
            ((0x85, report_id) if report_id != 0 else ()) +
            (0x05, 0x07,                 #   Usage Page (Keyboard)
             0x19, 224,                  #   Usage Minimum (224)
             0x29, 231,                  #   Usage Maximum (231)
             0x15, 0x00,                 #   Logical Minimum (0)
             0x25, 0x01,                 #   Logical Maximum (1)
             0x75, 0x01,                 #   Report Size (1)
             0x95, 0x08,                 #   Report Count (8)
             0x81, 0x02,                 #   Input (Data, Variable, Absolute)
             0x81, 0x01,                 #   Input (Constant)
             0x19, 0x00,                 #   Usage Minimum (0)
             0x29, 0xDD,                 #   Usage Maximum (221)
             0x15, 0x00,                 #   Logical Minimum (0)
             0x25, 0xDD,                 #   Logical Maximum (221)
             0x75, 0x08,                 #   Report Size (8)
             0x95, 0x06,                 #   Report Count (6)
             0x81, 0x00,                 #   Input (Data, Array)
             0x05, 0x08,                 #   Usage Page (LED)
             0x19, 0x01,                 #   Usage Minimum (1)
             0x29, 0x05,                 #   Usage Maximum (5)
             0x15, 0x00,                 #   Logical Minimum (0)
             0x25, 0x01,                 #   Logical Maximum (1)
             0x75, 0x01,                 #   Report Size (1)
             0x95, 0x05,                 #   Report Count (5)
             0x91, 0x02,                 #   Output (Data, Variable, Absolute)
             0x95, 0x03,                 #   Report Count (3)
             0x91, 0x01,                 #   Output (Constant)
             0xC0,                       # End Collection
            )))

def mouse_hid_descriptor(report_id):
    data = HID_DEVICE_DATA["MOUSE"]
    return hid.ReportDescriptor(
        description="MOUSE",
        report_descriptor=bytes(
            # Regular mouse
            (0x05, data.usage_page, # Usage Page (Generic Desktop)
             0x09, data.usage,  # Usage (Mouse)
             0xA1, 0x01,        # Collection (Application)
             0x09, 0x01,        #   Usage (Pointer)
             0xA1, 0x00,        #   Collection (Physical)
            ) +
            ((0x85, report_id) if report_id != 0 else ()) +
            (0x05, 0x09,        #     Usage Page (Button)
             0x19, 0x01,        #     Usage Minimum (0x01)
             0x29, 0x05,        #     Usage Maximum (0x05)
             0x15, 0x00,        #     Logical Minimum (0)
             0x25, 0x01,        #     Logical Maximum (1)
             0x95, 0x05,        #     Report Count (5)
             0x75, 0x01,        #     Report Size (1)
             0x81, 0x02,        #     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
             0x95, 0x01,        #     Report Count (1)
             0x75, 0x03,        #     Report Size (3)
             0x81, 0x01,        #     Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
             0x05, 0x01,        #     Usage Page (Generic Desktop Ctrls)
             0x09, 0x30,        #     Usage (X)
             0x09, 0x31,        #     Usage (Y)
             0x15, 0x81,        #     Logical Minimum (-127)
             0x25, 0x7F,        #     Logical Maximum (127)
             0x75, 0x08,        #     Report Size (8)
             0x95, 0x02,        #     Report Count (2)
             0x81, 0x06,        #     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
             0x09, 0x38,        #     Usage (Wheel)
             0x15, 0x81,        #     Logical Minimum (-127)
             0x25, 0x7F,        #     Logical Maximum (127)
             0x75, 0x08,        #     Report Size (8)
             0x95, 0x01,        #     Report Count (1)
             0x81, 0x06,        #     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
             0xC0,              #   End Collection
             0xC0,              # End Collection
            )))

def consumer_hid_descriptor(report_id):
    data = HID_DEVICE_DATA["CONSUMER"]
    return hid.ReportDescriptor(
        description="CONSUMER",
        report_descriptor=bytes(
            # Consumer ("multimedia") keys
            (0x05, data.usage_page, # Usage Page (Consumer)
             0x09, data.usage,  # Usage (Consumer Control)
             0xA1, 0x01,        # Collection (Application)
            ) +
            ((0x85, report_id) if report_id != 0 else ()) +
            (0x75, 0x10,        #   Report Size (16)
             0x95, 0x01,        #   Report Count (1)
             0x15, 0x01,        #   Logical Minimum (1)
             0x26, 0x8C, 0x02,  #   Logical Maximum (652)
             0x19, 0x01,        #   Usage Minimum (Consumer Control)
             0x2A, 0x8C, 0x02,  #   Usage Maximum (AC Send)
             0x81, 0x00,        #   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
             0xC0,              # End Collection
            )))

def sys_control_hid_descriptor(report_id):
    data = HID_DEVICE_DATA["SYS_CONTROL"]
    return hid.ReportDescriptor(
        description="SYS_CONTROL",
        report_descriptor=bytes(
            # Power controls
            (0x05, data.usage_page, # Usage Page (Generic Desktop Ctrls)
             0x09, data.usage,  # Usage (Sys Control)
             0xA1, 0x01,        # Collection (Application)
            ) +
            ((0x85, report_id) if report_id != 0 else ()) +
            (0x75, 0x02,        #   Report Size (2)
             0x95, 0x01,        #   Report Count (1)
             0x15, 0x01,        #   Logical Minimum (1)
             0x25, 0x03,        #   Logical Maximum (3)
             0x09, 0x82,        #   Usage (Sys Sleep)
             0x09, 0x81,        #   Usage (Sys Power Down)
             0x09, 0x83,        #   Usage (Sys Wake Up)
             0x81, 0x60,        #   Input (Data,Array,Abs,No Wrap,Linear,No Preferred State,Null State)
             0x75, 0x06,        #   Report Size (6)
             0x81, 0x03,        #   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
             0xC0,              # End Collection
            )))

def gamepad_hid_descriptor(report_id):
    data = HID_DEVICE_DATA["GAMEPAD"]
    return hid.ReportDescriptor(
        description="GAMEPAD",
        report_descriptor=bytes(
            # Gamepad with 16 buttons and two joysticks
            (0x05, data.usage_page, # Usage Page (Generic Desktop Ctrls)
             0x09, data.usage,  # Usage (Game Pad)
             0xA1, 0x01,        # Collection (Application)
            ) +
            ((0x85, report_id) if report_id != 0 else ()) +
            (0x05, 0x09,        #   Usage Page (Button)
             0x19, 0x01,        #   Usage Minimum (Button 1)
             0x29, 0x10,        #   Usage Maximum (Button 16)
             0x15, 0x00,        #   Logical Minimum (0)
             0x25, 0x01,        #   Logical Maximum (1)
             0x75, 0x01,        #   Report Size (1)
             0x95, 0x10,        #   Report Count (16)
             0x81, 0x02,        #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
             0x05, 0x01,        #   Usage Page (Generic Desktop Ctrls)
             0x15, 0x81,        #   Logical Minimum (-127)
             0x25, 0x7F,        #   Logical Maximum (127)
             0x09, 0x30,        #   Usage (X)
             0x09, 0x31,        #   Usage (Y)
             0x09, 0x32,        #   Usage (Z)
             0x09, 0x35,        #   Usage (Rz)
             0x75, 0x08,        #   Report Size (8)
             0x95, 0x04,        #   Report Count (4)
             0x81, 0x02,        #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
             0xC0,              # End Collection
            )))

def digitizer_hid_descriptor(report_id):
    data = HID_DEVICE_DATA["DIGITIZER"]
    return hid.ReportDescriptor(
        description="DIGITIZER",
        report_descriptor=bytes(
            # Digitizer (used as an absolute pointer)
            (0x05, data.usage_page, # Usage Page (Digitizers)
             0x09, data.usage,  # Usage (Pen)
             0xA1, 0x01,        # Collection (Application)
            ) +
            ((0x85, report_id) if report_id != 0 else ()) +
            (0x09, 0x01,        #   Usage (Stylus)
             0xA1, 0x00,        #   Collection (Physical)
             0x09, 0x32,        #     Usage (In-Range)
             0x09, 0x42,        #     Usage (Tip Switch)
             0x09, 0x44,        #     Usage (Barrel Switch)
             0x09, 0x45,        #     Usage (Eraser Switch)
             0x15, 0x00,        #     Logical Minimum (0)
             0x25, 0x01,        #     Logical Maximum (1)
             0x75, 0x01,        #     Report Size (1)
             0x95, 0x04,        #     Report Count (4)
             0x81, 0x02,        #     Input (Data,Var,Abs)
             0x75, 0x04,        #     Report Size (4) -- Filler
             0x95, 0x01,        #     Report Count (1) -- Filler
             0x81, 0x01,        #     Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
             0x05, 0x01,        #     Usage Page (Generic Desktop Ctrls)
             0x15, 0x00,        #     Logical Minimum (0)
             0x26, 0xff, 0x7f,  #     Logical Maximum (32767)
             0x09, 0x30,        #     Usage (X)
             0x09, 0x31,        #     Usage (Y)
             0x75, 0x10,        #     Report Size (16)
             0x95, 0x02,        #     Report Count (2)
             0x81, 0x02,        #     Input (Data,Var,Abs)
             0xC0,              #   End Collection
             0xC0,              # End Collection
            )))

def xac_compatible_gamepad_hid_descriptor(report_id):
    data = HID_DEVICE_DATA["XAC_COMPATIBLE_GAMEPAD"]
    return hid.ReportDescriptor(
        description="XAC",
        report_descriptor=bytes(
            # This descriptor mimics the simple joystick from PDP that the XBox likes
            (0x05, data.usage_page,  #  Usage Page (Desktop),
             0x09, data.usage,       #  Usage (Gamepad),
             0xA1, 0x01,             #  Collection (Application),
            ) +
            ((0x85, report_id) if report_id != 0 else ()) +
            (0x15, 0x00,         #      Logical Minimum (0),
             0x25, 0x01,         #      Logical Maximum (1),
             0x35, 0x00,         #      Physical Minimum (0),
             0x45, 0x01,         #      Physical Maximum (1),
             0x75, 0x01,         #      Report Size (1),
             0x95, 0x08,         #      Report Count (8),
             0x05, 0x09,         #      Usage Page (Button),
             0x19, 0x01,         #      Usage Minimum (01h),
             0x29, 0x08,         #      Usage Maximum (08h),
             0x81, 0x02,         #      Input (Variable),
             0x05, 0x01,         #      Usage Page (Desktop),
             0x26, 0xFF, 0x00,   #      Logical Maximum (255),
             0x46, 0xFF, 0x00,   #      Physical Maximum (255),
             0x09, 0x30,         #      Usage (X),
             0x09, 0x31,         #      Usage (Y),
             0x75, 0x08,         #      Report Size (8),
             0x95, 0x02,         #      Report Count (2),
             0x81, 0x02,         #      Input (Variable),
             0xC0                #  End Collection
            )))

def raw_hid_descriptor(report_id):
    if report_id != 0:
        raise ValueError("raw hid must not have a report id")
    data = HID_DEVICE_DATA["RAW"]
    return hid.ReportDescriptor(
        description="RAW",
        report_descriptor=bytes(
            # Provide vendor-defined
            # This is a two-byte page value.
            (0x06, data.usage_page & 0xff, (data.usage_page >> 8) & 0xff, #  Usage Page (Vendor 0xFFAF "Adafruit"),
             0x09, data.usage,         #  Usage (AF),
             0xA1, 0x01,         #  Collection (Application),
             0x75, 0x08,         #      Report Size (8),
             0x15, 0x00,         #      Logical Minimum (0),
             0x26, 0xFF, 0x00,   #      Logical Maximum (255),
             0x95, 0x08,         #      Report Count (8),
             0x09, 0x01,         #      Usage(xxx)
             0x81, 0x02,         #      Input (Variable)
             0x95, 0x08,         #      Report Count (8),
             0x09, 0x02,         #      Usage(xxx)
             0x91, 0x02,         #      Input (Variable)
             0xC0                #  End Collection
            )))

# Function to call for each kind of HID descriptor.
REPORT_DESCRIPTOR_FUNCTIONS = {
    "KEYBOARD" : keyboard_hid_descriptor,
    "MOUSE" : mouse_hid_descriptor,
    "CONSUMER" : consumer_hid_descriptor,
    "SYS_CONTROL" : sys_control_hid_descriptor,
    "GAMEPAD" : gamepad_hid_descriptor,
    "DIGITIZER" : digitizer_hid_descriptor,
    "XAC_COMPATIBLE_GAMEPAD" : xac_compatible_gamepad_hid_descriptor,
    "RAW" : raw_hid_descriptor,
}
