br_core.loot = {}
-- list of all loot tables by level and then tier, `table[level][tier]`
br_core.loot.table = {}

function br_core.get_loot_table_default()
    return {
        node_names = {"br_core:loot_crate"},
        on_generate = function(pos, level, tier)
            return false
        end,
        max_items = 1,
        items = {}
    }
end

function br_core.set_loot_table(level, tier, def)
    local ta = br_core.loot.table
    if not ta[level] then ta[level] = {} end
	local new_def = table.copy(def)
	new_def.items = {}
	for k, v in ipairs(def.items or {}) do
		if v.items then
			table.insert(new_def.items, v)
		else
			for l, h in ipairs(v) do
				table.insert(new_def.items, h)
			end
		end
	end
    ta[level][tier] = new_def
end

function br_core.add_to_loot_table(level, tier, items)
    local ta = br_core.loot.table
    if (not ta[level]) or not ta[level][tier] then
        br_core.set_loot_table(level, tier, br_core.get_loot_table_default())
    end
    local edef = ta[level][tier]
    for i, v in ipairs(items) do
		if v.items then
			table.insert(edef.items, v)
		else
			for l, h in ipairs(v) do
				table.insert(edef.items, h)
			end
		end
    end
end

local loot_table_warnings = {}
function br_core.get_loot_table(level, tier)
    local loot_table = (br_core.loot.table[level] and br_core.loot.table[level][tier])
    if (not loot_table) then
        loot_table = br_core.get_loot_table_default()
        if (not loot_table_warnings[level..":"..tier]) then
            loot_table_warnings[level..":"..tier] = true
            core.log("warning", "No loot table for level "..level..":"..tier.."! Using default.")
        end
    end
    return loot_table
end

function br_core.get_loot(level, tier, seed)
    local loot_table = br_core.get_loot_table(level, tier)
    local loot_list = {}
    local itemcount = 0
    local r = PcgRandom(seed)
    if loot_table.chance_of_nothing and (r:next() / 2147483647) % 1 < loot_table.chance_of_nothing then
        return
    end
    for i, def in pairs(loot_table.items) do
        local v = (r:next() / 2147483647) % 1
        if ((not def.rarity) or math.floor(def.rarity * v + 1) == 1)
        and itemcount < (loot_table.max_items or 9999) then
            itemcount = itemcount + 1
            for k, itemstring in pairs(def.items) do
                loot_list[#loot_list+1] = itemstring
            end
        end
    end
    return loot_list
end

function br_core.give_loot(loot_list, inventory, seed)
    if not loot_list then
        core.log("warning", "no loot list found")
        return
    end
    local r = PcgRandom(seed + 394)
    for i, itemstring in pairs(loot_list) do
        local stack = ItemStack(itemstring)
        local invsize = inventory:get_size("main")
        for k=0, invsize - 1 do
            local v = (r:next() / 2147483647) % 1
            local index = math.floor(invsize * v + 1)
            local to_stack = inventory:get_stack("main", (index + k) % invsize + 1)
            if (not to_stack) or to_stack:get_count() < 1 then
                inventory:set_stack("main", (index + k) % invsize + 1, stack)
                break
            end
        end
    end
end

function br_core.place_loot_at(pos, level, tier)
    if not br_core.flags.loot_crates then
        return false
    end
    local loot_table = br_core.get_loot_table(level, tier)
    local r = PcgRandom((pos.x * pos.z + pos.y) / 3)
    if loot_table.node_names then
        local v = (r:next() / 2147483647) % 1
        local ri = math.floor(#loot_table.node_names * v) + 1
        core.swap_node(pos, {name=loot_table.node_names[ri]})
    end
    local skip_generation = (loot_table.on_generate and loot_table.on_generate(pos, level, tier)) or false
    if skip_generation then
        return
    end
    local seed = tonumber(core.hash_node_position(pos))
    local loot_list = br_core.get_loot(level, tier, seed)
    local inv = core.get_meta(pos):get_inventory()
    br_core.give_loot(loot_list, inv, seed)
    return true
end
--[[
br_core.set_loot_table(167, 0, {
    -- if defined, a random node from this list is chosen
    node_names = {"br_core:loot_crate"},
    -- run before loot added, if return true, no loot added
    on_generate = function(pos, level, tier)
        return false
    end,
    max_items = 4,
    items = {
        {
            items = {'br_core:flashlight 1'},
            rarity = 20
        },
    },
    -- if defined, this will mean only one of these nodes and its loot will be chosen
    -- nodes = {
    --     [1] = {
    --         rarity = 1,
    --         node_name = "br_core:loot_crate_white",
    --         items = {
    --             {
    --                 items = {'br_core:flashlight 1'},
    --                 rarity = 20
    --             },
    --         },
    --     }
    -- }
})
--]]

function br_core.loot_crate_init(pos)
    if not br_core.flags.loot_crates then
        core.set_node(pos, {name="air"})
        return false
    end
    local node = core.get_node(pos)
    local def = core.registered_nodes[node.name]
    if core.get_meta(pos):get_inventory():get_size("main") ~= 0 then
        core.set_node(pos, {name="air"})
        return
    end
    if core.get_item_group(node.name, "loot_crate") <= 0 then
        core.set_node(pos, {name="br_core:loot_crate"})
    end
    local meta = core.get_meta(pos)
    local inv = meta:get_inventory()
    inv:set_size("main", 4*4)
    meta:set_string("formspec",
        "formspec_version[3]"..
        "size[9,9]"..
        "background[0,0;9,9;br_crate_background.png]"..
        "bgcolor[#ffffff00]"..
        "listcolors[#f7ebeb20;#f7ebeb00]"..
        "list[context;main;2.1,1;4,4;]"..
        "listring[context;main]"..
        "listring[current_player;main]"..
        br_core.get_inventory_list({x=0.9,y=7})
    )
    meta:set_string("infotext", "Loot Crate")
    local level = br_core.get_level_index(pos)

    if not br_core.place_loot_at(pos, level, (def._loot_tier or 0)) then
        core.set_node(pos, {name="air"})
    end
end

function br_core.loot_refresh_start(pos)
    local nt = core.get_node_timer(pos)
    if br_core.loot_respawn_time > 0 then
        nt:start(br_core.loot_respawn_time)
    end
end

function br_core.loot_refresh(pos, elapsed)
    -- if it was not unloaded when this triggered, ignore it
    -- in other words, loot will refresh after this time, but only if no players nearby
    if elapsed <= br_core.loot_respawn_time + 1 then
        return true -- try again
    end
    local meta = core.get_meta(pos)
    local inv = meta:get_inventory()
    inv:set_list("main", {})
    local seed = tonumber(core.hash_node_position(pos))
    local loot_list = br_core.get_loot(br_core.get_level_index(pos), tonumber(meta:get_string("loot_tier")) or 0, seed)
    br_core.give_loot(loot_list, inv, seed)
end

function br_core.loot_injector_init(pos)
    if not br_core.flags.loot_crates then
        core.set_node(pos, {name="air"})
        return false
    end
    local loot_pos = pos + core.facedir_to_dir(core.get_node(pos).param2 or 0)
    local level = br_core.get_level_index(loot_pos)
    local node = core.get_node(loot_pos)
    local injdef = core.registered_nodes[core.get_node(pos).name]
    core.set_node(pos, {name="air"})
    if not injdef then return end
    local ndef = core.registered_nodes[node.name]
    if ndef._loot_init then
        if ndef._loot_init(loot_pos, br_core.get_level_index(pos), injdef._loot_tier or 0) == false then
            return
        end
    end
    local meta = core.get_meta(loot_pos)
    local inv = meta:get_inventory()
    local seed = tonumber(core.hash_node_position(loot_pos))
    meta:set_string("loot_tier", tostring(injdef._loot_tier))
    local loot_list = br_core.get_loot(level, injdef._loot_tier or 0, seed)
    br_core.give_loot(loot_list, inv, seed)
end

br_core.loot.list = {}

function br_core.loot.list.junk(r)
    return {
        {items = {"br_core:rag 1"}, rarity = r},
        {items = {"br_core:duct_tape 1"}, rarity = r},
        {items = {"br_core:bottle 1"}, rarity = r},
        {items = {"br_core:bottle_water_dirty 1"}, rarity = r},
    }
end

function br_core.loot.list.food(r)
    return {
        {items = {"br_core:canned_fruit_0 1"}, rarity = r},
        {items = {"br_core:canned_fruit_1 1"}, rarity = r},
        {items = {"br_core:canned_beans_0 1"}, rarity = r},
        {items = {"br_core:canned_beans_1 1"}, rarity = r},
    }
end

function br_core.loot.list.drinks(r)
    return {
        {items = {"br_core:carton_orange_juice 1"}, rarity = r},
        {items = {"br_core:carton_banana_juice 1"}, rarity = r},
        {items = {"br_core:bottle_water 1"}, rarity = r},
        {items = {"br_core:bottle_cola 1"}, rarity = r},
    }
end

function br_core.loot.list.tools(r)
    return {
        {items = {"br_core:flashlight 1"}, rarity = r},
    }
end
