// Tutorial 19 — Class Adapters (C++ integration)
//
// Shows how to let daslang classes derive from a C++ abstract base class
// and have C++ call their virtual methods seamlessly.  This is the key
// pattern for game object systems where scripts define behavior but C++
// owns the update loop.
//
// Architecture (3-layer adapter pattern):
//
//   1. C++ base class (BaseClass) — pure virtual interface
//   2. Generated adapter (TutorialBaseClass) — maps virtual calls to
//      das_invoke_function; generated by daslib/cpp_bind
//   3. Dual-inheritance bridge (BaseClassAdapter) — inherits both the
//      real C++ class and the generated adapter, forwarding vtable calls
//      to daslang class methods
//
// On the daslang side, a class simply extends TutorialBaseClass and
// uses "def override" — the adapter handles the rest.
//
// Key concepts:
//   - Pre-generated class adapter (#include "..._gen.inc")
//   - compileBuiltinModule with XDD-embedded .das.inc
//   - RTTI (StructInfo *) for adapter construction
//   - getDasClassMethod / das_invoke_function
//   - Virtual dispatch across the C++/daslang boundary

#include "daScript/daScript.h"

// We need RTTI headers to bind the StructInfo* parameter.
#include "module_builtin_rtti.h"

using namespace das;

// =========================================================================
// Layer 1: C++ abstract base class
// =========================================================================

class BaseClass {
public:
    virtual ~BaseClass() = default;
    virtual void update(float dt) = 0;
    virtual float3 getPosition() = 0;
};

// Global list of objects — owned by C++, polymorphic via BaseClass*.
static vector<shared_ptr<BaseClass>> g_allObjects;

// C++ update loop: calls virtual methods on all registered objects.
static float3 tick(float dt) {
    if (g_allObjects.empty()) return float3(0.0f);
    float3 avg = float3(0.0f);
    for (auto & obj : g_allObjects) {
        obj->update(dt);
        float3 p = obj->getPosition();
        avg.x += p.x;
        avg.y += p.y;
        avg.z += p.z;
    }
    float inv = 1.0f / float(g_allObjects.size());
    avg.x *= inv;  avg.y *= inv;  avg.z *= inv;
    return avg;
}

// =========================================================================
// Layer 2: Generated class adapter (bridges C++ vtable → daslang methods)
// =========================================================================
//
// This file is typically auto-generated by daslib/cpp_bind:
//   log_cpp_class_adapter(file, "TutorialBaseClass",
//       typeinfo(ast_typedecl type<TutorialBaseClass>))
//
// It provides per-method helpers:
//   get_<method>(self)   — returns the Func pointer if the daslang class
//                          overrides the method
//   invoke_<method>(ctx, fn, self, args...) — calls the daslang method
//                          via das_invoke_function

#include "19_class_adapters_gen.inc"

// =========================================================================
// Layer 3: Dual-inheritance bridge
// =========================================================================
//
// Inherits both the real C++ class (for vtable) and the generated adapter
// (for daslang method lookup).  When C++ calls obj->update(dt) through
// the vtable, the adapter checks whether the daslang class overrides
// "update" and invokes it.

class BaseClassAdapter : public BaseClass, public TutorialBaseClass {
public:
    BaseClassAdapter(char * pClass, const StructInfo * info, Context * ctx)
        : TutorialBaseClass(info), classPtr(pClass), context(ctx) {}

    void update(float dt) override {
        if (auto fn = get_update(classPtr)) {
            invoke_update(context, fn, classPtr, dt);
        }
    }

    float3 getPosition() override {
        if (auto fn = get_get_position(classPtr)) {
            return invoke_get_position(context, fn, classPtr);
        }
        return float3(0.0f);
    }

protected:
    void *    classPtr;   // pointer to the daslang class instance
    Context * context;    // daslang execution context
};

// =========================================================================
// Registration function exposed to daslang
// =========================================================================

static void addObject(const void * pClass, const StructInfo * info,
                      Context * context) {
    auto obj = make_shared<BaseClassAdapter>((char *)pClass, info, context);
    g_allObjects.emplace_back(obj);
}

// =========================================================================
// Module setup
// =========================================================================
//
// We embed the daslang module file (19_class_adapters_module.das) as a
// C++ byte array using XDD.  compileBuiltinModule compiles it as part of
// the module initialization — no file I/O needed at runtime.

#include "class_adapters_module.das.inc"

class Module_Tutorial19 : public Module {
public:
    Module_Tutorial19() : Module("tutorial_19") {
        ModuleLibrary lib(this);
        lib.addBuiltInModule();
        // RTTI is needed so we can bind the StructInfo* parameter
        addBuiltinDependency(lib, Module::require("rtti_core"));

        // Expose addObject to daslang as "add_object"
        addExtern<DAS_BIND_FUN(addObject)>(*this, lib, "add_object",
            SideEffects::modifyExternal, "addObject");

        // Expose tick to daslang
        addExtern<DAS_BIND_FUN(tick)>(*this, lib, "tick",
            SideEffects::modifyExternal, "tick");

        // Compile the embedded module (the abstract class definition).
        // The XDD .inc file provides the byte array and its size.
        compileBuiltinModule(this, "class_adapters_module.das",
            class_adapters_module_das,
            sizeof(class_adapters_module_das));
    }
};

REGISTER_MODULE(Module_Tutorial19);

// =========================================================================
// Main
// =========================================================================

void tutorial() {
    TextPrinter tout;
    ModuleGroup dummyLibGroup;
    auto fAccess = make_smart<FsFileAccess>();

    auto program = compileDaScript(
        getDasRoot() + "/tutorials/integration/cpp/19_class_adapters.das",
        fAccess, tout, dummyLibGroup);
    if (program->failed()) {
        tout << "Compilation failed:\n";
        for (auto & err : program->errors) {
            tout << reportError(err.at, err.what, err.extra,
                                err.fixme, err.cerr);
        }
        return;
    }

    Context ctx(program->getContextStackSize());
    if (!program->simulate(ctx, tout)) {
        tout << "Simulation failed:\n";
        for (auto & err : program->errors) {
            tout << reportError(err.at, err.what, err.extra,
                                err.fixme, err.cerr);
        }
        return;
    }

    auto fnTest = ctx.findFunction("test");
    if (!fnTest) {
        tout << "Function 'test' not found\n";
        return;
    }

    // Clear any objects from previous runs.
    g_allObjects.clear();

    ctx.evalWithCatch(fnTest, nullptr);
    if (auto ex = ctx.getException()) {
        tout << "Script exception: " << ex << "\n";
    }

    // Clean up.
    g_allObjects.clear();
}

int main(int, char * []) {
    NEED_ALL_DEFAULT_MODULES;
    NEED_MODULE(Module_Tutorial19);
    Module::Initialize();
    tutorial();
    Module::Shutdown();
    return 0;
}
