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


"""Utility functions for constructing HID report descriptors.
"""

import struct

import hid_constants


def ReportDescriptor(*items):
  return ''.join(items)


def _PackItem(tag, typ, value=0, force_length=0):
  """Pack a multibyte value.

  See Device Class Definition for Human Interface Devices (HID) Version 1.11
  section 5.8.

  Args:
    tag: Item tag.
    typ: Item type.
    value: Item value.
    force_length: Force packing to a specific width.

  Returns:
    Packed string.
  """
  if value == 0 and force_length <= 0:
    return struct.pack('<B', tag << 4 | typ << 2 | 0)
  elif value <= 0xff and force_length <= 1:
    return struct.pack('<BB', tag << 4 | typ << 2 | 1, value)
  elif value <= 0xffff and force_length <= 2:
    return struct.pack('<BH', tag << 4 | typ << 2 | 2, value)
  elif value <= 0xffffffff and force_length <= 4:
    return struct.pack('<BI', tag << 4 | typ << 2 | 3, value)
  else:
    raise NotImplementedError('Long items are not implemented.')


def _DefineItem(name, tag, typ):
  """Create a function which encodes a HID item.

  Args:
    name: Function name.
    tag: Item tag.
    typ: Item type.

  Returns:
    A function which encodes a HID item of the given type.
  """
  assert tag >= 0 and tag <= 0xF
  assert typ >= 0 and typ <= 3

  def EncodeItem(value=0, force_length=0):
    return _PackItem(tag, typ, value, force_length)

  EncodeItem.__name__ = name
  return EncodeItem


def _DefineMainItem(name, tag):
  """Create a function which encodes a HID Main item.

  See Device Class Definition for Human Interface Devices (HID) Version 1.11
  section 6.2.2.4.

  Args:
    name: Function name.
    tag: Item tag.

  Returns:
    A function which encodes a HID item of the given type.

  Raises:
    ValueError: If the tag value is out of range.
  """
  assert tag >= 0 and tag <= 0xF

  def EncodeMainItem(*properties):
    value = 0
    for bit, is_set in properties:
      if is_set:
        value |= 1 << bit
    return _PackItem(tag, hid_constants.Scope.MAIN, value, force_length=1)

  EncodeMainItem.__name__ = name
  return EncodeMainItem

Input = _DefineMainItem('Input', 8)
Output = _DefineMainItem('Output', 9)
Feature = _DefineMainItem('Feature', 11)

# Input, Output and Feature Item Properties
#
# See Device Class Definition for Human Interface Devices (HID) Version 1.11
# section 6.2.2.5.
Data = (0, False)
Constant = (0, True)
Array = (1, False)
Variable = (1, True)
Absolute = (2, False)
Relative = (2, True)
NoWrap = (3, False)
Wrap = (3, True)
Linear = (4, False)
NonLinear = (4, True)
PreferredState = (5, False)
NoPreferred = (5, True)
NoNullPosition = (6, False)
NullState = (6, True)
NonVolatile = (7, False)
Volatile = (7, True)
BitField = (8, False)
BufferedBytes = (8, True)


def Collection(typ, *items):
  start = struct.pack('<BB', 0xA1, typ)
  end = struct.pack('<B', 0xC0)
  return start + ''.join(items) + end

# Global Items
#
# See Device Class Definition for Human Interface Devices (HID) Version 1.11
# section 6.2.2.7.
UsagePage = _DefineItem('UsagePage', 0, hid_constants.Scope.GLOBAL)
LogicalMinimum = _DefineItem('LogicalMinimum', 1, hid_constants.Scope.GLOBAL)
LogicalMaximum = _DefineItem('LogicalMaximum', 2, hid_constants.Scope.GLOBAL)
PhysicalMinimum = _DefineItem('PhysicalMinimum', 3, hid_constants.Scope.GLOBAL)
PhysicalMaximum = _DefineItem('PhysicalMaximum', 4, hid_constants.Scope.GLOBAL)
UnitExponent = _DefineItem('UnitExponent', 5, hid_constants.Scope.GLOBAL)
Unit = _DefineItem('Unit', 6, hid_constants.Scope.GLOBAL)
ReportSize = _DefineItem('ReportSize', 7, hid_constants.Scope.GLOBAL)
ReportID = _DefineItem('ReportID', 8, hid_constants.Scope.GLOBAL)
ReportCount = _DefineItem('ReportCount', 9, hid_constants.Scope.GLOBAL)
Push = _DefineItem('Push', 10, hid_constants.Scope.GLOBAL)
Pop = _DefineItem('Pop', 11, hid_constants.Scope.GLOBAL)

# Local Items
#
# See Device Class Definition for Human Interface Devices (HID) Version 1.11
# section 6.2.2.8.
Usage = _DefineItem('Usage', 0, hid_constants.Scope.LOCAL)
UsageMinimum = _DefineItem('UsageMinimum', 1, hid_constants.Scope.LOCAL)
UsageMaximum = _DefineItem('UsageMaximum', 2, hid_constants.Scope.LOCAL)
DesignatorIndex = _DefineItem('DesignatorIndex', 3, hid_constants.Scope.LOCAL)
DesignatorMinimum = _DefineItem('DesignatorMinimum', 4,
                                hid_constants.Scope.LOCAL)
DesignatorMaximum = _DefineItem('DesignatorMaximum', 5,
                                hid_constants.Scope.LOCAL)
StringIndex = _DefineItem('StringIndex', 7, hid_constants.Scope.LOCAL)
StringMinimum = _DefineItem('StringMinimum', 8, hid_constants.Scope.LOCAL)
StringMaximum = _DefineItem('StringMaximum', 9, hid_constants.Scope.LOCAL)
Delimiter = _DefineItem('Delimiter', 10, hid_constants.Scope.LOCAL)
