#!/usr/bin/env python3
from pathlib import Path
import re

from tr2x.ida_progress import FUNC_PTR_RE, VAR_RE, Symbol, parse_progress_file
from tr2x.paths import TR2X_PROGRESS_FILE, TR2X_SRC_DIR

FUNCS_H_FILE = TR2X_SRC_DIR / "global/funcs.h"
VARS_H_FILE = TR2X_SRC_DIR / "global/vars_decomp.h"
TYPES_H_FILE = TR2X_SRC_DIR / "global/types.h"


COMMON_HEADER = [
    "// This file is autogenerated. To update it, run tools/generate_funcs.",
    "",
    "#pragma once",
    "",
]


def update_file(path: Path, new_content: str) -> None:
    if path.read_text() != new_content:
        path.write_text(new_content)


def make_func_pointer_define(function: Symbol) -> str:
    if match := re.match(
        r"(?P<ret_type>.+?\W)(?P<func_name>\b\w+)\s*\((?P<args>.+)\);?",
        function.signature,
    ):
        ret_type = match.group("ret_type").strip()
        func_name = match.group("func_name").strip()
        args = match.group("args").strip()
        return f"#define {func_name} (({ret_type} (*)({args})){function.offset_str})"
    return ""


def make_var_pointer_define(variable: Symbol) -> str:
    if match := FUNC_PTR_RE.match(variable.signature):
        ret_type = match.group("ret_type")
        call_type = match.group("call_type")
        array_def = match.group("array_def")
        func_name = match.group("func_name")
        args = match.group("args")
        if array_def:
            return f"#define {func_name} (*(({ret_type}({call_type} *(*){array_def})({args})){variable.offset_str}))"
        else:
            return f"#define {func_name} (*({ret_type}({call_type}**)({args})){variable.offset_str})"

    if match := VAR_RE.match(variable.signature):
        ret_type = match.group("ret_type")
        var_name = match.group("var_name")
        array_def = match.group("array_def")
        value_def = match.group("value_def")
        comment = match.group("comment")
        if not array_def and not value_def and comment == "no-dereferencing":
            return f"#define {var_name} (({ret_type}){variable.offset_str})"
        if array_def:
            return f"#define {var_name} (*({ret_type}(*){array_def}){variable.offset_str})"
        elif value_def:
            return f"#define {var_name} (*({ret_type}*){variable.offset_str}) // = {value_def}"
        else:
            return f"#define {var_name} (*({ret_type}*){variable.offset_str})"

    print("warn: unrecognized signature", variable.signature)
    return ""


def make_funcs_h(functions: list[Symbol]) -> None:
    header = [
        *COMMON_HEADER,
        '#include "global/types.h"',
        "",
        "// clang-format off",
    ]
    footer = ["// clang-format on"]

    defines = []
    for function in functions:
        if "+" not in function.flags and (
            define := make_func_pointer_define(function)
        ):
            defines.append(define)

    update_file(FUNCS_H_FILE, "\n".join([*header, *defines, *footer]) + "\n")


def make_vars_h(variables: list[Symbol]) -> None:
    header = [
        *COMMON_HEADER,
        '#include "global/types.h"',
        '#include "inject_util.h"',
        "",
        "// clang-format off",
    ]
    footer = [
        "",
        "// clang-format on",
    ]

    defines = []
    for variable in sorted(variables, key=lambda symbol: symbol.offset):
        if "+" not in variable.flags and (
            define := make_var_pointer_define(variable)
        ):
            defines.append(define)

    update_file(VARS_H_FILE, "\n".join([*header, *defines, *footer]) + "\n")


def make_types_h(types: list[str]) -> None:
    header = [
        *COMMON_HEADER,
        '#include "const.h"',
        "",
        "#include <libtrx/game/collision.h>",
        "#include <libtrx/game/effects.h>",
        "#include <libtrx/game/gameflow/types.h>",
        "#include <libtrx/game/items.h>",
        "#include <libtrx/game/lara/types.h>",
        "#include <libtrx/game/lot.h>",
        "#include <libtrx/game/math.h>",
        "#include <libtrx/game/objects/common.h>",
        "#include <libtrx/game/rooms/types.h>",
        "",
        "#include <ddraw.h>",
        "#include <ddrawi.h>",
        "#include <d3dhal.h>",
        "#include <dsound.h>",
        "#include <stdbool.h>",
        "#include <stdint.h>",
        "#include <windows.h>",
        "",
        "#pragma pack(push, 1)",
        "",
        "// clang-format off",
    ]
    footer = [
        "",
        "// clang-format on",
    ]

    update_file(
        TYPES_H_FILE,
        "\n".join(
            [
                *header,
                "\n\n".join([definition.strip() for definition in types if '// decompiled' not in definition.strip()]),
                *footer,
            ]
        )
        + "\n",
    )


def main() -> None:
    progress_file = parse_progress_file(TR2X_PROGRESS_FILE)

    make_funcs_h(progress_file.functions)
    make_vars_h(progress_file.variables)
    make_types_h(progress_file.types)


if __name__ == "__main__":
    main()
