pub const core = @import("core");
pub const platform = @import("platform");
pub const assets = @import("assets");
pub const audio = @import("audio");
// pub const graphics = @import("graphics");
// pub const vkImgui = @import("vkImgui");
// pub const ui = @import("ui");
pub const papyrus = @import("papyrus");
pub const physics = @import("physics");

const modulelist = @import("modulelist.zig").list;

const std = @import("std");

pub const NwArgs = struct {
    useGPA: bool = true, // slower zig based memory allocator, provides detailed tracking of leaks and memory violations
    vulkanValidation: bool = true,
    fastTest: bool = false,
    dmt: bool = false, // detailed memory tracking, implements a timeline for tracking all memory allocations
    fatDump: bool = false, // takes a full fat minidump on crash, very large files are produced
};

pub fn getArgs() !NwArgs {
    const a = try core.ParseArgs(NwArgs);

    return a;
}

// entry function for a backlog program
// when this function is called it will use the settings specified by the program spec
// to conditionally start up feature modules within the engine

var shutdownList: std.ArrayListUnmanaged(*const fn (std.mem.Allocator) void) = .{};

pub fn start_modules(comptime programSpec: anytype, maybeArgs: ?NwArgs, allocator: std.mem.Allocator) !void {
    const Backlog = @This();

    inline for (modulelist) |feature| {
        if (@hasDecl(Backlog, feature)) {
            const Struct = @field(Backlog, feature);
            if (comptime core.isModuleEnabled(Struct.Module, programSpec)) {
                if (maybeArgs) |args| {
                    try Struct.start_module(programSpec, args, allocator);
                } else {
                    try Struct.start_module(programSpec, NwArgs{}, allocator);
                }
                try shutdownList.append(allocator, Struct.shutdown_module);
                core.engine_logs("module started >>>> " ++ feature ++ " <<<<");
            }
        }
    }
}

pub fn shutdown_modules(allocator: std.mem.Allocator) void {
    var i: isize = @intCast(shutdownList.items.len - 1);
    while (i >= 0) : (i -= 1) {
        shutdownList.items[@intCast(i)](allocator);
    }
    shutdownList.deinit(allocator);
}

pub fn start_everything(comptime spec: anytype, allocator: std.mem.Allocator, maybeArgs: ?NwArgs) !void {
    //if (maybeArgs) |args| {
    //    //if (args.vulkanValidation)
    //        // graphics.setStartupSettings("vulkanValidation", true);
    //}

    try start_modules(spec, maybeArgs, allocator);
}

pub fn shutdown_everything(allocator: std.mem.Allocator) void {
    shutdown_modules(allocator);
}

pub fn run_everything(comptime GameContext: type) !void {
    var canTick: bool = false;

    if (@hasDecl(GameContext, "tick")) {
        canTick = true;
    }

    var gameContext = try core.createObject(GameContext, .{ .can_tick = canTick });

    if (@hasDecl(GameContext, "prepare_game")) {
        gameContext.prepare_game() catch @panic("Unable to run base level prepare script");
    } else if (@hasDecl(GameContext, "prepare")) {
        gameContext.prepare() catch @panic("Unable to run base level prepare script");
    }

    try core.gEngine.run();

    while (!core.gEngine.exitFinished()) {
        const z = core.tracy.ZoneN(@src(), "shutdown poll");
        platform.getInstance().pollEvents();
        z.End();
    }
}

pub fn initializeAndRunStandardProgram(comptime GameContext: type, comptime spec: anytype) !void {
    const args = try getArgs();

    var backingAllocator: std.mem.Allocator = std.heap.c_allocator;
    var gpa: std.heap.GeneralPurposeAllocator(.{
        .stack_trace_frames = 20,
    }) = .{};

    defer {
        const cleanupStatus = gpa.deinit();
        if (cleanupStatus == .leak) {
            std.debug.print("gpa cleanup leaked memory\n", .{});
        }
    }

    if (args.useGPA) {
        backingAllocator = gpa.allocator();
    }

    const memory = core.MemoryTracker;
    memory.MTSetup(backingAllocator, .{ .timeline = args.dmt });
    defer memory.MTShutdown();

    var tracker = memory.MTGet().?;
    const allocator = tracker.allocator();

    if (args.vulkanValidation) {
        core.engine_logs("Using vulkan validation");
    }

    // graphics.setStartupSettings("vulkanValidation", args.vulkanValidation);

    if (@hasField(@TypeOf(spec), "windowName")) {
        platform.setWindowSettings(.{ .windowName = spec.windowName });
    } else {
        platform.setWindowSettings(.{ .windowName = spec.name });
    }

    try start_everything(spec, allocator, args);
    defer shutdown_everything(allocator);

    try run_everything(GameContext);
}
