"""
UDev Rules Installer
********************

Classes and utilities for installing device udev rules.
"""

from .errors import NeedsRootError

from subprocess import check_call, CalledProcessError, DEVNULL
from shutil import copy
from os import path, listdir, getlogin, geteuid

class UDevInstaller(object):
    """
    Installs the udev rules
    """
    @staticmethod
    def install(source: str, location: str) -> bool:
        """
        Install the udev rules from source into location.
        This will also reload and trigger udevadm so that devices matching the new rules will be detected.
        The user will be added to the ``plugdev`` group. If the group doesn't exist, the user will be added to it.

        :param source: The path to the source directory containing the rules
        :param location: The path to the directory to copy the rules to
        :return: Whether the install was successful
        """
        try:
            udev_installer = UDevInstaller()
            udev_installer.copy_udev_rule_files(source, location)
            udev_installer.trigger()
            udev_installer.reload_rules()
            udev_installer.add_user_plugdev_group()
        except CalledProcessError:
            if geteuid() != 0:
                raise NeedsRootError("Need to be root.")
            raise
        return True

    def __init__(self) -> None:
        self._udevadm = '/sbin/udevadm'
        self._groupadd = '/usr/sbin/groupadd'
        self._usermod = '/usr/sbin/usermod'

    def _execute(self, cmd: str, *args: str) -> None:
        command = [cmd] + list(args)
        check_call(command, stderr=DEVNULL, stdout=DEVNULL)

    def trigger(self) -> None:
        """
        Run ``udevadm trigger``
        """
        self._execute(self._udevadm, 'trigger')

    def reload_rules(self) -> None:
        """
        Run ``udevadm control --reload-rules``
        """
        self._execute(self._udevadm, 'control', '--reload-rules')

    def add_user_plugdev_group(self) -> None:
        """
        Add the user to the ``plugdev`` group
        """
        self._create_group('plugdev')
        self._add_user_to_group(getlogin(), 'plugdev')

    def _create_group(self, name: str) -> None:
        try:
            self._execute(self._groupadd, name)
        except CalledProcessError as e:
            if e.returncode != 9: # group already exists
                raise

    def _add_user_to_group(self, user: str, group: str) -> None:
        self._execute(self._usermod, '-aG', group, user)

    def copy_udev_rule_files(self, source: str, location: str) -> None:
        """
        Copy the udev rules from source to location

        :param source: The path to the source directory containing the rules
        :param location: The path to the directory to copy the rules to
        """
        src_dir_path = source
        for rules_file_name in listdir(_resource_path(src_dir_path)):
            if '.rules' in rules_file_name:
                rules_file_path = _resource_path(path.join(src_dir_path, rules_file_name))
                copy(rules_file_path, location)

def _resource_path(relative_path: str) -> str:
    return path.join(path.dirname(__file__), relative_path)
