from __future__ import annotations

from functools import lru_cache
from pathlib import Path
from typing import Dict, List, Optional

import omvll

_CONFIG_PATH = Path(__file__).with_name("obfuscation.yml")


def _parse_settings() -> Dict[str, List[str] | Optional[int]]:
    settings: Dict[str, List[str] | Optional[int]] = {
        "seed": None,
        "functions": [],
        "passes": [],
    }
    current: Optional[str] = None
    if not _CONFIG_PATH.exists():
        return settings

    for raw_line in _CONFIG_PATH.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#"):
            continue
        if line.startswith("seed:"):
            value = line.split(":", 1)[1].strip()
            try:
                settings["seed"] = int(value, 0)
            except ValueError:
                settings["seed"] = None
            continue
        if line.startswith("functions"):
            current = "functions"
            continue
        if line.startswith("passes"):
            current = "passes"
            continue
        if line.startswith("-") and current in ("functions", "passes"):
            entry = line[1:].strip().strip('"').strip("'")
            if entry:
                settings[current].append(entry)
    return settings


class PurrfectSnapConfig(omvll.ObfuscationConfig):
    def __init__(self, settings: Dict[str, List[str] | Optional[int]]):
        super().__init__()
        self._targets = set(settings.get("functions") or [])
        self._passes = set(settings.get("passes") or [])

    def _should_obfuscate(self, func: omvll.Function) -> bool:
        if not self._targets:
            return True
        return func.demangled_name in self._targets or func.name in self._targets

    def _pass_enabled(self, name: str) -> bool:
        return not self._passes or name in self._passes

    def obfuscate_arithmetic(self, mod: omvll.Module, func: omvll.Function) -> bool:
        return self._should_obfuscate(func) and self._pass_enabled("InstructionsSubstitution")

    def flatten_cfg(self, mod: omvll.Module, func: omvll.Function) -> bool:
        return self._should_obfuscate(func) and self._pass_enabled("ControlFlowFlattening")

    def bogus_control_flow(self, mod: omvll.Module, func: omvll.Function) -> bool:
        return self._should_obfuscate(func) and self._pass_enabled("BogusControlFlow")

    def obfuscate_string(
        self,
        mod: omvll.Module,
        func: omvll.Function,
        string: bytes,
    ):
        if self._should_obfuscate(func) and self._pass_enabled("StringsEncryption"):
            return omvll.StringEncOptGlobal()
        return False


@lru_cache(maxsize=1)
def omvll_get_config() -> omvll.ObfuscationConfig:
    """Return an instance of the obfuscation configuration used by O-MVLL."""
    return PurrfectSnapConfig(_parse_settings())
