#!/usr/bin/env python3
import itertools
import re
import sys
import xml.etree.ElementTree
from typing import Iterable, List, NamedTuple


class Device(NamedTuple):
    """Represent a device"""
    vendor: str
    product: str

    def modalias(self) -> str:
        """Format the device info for use in AppStream."""
        return f"usb:v{self.vendor.upper()}p{self.product.upper()}d*"


class Rule(NamedTuple):
    """Represent a udev rule."""
    vendors: List[str]
    products: List[str]

    def devices(self) -> Iterable[Device]:
        return map(lambda x: Device(*x),
                   itertools.product(self.vendors, self.products))


def parse_rule(f) -> Iterable[Rule]:
    """Parse a udev rule file."""
    syntax = re.compile(r'KERNEL=="hidraw\*", SUBSYSTEM=="hidraw", '
                        r'ATTRS\{idVendor\}=="(?P<vendors>[^"]*)", '
                        r'ATTRS\{idProduct\}=="(?P<products>[^"]*)"')

    for line_no, line in enumerate(f.readlines(), start=1):
        line = line.strip()

        # Skip irrelevant lines
        if not line or line.startswith(("#", "LABEL=", "ACTION!=")):
            continue

        parse = syntax.match(line)
        if parse is None:
            raise ValueError(f"Invalid syntax on line {line_no}: '{line}'")

        yield Rule(parse.group('vendors').split('|'),
                   parse.group('products').split('|'))


if __name__ == '__main__':
    with open('debian/libu2f-udev.metainfo.xml', 'rb') as metainfo:
        data = xml.etree.ElementTree.parse(metainfo)
        provides = data.getroot().find('provides')
        assert provides is not None

    with open('70-u2f.rules') as rules_file:
        for rule in parse_rule(rules_file):
            for device in rule.devices():
                modalias = xml.etree.ElementTree.Element('modalias')
                modalias.text = device.modalias()
                provides.append(modalias)

    data.write(sys.stdout.buffer)
