const std = @import("std");
const entity = @import("entity.zig");
const misc = @import("misc.zig");

pub fn Storage(comptime T: type) type {
    return struct {
        items: std.ArrayList(T),

        pub fn init(allocator: std.mem.Allocator) !@This() {
            return .{
                .items = try std.ArrayList(T).initCapacity(allocator, 32),
            };
        }

        pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
            self.items.deinit(allocator);
        }

        pub fn update(self: *@This()) void {
            for (self.items.items, 0..) |*item, i| {
                _ = i;
                if (@hasDecl(T, "update"))
                    item.update();
            }
        }
    };
}

pub fn Chunk() type {
    const FieldCount = entity.EntityKinds.len;

    var fields: [FieldCount]std.builtin.Type.StructField = undefined;

    inline for (entity.EntityKinds, 0..) |T, i| {
        fields[i] = .{
            .name = misc.short_type_name(T),
            .type = Storage(T),
            .default_value_ptr = null,
            .is_comptime = false,
            .alignment = @alignOf(Storage(T)),
        };
    }

    return @Type(.{
        .@"struct" = .{
            .layout = .auto,
            .fields = &fields,
            .decls = &.{},
            .is_tuple = false,
        },
    });
}

pub fn initChunk(allocator: std.mem.Allocator) !Chunk() {
    var chunk: Chunk() = undefined;

    switch (@typeInfo(Chunk())) {
        .@"struct" => |s| {
            inline for (s.fields) |field| {
                const StorageT = field.type;
                @field(chunk, field.name) = try StorageT.init(allocator);
            }
        },
        else => unreachable,
    }

    return chunk;
}

pub fn deinitChunk(chunk: *Chunk(), allocator: std.mem.Allocator) void {
    switch (@typeInfo(Chunk())) {
        .@"struct" => |s| {
            inline for (s.fields) |field| {
                @field(chunk, field.name).deinit(allocator);
            }
        },
        else => unreachable,
    }
}

pub fn updateChunk(chunk: *Chunk()) void {
    const info = @typeInfo(Chunk());

    switch (info) {
        .@"struct" => |s| {
            inline for (s.fields) |field| {
                @field(chunk, field.name).update();
            }
        },
        else => unreachable,
    }
}
