# Copyright (c) 2015 SUSE Linux GmbH.  All rights reserved.
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi.  If not, see <http://www.gnu.org/licenses/>
#
import os
import re
import logging
from typing import List

# project
from kiwi.command import Command
from kiwi.path import Path
from kiwi.exceptions import KiwiCommandCapabilitiesError

log = logging.getLogger('kiwi')


class CommandCapabilities:
    """
    **Validation of command version flags or version**

    Performs commands calls and parses the output
    so it can look specific flags on help message, check
    command version, etc.
    """
    @staticmethod
    def has_option_in_help(
        call: str, flag: str, help_flags: List[str] = [],
        root: str = '', raise_on_error: bool = True, silent: bool = False
    ):
        """
        Checks if the given flag is present in the help output
        of the given command.

        :param str call: the command to check
        :param str flag: the flag or substring to find in stdout
        :param list help_flags: a list with the required command arguments.
        :param str root: root directory of the env to validate
        :param bool raise_on_error:
            raises KiwiCommandCapabilitiesError and message if the
            specified flag does not occur on stdout/stderr of the
            command call
        :param bool silent: don't log parsing failures

        :return: True if the flag is found, False in any other case

        :rtype: bool
        """
        help_args = help_flags or ['--help']
        if root:
            arguments = ['chroot', root, call] + help_args
        else:
            arguments = [call] + help_args
        if raise_on_error and not Path.which(
            filename=call, access_mode=os.X_OK, root_dir=root or None
        ):
            raise KiwiCommandCapabilitiesError(
                f'Attempting to call "{arguments}" but {call} was not found'
            )
        command = Command.run(arguments, raise_on_error=False)
        for line in command.output.splitlines():
            if flag in line:
                return True
        for line in command.error.splitlines():
            if flag in line:
                return True
        message = 'Could not find flag {} in {} output'.format(flag, call)
        if raise_on_error:
            raise KiwiCommandCapabilitiesError(message)
        if not silent:
            log.warning(message)
        return False

    @staticmethod
    def check_version(
        call: str, version_waterline: tuple, version_flags: List[str] = [],
        root: str = '', raise_on_error: bool = True, silent: bool = False
    ) -> bool:
        """
        Checks if the given command version is equal or higher than
        the given version tuple.

        :param str call: the command the check
        :param tuple version_waterline: minimum desired version of the command
        :param list version_flags: a list with the required command arguments.
        :param str root: root directory of the env to validate
        :param bool raise_on_error: control error behavior
        :param bool silent: don't log parsing failures

        :raises KiwiCommandCapabilitiesError: if raise_on_error is True and
            command execution fails or version can't be parsed.
        :return: True if the current command version is equal or higher to
            version_waterline

        :rtype: bool
        """
        version_args = version_flags or ['--version']
        if root:
            arguments = ['chroot', root, call] + version_args
        else:
            arguments = [call] + version_args
        version_info = None
        try:
            command = Command.run(arguments)
            for line in command.output.splitlines():
                matches = re.findall(r'([0-9]+(\.[0-9]+)*)', line)
                if matches:
                    match = max([m[0] for m in matches], key=len)
                    version_info = tuple(
                        int(elt) for elt in match.split('.')
                    )
                    break
            if version_info is None:
                raise Exception
        except Exception:
            message = 'Could not parse {0} version'.format(call)
            if raise_on_error:
                raise KiwiCommandCapabilitiesError(message)
            if not silent:
                log.warning(message)
            return False
        return version_info >= version_waterline
