// Tutorial 05 — Binding C++ Enumerations (C++ integration)
//
// Demonstrates how to expose C++ enums to daslang:
//   - DAS_BASE_BIND_ENUM / DAS_BIND_ENUM_CAST — the macro approach
//   - addEnumeration — registering enums in a module
//   - Using enums in addExtern functions (parameters and return types)
//   - Manual Enumeration construction (commented alternative)
//
// Both enum class (scoped) enums are shown here; unscoped enums work
// similarly with the _98 macro variants (DAS_BASE_BIND_ENUM_98).

#include "daScript/daScript.h"
#include "daScript/ast/ast_interop.h"
#include "daScript/ast/ast_typefactory_bind.h"
#include "daScript/simulate/bind_enum.h"

#include <cstdio>

// NOTE: `using namespace das` is placed AFTER the DAS_BIND_ENUM_CAST
// macros (below) because those macros create names inside `namespace das`
// that would collide with the global enum names.

// -----------------------------------------------------------------------
// C++ enumerations to expose
// -----------------------------------------------------------------------

// A scoped enum (enum class) — the common modern C++ style.
enum class Direction : int {
    North = 0,
    East  = 1,
    South = 2,
    West  = 3
};

// Another scoped enum for log severity.
enum class Severity : int {
    Debug   = 0,
    Info    = 1,
    Warning = 2,
    Error   = 3
};

// -----------------------------------------------------------------------
// C++ functions that use the enums
// -----------------------------------------------------------------------

const char * direction_name(Direction d) {
    switch (d) {
        case Direction::North: return "North";
        case Direction::East:  return "East";
        case Direction::South: return "South";
        case Direction::West:  return "West";
        default:               return "Unknown";
    }
}

Direction opposite_direction(Direction d) {
    switch (d) {
        case Direction::North: return Direction::South;
        case Direction::South: return Direction::North;
        case Direction::East:  return Direction::West;
        case Direction::West:  return Direction::East;
        default:               return d;
    }
}

Direction rotate_cw(Direction d) {
    return Direction(((int)d + 1) % 4);
}

void log_message(Severity level, const char * message) {
    const char * prefix = "???";
    switch (level) {
        case Severity::Debug:   prefix = "DEBUG";   break;
        case Severity::Info:    prefix = "INFO";    break;
        case Severity::Warning: prefix = "WARN";    break;
        case Severity::Error:   prefix = "ERROR";   break;
    }
    printf("[%s] %s\n", prefix, message);
}

bool is_severe(Severity level) {
    return level >= Severity::Warning;
}

// -----------------------------------------------------------------------
// Enum binding with macros  (recommended approach)
// -----------------------------------------------------------------------
// DAS_BASE_BIND_ENUM(CppEnum, DasName, value1, value2, ...)
//   Creates a class Enumeration<DasName> plus the typeFactory<>
//   needed for addExtern to resolve the enum type automatically.
//
// DAS_BIND_ENUM_CAST(CppEnum)
//   Creates the cast<> specialization so enum values can cross
//   the C++ ↔ daslang boundary.

DAS_BASE_BIND_ENUM(Severity, Severity,
    Debug,
    Info,
    Warning,
    Error
)

DAS_BASE_BIND_ENUM(Direction, Direction,
    North,
    East,
    South,
    West
)

using namespace das;

// -----------------------------------------------------------------------
// Module
// -----------------------------------------------------------------------

class Module_Tutorial05 : public Module {
public:
    Module_Tutorial05() : Module("tutorial_05_cpp") {
        ModuleLibrary lib(this);
        lib.addBuiltInModule();

        // --- Register enums ---
        // The class generated by DAS_BASE_BIND_ENUM is named
        // Enumeration<DasName>, e.g. EnumerationDirection.
        addEnumeration(new EnumerationDirection());
        addEnumeration(new EnumerationSeverity());

        // --- Alternative: manual enum construction ---
        // If you cannot use the macros (e.g. you need to rename values,
        // or the enum lives in a complex namespace), you can build the
        // Enumeration object by hand.  You still need DAS_BASE_BIND_ENUM
        // (or DAS_BASE_BIND_ENUM_GEN) for the typeFactory<>.
        //
        //   auto pEnum = make_smart<Enumeration>("Severity");
        //   pEnum->cppName  = "Severity";
        //   pEnum->external = true;
        //   pEnum->baseType = Type::tInt;
        //   pEnum->addIEx("Debug",   "Severity::Debug",   0, LineInfo());
        //   pEnum->addIEx("Info",    "Severity::Info",    1, LineInfo());
        //   pEnum->addIEx("Warning", "Severity::Warning", 2, LineInfo());
        //   pEnum->addIEx("Error",   "Severity::Error",   3, LineInfo());
        //   addEnumeration(pEnum);

        // --- Functions using Direction ---
        addExtern<DAS_BIND_FUN(direction_name)>(*this, lib, "direction_name",
            SideEffects::none, "direction_name")
                ->args({"d"});

        addExtern<DAS_BIND_FUN(opposite_direction)>(*this, lib, "opposite_direction",
            SideEffects::none, "opposite_direction")
                ->args({"d"});

        addExtern<DAS_BIND_FUN(rotate_cw)>(*this, lib, "rotate_cw",
            SideEffects::none, "rotate_cw")
                ->args({"d"});

        // --- Functions using Severity ---
        addExtern<DAS_BIND_FUN(log_message)>(*this, lib, "log_message",
            SideEffects::modifyExternal, "log_message")
                ->args({"level", "message"});

        addExtern<DAS_BIND_FUN(is_severe)>(*this, lib, "is_severe",
            SideEffects::none, "is_severe")
                ->args({"level"});
    }
};

REGISTER_MODULE(Module_Tutorial05);

// -----------------------------------------------------------------------
// Host program
// -----------------------------------------------------------------------

#define SCRIPT_NAME "/tutorials/integration/cpp/05_binding_enums.das"

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

    auto program = compileDaScript(getDasRoot() + SCRIPT_NAME,
                                   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;
    }

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

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