# Copyright 2021-2022 Timo Röhling <roehling@debian.org>
# SPDX-License-Identifier: MIT
from configparser import ConfigParser
from os import environ
from pathlib import Path
from re import sub
from typing import List, Dict, Tuple, Optional
from catkin_pkg.package import Package, parse_package


def get_build_var(
    package: Optional[str],
    name: str,
    default: Optional[str] = None,
    env: Dict[str, str] = None,
) -> Optional[str]:
    if env is None:
        env = environ
    if package is not None:
        sanitized_package = sub(r"[^A-Za-z0-9]", "_", package)
        if f"{name}_{sanitized_package}" in env:
            return env.get(f"{name}_{sanitized_package}")
    if name in env:
        return env.get(name)
    return default


def find_packages(srcdir: Path, depth: int) -> List[Tuple[Path, Package]]:
    try:
        package = parse_package(srcdir)
        package.evaluate_conditions(environ)
        return [(srcdir, package)]
    except Exception:
        pass
    if depth == 0:
        return []
    packages = []
    for p in srcdir.iterdir():
        if p.is_dir():
            packages += find_packages(p, max(-1, depth - 1))
    return packages


def topological_sort(graph: Dict[str, List[str]]) -> List[str]:
    order = {}
    loop = set()

    def visit(node, depth):
        if node in loop:
            raise RuntimeError(f"Dependency loop involving {node!r} detected!")
        if order.get(node, -1) < depth:
            order[node] = depth
            for dep in graph[node]:
                if dep in graph:
                    loop.add(node)
                    visit(dep, depth + 1)
                    loop.remove(node)

    for node in graph.keys():
        visit(node, 0)

    flattened = [(-depth, node) for node, depth in order.items()]
    flattened.sort()
    return [node for _, node in flattened]


def sort_packages(
    packages: List[Tuple[Path, Package]], test_depends=True
) -> List[Tuple[Path, Package]]:
    lookup = {}
    graph = {}
    for path, package in packages:
        lookup[package.name] = (path, package)
        depends = set(
            d.name
            for d in package.build_export_depends
            + package.buildtool_export_depends
            + package.build_depends
            + package.buildtool_depends
            + (package.exec_depends if test_depends else [])
            + (package.test_depends if test_depends else [])
        )
        graph[package.name] = list(depends)
    sorted_names = topological_sort(graph)
    return [lookup[name] for name in sorted_names]


# Proudly stolen from dh-python
def cpython_versions(major: int):
    result = [None, None]
    ver = "" if major == 2 else "3"
    supported = environ.get("DEBPYTHON{}_SUPPORTED".format(ver))
    default = environ.get("DEBPYTHON{}_DEFAULT".format(ver))
    if not supported or not default:
        config = ConfigParser()
        config.read("/usr/share/python{}/debian_defaults".format(ver))
        if not default:
            default = config.get("DEFAULT", "default-version", fallback="")[6:]
        if not supported:
            supported = config.get(
                "DEFAULT", "supported-versions", fallback=""
            ).replace("python", "")
    if default:
        try:
            result[0] = tuple(int(i) for i in default.split("."))
        except Exception as err:
            pass
    if supported:
        try:
            result[1] = tuple(
                tuple(int(j) for j in i.strip().split("."))
                for i in supported.split(",")
            )
        except Exception as err:
            pass
    return result


DEFAULT_PYTHON_VERSION = None
SUPPORTED_PYTHON_VERSIONS = None


def compute_ament_cmake_python_path(builddir: Path) -> List[str]:
    ament_cmake_python_path = (builddir / "ament_cmake_python").resolve()
    if ament_cmake_python_path.is_dir():
        return [str(d) for d in ament_cmake_python_path.iterdir() if d.is_dir()]
    return []


def compute_python_package_path(prefix: Path) -> List[str]:
    global SUPPORTED_PYTHON_VERSIONS, DEFAULT_PYTHON_VERSION

    if DEFAULT_PYTHON_VERSION is None or SUPPORTED_PYTHON_VERSIONS is None:
        try:
            major = int(environ.get("ROS_PYTHON_VERSION", "3"))
        except ValueError:
            major = 3
        DEFAULT_PYTHON_VERSION, SUPPORTED_PYTHON_VERSIONS = cpython_versions(major)

    def append_sitedir(path):
        if path.is_dir():
            result.append(str(path))

    result = []
    if DEFAULT_PYTHON_VERSION is not None:
        if DEFAULT_PYTHON_VERSION[0] == 3:
            append_sitedir(prefix / "lib" / "python3" / "dist-packages")

    if SUPPORTED_PYTHON_VERSIONS is not None:
        for version in SUPPORTED_PYTHON_VERSIONS:
            append_sitedir(
                prefix / "lib" / "python{}.{}".format(*version[:2]) / "dist-packages"
            )
            append_sitedir(
                prefix / "lib" / "python{}.{}".format(*version[:2]) / "site-packages"
            )

    return result
