from __future__ import annotations
from dataclasses import dataclass
import platform
import re
from typing import Optional


@dataclass
class MachineSpec:
    os: str
    arch: str
    config: Optional[str] = None
    triplet: Optional[str] = None

    @staticmethod
    def make_from_local_system() -> MachineSpec:
        return MachineSpec(detect_os(), detect_arch())

    @staticmethod
    def parse(raw_spec: str) -> MachineSpec:
        os = None
        arch = None
        config = None
        triplet = None

        tokens = raw_spec.split("-")
        if len(tokens) in {3, 4}:
            arch = tokens[0]
            m = TARGET_TRIPLET_ARCH_PATTERN.match(arch)
            if m is not None:
                kernel = tokens[-2]
                system = tokens[-1]

                if kernel == "w64":
                    os = "windows"
                elif kernel == "nto":
                    os = "qnx"
                else:
                    os = kernel

                if arch[0] == "i":
                    arch = "x86"
                elif arch == "arm":
                    if system == "gnueabihf":
                        arch = "armhf"
                    elif os == "qnx" and system.endswith("eabi"):
                        arch = "armeabi"
                elif arch == "aarch64":
                    arch = "arm64"

                if system.startswith("musl"):
                    config = "musl"
                elif kernel == "w64":
                    config = "mingw"

                triplet = raw_spec

        if os is None:
            os, arch, *rest = tokens
            if rest:
                assert len(rest) == 1
                config = rest[0]

        return MachineSpec(os, arch, config, triplet)

    def evolve(self,
               os: Optional[str] = None,
               arch: Optional[str] = None,
               config: Optional[str] = None,
               triplet: Optional[str] = None) -> MachineSpec:
        return MachineSpec(
            os if os is not None else self.os,
            arch if arch is not None else self.arch,
            config if config is not None else self.config,
            triplet if triplet is not None else self.triplet,
        )

    def default_missing(self, recommended_vscrt: Optional[str] = None) -> MachineSpec:
        config = self.config
        if config is None and self.toolchain_is_msvc:
            if recommended_vscrt is not None:
                config = recommended_vscrt
            else:
                config = "mt"
        return self.evolve(config=config)

    def maybe_adapt_to_host(self, host_machine: MachineSpec) -> MachineSpec:
        if self.identifier == host_machine.identifier and host_machine.triplet is not None:
            return host_machine
        if self.os == "windows":
            if self.arch == "x86_64" and host_machine.arch == "x86":
                return host_machine
            if self.arch == host_machine.arch:
                return host_machine
        return self

    @property
    def identifier(self) -> str:
        parts = [self.os, self.arch]
        if self.config is not None:
            parts += [self.config]
        return "-".join(parts)

    @property
    def os_dash_arch(self) -> str:
        return f"{self.os}-{self.arch}"

    @property
    def os_dash_config(self) -> str:
        parts = [self.os]
        if self.config is not None:
            parts += [self.config]
        return "-".join(parts)

    @property
    def config_is_optimized(self) -> bool:
        if self.toolchain_is_msvc:
            return self.config in {"md", "mt"}
        return True

    @property
    def meson_optimization_options(self) -> list[str]:
        if self.config_is_optimized:
            optimization = "s"
            ndebug = "true"
        else:
            optimization = "0"
            ndebug = "false"
        return [
            f"-Doptimization={optimization}",
            f"-Db_ndebug={ndebug}",
        ]

    @property
    def executable_suffix(self) -> str:
        return ".exe" if self.os == "windows" else ""

    @property
    def msvc_platform(self) -> str:
        return "x64" if self.arch == "x86_64" else "x86"

    @property
    def is_apple(self) -> str:
        return self.os in {"macos", "ios", "watchos", "tvos"}

    @property
    def system(self) -> str:
        return "darwin" if self.is_apple else self.os

    @property
    def subsystem(self) -> str:
        return self.os_dash_config if self.is_apple else self.os

    @property
    def kernel(self) -> str:
        return KERNELS.get(self.os, self.os)

    @property
    def cpu_family(self) -> str:
        arch = self.arch
        return CPU_FAMILIES.get(arch, arch)

    @property
    def cpu(self) -> str:
        arch = self.arch

        mappings_to_search = [
            CPU_TYPES_PER_OS_OVERRIDES.get(self.os, {}),
            CPU_TYPES,
        ]
        for m in mappings_to_search:
            cpu = m.get(arch, None)
            if cpu is not None:
                return cpu

        return arch

    @property
    def endian(self) -> str:
        return "big" if self.arch in BIG_ENDIAN_ARCHS else "little"

    @property
    def pointer_size(self) -> int:
        arch = self.arch
        if arch in {"x86_64", "s390x"}:
            return 8
        if arch.startswith("arm64") or arch.startswith("mips64"):
            return 8
        return 4

    @property
    def libdatadir(self) -> str:
        return "libdata" if self.os == "freebsd" else "lib"

    @property
    def toolchain_is_msvc(self) -> bool:
        return self.os == "windows" and self.config != "mingw"

    @property
    def toolchain_can_strip(self) -> bool:
        return not self.toolchain_is_msvc

    def __eq__(self, other):
        if isinstance(other, MachineSpec):
            return other.identifier == self.identifier
        return False


def detect_os() -> str:
    os = platform.system().lower()
    if os == "darwin":
        os = "macos"
    return os


def detect_arch() -> str:
    arch = platform.machine().lower()
    return ARCHS.get(arch, arch)


ARCHS = {
    "amd64": "x86_64",
    "armv7l": "armhf",
    "aarch64": "arm64",
}

KERNELS = {
    "windows": "nt",

    "macos":   "xnu",
    "ios":     "xnu",
    "watchos": "xnu",
    "tvos":    "xnu",

    "qnx":     "nto",
}

CPU_FAMILIES = {
    "armbe8":     "arm",
    "armeabi":    "arm",
    "armhf":      "arm",

    "arm64":      "aarch64",
    "arm64e":     "aarch64",
    "arm64eoabi": "aarch64",

    "mipsel":     "mips",
    "mips64el":   "mips64",
}

CPU_TYPES = {
    "arm":        "armv7",
    "armbe8":     "armv6",
    "armhf":      "armv7hf",
    "armeabi":    "armv7eabi",

    "arm64":      "aarch64",
    "arm64e":     "aarch64",
    "arm64eoabi": "aarch64",
}

CPU_TYPES_PER_OS_OVERRIDES = {
    "linux": {
        "arm":        "armv5t",
        "armbe8":     "armv6t",
        "armhf":      "armv7a",

        "mips":       "mips1",
        "mipsel":     "mips1",

        "mips64":     "mips64r2",
        "mips64el":   "mips64r2",
    },
    "android": {
        "x86":        "i686",
    },
    "qnx": {
        "arm":        "armv6",
        "armeabi":    "armv7",
    },
}

BIG_ENDIAN_ARCHS = {
    "armbe8",
    "mips",
    "mips64",
    "s390x",
}

TARGET_TRIPLET_ARCH_PATTERN = re.compile(r"^(i.86|x86_64|arm(v\w+)?|aarch64|mips\w*|s390x)$")
