from typing import cast
from collections.abc import Generator, Sequence

from dataclasses import dataclass
from pathlib import Path

import usb.core


@dataclass
class DfuType:
    name: str
    description: str
    vid: int
    pid: int
    manufacturer: str
    product: str

    def emit_udev_rule(self):
        return (
            f'# {self.description}\n'
            f'SUBSYSTEM="usb", ATTR{{idVendor}}=="{self.vid:04x}", ATTR{{idProduct}}=="{self.pid:04x}", '
            f'ATTR{{manufacturer}}=="{self.manufacturer}", ATTR{{product}}=="{self.product}", TAG+="uaccess"'
        )


KNOWN_DEVICE_TYPES: Sequence[DfuType] = (
    DfuType(
        name='s3c',
        description='S3C2416 family bootloader',
        vid=0x054b,
        pid=0x1582,
        manufacturer='SAMSUNG',
        product='S3C2443',
    ),
    DfuType(
        name='imx233',
        description='iMX233 family bootloader',
        vid=0x0419,
        pid=0x0133,
        manufacturer='Besta',
        product='UDisk',
    ),
    DfuType(
        name='w55pa71',
        description='Nuvoton W55PA71 family bootloader',
        vid=0x0417,
        pid=0x9394,
        manufacturer='Winbond      ',
        product='W55PA71      ',
    ),
    DfuType(
        name='fa32',
        description='Nuvoton FA32 family bootloader',
        vid=0x0483,
        pid=0x5720,
        manufacturer='nuvoTon',
        product='USB MSC',
    ),
)

KNOWN_DEVICE_INDEX: dict[tuple[int | None, int | None, str | None, str | None], DfuType] = {
    (dev.vid, dev.pid, dev.manufacturer, dev.product): dev for dev in KNOWN_DEVICE_TYPES
}


def generate_udev_file(path: Path) -> None:
    with path.open('w') as udev_file:
        udev_file.write('# Generated by dfutool from besta-tools\n\n')
        for dev_type in KNOWN_DEVICE_TYPES:
            udev_file.write(dev_type.emit_udev_rule())
            udev_file.write('\n')


def trim_nul_terminated(value: str | None) -> str | None:
    if value is None:
        return None
    first_zero = value.find('\x00')
    if first_zero == -1:
        return value
    return value[:first_zero]


def enumerate_device() -> Generator[tuple[DfuType, usb.core.Device]]:
    for dev in usb.core.find(find_all=True):
        try:
            device_type_key = (
                cast(int | None, dev.idVendor),
                cast(int | None, dev.idProduct),
                trim_nul_terminated(dev.manufacturer),
                trim_nul_terminated(dev.product)
            )
            if device_type_key in KNOWN_DEVICE_INDEX:
                yield KNOWN_DEVICE_INDEX[device_type_key], dev
        except ValueError:
            # No langid and probably no access to device. Skipping,
            continue
