#!/usr/bin/env python3
"""Writes wasmmodules.cpp: the table that tgf/wasmdl.h looks modules up in.

Usage: wasmmodules.py <out.cpp> module... -- robot...
Every module and robot is linked into the game with its entry points
prefixed by its name (SD_ADD_LIBRARY in cmake/macros.cmake); robots also
keep the old <name> / <name>Shut entry points under their own names.
"""
import sys

out = sys.argv[1]
args = sys.argv[2:]
split = args.index("--")
modules, robots = args[:split], args[split + 1:]

# Wasm checks function signatures, so these match the real ones (pointers are
# all one type there).
entry = [
    ("openGfModule", "int", "const char*, void*"),
    ("closeGfModule", "int", ""),
    ("moduleWelcome", "int", "const void*, void*"),
    ("moduleInitialize", "int", "void*"),
    ("moduleTerminate", "int", ""),
]

lines = ["// Generated by wasmmodules.py.", "#include <cstring>", "#include <string>", "", 'extern "C" {']
for name in modules + robots:
    for sym, ret, params in entry:
        lines.append(f"{ret} {name}_{sym}({params}) __attribute__((weak));")
for name in robots:
    lines.append(f"int {name}(void*) __attribute__((weak));")
    lines.append(f"int {name}Shut() __attribute__((weak));")
lines += ["}", "", "namespace {", "struct Sym { const char* name; void* fn; };",
          "struct Mod { const char* name; Sym syms[7]; };", "const Mod mods[] = {"]
for name in modules + robots:
    syms = [f'{{"{sym}", (void*)&{name}_{sym}}}' for sym, _, _ in entry]
    if name in robots:
        syms += [f'{{"{name}", (void*)&{name}}}', f'{{"{name}Shut", (void*)&{name}Shut}}']
    lines.append(f'  {{"{name}", {{{", ".join(syms)}}}}},')
lines += ["};", "const char* lastError = \"\";", "}", "",
          'extern "C" void* sd_wasm_dlopen(const char* path)', "{",
          "  std::string base(path);",
          "  size_t slash = base.find_last_of('/');",
          "  if (slash != std::string::npos) base = base.substr(slash + 1);",
          "  size_t dot = base.rfind('.');",
          "  if (dot != std::string::npos) base = base.substr(0, dot);",
          "  for (const Mod& mod : mods)",
          "    if (base == mod.name) return (void*)&mod;",
          '  lastError = "module not linked into this build";',
          "  return nullptr;", "}", "",
          'extern "C" void* sd_wasm_dlsym(void* handle, const char* name)', "{",
          "  const Mod* mod = static_cast<const Mod*>(handle);",
          "  for (const Sym& sym : mod->syms)",
          "    if (sym.name && !strcmp(sym.name, name)) return sym.fn;",
          '  lastError = "symbol not found";',
          "  return nullptr;", "}", "",
          'extern "C" int sd_wasm_dlclose(void*) { return 0; }',
          'extern "C" const char* sd_wasm_dlerror() { return lastError; }', ""]
open(out, "w").write("\n".join(lines))
