const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("../services/config.zig");
const Res_config = @import("../services/res_config.zig");
const Item = @import("../services/item.zig");
const UidGenerator = Item.UidGenerator();
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;

pub const SceneManager = struct {
    allocator: Allocator,

    pub fn init(allocator: Allocator) SceneManager {
        return SceneManager{ .allocator = allocator };
    }

    fn addAvatarEntities(
        scene_group: *protocol.SceneEntityGroupInfo,
        avatar_configs: []const Config.Avatar,
        tele_pos: protocol.Vector,
        tele_rot: protocol.Vector,
        uid: u32,
    ) !void {
        for (avatar_configs) |avatarConf| {
            try scene_group.entity_list.append(.{
                .inst_id = 1,
                .entity_id = @intCast(avatarConf.id + 100000),
                .entity = .{ .actor = .{
                    .base_avatar_id = avatarConf.id,
                    .avatar_type = .AVATAR_FORMAL_TYPE,
                    .uid = uid,
                    .map_layer = 0,
                } },
                .motion = .{ .pos = tele_pos, .rot = tele_rot },
            });
        }
    }
    fn addPropEntities(
        allocator: Allocator,
        group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo),
        prop_configs: []const Res_config.Props,
        generator: *UidGenerator,
    ) !void {
        for (prop_configs) |propConf| {
            var scene_group = try getOrCreateGroup(group_map, propConf.groupId, allocator);
            var prop_info = protocol.ScenePropInfo.init(allocator);
            prop_info.prop_id = propConf.propId;
            prop_info.prop_state = propConf.propState;
            try scene_group.entity_list.append(.{
                .entity = .{ .prop = prop_info },
                .group_id = scene_group.group_id,
                .inst_id = propConf.instId,
                .entity_id = 1000 + generator.nextId(),
                .motion = .{
                    .pos = .{ .x = propConf.pos.x, .y = propConf.pos.y, .z = propConf.pos.z },
                    .rot = .{ .x = propConf.rot.x, .y = propConf.rot.y, .z = propConf.rot.z },
                },
            });
        }
    }
    fn addMonsterEntities(
        allocator: Allocator,
        group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo),
        monster_configs: []const Res_config.Monsters,
        generator: *UidGenerator,
    ) !void {
        for (monster_configs) |monsConf| {
            var scene_group = try getOrCreateGroup(group_map, monsConf.groupId, allocator);
            var monster_info = protocol.SceneNpcMonsterInfo.init(allocator);
            monster_info.monster_id = monsConf.monsterId;
            monster_info.event_id = monsConf.eventId;
            monster_info.world_level = 6;
            try scene_group.entity_list.append(.{
                .entity = .{ .npc_monster = monster_info },
                .group_id = scene_group.group_id,
                .inst_id = monsConf.instId,
                .entity_id = if ((monsConf.monsterId / 1000) % 10 == 3) monster_info.monster_id else generator.nextId(),
                .motion = .{
                    .pos = .{ .x = monsConf.pos.x, .y = monsConf.pos.y, .z = monsConf.pos.z },
                    .rot = .{ .x = monsConf.rot.x, .y = monsConf.rot.y, .z = monsConf.rot.z },
                },
            });
        }
    }
    pub fn createScene(
        self: *SceneManager,
        plane_id: u32,
        floor_id: u32,
        entry_id: u32,
        teleport_id: u32,
    ) !protocol.SceneInfo {
        const config = try Config.loadGameConfig(self.allocator, "config.json");
        const res_config = global_game_config_cache.res_config;
        var generator = Item.UidGenerator().init();

        var scene_info = protocol.SceneInfo.init(self.allocator);
        scene_info.game_mode_type = 1;
        scene_info.plane_id = plane_id;
        scene_info.floor_id = floor_id;
        scene_info.entry_id = entry_id;
        scene_info.leader_entity_id = config.avatar_config.items[0].id + 100000;
        scene_info.world_id = 501;
        scene_info.client_pos_version = 1;

        var group_map = std.AutoHashMap(u32, protocol.SceneEntityGroupInfo).init(self.allocator);
        defer group_map.deinit();

        for (res_config.scene_config.items) |sceneConf| {
            for (sceneConf.teleports.items) |teleConf| {
                if (teleConf.teleportId == teleport_id) {
                    var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
                    scene_group.state = 1;
                    const proto_tele_pos = protocol.Vector{
                        .x = teleConf.pos.x,
                        .y = teleConf.pos.y,
                        .z = teleConf.pos.z,
                    };
                    const proto_tele_rot = protocol.Vector{
                        .x = teleConf.rot.x,
                        .y = teleConf.rot.y,
                        .z = teleConf.rot.z,
                    };

                    try addAvatarEntities(&scene_group, config.avatar_config.items, proto_tele_pos, proto_tele_rot, 0);
                    try scene_info.entity_group_list.append(scene_group);
                    break;
                }
            }
            if (scene_info.plane_id != 10000 and scene_info.plane_id != 10202 and sceneConf.planeID == scene_info.plane_id) {
                try addPropEntities(self.allocator, &group_map, sceneConf.props.items, &generator);
                try addMonsterEntities(self.allocator, &group_map, sceneConf.monsters.items, &generator);
            }
        }

        var iter = group_map.iterator();
        while (iter.next()) |entry| {
            try scene_info.entity_group_list.append(entry.value_ptr.*);
            try scene_info.entity_list.appendSlice(entry.value_ptr.entity_list.items);
            try scene_info.DJBIBIJMEBH.append(entry.value_ptr.group_id);
            try scene_info.custom_data_list.append(protocol.CustomSaveData{
                .group_id = entry.value_ptr.group_id,
            });
            try scene_info.group_state_list.append(protocol.SceneGroupState{
                .group_id = entry.value_ptr.group_id,
                .state = 0,
                .is_default = true,
            });
        }
        const ranges = [_][2]usize{
            .{ 0, 101 },
            .{ 10000, 10051 },
            .{ 20000, 20001 },
            .{ 30000, 30020 },
        };
        for (ranges) |range| {
            for (range[0]..range[1]) |i| {
                try scene_info.lighten_section_list.append(@intCast(i));
            }
        }
        return scene_info;
    }
    fn getOrCreateGroup(group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo), group_id: u32, allocator: Allocator) !*protocol.SceneEntityGroupInfo {
        if (group_map.getPtr(group_id)) |existing_group| {
            return existing_group;
        }
        var new_group = protocol.SceneEntityGroupInfo.init(allocator);
        new_group.state = 1;
        new_group.group_id = group_id;
        try group_map.put(group_id, new_group);
        return group_map.getPtr(group_id).?;
    }
};

pub const ChallengeSceneManager = struct {
    allocator: Allocator,

    pub fn init(allocator: Allocator) ChallengeSceneManager {
        return ChallengeSceneManager{ .allocator = allocator };
    }
    pub fn createScene(
        self: *ChallengeSceneManager,
        avatar_list: ArrayList(u32),
        plane_id: u32,
        floor_id: u32,
        entry_id: u32,
        world_id: u32,
        monster_id: u32,
        event_id: u32,
        group_id: u32,
        maze_group_id: u32,
    ) !protocol.SceneInfo {
        const res_config = global_game_config_cache.res_config;
        var generator = Item.UidGenerator().init();

        var scene_info = protocol.SceneInfo.init(self.allocator);
        scene_info.game_mode_type = 4;
        scene_info.plane_id = plane_id;
        scene_info.floor_id = floor_id;
        scene_info.entry_id = entry_id;
        scene_info.leader_entity_id = avatar_list.items[0];
        scene_info.world_id = world_id;
        try scene_info.group_state_list.append(protocol.SceneGroupState{
            .group_id = maze_group_id,
            .is_default = true,
        });
        { // Character Group
            var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
            scene_group.state = 1;
            scene_group.group_id = 0;
            for (avatar_list.items) |avatar_base_id| {
                try scene_group.entity_list.append(.{
                    .inst_id = 1,
                    .entity_id = @intCast(avatar_base_id + 100000),
                    .entity = .{
                        .actor = .{
                            .base_avatar_id = avatar_base_id,
                            .avatar_type = .AVATAR_FORMAL_TYPE,
                            .uid = 1,
                            .map_layer = 0,
                        },
                    },
                    .motion = .{ .pos = .{}, .rot = .{} },
                });
            }
            try scene_info.entity_group_list.append(scene_group);
        }
        for (res_config.scene_config.items) |sceneConf| {
            if (sceneConf.planeID == scene_info.plane_id) {
                for (sceneConf.monsters.items) |monsConf| {
                    if (monsConf.groupId == group_id) {
                        var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
                        scene_group.state = 1;
                        scene_group.group_id = group_id;

                        var monster_info = protocol.SceneNpcMonsterInfo.init(self.allocator);
                        monster_info.monster_id = monster_id;
                        monster_info.event_id = event_id;
                        monster_info.world_level = 6;

                        try scene_group.entity_list.append(.{
                            .entity = .{ .npc_monster = monster_info },
                            .group_id = group_id,
                            .inst_id = monsConf.instId,
                            .entity_id = generator.nextId(),
                            .motion = .{
                                .pos = .{ .x = monsConf.pos.x, .y = monsConf.pos.y, .z = monsConf.pos.z },
                                .rot = .{ .x = monsConf.rot.x, .y = monsConf.rot.y, .z = monsConf.rot.z },
                            },
                        });
                        try scene_info.entity_group_list.append(scene_group);
                        break;
                    }
                }
                for (sceneConf.props.items) |propConf| {
                    var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
                    scene_group.state = 1;
                    scene_group.group_id = group_id;

                    var prop_info = protocol.ScenePropInfo.init(self.allocator);
                    prop_info.prop_id = propConf.propId;
                    prop_info.prop_state = propConf.propState;

                    try scene_group.entity_list.append(.{
                        .entity = .{ .prop = prop_info },
                        .group_id = group_id,
                        .inst_id = propConf.instId,
                        .entity_id = generator.nextId(),
                        .motion = .{
                            .pos = .{ .x = propConf.pos.x, .y = propConf.pos.y, .z = propConf.pos.z },
                            .rot = .{ .x = propConf.rot.x, .y = propConf.rot.y, .z = propConf.rot.z },
                        },
                    });
                    try scene_info.entity_group_list.append(scene_group);
                }
            }
        }
        return scene_info;
    }
};
pub const MazeMapManager = struct {
    allocator: Allocator,
    pub fn init(allocator: Allocator) MazeMapManager {
        return MazeMapManager{ .allocator = allocator };
    }
    pub fn setMazeMapData(
        self: *MazeMapManager,
        map_info: *protocol.SceneMapInfo,
        floor_id: u32,
    ) !void {
        const map_entrance_config = global_game_config_cache.map_entrance_config;

        const res_config = global_game_config_cache.res_config;
        var plane_ids = ArrayList(u32).init(self.allocator);
        defer plane_ids.deinit();
        for (map_entrance_config.map_entrance_config.items) |entrConf| {
            if (entrConf.floor_id == floor_id) {
                try plane_ids.append(entrConf.plane_id);
            }
        }
        map_info.maze_group_list = ArrayList(protocol.MazeGroup).init(self.allocator);
        map_info.maze_prop_list = ArrayList(protocol.MazePropState).init(self.allocator);
        for (res_config.scene_config.items) |sceneConf| {
            for (plane_ids.items) |plane_id| {
                if (sceneConf.planeID == plane_id) {
                    for (sceneConf.props.items) |propConf| {
                        try map_info.maze_group_list.append(protocol.MazeGroup{
                            .NOBKEONAKLE = ArrayList(u32).init(self.allocator),
                            .group_id = propConf.groupId,
                        });
                        try map_info.maze_prop_list.append(protocol.MazePropState{
                            .group_id = propConf.groupId,
                            .config_id = propConf.instId,
                            .state = propConf.propState,
                        });
                    }
                }
            }
        }
    }
};
pub const GameConfigCache = struct {
    allocator: Allocator,
    res_config: Res_config.SceneConfig,
    map_entrance_config: Config.MapEntranceConfig,
    stage_config: Config.StageConfig,

    pub fn init(allocator: Allocator) !GameConfigCache {
        const res_cfg = try Res_config.anchorLoader(allocator, "resources/res.json");
        const map_entr_cfg = try Config.loadMapEntranceConfig(allocator, "resources/MapEntrance.json");
        const stage_cfg = try Config.loadStageConfig(allocator, "resources/StageConfig.json");

        return GameConfigCache{
            .allocator = allocator,
            .res_config = res_cfg,
            .map_entrance_config = map_entr_cfg,
            .stage_config = stage_cfg,
        };
    }
    pub fn deinit(_: *GameConfigCache) void {}
};

pub var global_game_config_cache: GameConfigCache = undefined;
pub var global_main_allocator: Allocator = undefined;

pub fn initGameGlobals(main_allocator: Allocator) !void {
    global_main_allocator = main_allocator;
    global_game_config_cache = try GameConfigCache.init(main_allocator);
}

pub fn deinitGameGlobals() void {
    global_game_config_cache.deinit();
}
