// zig fmt: off
const std = @import("std");
const Store = @import("../store.zig");
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;
const json_utils = @import("../json_utils.zig");
pub const Equipment = @import("./equipment.zig");
const Task = @import("./task.zig");
const SimpleItem = @import("./simple_item.zig");

const Character = @This();

pub const Skin = enum {
    men1,
    men2,
    men3,
    women1,
    women2,
    women3,

    const Utils = EnumStringUtils(Skin, .{
        .{ "men1"  , Skin.men1   },
        .{ "men2"  , Skin.men2   },
        .{ "men3"  , Skin.men3   },
        .{ "women1", Skin.women1 },
        .{ "women2", Skin.women2 },
        .{ "women3", Skin.women3 },
    });

    pub const fromString = Utils.fromString;
    pub const toString = Utils.toString;
};

pub const Skill = enum {
    combat,
    fishing,
    weaponcrafting,
    gearcrafting,
    jewelrycrafting,
    cooking,
    woodcutting,
    mining,
    alchemy,

    const Utils = EnumStringUtils(Skill, .{
        .{ "combat"         , Skill.combat  },
        .{ "fishing"        , Skill.fishing  },
        .{ "weaponcrafting" , Skill.weaponcrafting  },
        .{ "gearcrafting"   , Skill.gearcrafting    },
        .{ "jewelrycrafting", Skill.jewelrycrafting },
        .{ "cooking"        , Skill.cooking         },
        .{ "woodcutting"    , Skill.woodcutting     },
        .{ "mining"         , Skill.mining          },
        .{ "alchemy"        , Skill.alchemy         },
    });

    pub const toString = Utils.toString;
    pub const fromString = Utils.fromString;
};

pub const SkillStats = struct {
    level: u64 = 0,
    xp: u64 = 0,
    max_xp: u64 = 0,

    pub fn parse(object: std.json.ObjectMap, level: []const u8, xp: []const u8, max_xp: []const u8) !SkillStats {
        return SkillStats{
            .level  = try json_utils.getPositiveIntegerRequired(object, level),
            .xp     = try json_utils.getPositiveIntegerRequired(object, xp),
            .max_xp = try json_utils.getPositiveIntegerRequired(object, max_xp),
        };
    }
};

pub const Element = enum {
    water,
    fire,
    earth,
    air,
};

pub const ElementalStats = struct {
    attack: i64,
    damage: i64,
    resistance: i64,

    pub fn parse(object: std.json.ObjectMap, attack: []const u8, damage: []const u8, resistance: []const u8) !ElementalStats {
        return ElementalStats{
            .attack = try json_utils.getIntegerRequired(object, attack),
            .damage = try json_utils.getIntegerRequired(object, damage),
            .resistance = try json_utils.getIntegerRequired(object, resistance),
        };
    }
};

const TaskMasterTask = struct {
    target: Store.Id,
    type: Task.Type,
    progress: u64,
    total: u64,

    fn parse(store: *Store, obj: std.json.ObjectMap) !?TaskMasterTask {
        const task_target = try json_utils.getStringRequired(obj, "task");
        if (task_target.len == 0) {
            return null;
        }

        const task_type = try json_utils.getStringRequired(obj, "task_type");
        const progress = try json_utils.getPositiveIntegerRequired(obj, "task_progress");
        const total = try json_utils.getPositiveIntegerRequired(obj, "task_total");

        return TaskMasterTask{
            .target = try store.tasks.getOrReserveId(task_target),
            .type = Task.Type.fromString(task_type) orelse return error.InvalidTaskType,
            .total = total,
            .progress = progress,
        };
    }
};

pub const ElementalStatsArray = std.EnumArray(Element, ElementalStats);

pub const Skills = std.EnumArray(Skill, SkillStats);

pub const max_name_size = 12;
pub const Name = std.BoundedArray(u8, max_name_size);

pub const max_account_size = 32;
pub const Account = std.BoundedArray(u8, max_account_size);

pub const Inventory = SimpleItem.BoundedArray(20);

name: Name,
account: Account,
skin: Skin,
gold: u64,
skills: Skills,
elemental_stats: ElementalStatsArray,
equipment: Equipment,
task: ?TaskMasterTask,
inventory_max_items: u64,
inventory: Inventory,

pub fn parse(store: *Store, obj: std.json.ObjectMap) !Character {
    const name = try json_utils.getStringRequired(obj, "name");
    if (name.len == 0) {
        return error.InvalidName;
    }

    const inventory = try json_utils.getArrayRequired(obj, "inventory");
    const account = try json_utils.getStringRequired(obj, "name");
    const skin = try json_utils.getStringRequired(obj, "skin");
    const gold = try json_utils.getPositiveIntegerRequired(obj, "gold");
    const inventory_max_items = try json_utils.getPositiveIntegerRequired(obj, "inventory_max_items");

    const skill_stats = Skills.init(.{
        .combat          = try SkillStats.parse(obj, "level", "xp", "max_xp"),
        .mining          = try SkillStats.parse(obj, "mining_level", "mining_xp", "mining_max_xp"),
        .woodcutting     = try SkillStats.parse(obj, "woodcutting_level", "woodcutting_xp", "woodcutting_max_xp"),
        .fishing         = try SkillStats.parse(obj, "fishing_level", "fishing_xp", "fishing_max_xp"),
        .weaponcrafting  = try SkillStats.parse(obj, "weaponcrafting_level", "weaponcrafting_xp", "weaponcrafting_max_xp"),
        .gearcrafting    = try SkillStats.parse(obj, "gearcrafting_level", "gearcrafting_xp", "gearcrafting_max_xp"),
        .jewelrycrafting = try SkillStats.parse(obj, "jewelrycrafting_level", "jewelrycrafting_xp", "jewelrycrafting_max_xp"),
        .cooking         = try SkillStats.parse(obj, "cooking_level", "cooking_xp", "cooking_max_xp"),
        .alchemy         = try SkillStats.parse(obj, "alchemy_level", "alchemy_xp", "alchemy_max_xp"),
    });

    const elemental_stats = ElementalStatsArray.init(.{
        .water = try ElementalStats.parse(obj, "attack_water", "dmg_water", "res_water"),
        .fire  = try ElementalStats.parse(obj, "attack_fire", "dmg_fire", "res_fire"),
        .earth = try ElementalStats.parse(obj, "attack_earth", "dmg_earth", "res_earth"),
        .air   = try ElementalStats.parse(obj, "attack_air", "dmg_air", "res_air"),
    });

    return Character{
        .name = try Name.fromSlice(name),
        .account = try Account.fromSlice(account),
        .skin = Skin.fromString(skin) orelse return error.SkinNotFound,
        .gold = gold,
        .skills = skill_stats,
        .elemental_stats = elemental_stats,
        .equipment = try Equipment.parse(store, obj),
        .task = try TaskMasterTask.parse(store, obj),
        .inventory_max_items = inventory_max_items,
        .inventory = try Inventory.parse(store, inventory)
    };
}

pub fn parseAndAppend(store: *Store, obj: std.json.ObjectMap) !Store.Id {
    return store.characters.appendOrUpdate(try parse(store, obj));
}
