import io

import buildconfig
import yaml
from mozbuild.preprocessor import Preprocessor

HEADER_TEMPLATE = """\
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef %(includeguard)s
#define %(includeguard)s

/* This file is generated by wasm/GenerateBuiltinModules.py. Do not edit! */

%(contents)s

#endif // %(includeguard)s
"""


def generate_header(c_out, includeguard, contents):
    c_out.write(
        HEADER_TEMPLATE
        % {
            "includeguard": includeguard,
            "contents": contents,
        }
    )


def load_yaml(yaml_path):
    # First invoke preprocessor.py so that we can use #ifdef JS_SIMULATOR in
    # the YAML file.
    pp = Preprocessor()
    pp.context.update(buildconfig.defines["ALLDEFINES"])
    pp.out = io.StringIO()
    pp.do_filter("substitution")
    pp.do_include(yaml_path)
    contents = pp.out.getvalue()
    return yaml.safe_load(contents)


def cppBool(v):
    if v:
        return "true"
    return "false"


def specTypeToMIRType(specType):
    if specType == "i32" or specType == "i64" or specType == "f32" or specType == "f64":
        return f"ValType::{specType}().toMIRType()"
    if (
        specType == "externref"
        or specType == "anyref"
        or specType == "funcref"
        or specType == "exnref"
        or isinstance(specType, dict)
    ):
        return "MIRType::WasmAnyRef"
    raise ValueError()


def specHeapTypeToTypeCode(specHeapType):
    if specHeapType == "func":
        return "Func"
    if specHeapType == "any":
        return "Any"
    if specHeapType == "extern":
        return "Extern"
    if specHeapType == "exn":
        return "Exn"
    if specHeapType == "array":
        return "Array"
    if specHeapType == "struct":
        return "Struct"
    raise ValueError()


def specTypeToValType(specType):
    if specType == "i32" or specType == "i64" or specType == "f32" or specType == "f64":
        return f"ValType::{specType}()"

    if specType == "externref":
        return "ValType(RefType::extern_())"

    if specType == "exnref":
        return "ValType(RefType::exn())"

    if specType == "anyref":
        return "ValType(RefType::any())"

    if specType == "funcref":
        return "ValType(RefType::func())"

    if isinstance(specType, dict):
        nullable = cppBool(specType["nullable"])
        if "type" in specType:
            ref = specType["type"]
            return f"ValType(RefType::fromTypeDef({ref}, {nullable}))"
        else:
            code = specType["code"]
            return f"ValType(RefType::fromTypeCode(TypeCode(RefType::{specHeapTypeToTypeCode(code)}), {nullable}))"

    raise ValueError()


def failureTrap(op):
    if not "fail_trap" in op:
        if op["fail_mode"] == "Infallible":
            return "Limit"
        return "ThrowReported"
    return op["fail_trap"]


def main(c_out, yaml_path):
    data = load_yaml(yaml_path)

    # Iterate for all defined builtin methods
    contents = "#define FOR_EACH_BUILTIN_MODULE_FUNC(M) \\\n"
    for i in range(len(data)):
        op = data[i]
        sa = op["symbolic_address"]
        inlineOp = "BuiltinInlineOp::None"
        if "inline_op" in op:
            inlineOp = f"BuiltinInlineOp::{op['inline_op']}"
        contents += (
            f"    M({op['op']}, \"{op['export']}\", "
            f"{sa['name']}, {sa['type']}, {cppBool(sa['needs_thunk'])}, {op['entry']}, {cppBool(op['uses_memory'])}, {inlineOp}, {i})\\\n"
        )
    contents += "\n"

    for op in data:
        # Define DECLARE_BUILTIN_MODULE_FUNC_PARAM_VALTYPES_<op> as:
        # `{ValType::I32, ValType::I32, ...}`.
        valTypes = ", ".join(specTypeToValType(p) for p in op["params"])
        contents += (
            f"#define DECLARE_BUILTIN_MODULE_FUNC_PARAM_VALTYPES_{op['op']} "
            f"{{{valTypes}}}\n"
        )

        # Define DECLARE_BUILTIN_MODULE_FUNC_PARAM_MIRTYPES_<op> as:
        # `<num_types>, {MIRType::Pointer, _I32, ..., MIRType::Pointer, _END}`.
        num_types = len(op["params"]) + 1
        mir_types = "{MIRType::Pointer"
        mir_types += "".join(", " + specTypeToMIRType(p) for p in op["params"])
        if op["uses_memory"]:
            mir_types += ", MIRType::Pointer"
            num_types += 1
        # Add the end marker
        mir_types += ", MIRType::None}"

        contents += f"#define DECLARE_BUILTIN_MODULE_FUNC_PARAM_MIRTYPES_{op['op']} {num_types}, {mir_types}\n"

        # Define DECLARE_BUILTIN_MODULE_FUNC_RESULT_VALTYPE_<op> as:
        # `Some(X)` if present, or else `Nothing()`.
        result_valtype = ""
        if "result" in op:
            result_valtype = f"mozilla::Some({specTypeToValType(op['result'])})\n"
        else:
            result_valtype = "mozilla::Nothing()"
        contents += f"#define DECLARE_BUILTIN_MODULE_FUNC_RESULT_VALTYPE_{op['op']} {result_valtype}\n"

        # Define DECLARE_BUILTIN_MODULE_FUNC_RESULT_MIRTYPE_<op> as:
        # `X` if present, or else `MIRType::None`.
        result_mirtype = ""
        if "result" in op:
            result_mirtype = specTypeToMIRType(op["result"]) + "\n"
        else:
            result_mirtype = "MIRType::None"
        contents += f"#define DECLARE_BUILTIN_MODULE_FUNC_RESULT_MIRTYPE_{op['op']} {result_mirtype}\n"

        # Define DECLARE_BUILTIN_MODULE_FUNC_FAILMODE_<op> as:
        # `FailureMode::X`.
        contents += f"#define DECLARE_BUILTIN_MODULE_FUNC_FAILMODE_{op['op']} FailureMode::{op['fail_mode']}\n"

        # Define DECLARE_BUILTIN_MODULE_FUNC_FAILTRAP_<op> as:
        # `Trap::X`.
        contents += f"#define DECLARE_BUILTIN_MODULE_FUNC_FAILTRAP_{op['op']} Trap::{failureTrap(op)}\n"

    generate_header(c_out, "wasm_WasmBuiltinModuleGenerated_h", contents)
