"""Load a user-provided Oodle/ooz DLL. Never redistributes Oodle."""
from __future__ import annotations

import ctypes
from pathlib import Path

from .paths import find_oodle_dll

_DLL = None
_BACKEND = "none"
_MODE = "none"  # oo2core | ooz14 | kraken4


class OodleUnavailable(RuntimeError):
    pass


def oodle_backend() -> str:
    return _BACKEND


def load_oodle(explicit: str | Path | None = None) -> ctypes.WinDLL:
    global _DLL, _BACKEND, _MODE
    if _DLL is not None:
        return _DLL
    path = find_oodle_dll(explicit)
    if path is None:
        raise OodleUnavailable(
            "Oodle/ooz DLL not found. PoE2 0.5.5 does not ship oo2core*.dll. "
            "Build libooz.dll (zao/ooz) into tools/bin/ or set AUTOPOE2_OODLE_DLL."
        )
    dll = ctypes.WinDLL(str(path))
    if hasattr(dll, "OodleLZ_Decompress"):
        fn = dll.OodleLZ_Decompress
        fn.argtypes = [
            ctypes.c_void_p,
            ctypes.c_int64,
            ctypes.c_void_p,
            ctypes.c_int64,
            ctypes.c_int,
            ctypes.c_int,
            ctypes.c_int,
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.c_int,
        ]
        fn.restype = ctypes.c_int64
        _BACKEND = f"oo2core:{path.name}"
        _MODE = "oo2core"
    elif hasattr(dll, "Ooz_Decompress"):
        fn = dll.Ooz_Decompress
        fn.argtypes = [
            ctypes.c_void_p,
            ctypes.c_int,
            ctypes.c_void_p,
            ctypes.c_size_t,
            ctypes.c_int,
            ctypes.c_int,
            ctypes.c_int,
            ctypes.c_void_p,
            ctypes.c_size_t,
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.c_size_t,
            ctypes.c_int,
        ]
        fn.restype = ctypes.c_int
        _BACKEND = f"ooz14:{path.name}"
        _MODE = "ooz14"
    elif hasattr(dll, "Kraken_Decompress"):
        fn = dll.Kraken_Decompress
        fn.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p, ctypes.c_size_t]
        fn.restype = ctypes.c_int
        _BACKEND = f"kraken4:{path.name}"
        _MODE = "kraken4"
    else:
        raise OodleUnavailable(
            f"{path} has neither OodleLZ_Decompress, Ooz_Decompress, nor Kraken_Decompress"
        )
    _DLL = dll
    return dll


def decompress_chunk(src: bytes, dst_len: int) -> bytes:
    dll = load_oodle()
    src_buf = (ctypes.c_uint8 * len(src)).from_buffer_copy(src)
    out = (ctypes.c_uint8 * (dst_len + 64))()
    src_ptr = ctypes.cast(src_buf, ctypes.c_void_p)
    dst_ptr = ctypes.cast(out, ctypes.c_void_p)
    if _MODE == "oo2core":
        wrote = dll.OodleLZ_Decompress(
            src_ptr,
            len(src),
            dst_ptr,
            dst_len,
            0,
            0,
            0,
            None,
            None,
            None,
            None,
            None,
            None,
            3,
        )
    elif _MODE == "ooz14":
        wrote = dll.Ooz_Decompress(
            src_ptr,
            len(src),
            dst_ptr,
            dst_len,
            0,
            0,
            0,
            None,
            0,
            None,
            None,
            None,
            0,
            0,
        )
    else:
        wrote = dll.Kraken_Decompress(src_ptr, len(src), dst_ptr, dst_len)
    if int(wrote) < 0 or int(wrote) != dst_len:
        raise OodleUnavailable(
            f"Oodle decompress failed (wrote={wrote}, expected={dst_len}, src={len(src)}, magic={src[:2].hex()})"
        )
    return bytes(out[:dst_len])
