const MeshConfig = struct {
    info: CookInfo = .{ .assetType = "Mesh" }, // there must always be a CookInfo field
    sourceType: []const u8 = "obj",
    animated: bool = false,
};

const extList = [_][]const u8{ "gltf", "obj", "glb" };
pub fn generateFunction(allocator: std.mem.Allocator, path: []const u8, out: *std.ArrayList(u8)) GenerateError!void {
    _ = allocator;
    out.clearRetainingCapacity();
    const ext = core.getFileExtension(path)[1..];

    var config: MeshConfig = .{};

    for (extList) |e| {
        if (std.mem.eql(u8, e, ext)) {
            config.sourceType = e;

            if (std.mem.eql(u8, e, "glb")) {
                config.sourceType = "gltf";
            }
        }
    }

    std.json.stringify(
        config,
        .{ .whitespace = .indent_4 },
        out.writer(),
    ) catch return GenerateError.UnableToGenerate;
}

fn cookObj(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8) cook.CookResult {
    const rawFileBytes = cook.loadFileAlloc(allocator, dir, path) catch unreachable;
    defer allocator.free(rawFileBytes);

    var out = std.ArrayList(u8).init(allocator);

    var vertices = std.ArrayList(Vertex).init(allocator);
    defer vertices.deinit();

    var objs = obj.loadObjBytes(rawFileBytes, allocator) catch unreachable;
    defer objs.deinit();

    if (objs.meshes.items.len > 0) {
        mesh.loadObjMeshVertices(&vertices, objs.meshes.items[0]) catch unreachable;
        for (vertices.items) |vert| {
            out.appendSlice(&@as([@sizeOf(Vertex)]u8, @bitCast(vert))) catch unreachable;
        }

        return .{
            .bytes = out,
            .result = .Success,
        };
    } else {
        return .{
            .bytes = out,
            .result = .Failure,
        };
    }
}

fn ensureGltf2ozz(allocator: std.mem.Allocator) !void {
    const suffix = if (builtin.os.tag == .windows) ".exe" else "";
    std.fs.cwd().access("zig-out/tools/gltf2ozz" ++ suffix, .{}) catch {
        const argv: []const []const u8 = &.{ "zig", "build", "tools" };

        core.engine_log("gltf2ozz missing, building it...", .{});

        var child = std.process.Child.init(argv, allocator);
        child.stdin_behavior = .Ignore;
        child.stdout_behavior = .Pipe;
        child.stderr_behavior = .Pipe;
        child.cwd = ".";

        switch (try child.spawnAndWait()) {
            .Exited => |value| {
                if (value == 0) {
                    core.engine_log("gltf2ozz built", .{});
                } else {
                    core.engine_logs("unable to build gltf2ozz");
                }
            },
            .Signal => {
                core.engine_logs("unable to build gltf2ozz");
            },
            .Stopped => {},
            .Unknown => {
                unreachable;
            },
        }

        return;
    };

    core.engine_log("gltf2ozz found", .{});
}

fn cookAnimations(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8, config: MeshConfig) !void {
    _ = config;

    // 1. check if it has an associated .ozzconfig file.

    const gltf2OzzAbs = try std.fs.cwd().realpathAlloc(allocator, "zig-out/tools/gltf2ozz.exe");
    defer allocator.free(gltf2OzzAbs);

    const ozzconfig = try std.fmt.allocPrint(allocator, "{s}.ozzconfig", .{path});
    defer allocator.free(ozzconfig);

    const fileArg = try std.fmt.allocPrint(allocator, "--file={s}", .{core.getBasePath(path)});
    defer allocator.free(fileArg);

    const configArg = try std.fmt.allocPrint(allocator, "--config_file={s}", .{core.getBasePath(ozzconfig)});
    defer allocator.free(configArg);

    // todo, fix this later, idrc right now.
    const newConfigArg = try std.fmt.allocPrint(allocator, "--config_dump_reference={s}", .{core.getBasePath(ozzconfig)});
    defer allocator.free(newConfigArg);

    const absFile = try dir.realpathAlloc(allocator, path);
    defer allocator.free(absFile);

    var argv: []const []const u8 = &.{
        gltf2OzzAbs,
        fileArg,
        configArg,
    };

    dir.access(ozzconfig, .{}) catch {
        argv = &.{
            gltf2OzzAbs,
            fileArg,
            newConfigArg,
        };

        core.engine_log("creating ozz config for file, marked as animated but no animation data", .{});
    };

    // std.debug.print("{s} {s} {s} cwd = {s}\n", .{ argv[0], argv[1], argv[2], core.getFolder(absFile) });

    const result = try std.process.Child.run(.{
        .argv = argv,
        .allocator = allocator,
        .cwd = core.getFolder(absFile),
        .max_output_bytes = 150 * 1024 * 1024,
    });

    defer allocator.free(result.stdout);
    defer allocator.free(result.stderr);

    var success: bool = true;
    switch (result.term) {
        .Exited => |value| {
            if (value != 0) {
                success = false;
            }
        },
        .Signal => {
            success = false;
        },
        .Stopped => {
            success = false;
            // no-op should be ok?
        },
        .Unknown => {
            unreachable;
        },
    }

    if (success) {
        core.engine_log("generated animations for {s}", .{path});
    } else {
        core.engine_log("error generating animations {s} stdout:\n{s}\n stderr:{s}\n", .{ path, result.stdout, result.stderr });
    }
    // 2. if so, run it through gltf2ozz with that file.
}

fn cookGltf(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8, config: MeshConfig) cook.CookResult {
    core.engine_logs("gltf cooking not implemeted");
    const out = std.ArrayList(u8).init(allocator);

    // check if it's animated. if it's animated, then invoke gltf2ozz and create a .ozzconfig file and
    // make a subfolder called

    if (config.animated) {
        // if gltf2ozz isn't there then we have to call zig build tools
        ensureGltf2ozz(allocator) catch unreachable;

        cookAnimations(allocator, dir, path, config) catch unreachable;
    }

    return .{ .bytes = out, .result = .Failure };
}

pub fn cookFunction(
    allocator: std.mem.Allocator,
    dir: std.fs.Dir,
    path: []const u8,
    params: cook.CookParams,
) cook.CookResult {
    core.engine_log("{s}", .{params.cookFileName});
    const fc = cook.loadFileAlloc(allocator, dir, params.cookFileName) catch unreachable;
    defer allocator.free(fc);

    const config = std.json.parseFromSlice(MeshConfig, allocator, fc[0 .. fc.len - 1], .{}) catch unreachable;
    defer config.deinit();

    if (std.mem.eql(u8, config.value.sourceType, "obj")) {
        return cookObj(allocator, dir, path);
    } else {
        return cookGltf(allocator, dir, path, config.value);
    }
}

pub fn initCooker(allocator: std.mem.Allocator) !void {
    _ = allocator;
    const registry = assets.cook.getRegistry();

    try registry.install("Mesh", generateFunction, cookFunction, &.{
        ".obj",
        ".gltf",
        ".glb",
    });
}

pub fn deinitCooker() void {
    //
}

const std = @import("std");
const assets = @import("assets");
const cook = assets.cook;
const CookInfo = assets.cook.CookInfo;
const GenerateError = assets.cook.GenerateError;
const core = @import("core");
const obj = @import("objLoader");
const builtin = @import("builtin");
const mesh = @import("../mesh.zig");
const Mesh = mesh.Mesh;
const Vertex = mesh.MeshVertex;
