# -*- coding: utf-8 -*-

"""Additional utility functions to use with application detectors."""

from __future__ import annotations

__all__ = [
    'is_32bit', 'is_64bit', 'is_linux', 'is_not_linux', 'is_osx', 'is_not_osx', 'is_win', 'is_not_win',
    'CliVersionChecker',
]

import re
import typing

from anaconda_navigator import config as navigator_config
from anaconda_navigator.utils.conda import launch as conda_launch_utils

from .. import detectors
from .. import validation


def is_32bit() -> bool:
    """Check if user runs a 32-bit OS."""
    return navigator_config.BITS_32


def is_64bit() -> bool:
    """Check if user runs a 64-bit OS."""
    return navigator_config.BITS_64


def is_linux() -> bool:
    """Check if user runs Linux OS."""
    return navigator_config.LINUX


def is_not_linux() -> bool:
    """Check if user runs anything but Linux OS."""
    return not navigator_config.LINUX


def is_osx() -> bool:
    """Check if user runs OS X."""
    return navigator_config.MAC


def is_not_osx() -> bool:
    """Check if user runs anything but OS X."""
    return not navigator_config.MAC


def is_win() -> bool:
    """Check if user runs Windows OS."""
    return navigator_config.WIN


def is_not_win() -> bool:
    """Check if user runs anything but Windows OS."""
    return not navigator_config.WIN


class CliVersionChecker(detectors.Filter):  # pylint: disable=too-few-public-methods
    """
    Detect version of the application from the terminal output of the application.

    It executes application with a given set of `arguments` and searches for `pattern` in output. The latter one must
    have a single group, value from which will be used as a result version.
    """

    __slots__ = ('__arguments', '__pattern')

    def __init__(
            self,
            pattern: str = r'(\d+\.[\d\.]+)',
            arguments: typing.Union[str, typing.Iterable[str]] = ('--version',),
    ) -> None:
        """Initialize new :class:`~CliVersionChecker` instance."""
        if isinstance(arguments, str):
            arguments = (arguments,)
        elif not isinstance(arguments, tuple):
            arguments = tuple(arguments)

        self.__arguments: typing.Final[typing.Tuple[str, ...]] = arguments
        self.__pattern: typing.Final[typing.Pattern[str]] = re.compile(pattern)

    @classmethod
    def _parse_configuration(cls, *args: typing.Any, **kwargs: typing.Any) -> detectors.Detector:
        """Parse configuration for this particular :class:`~Detector`."""
        validation.OfLength(0)(args, field='args')

        pattern: str = validation.ItemPopper('pattern', default=r'(\d+\.[\d\.]+)').convert(validation.IS_STR)(kwargs)
        arguments: typing.Tuple[str, ...] = validation.ItemPopper('arguments', default=('--version',)).convert(
            validation.sequence_of(str),
        )(kwargs)

        validation.IsEmptyMapping()(kwargs)

        with validation.catch_exception():
            return cls(pattern=pattern, arguments=arguments)

    def __call__(
            self,
            parent: typing.Iterator[detectors.DetectedApplication],
            *,
            context: detectors.DetectorContext,
    ) -> typing.Iterator[detectors.DetectedApplication]:
        """Iterate through detected applications."""
        application: 'detectors.DetectedApplication'
        for application in parent:
            try:
                executable: str = application.executable
            except AttributeError:
                continue

            stdout: str
            try:
                stdout, _, _ = conda_launch_utils.run_process([executable, *self.__arguments])
                match: typing.Optional[typing.Match[str]] = self.__pattern.search(stdout)
                if match:
                    yield application.replace(version=match.group(1))
                    continue
            except (OSError, LookupError, TypeError, ValueError):
                pass

            yield application
