// Describes player-local state of the world.
const World = @This();
const std = @import("std");
const mem = @import("common").mem;
const logic = @import("../logic.zig");
const Session = @import("../Session.zig");
const Assets = @import("../Assets.zig");

const Io = std.Io;
const Allocator = std.mem.Allocator;

pub const PlayerId = struct {
    pub const max_length: usize = 16;

    uid: mem.LimitedString(max_length),
};

player_id: PlayerId,
session: *Session, // TODO: should it be here this way? Do we need an abstraction?
res: logic.Resource,
player: logic.Player,
level: logic.Level = .init,

pub fn init(
    session: *Session,
    assets: *const Assets,
    uid: mem.LimitedString(PlayerId.max_length),
    player: logic.Player,
    io: Io,
) World {
    return .{
        .player_id = .{ .uid = uid },
        .session = session,
        .player = player,
        .res = .init(assets, io),
    };
}

pub fn deinit(world: *World, gpa: Allocator) void {
    world.player.deinit(gpa);
    world.level.deinit(gpa);
}

pub const GetComponentError = error{
    ComponentUnavailable,
};

pub fn getComponentByType(world: *World, comptime T: type) GetComponentError!T {
    switch (T) {
        PlayerId => return world.player_id,
        *Session => return world.session,
        *logic.Level => return &world.level,
        *logic.Resource.PingTimer => return &world.res.ping_timer,
        *const Assets => return world.res.assets,
        Io => return world.res.io(),
        else => {
            if (comptime logic.Player.isComponent(T)) {
                return world.player.getComponentByType(T);
            }

            @compileError("World.getComponentByType(" ++ @typeName(T) ++ ") is unsupported");
        },
    }
}
