import os
import sys
import subprocess
import unittest
import functools
import re

if sys.version_info >= (3, 9):
    functools_cache = functools.cache
else:
    functools_cache = functools.lru_cache

@functools_cache
def is_pipewire():
    # Check that pulseaudio or pipewire-pulse is running.
    proc = subprocess.run(['pactl', 'list', 'message-handlers'],
                                        capture_output=True, text=True,
                                        check=True)
    match = re.search('[Pp]ipe[Ww]ire', proc.stdout)
    return bool(match)

def _id(obj):
    return obj

@functools_cache
def requires_resources(resources):
    """Skip the test when one of the resource is not available.

    'resources' is a string or a tuple instance (MUST be hashable).
    """

    resources = [resources] if isinstance(resources, str) else resources
    for res in resources:
        try:
            if res == 'os.devnull':
                # Check that os.devnull is writable.
                with open(os.devnull, 'w'):
                    pass
            elif res == 'libpulse':
                is_pipewire()
            elif res == 'pulseaudio':
                if is_pipewire():
                    raise Exception
            else:
                # Otherwise check that the module can be imported.
                exec(f'import {res}')
        except Exception:
            return unittest.skip(f"'{res}' is not available")
    else:
        return _id

def load_ordered_tests(loader, standard_tests, pattern):
    """Keep the tests in the order they were declared in the class.

    Thanks to https://stackoverflow.com/a/62073640
    """

    ordered_cases = []
    for test_suite in standard_tests:
        ordered = []
        for test_case in test_suite:
            test_case_type = type(test_case)
            method_name = test_case._testMethodName
            testMethod = getattr(test_case, method_name)
            line = testMethod.__code__.co_firstlineno
            ordered.append( (line, test_case_type, method_name) )
        ordered.sort()
        for line, case_type, name in ordered:
            ordered_cases.append(case_type(name))
    return unittest.TestSuite(ordered_cases)

def find_in_logs(logs, logger, msg):
    """Return True if 'msg' from 'logger' is in 'logs'."""

    for log in (log.split(':', maxsplit=2) for log in logs):
        if len(log) == 3 and log[1] == logger and log[2] == msg:
            return True
    return False

def search_in_logs(logs, logger, matcher):
    """Return True if the matcher's pattern is found in a message in 'logs'."""

    for log in (log.split(':', maxsplit=2) for log in logs):
        if (len(log) == 3 and log[1] == logger and
                matcher.search(log[2]) is not None):
            return True
    return False

def min_python_version(sys_version):
    return unittest.skipIf(sys.version_info < sys_version,
                        f'Python version {sys_version} or higher required')
