// zig fmt: off
const std = @import("std");
const Store = @import("../store.zig");
const Position = @import("./position.zig");
const json_utils = @import("../json_utils.zig");
const Monster = @import("./monster.zig");
const Resource = @import("./resource.zig");
const Craft = @import("./craft.zig");
const EnumStringUtils = @import("../enum_string_utils.zig").EnumStringUtils;

const Map = @This();

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

pub const max_skin_size = 32;
pub const Skin = std.BoundedArray(u8, max_skin_size);

pub const Content = struct {
    pub const Type = enum {
        monster,
        resource,
        workshop,
        bank,
        grand_exchange,
        tasks_master,
        santa_claus,

        const Utils = EnumStringUtils(Type, .{
            .{ "monster"       , Type.monster        },
            .{ "resource"      , Type.resource       },
            .{ "workshop"      , Type.workshop       },
            .{ "bank"          , Type.bank           },
            .{ "grand_exchange", Type.grand_exchange },
            .{ "tasks_master"  , Type.tasks_master   },
            .{ "santa_claus"   , Type.santa_claus   },
        });

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

    pub const max_code_size = size: {
        var max: usize = 0;
        max = @max(max, Monster.max_code_size);
        max = @max(max, Resource.max_code_size);
        for (std.meta.fields(Craft.Skill)) |field| {
            max = @max(max, Craft.Skill.toString(@enumFromInt(field.value)).len);
        }
        max = @max(max, "bank".len);
        max = @max(max, "grand_exchange".len);
        // TODO: max type 'tasks_master'
        break :size max;
    };
    pub const Code = std.BoundedArray(u8, max_code_size);

    type: Type,
    code: Code,
};

name: Name,
skin: Skin,
position: Position,
content: ?Content,

pub fn parse(store: *Store, obj: std.json.ObjectMap) !Map {
    _ = store;

    const name = try json_utils.getStringRequired(obj, "name");
    const skin = try json_utils.getStringRequired(obj, "skin");
    const x = try json_utils.getIntegerRequired(obj, "x");
    const y = try json_utils.getIntegerRequired(obj, "y");

    var content: ?Content = null; 
    if (json_utils.getObject(obj, "content")) |content_obj| {
        const content_code = try json_utils.getStringRequired(content_obj, "code");
        const content_type = try json_utils.getStringRequired(content_obj, "type");
        content = Content{
            .code = try Content.Code.fromSlice(content_code),
            .type = Content.Type.fromString(content_type) orelse return error.InvalidContentType
        };
    }

    return Map{
        .name = try Name.fromSlice(name),
        .skin = try Skin.fromSlice(skin),
        .position = Position.init(x, y),
        .content = content
    };
}

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

pub fn parseAndAppendObject(store: *Store, obj: std.json.ObjectMap) !Map {
    const position = try parseAndAppend(store, obj);
    return store.getMap(position).?.*;
}
