// main public facing root file for core

const std = @import("std");

pub const inputs = @import("inputs/inputStack.zig");
pub const getInputStack = inputs.getInputStack;
pub const ActionEvent = inputs.ActionEvent;
pub const Axis1dBinding = inputs.Axis1dBinding;
pub const Axis2dBinding = inputs.Axis2dBinding;
pub const ActionBinding = inputs.ActionBinding;
pub const IOEvent = inputs.IOEvent;

pub const debug_draw = @import("debug_draw.zig");
pub const DebugDrawParams = debug_draw.DebugDrawParams;
pub const DebugDrawInterface = debug_draw.DebugDrawInterface;
pub const installDebugDrawInterface = debug_draw.installDebugDrawInterface;
pub const debugSphere = debug_draw.debugSphere;
pub const debugSphereTransform = debug_draw.debugSphereTransform;
pub const debugBox = debug_draw.debugBox;
pub const debugLine = debug_draw.debugLine;
pub const script_bindings = script.script_bindings;

pub usingnamespace @import("misc.zig");
pub usingnamespace @import("logging.zig");
pub usingnamespace @import("engineTime.zig");
pub usingnamespace @import("engineObject.zig");
pub usingnamespace @import("jobs.zig");
pub usingnamespace @import("file_utils.zig");
pub usingnamespace @import("string_utils.zig");
pub usingnamespace @import("type_utils.zig");
pub const engine = @import("engine.zig");
pub const tracy = @import("tracy");
pub const png = @import("png.zig");

pub const colors = @import("colors.zig");

pub const zgltf = @import("zgltf");

pub const zm = @import("zmath");
pub usingnamespace @import("p2");
pub const algorithm = @import("p2");
pub const nfd = @import("nfd");
pub usingnamespace @import("math.zig");
pub usingnamespace @import("args.zig");
pub usingnamespace @import("file_dialogue.zig");

pub const panickers = @import("panickers.zig");
pub const scene = @import("scene.zig");
pub const SceneSystem = scene.SceneSystem;

pub const Engine = engine.Engine;

const Name = algorithm.Name;
pub const spng = @import("spng");

pub const MemoryTracker = @import("MemoryTracker.zig");

pub const DefaultSavePath = "Saved";

const logging = @import("logging.zig");
const c = @This();

const logs = logging.engine_logs;
const log = logging.engine_log;

pub const packer = @import("packer");

pub const FileSystem = packer.PackerFS;
const PackerFS = packer.PackerFS;

pub const StackCompactor = stacks.StackCompactor;

var gPackerFS: *PackerFS = undefined;

pub var gScene: *SceneSystem = undefined;
pub const Scene = scene.Scene;

pub const ecs = @import("ecs.zig");
pub usingnamespace ecs;

pub const script = @import("script.zig");

pub const stacks = @import("stacks.zig");
pub const walkAndPrintStack = stacks.walkAndPrintStack;

pub fn fs() *PackerFS {
    return gPackerFS;
}

pub const Module = ModuleDescription{
    .name = "core",
    .enabledByDefault = true,
};

var gIsUtility: bool = false;

pub fn isUtility() bool {
    return gIsUtility;
}

pub fn checkArgBool(args: anytype, comptime field: []const u8) bool {
    if (@hasField(@TypeOf(args), "fatDump")) {
        return @field(args, field);
    }
    return false;
}

pub fn start_module(comptime programSpec: anytype, args: anytype, allocator: std.mem.Allocator) !void {
    if (@hasField(@TypeOf(programSpec), "utility")) {
        logs("utility mode - no gui");
        gIsUtility = true;
    }

    var fatDump: bool = false;
    if (checkArgBool(args, "fatDump")) {
        fatDump = true;
    }

    script.lua.setupMiniDump(fatDump);
    _ = try algorithm.createNameRegistry(allocator);
    // LUA BEGIN -- what if i want to make the scripting integration optional?
    try script.start_lua(allocator);
    // LUA END
    gPackerFS = try PackerFS.init(allocator, .{});
    gEngine = try allocator.create(Engine);
    gEngine.* = try Engine.init(allocator);

    if (@hasField(@TypeOf(args), "unitTest") and args.unitTest) {} else {
        try logging.setupLogging(gEngine);
    }

    try ecs.setup(allocator);

    gScene = try gEngine.createObject(scene.SceneSystem, .{ .can_tick = true });

    try algorithm.string_pool.setup(allocator);

    _ = try gEngine.createObject(script_bindings.ScriptTicks, .{ .can_tick = true });

    _ = try inputs.initInputStack();

    logs("core module starting up... ");
    return;
}

pub fn shutdown_module(_: std.mem.Allocator) void {
    logs("core module shutting down...");

    MemoryTracker.MTPrintStatsDelta();

    logging.shutdownLogging();
    ecs.shutdown();

    debug_draw.shutdownDrawInterface();
    algorithm.destroyNameRegistry();
    gEngine.deinit();
    gPackerFS.destroy();
    // LUA BEGIN
    script.shutdown_lua();
    // LUA END
    algorithm.string_pool.shutdown();
    return;
}

pub fn dispatchJob(capture: anytype) !void {
    try gEngine.jobManager.newJob(capture);
}

pub var gEngine: *Engine = undefined;

pub fn createObject(comptime T: type, params: engine.NeonObjectParams) !*T {
    return gEngine.createObject(T, params);
}

pub fn setupEnginePlatform(ctx: *anyopaque, poll: engine.PollFuncFn, proc: engine.ProcEventsFn) void {
    gEngine.platformPollFunc = poll;
    gEngine.platformCtx = ctx;
    gEngine.platformProcEventsFunc = proc;
}

pub fn exitNow() void {
    signalShutdown();
}

pub fn signalShutdown() void {
    gEngine.exit();
}

pub fn getEngine() *Engine {
    return gEngine;
}

pub fn BuildOption(comptime option: []const u8) bool {
    if (@hasDecl(@import("root"), "options")) {
        const r = @import("root").options;
        if (@hasDecl(r, option)) {
            return @field(r, option);
        } else {
            return false;
        }
    }
    return false;
}

const EngineDelegates = @import("EngineDelegates.zig");

// binds a function + a context to
pub fn addEngineDelegateBinding(comptime event: []const u8, func: anytype, ctx: *anyopaque) !usize {
    if (!@hasField(EngineDelegates, event)) {
        @compileError("Engine does not have delegate " ++ event);
    }
    const handle = @field(gEngine.delegates, event).items.len;
    try @field(gEngine.delegates, event).append(gEngine.delegates.allocator, .{ .func = func, .ctx = ctx });

    return handle;
}

const getEngineTime = @import("engineTime.zig").getEngineTime;

pub fn getEngineUptime() f64 {
    return getEngineTime() - gEngine.engineStartTime;
}

pub const modules = @import("modules.zig");
pub const isModuleEnabled = modules.isModuleEnabled;
pub const ModuleDescription = modules.ModuleDescription;
