import re


def _build_regex(pattern):
    regex = '^'
    part_beginning = 0
    while part_beginning < len(pattern):
        ix1 = pattern.find('*', part_beginning)
        ix2 = pattern.find('?', part_beginning)
        ix1 = len(pattern) if ix1 < 0 else ix1
        ix2 = len(pattern) if ix2 < 0 else ix2
        part_end = min(ix1, ix2)

        regex += re.escape(pattern[part_beginning:part_end])

        if part_end < len(pattern):
            if pattern[part_end] == '*':
                regex += '.*'
            else:
                regex += '.'

        part_beginning = part_end + 1

    regex += '$'
    return regex


def _pattern_matches(pattern, string):
    if pattern.lower() == 'all':
        return True
    regex = _build_regex(pattern)
    return re.match(regex, string, re.IGNORECASE) is not None


def _daemon_list_matches_daemon(daemon_list, daemon, recursion_depth):
    try:
        cur_list_end = daemon_list.index('except')
    except ValueError:
        cur_list_end = len(daemon_list)
    cur_list = daemon_list[:cur_list_end]
    matches_cur_list = False
    for item in cur_list:
        try:
            ix = item.index('@')
            # For simplicity, we ignore the host part. So we must make sure
            # that a daemon list containing a host-based pattern will always match
            # the daemon part of that host-based pattern (e.g. 'all except vsftpd@localhost
            # matches 'vsftpd'). See test_config_applies_to_daemon_with_host_except().
            if recursion_depth % 2 == 1:
                continue
            pattern = item[:ix]
        except ValueError:
            pattern = item
        if _pattern_matches(pattern, daemon):
            matches_cur_list = True
            break

    next_list = daemon_list[cur_list_end + 1:]
    if not next_list:
        matches_next_list = False
    else:
        matches_next_list = _daemon_list_matches_daemon(next_list, daemon, recursion_depth + 1)

    return matches_cur_list and not matches_next_list


def config_applies_to_daemon(facts, daemon):
    """
    Returns True if the specified tcp_wrappers configuration applies to the specified daemon.
    Otherwise returns False.

    This information is intended to be used in the Checks phase to check whether there is
    any tcp_wrappers configuration that the user needs to migrate manually and whether we
    should inhibit the upgrade, so that the upgraded system is not insecure.

    :param facts: A TcpWrappersFacts representation of the tcp_wrappers configuration
    :param daemon: The daemon name
    """
    for daemon_list in facts.daemon_lists:
        value = [item.lower() for item in daemon_list.value]
        if _daemon_list_matches_daemon(value, daemon, 0):
            return True
    return False
