# SPDX-License-Identifier: Apache-2.0
# Copyright 2021 The Meson development team
from __future__ import annotations

from .common import cmake_is_debug
from .. import mlog
from ..mesonlib import Version

from pathlib import Path
import re
import typing as T

if T.TYPE_CHECKING:
    from .traceparser import CMakeTraceParser
    from ..environment import Environment
    from ..compilers import Compiler
    from ..dependencies import MissingCompiler

# Small duplication of ExtraFramework to parse full
# framework paths as exposed by CMake
def _get_framework_latest_version(path: Path) -> str:
    versions: list[Version] = []
    for each in path.glob('Versions/*'):
        # macOS filesystems are usually case-insensitive
        if each.name.lower() == 'current':
            continue
        versions.append(Version(each.name))
    if len(versions) == 0:
        # most system frameworks do not have a 'Versions' directory
        return 'Headers'
    return 'Versions/{}/Headers'.format(sorted(versions)[-1]._s)

def _get_framework_include_path(path: Path) -> T.Optional[str]:
    trials = ('Headers', 'Versions/Current/Headers', _get_framework_latest_version(path))
    for each in trials:
        trial = path / each
        if trial.is_dir():
            return trial.as_posix()
    return None

class ResolvedTarget:
    def __init__(self) -> None:
        self.include_directories: T.List[str] = []
        self.link_flags:          T.List[str] = []
        self.public_link_flags:   T.List[str] = []
        self.public_compile_opts: T.List[str] = []
        self.libraries:           T.List[str] = []
        self.target_dependencies: T.List[str] = []

def resolve_cmake_trace_targets(target_name: str,
                                trace: 'CMakeTraceParser',
                                env: 'Environment',
                                *,
                                clib_compiler: T.Union['MissingCompiler', 'Compiler'] = None,
                                not_found_warning: T.Callable[[str], None] = lambda x: None) -> ResolvedTarget:
    res = ResolvedTarget()
    targets = [target_name]

    # recognise arguments we should pass directly to the linker
    reg_is_lib = re.compile(r'^(-l[a-zA-Z0-9_]+|-l?pthread)$')
    reg_is_maybe_bare_lib = re.compile(r'^[a-zA-Z0-9_]+$')

    is_debug = cmake_is_debug(env)

    processed_targets: T.List[str] = []
    while len(targets) > 0:
        curr = targets.pop(0)

        # Skip already processed targets
        if curr in processed_targets:
            continue

        if curr not in trace.targets:
            curr_path = Path(curr)
            if reg_is_lib.match(curr):
                res.libraries += [curr]
            elif curr_path.is_absolute() and curr_path.exists():
                if any(x.endswith('.framework') for x in curr_path.parts):
                    # Frameworks detected by CMake are passed as absolute paths
                    # Split into -F/path/to/ and -framework name
                    path_to_framework = []
                    # Try to slice off the `Versions/X/name.tbd`
                    for x in curr_path.parts:
                        path_to_framework.append(x)
                        if x.endswith('.framework'):
                            break
                    curr_path = Path(*path_to_framework)
                    framework_path = curr_path.parent
                    framework_name = curr_path.stem
                    res.libraries += [f'-F{framework_path}', '-framework', framework_name]
                else:
                    res.libraries += [curr]
            elif reg_is_maybe_bare_lib.match(curr) and clib_compiler:
                # CMake library dependencies can be passed as bare library names,
                # CMake brute-forces a combination of prefix/suffix combinations to find the
                # right library. Assume any bare argument passed which is not also a CMake
                # target must be a system library we should try to link against.
                flib = clib_compiler.find_library(curr, env, [])
                if flib is not None:
                    res.libraries += flib
                else:
                    not_found_warning(curr)
            else:
                not_found_warning(curr)
            continue

        tgt = trace.targets[curr]
        cfgs = []
        cfg = ''
        mlog.debug(tgt)

        if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties:
            res.include_directories += [x for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x]

        if 'INTERFACE_LINK_OPTIONS' in tgt.properties:
            res.public_link_flags += [x for x in tgt.properties['INTERFACE_LINK_OPTIONS'] if x]
            res.link_flags += res.public_link_flags

        if 'INTERFACE_COMPILE_DEFINITIONS' in tgt.properties:
            res.public_compile_opts += ['-D' + re.sub('^-D', '', x) for x in tgt.properties['INTERFACE_COMPILE_DEFINITIONS'] if x]

        if 'INTERFACE_COMPILE_OPTIONS' in tgt.properties:
            res.public_compile_opts += [x for x in tgt.properties['INTERFACE_COMPILE_OPTIONS'] if x]

        if 'IMPORTED_CONFIGURATIONS' in tgt.properties:
            cfgs = [x for x in tgt.properties['IMPORTED_CONFIGURATIONS'] if x]
            cfg = cfgs[0]

        if is_debug:
            if 'DEBUG' in cfgs:
                cfg = 'DEBUG'
            elif 'RELEASE' in cfgs:
                cfg = 'RELEASE'
        else:
            if 'RELEASE' in cfgs:
                cfg = 'RELEASE'

        if f'IMPORTED_IMPLIB_{cfg}' in tgt.properties:
            res.libraries += [x for x in tgt.properties[f'IMPORTED_IMPLIB_{cfg}'] if x]
        elif 'IMPORTED_IMPLIB' in tgt.properties:
            res.libraries += [x for x in tgt.properties['IMPORTED_IMPLIB'] if x]
        elif f'IMPORTED_LOCATION_{cfg}' in tgt.properties:
            targets += [x for x in tgt.properties[f'IMPORTED_LOCATION_{cfg}'] if x]
        elif 'IMPORTED_LOCATION' in tgt.properties:
            targets += [x for x in tgt.properties['IMPORTED_LOCATION'] if x]

        if 'LINK_LIBRARIES' in tgt.properties:
            link_libraries = [x for x in tgt.properties['LINK_LIBRARIES'] if x]
            targets += link_libraries
            res.target_dependencies += link_libraries
        if 'INTERFACE_LINK_LIBRARIES' in tgt.properties:
            link_libraries = [x for x in tgt.properties['INTERFACE_LINK_LIBRARIES'] if x]
            targets += link_libraries
            res.target_dependencies += link_libraries

        if f'IMPORTED_LINK_DEPENDENT_LIBRARIES_{cfg}' in tgt.properties:
            targets += [x for x in tgt.properties[f'IMPORTED_LINK_DEPENDENT_LIBRARIES_{cfg}'] if x]
        elif 'IMPORTED_LINK_DEPENDENT_LIBRARIES' in tgt.properties:
            targets += [x for x in tgt.properties['IMPORTED_LINK_DEPENDENT_LIBRARIES'] if x]

        processed_targets += [curr]

    # Do not sort flags here -- this breaks
    # semantics of eg. `-framework CoreAudio`
    # or `-Lpath/to/root -llibrary`
    # see eg. #11113

    return res
