#!/usr/bin/env python3 -i
# Copyright 2019-2020 Collabora, Ltd
# SPDX-License-Identifier: BSL-1.0
# Author: Ryan Pavlik <ryan.pavlik@collabora.com>
"""XR Hardware device database element type."""

import attr
import re

_VID_PID_RE = re.compile(r'^[0-9a-f]{4}$')

_USBSERIALNAME_RE = re.compile(r'^[._0-9a-zA-Z]+$')


def _is_four_hexits(_self, _attribute, val):
    if not _VID_PID_RE.match(val):
        raise ValueError("Must be four hexadecimal characters in lowercase")


def _is_suitable_device_name(_self, _attribute, val):
    if val is None:
        return
    if not _USBSERIALNAME_RE.match(val):
        raise ValueError(
            "Must be a reasonable thing to put in a device node name: "
            "a-z, A-Z, numbers, ., _")


@attr.s
class Device:
    """An XR hardware device that we should permit access to."""

    description = attr.ib()
    vid = attr.ib(default=None, validator=_is_four_hexits)
    pid = attr.ib(default=None, validator=_is_four_hexits)
    usb_serial_name = attr.ib(default=None, validator=_is_suitable_device_name)
    usb = attr.ib(default=True)
    bluetooth = attr.ib(default=False)
    extra_properties = attr.ib(default=None)

    def get_properties_to_set(self):
        """Generate the udev properties to set for this device."""
        yield ('ID_xrhardware', 1)

        if self.usb_serial_name:
            yield ('ID_xrhardware_USBSERIAL_NAME', self.usb_serial_name)
        if self.extra_properties:
            for k, v in self.extra_properties.items():
                yield (k, v)

    def yield_hwdb_identification(self):
        """Compute identification for hwdb recognition of this device."""
        if not self.vid:
            raise RuntimeError(
                "Can't make a hwdb entry for something without a vid!")
        # usb: and bluetooth: prefix come later
        parts = []
        parts.append('v%s' % self.vid)
        if self.pid:
            parts.append('p%s' % self.pid)
        parts.append('*')
        identifier_suffix = ''.join(parts)
        if self.usb:
            yield 'usb:' + identifier_suffix
        if self.bluetooth:
            yield 'bluetooth:' + identifier_suffix

    def make_hwdb_entry(self):
        """Return a hwdb entry for this device."""
        lines = ['# ' + self.description]
        lines.extend(list(self.yield_hwdb_identification()))
        for k, v in self.get_properties_to_set():
            lines.append(' %s=%s' % (k, v))
        return '\n'.join(lines)

    @property
    def extended_description(self):
        """Return the description augmented with the interface types."""
        interfaces = []
        if self.bluetooth:
            interfaces.append("Bluetooth")
        if self.usb:
            interfaces.append("USB")
        return "%s - %s" % (self.description, ", ".join(interfaces))

    def make_commented_rule(self):
        """Return a comment and udev rule for this device."""
        return '\n'.join((
            "# %s" % self.extended_description,
            self.make_rule(),
            ""))

    def yield_rule_condition_lists(self):
        """Yield the udev rule conditions (as lists) to select this device."""
        if self.usb:
            parts = []
            if self.vid:
                parts.append('ATTRS{idVendor}=="%s"' % self.vid)
            if self.pid:
                parts.append('ATTRS{idProduct}=="%s"' % self.pid)
            yield parts

        if self.bluetooth:
            yield ['KERNELS=="0005:%s:%s.*"' %
                   (self.vid.upper(), self.pid.upper())]
            # Bluetooth devices don't get idVendor and idProduct,
            # but they do get this which is pretty similar.
            # However, these fail to trigger the rule properly for the PS Move
            # at least.
            #
            # parts = ['ATTRS{id/bustype}=="0005"']
            # if self.vid:
            #     parts.append('ATTRS{id/vendor}=="%s"' % self.vid)
            # if self.pid:
            #     parts.append('ATTRS{id/product}=="%s"' % self.pid)
            # yield parts

    def yield_rule_conditions(self):
        """Yield the udev rule conditions to select this device."""
        for conditions in self.yield_rule_condition_lists():
            yield ", ".join(conditions)

    def make_rule(self):
        """Return a udev rule for this device."""
        rules = []
        for condition in self.yield_rule_conditions():
            parts = [condition, 'TAG+="uaccess"']
            parts.extend('ENV{%s}="%s"' % (k, v)
                         for k, v in self.get_properties_to_set())
            rules.append(', '.join(parts))
        return "\n".join(rules)
