local S = core.get_translator(core.get_current_modname())

-- Figures out which player the provided award data belongs to by comparing
-- table references; this is ugly but the awards API has some deficiencies that
-- require workarounds like these
-- This makes this O(n) instead of O(1) but n should be small
local function get_owner_of_data(data)
    for _, player in pairs(core.get_connected_players()) do
        if data == awards.player(player:get_player_name()) then
            return player
        end
    end

    return nil
end

-- The awards API has a lot of limitations, so we keep track of some of the
-- award data ourselves; this also has the advantage that it is only in memory
-- while the player is currently playing
local yams_awards_key = "yams_awards:award_data"

local function get_yams_awards_data(player)
    local meta = player:get_meta()
    local res = core.deserialize(meta:get_string(yams_awards_key))
    return res or {}
end

local function save_yams_awards_data(player, data)
    local meta = player:get_meta()
    meta:set_string(yams_awards_key, core.serialize(data))
end

awards.register_award("yams_awards:first_coal", {
    title = S("Coal Get!"),
    description = S("Mine a coal lump for the first time."),
    difficulty = 0.01,  -- The difficulty value is meaningless in yams
    icon = "default_coal_lump.png",
    trigger = {
        type = "dig",
        node = "default:stone_with_coal",
        target = 1,
    },
    _yams_sort_key = 10,  -- This is what determines an award's index
})

awards.register_award("yams_awards:first_copper", {
    title = S("Copper Get!"),
    description = S("Mine a copper lump for the first time."),
    difficulty = 0.01,
    icon = "default_copper_lump.png",
    trigger = {
        type = "dig",
        node = "default:stone_with_copper",
        target = 1,
    },
    _yams_sort_key = 11,
})

awards.register_award("yams_awards:first_tin", {
    title = S("Tin Get!"),
    description = S("Mine a tin lump for the first time."),
    difficulty = 0.01,
    icon = "default_tin_lump.png",
    trigger = {
        type = "dig",
        node = "default:stone_with_tin",
        target = 1,
    },
    _yams_sort_key = 12,
})

awards.register_award("yams_awards:first_iron", {
    title = S("Iron Get!"),
    description = S("Mine an iron lump for the first time."),
    difficulty = 0.01,
    icon = "default_iron_lump.png",
    trigger = {
        type = "dig",
        node = "default:stone_with_iron",
        target = 1,
    },
    _yams_sort_key = 13,
})

awards.register_award("yams_awards:first_gold", {
    title = S("Gold Get!"),
    description = S("Mine a gold lump for the first time."),
    difficulty = 0.01,
    icon = "default_gold_lump.png",
    trigger = {
        type = "dig",
        node = "default:stone_with_gold",
        target = 1,
    },
    _yams_sort_key = 14,
})

awards.register_award("yams_awards:first_mese", {
    title = S("Mese Get!"),
    description = S("Mine a mese crystal for the first time."),
    difficulty = 0.01,
    icon = "default_mese_crystal.png",
    trigger = {
        type = "dig",
        node = "default:stone_with_mese",
        target = 1,
    },
    _yams_sort_key = 15,
})

awards.register_award("yams_awards:first_diamond", {
    title = S("Diamond Get!"),
    description = S("Mine a diamond for the first time."),
    difficulty = 0.01,
    icon = "default_diamond.png",
    trigger = {
        type = "dig",
        node = "default:stone_with_diamond",
        target = 1,
    },
    _yams_sort_key = 16,
})


awards.register_award("yams_awards:first_mithril", {
    title = S("Mithril Get!"),
    description = S("Mine a mithril lump for the first time."),
    difficulty = 0.01,
    icon = "moreores_mithril_lump.png",
    trigger = {
        type = "dig",
        node = "moreores:mineral_mithril",
        target = 1,
    },
    _yams_sort_key = 17,
})

awards.register_award("yams_awards:first_egerum", {
    title = S("Egerum Get!"),
    description = S("Mine an egerum crystal for the first time."),
    difficulty = 0.01,
    icon = "magic_materials_egerum_crystal.png",
    trigger = {
        type = "dig",
        node = "magic_materials:stone_with_egerum",
        target = 1,
    },
    _yams_sort_key = 18,
})

awards.register_award("yams_awards:first_februm", {
    title = S("Februm Get!"),
    description = S("Mine a februm crystal for the first time."),
    difficulty = 0.01,
    icon = "magic_materials_februm_crystal.png",
    trigger = {
        type = "dig",
        node = "magic_materials:stone_with_februm",
        target = 1,
    },
    _yams_sort_key = 19,
})

awards.register_award("yams_awards:first_nether_ore", {
    title = S("Nether Ore Get!"),
    description = S("Mine a nether lump for the first time."),
    requires = {"yams_awards:nether"},  -- Prevent spoilers
    difficulty = 0.01,
    icon = "nether_nether_lump.png",
    trigger = {
        type = "dig",
        node = "yams_mapgen:rack_with_nether_ore",
        target = 1,
    },
    _yams_sort_key = 20,
})

awards.register_award("yams_awards:first_stardust", {
    title = S("Stardust Get!"),
    description = S("Mine stardust for the first time."),
    requires = {"yams_awards:cloudlands"},  -- Prevent spoilers
    difficulty = 0.01,
    icon = "stardust_stardust.png",
    trigger = {
        type = "dig",
        node = "stardust:stone_with_stardust",
        target = 1,
    },
    _yams_sort_key = 21,
})

-- Unlock the relevant awards if someone's first ore was obtained in the nether
local nether_ore_map = {
    ["yams_mapgen:rack_deep_with_nether_ore"] = "yams_awards:first_nether_ore",
    ["yams_mapgen:rack_with_gold"] = "yams_awards:first_gold",
    ["yams_mapgen:rack_deep_with_mese"] = "yams_awards:first_mese",
    ["yams_mapgen:rack_with_diamond"] = "yams_awards:first_diamond",
    ["yams_mapgen:rack_deep_with_mithril"] = "yams_awards:first_mithril",
}

if core.get_modpath("yams_mapgen") then
    for node, award in pairs(nether_ore_map) do
        core.override_item(node, {
            after_dig_node = function(pos, oldnode, oldmetadata, digger)
                if yams.is_player(digger) then
                    local name = digger:get_player_name()
                    awards.unlock(name, award)
                end
            end
        })
    end
end

awards.register_award("yams_awards:basic_tools", {
    title = S("Back to Basics"),
    description = S("Craft a sword, axe, pickaxe, shovel, and hoe."),
    difficulty = 1,
    icon = "default_tool_stonepick.png",
    _yams_sort_key = 30,
})

local tmp = awards.registered_awards["yams_awards:basic_tools"]
tmp.get_progress = function(_, data)
    local player = get_owner_of_data(data)

    if player then
        local t = get_yams_awards_data(player)
        local num = (t.sword and 1 or 0) + (t.axe and 1 or 0) +
                    (t.pickaxe and 1 or 0) + (t.shovel and 1 or 0) +
                    (t.hoe and 1 or 0)

        return {
            current = num,
            target = 5,
            label = S("@1/@2 crafted", num, 5)
        }
    end
end

awards.register_award("yams_awards:anvil_and_hammer", {
    title = S("Aspiring Blacksmith"),
    description = S("Craft an anvil and a steel hammer."),
    difficulty = 1,
    icon = "anvil_tool_steelhammer.png",
    _yams_sort_key = 32,
})

-- In Lua, this shadows the earlier instance of the variable
local tmp = awards.registered_awards["yams_awards:anvil_and_hammer"]
tmp.get_progress = function(_, data)
    local d = data["craft"]
    local num = 0

    if d then  -- In case the player has not crafted anything yet
        if d["anvil:anvil"] then
            num = num + 1
        end
        if d["anvil:hammer"] then
            num = num + 1
        end
    end

    local res = {
        current = num,
        target = 2,
        label = S("@1/@2 crafted", num, 2)
    }

    return res
end

awards.register_award("yams_awards:home_essentials", {
    title = S("Home Essentials"),
    description = S("Craft a chest, furnace, torch, and bed."),
    difficulty = 1,
    icon = "beds_bed_fancy.png",
    _yams_sort_key = 31,
})

local tmp = awards.registered_awards["yams_awards:home_essentials"]
tmp.get_progress = function(_, data)
    local d = data["craft"]
    local num = 0

    if d then  -- In case the player has not crafted anything yet
        if d["default:chest"] then
            num = num + 1
        end
        if d["default:furnace"] then
            num = num + 1
        end
        if d["default:torch"] then
            num = num + 1
        end
        if d["beds:bed_bottom"] or d["beds:fancy_bed_bottom"] then
            num = num + 1
        end
    end

    local res = {
        current = num,
        target = 4,
        label = S("@1/@2 crafted", num, 4)
    }

    return res
end

awards.register_award("yams_awards:runes", {
    title = S("Runecrafter"),
    description = S("Craft every type of magical rune."),
    difficulty = 1,
    icon = "magic_materials_enchanted_rune.png",
    _yams_sort_key = 33,
})

local runes = {
    "magic_materials:enchanted_rune",
    "magic_materials:fire_rune",
    "magic_materials:ice_rune",
    "magic_materials:earth_rune",
    "magic_materials:storm_rune",
    "magic_materials:energy_rune",
    "magic_materials:light_rune",
    "magic_materials:void_rune",
}

local tmp = awards.registered_awards["yams_awards:runes"]
tmp.get_progress = function(_, data)
    local count = 0
    local d = data["craft"]

    if d then  -- In case the player has not crafted anything yet
        for i, rune in ipairs(runes) do
            if d[rune] then
                count = count + 1
            end
        end
    end

    return {
        current = count,
        target = #runes,
        label = S("@1/@2 crafted", count, #runes)
    }
end

local function handle_crafting_awards(itemstack, player, _, __)
    local name = player:get_player_name()
    local itemname = itemstack:get_name()

    local data = awards.player(name)
    local d = data["craft"]
    -- This should not be nil since the callback within the 'awards' mod is
    -- executed first
    assert(d ~= nil)

    if d["default:chest"] and d["default:furnace"] and d["default:torch"] and
            (d["beds:bed_bottom"] or d["beds:fancy_bed_bottom"]) then
        awards.unlock(name, "yams_awards:home_essentials")
    end

    if d["anvil:anvil"] and d["anvil:hammer"] then
        awards.unlock(name, "yams_awards:anvil_and_hammer")
    end

    if string.find(itemname, "magic_materials:[%w_]+_rune") then
        local all_runes = true
        for i, rune in ipairs(runes) do
            if not d[rune] then
                all_runes = false
                break
            end
        end

        if all_runes then
            awards.unlock(name, "yams_awards:runes")
        end
    end

    local def = itemstack:get_definition()
    local t = get_yams_awards_data(player)

    if def and def.groups then
        if def.groups.sword then
            t.sword = true
        elseif def.groups.axe then
            t.axe = true
        elseif def.groups.pickaxe then
            t.pickaxe = true
        elseif def.groups.shovel then
            t.shovel = true
        elseif def.groups.hoe then
            t.hoe = true
        end
    end

    save_yams_awards_data(player, t)

    if t.sword and t.axe and t.pickaxe and t.shovel and t.hoe then
        awards.unlock(name, "yams_awards:basic_tools")
    end
end

core.register_on_craft(handle_crafting_awards)

local sync_item_discovery_award = nil  -- So that it is in scope elsewhere
if core.get_modpath("craft_lookup") and craft_lookup.discovery and
        craft_lookup.recommends then
    local cl = craft_lookup
    local recommended_items_count = 0
    for _, item in pairs(cl.recommends) do
        recommended_items_count = recommended_items_count + 1
    end

    awards.register_award("yams_awards:item_discovery", {
        title = S("Knowledge is Power"),
        description = S("Discover every recommended item in the game."),
        difficulty = 1,
        icon = "awards_ui_icon.png",  -- YAMS-TODO: placeholder
        _yams_sort_key = 1000,
    })

    local tmp = awards.registered_awards["yams_awards:item_discovery"]
    tmp.get_progress = function(_, data)
        local player = get_owner_of_data(data)
        local count = 0

        if player then
            local t = get_yams_awards_data(player)
            count = t.discovered_items
        end

        return {
            current = count,
            target = recommended_items_count,
            label = S("@1/@2 items", count, recommended_items_count)
        }
    end

    local old_discovery_func = cl.discovery.discover
    cl.discovery.discover = function(playername, itemname)
        -- discovery.discover is called regardless of whether the item is
        -- already known, so in order to know when a new item is actually
        -- discovered, check whether the item was known already; if not, this
        -- is a new item
        if not cl.discovery.knows(playername, itemname) and
                cl.recommends[itemname] then
            local player = core.get_player_by_name(playername)
            local t = get_yams_awards_data(player)

            -- Should never be nil due to the login code
            assert(t.discovered_items ~= nil)
            t.discovered_items = t.discovered_items + 1
            save_yams_awards_data(player, t)

            if t.discovered_items == recommended_items_count then
                awards.unlock(pname, "yams_awards:item_discovery")
            end
        end

        old_discovery_func(playername, itemname)
    end

    -- In case new recommended items are added in the future or if there was
    -- a bug
    sync_item_discovery_award = function(player)
        local pname = player:get_player_name()
        local database = cl.discovery.get_database()
        local data = database.players[pname]
        assert(data ~= nil)

        local count = 0
        for itemname, _ in pairs(data.items) do
            if cl.recommends[itemname] then
                count = count + 1
            end
        end

        local t = get_yams_awards_data(player)
        t.discovered_items = count
        save_yams_awards_data(player, t)

        -- Just in case the player did not get the award somehow
        core.after(1.0, function()
            -- In case player logged out
            if core.get_player_by_name(pname) == nil then
                return
            end

            if t.discovered_items == recommended_items_count then
                awards.unlock(pname, "yams_awards:item_discovery")
            end
        end)
    end

    core.register_on_joinplayer(sync_item_discovery_award)
end

awards.register_award("yams_awards:food_connoisseur", {
    title = S("Food Connoisseur"),
    description = S("Eat 40 different types of food items."),
    difficulty = 1,
    icon = "farming_salad.png",
    _yams_sort_key = 51,
})

local tmp = awards.registered_awards["yams_awards:food_connoisseur"]
tmp.get_progress = function(_, data)
    local player = get_owner_of_data(data)
    local count = 0

    if player then
        local t = get_yams_awards_data(player)
        count = t.foods or 0
    end

    return {
        current = count,
        target = 40,
        label = S("@1/@2 eaten", count, 40)
    }
end

local function check_food_award(hp_change, rwi, itemstack, user, pt)
    -- The 'stamina' mod has this check, so why not I guess
    if not yams.is_player(user) or not itemstack then
        return
    end

    -- The player must consume the item to get credit for the award
    local force_eat = user:get_player_control().sneak
    local level = stamina.get_saturation(user) or 0
    if level >= 30 and hp_change > 0 and not force_eat then
        return
    end

    local pname = user:get_player_name()
    local data = awards.player(pname)

    -- The callback in the 'awards' mod should have executed first so this
    -- should not be nil
    assert(data["eat"])

    local t = get_yams_awards_data(user)
    t.foods = t.foods or 0

    -- Only increment if it is the first time the player ate the item
    if data["eat"][itemstack:get_name()] == 1 then
        t.foods = t.foods + 1
        save_yams_awards_data(user, t)
    end

    if t.foods >= 40 then
        awards.unlock(pname, "yams_awards:food_connoisseur")
    end
end

core.register_on_item_eat(check_food_award)

local function sync_food_award(player)
    local pname = player:get_player_name()
    local data = awards.player(pname)
    local count = 0

    if data and data["eat"] then
        for item, _ in pairs(data["eat"]) do
            count = count + 1
        end

        -- Subtract the __total entry
        if count > 0 then
            count = count - 1
        end
    end

    local t = get_yams_awards_data(player)
    t.foods = count
    save_yams_awards_data(player, t)

    -- Unlike the award for discovering all of the recommended items, we do not
    -- check for the award here because it is checked every time a player eats
    -- an item anyway; this avoids issues with core.after() and player validity
end

core.register_on_joinplayer(sync_food_award)

awards.register_award("yams_awards:fast_food_combo", {
    title = S("Fast Food Combo"),
    description = S("Eat a burger and fries within 10 seconds of each other."),
    difficulty = 1,
    icon = "farming_burger.png",
    _yams_sort_key = 50,
})

-- Reset this award on logout to avoid inconsistent states
local function reset_food_combo_award(player)
    local t = get_yams_awards_data(player)

    t.fries = 0
    t.burger = 0

    save_yams_awards_data(player, t)
end

core.register_on_leaveplayer(reset_food_combo_award)

local function check_food_combo_award(hp_change, rwi, itemstack, user, pt)
    -- The 'stamina' mod has this check, so why not I guess
    if not yams.is_player(user) or not itemstack then
        return
    end

    -- The player must consume the item to get credit for the award
    local force_eat = user:get_player_control().sneak
    local level = stamina.get_saturation(user) or 0
    if level >= 30 and hp_change > 0 and not force_eat then
        return
    end

    local item_name = itemstack:get_name()
    if item_name ~= "farming:burger" and item_name ~= "yams_farming:fries" then
        return
    end

    local pname = user:get_player_name()
    local t = get_yams_awards_data(user)

    local function award_timeout(item)
        -- The player may have left
        local player = core.get_player_by_name(pname)
        if not player then
            return
        end

        local t = get_yams_awards_data(user)
        if t[item] then  -- Awards could have been cleared
            t[item] = t[item] - 1
        end
    end

    if item_name == "farming:burger" then
        -- We use an integer so that the most recent burger is taken into
        -- account in case the player eats multiple during that time period
        t.burger = t.burger and t.burger + 1 or 1
        core.after(10.0, award_timeout, "burger")
    elseif item_name == "yams_farming:fries" then
        t.fries = t.fries and t.fries + 1 or 1
        core.after(10.0, award_timeout, "fries")
    else
        assert(false)
    end

    save_yams_awards_data(user, t)

    if (t.burger and t.burger > 0) and (t.fries and t.fries > 0) then
        awards.unlock(pname, "yams_awards:fast_food_combo")
    end
end

core.register_on_item_eat(check_food_combo_award)

if core.get_modpath("sickles") then
    awards.register_award("yams_awards:scythe_usage", {
        title = S("Reaping the Rewards"),
        description = S("Harvest 100 crops using a scythe."),
        difficulty = 1,
        icon = "sickles_scythe_bronze.png",
        _yams_sort_key = 40,
    })

    local tmp = awards.registered_awards["yams_awards:scythe_usage"]
    tmp.get_progress = function(_, data)
        local player = get_owner_of_data(data)
        if player then
            local t = get_yams_awards_data(player)
            local count = t.scythe_uses or 0

            return {
                current = count,
                target = 100,
                label = S("@1/@2 crops", count, 100)
            }
        end
    end

    local scythes = {
        "sickles:scythe_bronze",
        "sickles:scythe_steel",
    }

    for _, scythe in pairs(scythes) do
        local def = core.registered_tools[scythe]
        assert(def ~= nil)

        local old_on_place = def.on_place
        core.override_item(scythe, {
            on_place = function(itemstack, user, pointed_thing)
                -- Compare the wear before and after to figure out how many
                -- crops were harvested
                local before = itemstack:get_wear()
                local res = old_on_place(itemstack, user, pointed_thing)
                local after = itemstack:get_wear()

                if after ~= before then
                    -- If the current wear is now 0, that means that the tool
                    -- broke so handle this special case
                    if after == 0 then
                        after = 65536
                    end

                    local scythe_uses = def.groups.scythe_uses or 30
                    local crops = 0

                    for i = 1, 9 do
                        -- Same formula used in sickles.use_scythe
                        local val = math.ceil((65535 / scythe_uses) * i)
                        if val >= after - before then  -- Wear goes up, not down
                            crops = i
                            break
                        end
                    end

                    local t = get_yams_awards_data(user)
                    if t.scythe_uses then
                        t.scythe_uses = t.scythe_uses + crops
                    else
                        t.scythe_uses = crops
                    end

                    save_yams_awards_data(user, t)

                    if t.scythe_uses >= 100 then
                        local pname = user:get_player_name()
                        awards.unlock(pname, "yams_awards:scythe_usage")
                    end
                end

                return res
            end
        })
    end
end

if core.get_modpath("bonemeal") then
    awards.register_award("yams_awards:bonemeal", {
        title = S("Instant Growth"),
        description = S("Use mulch, bonemeal, or fertiliser on crops 100 times."),
        difficulty = 1,
        icon = "bonemeal_item.png",
        _yams_sort_key = 41,
    })

    local tmp = awards.registered_awards["yams_awards:bonemeal"]
    tmp.get_progress = function(_, data)
        local player = get_owner_of_data(data)
        if player then
            local t = get_yams_awards_data(player)
            local count = t.bonemeal_uses or 0

            return {
                current = count,
                target = 100,
                label = S("@1/@2 used", count, 100)
            }
        end
    end

    local bonemeal_items = {
        "bonemeal:mulch",
        "bonemeal:bonemeal",
        "bonemeal:fertiliser",
    }

    for _, item in pairs(bonemeal_items) do
        local def = core.registered_items[item]
        assert(def ~= nil)

        local old_on_use = def.on_use
        core.override_item(item, {
            on_use = function(itemstack, user, pointed_thing)
                -- Compare the node before and after the item is used to see
                -- if an item was actually consumed; only crops count too
                local before = core.get_node(pointed_thing.under)
                local ndef = core.registered_nodes[before.name]

                local is_crop = false
                if ndef.groups and ndef.groups.growing then
                    is_crop = true
                end

                local res = old_on_use(itemstack, user, pointed_thing)

                local after = core.get_node(pointed_thing.under)
                if is_crop and before.name ~= after.name then
                    local t = get_yams_awards_data(user)
                    if t.bonemeal_uses then
                        t.bonemeal_uses = t.bonemeal_uses + 1
                    else
                        t.bonemeal_uses = 1
                    end

                    save_yams_awards_data(user, t)

                    if t.bonemeal_uses >= 100 then
                        local pname = user:get_player_name()
                        awards.unlock(pname, "yams_awards:bonemeal")
                    end
                end

                return res
            end,
        })
    end
end

awards.register_award("yams_awards:deep_cave", {
    title = S("Deep Into the Caverns"),
    description = S("Within the caves, reach a depth of 2000 meters."),
    difficulty = 0.5,
    icon = "default_tool_steelpick.png",
    _yams_sort_key = 130,
})

awards.register_award("yams_awards:deeper_cave", {
    title = S("Deeper Into the Caverns"),
    description = S("Within the caves, reach a depth of 10000 meters."),
    requires = {"yams_awards:deep_cave"},
    difficulty = 2,
    icon = "default_tool_diamondpick.png",
    _yams_sort_key = 131,
})

awards.register_award("yams_awards:deepest_cave", {
    title = S("Deepest Reaches"),
    description = S("Reach the realm located at the bottom of the caves."),
    requires = {"yams_awards:deep_cave", "yams_awards:deeper_cave"},
    difficulty = 5,
    icon = "moreores_tool_mithrilpick.png",
    _yams_sort_key = 132,
})

awards.register_award("yams_awards:nether", {
    title = S("Into the Fiery Depths"),
    description = S("Enter the Nether."),
    difficulty = 1,
    icon = "awards_lava_miner.png",
    _yams_sort_key = 133,
})

awards.register_award("yams_awards:cloudlands", {
    title = S("Among the Clouds"),
    description = S("Reach the Cloudlands."),
    difficulty = 1.5,
    icon = "awards_long_ladder.png",
    _yams_sort_key = 134,
})

local function y_pos_award_check(dtime)
    for _, player in pairs(core.get_connected_players()) do
        local y = player:get_pos().y
        local name = player:get_player_name()
        if y < -2000 and y > -20000 then
            awards.unlock(name, "yams_awards:deep_cave")

            if y < -10000 then
                awards.unlock(name, "yams_awards:deeper_cave")
            end
            if y < -18000 then
                awards.unlock(name, "yams_awards:deepest_cave")
            end
        elseif y < -20000 then
            awards.unlock(name, "yams_awards:nether")
        elseif y > 1000 then
            awards.unlock(name, "yams_awards:cloudlands")
        end
    end
end

core.register_globalstep(y_pos_award_check)

local storage = core.get_mod_storage()
local found_chests = nil

if storage:contains("found_chests") then
    found_chests = core.deserialize(storage:get("found_chests"))
else
    -- We keep track of this data ourselves instead of letting the 'awards'
    -- mod do it because progress towards these awards is shared by everyone
    -- in multiplayer servers
    found_chests = {}
    found_chests["dungeon"] = {chests = {}, count = 0}
    found_chests["tsm_pyramids"] = {chests = {}, count = 0}
    found_chests["tsm_railcorridors"] = {chests = {}, count = 0}
    found_chests["shipwrecks"] = {chests = {}, count = 0}
end

awards.register_trigger("loot", {
    type = "custom",
    -- YAMS-TODO: figure out how translation actually works here
    progress = "@1/@2 chests",
    auto_description = {"Loot a chest.", "Loot @1 chests."},

    on_register = function(self, award)
        award.get_progress = function(_, data)
            assert(found_chests[award.trigger.key] ~= nil)

            local cur = found_chests[award.trigger.key].count
            local goal = award.trigger.target
            local res = {
                current = cur,
                target = goal,
                label = S("@1/@2 chests", cur, goal)
            }
            return res
        end
    end,
})

local mp_loot_desc = ""
if not core.is_singleplayer() then
    mp_loot_desc = "\n" .. S("This award's progress is shared by all players.")
end

awards.register_award("yams_awards:dungeon_loot", {
    title = S("Dungeon Diver"),
    description = S("Loot 20 unique chests within dungeons.") .. mp_loot_desc,
    difficulty = 5,
    icon = "awards_treasurer.png",
    trigger = {
        type = "loot",
        key = "dungeon",
        target = 20,
    },
    _yams_sort_key = 120,
})

awards.register_award("yams_awards:pyramid_loot", {
    title = S("Pyramid Raider"),
    description = S("Loot 10 unique chests within pyramids.") .. mp_loot_desc,
    difficulty = 5,
    icon = "awards_pharaoh.png",
    trigger = {
        type = "loot",
        key = "tsm_pyramids",
        target = 10,
    },
    _yams_sort_key = 121,
})

awards.register_award("yams_awards:rail_corridor_loot", {
    title = S("The Abandoned Mines"),
    description = S("Loot 10 unique chests within rail corridors.") .. mp_loot_desc,
    difficulty = 5,
    icon = "awards_treasurer.png",
    trigger = {
        type = "loot",
        key = "tsm_railcorridors",
        target = 10,
    },
    _yams_sort_key = 123,
})

awards.register_award("yams_awards:shipwreck_loot", {
    title = S("Shipwreck Salvager"),
    description = S("Loot 10 unique chests within shipwrecks.") .. mp_loot_desc,
    difficulty = 5,
    icon = "awards_treasurer.png",
    trigger = {
        type = "loot",
        key = "shipwrecks",
        target = 10,
    },
    _yams_sort_key = 122,
})

local function check_loot_awards()
    if found_chests["dungeon"].count >= 20 then
        for _, player in ipairs(core.get_connected_players()) do
            local name = player:get_player_name()
            awards.unlock(name, "yams_awards:dungeon_loot")
        end
    end
    if found_chests["tsm_pyramids"].count >= 10 then
        for _, player in ipairs(core.get_connected_players()) do
            local name = player:get_player_name()
            awards.unlock(name, "yams_awards:pyramid_loot")
        end
    end
    if found_chests["tsm_railcorridors"].count >= 10 then
        for _, player in ipairs(core.get_connected_players()) do
            local name = player:get_player_name()
            awards.unlock(name, "yams_awards:rail_corridor_loot")
        end
    end
    if found_chests["shipwrecks"].count >= 10 then
        for _, player in ipairs(core.get_connected_players()) do
            local name = player:get_player_name()
            awards.unlock(name, "yams_awards:shipwreck_loot")
        end
    end

    storage:set_string("found_chests", core.serialize(found_chests))
end

local function record_opened_chest(pos)
    local meta = core.get_meta(pos)
    local chest_type = meta:get_string("yams_loot_chest_type")
    if chest_type ~= "" then
        local pos_num = core.hash_node_position(pos)
        if not found_chests[chest_type].chests[pos_num] then
            local cur = found_chests[chest_type].count
            found_chests[chest_type].count = cur + 1

            found_chests[chest_type].chests[pos_num] = true
        end

        check_loot_awards()
    end
end

local dungeon_chests = {"default:chest"}  -- Also used in pyramids

if core.registered_nodes["stone_chests:stone_chest"] then
    table.insert(dungeon_chests, "stone_chests:stone_chest")
end

if core.registered_nodes["stone_chests:desert_stone_chest"] then
    table.insert(dungeon_chests, "stone_chests:desert_stone_chest")
end

for _, chest_name in pairs(dungeon_chests) do
    local old_chest_func = core.registered_nodes[chest_name].on_rightclick

    core.override_item(chest_name, {
        on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
            record_opened_chest(pos)

            return old_chest_func(pos, node, clicker, itemstack, pointed_thing)
        end
    })
end

-- Chest nodes used in shipwrecks; this assumes that these nodes do not appear
-- anywhere else, which is the case in yams
local shipwreck_chests = {}

if core.get_modpath("lootchests_default") then
    table.insert(shipwreck_chests, "lootchests_default:ocean_chest")
    table.insert(shipwreck_chests, "lootchests_default:barrel")
end

for _, name in pairs(shipwreck_chests) do
    core.override_item(name, {
        on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
            record_opened_chest(pos)

            -- Then perform the default behavior; apparently the formspec gets
            -- shown even without this code, which does not match what the API
            -- docs say
            local form = core.get_meta(pos):get_string("formspec")
            core.show_formspec(clicker:get_player_name(), name, form)

            return itemstack
        end
    })
end

local mp_mob_desc = ""
if not core.is_singleplayer() then
    mp_mob_desc = "\n" .. S("Multiple players may gain credit for the same " ..
                  "monster as long as they obtained EXP from its defeat.")
end

-- Ugly hack so that the mod dependency tree is less fragile overall
if mobs.global_after_exp then
    awards.register_trigger("monster_defeat", {
        type = "counted_key",
        -- YAMS-TODO: figure out how translation actually works here
        progress = "@1/@2 monsters",
        auto_description = {"Defeat a @2.", "Defeat @1 @2."},
        auto_description_total = {"Defeat @1 monster.", "Defeat @1 monsters."},
        get_key = function(self, def)
            return def.trigger.mob
        end,
        key_is_item = false,
    })

    awards.register_award("yams_awards:a_true_warrior", {
        title = S("A True Warrior"),
        description = S("Defeat 1000 monsters.") .. mp_mob_desc,
        difficulty = 1,
        icon = "default_tool_diamondsword.png",
        trigger = {
            type = "monster_defeat",
            target = 1000,
        },
        _yams_sort_key = 87,
    })

    awards.register_award("yams_awards:land_guard", {
        title = S("Guard Broken!"),
        description = S("Defeat a land guard.") .. mp_mob_desc,
        difficulty = 1,
        icon = "default_tool_bronzesword.png",
        trigger = {
            type = "monster_defeat",
            mob = "mobs_monster:land_guard",
            target = 1,
        },
        _yams_sort_key = 84,
    })

    awards.register_award("yams_awards:dungeon_master", {
        title = S("Dungeon Mastered!"),
        description = S("Defeat a dungeon master.") .. mp_mob_desc,
        difficulty = 1,
        icon = "default_tool_bronzesword.png",
        trigger = {
            type = "monster_defeat",
            mob = "mobs_monster:dungeon_master",
            target = 1,
        },
        _yams_sort_key = 85,
    })

    -- The below awards are handled manually
    awards.register_award("yams_awards:slingshot_combat", {
        title = S("Sticks and Stones"),
        description = S("Deal the final blow to 10 monsters with a slingshot."),
        difficulty = 1,
        icon = "yams_slingshot.png",
        _yams_sort_key = 80,
    })

    local tmp = awards.registered_awards["yams_awards:slingshot_combat"]
    tmp.get_progress = function(_, data)
        local player = get_owner_of_data(data)
        local t = get_yams_awards_data(player)
        local count = t.slingshot or 0

        return {
            current = count,
            target = 10,
            label = S("@1/@2 monsters", count, 10)
        }
    end

    awards.register_award("yams_awards:wand_combat", {
        title = S("Neophyte Wizard"),
        description = S("Deal the final blow to 10 monsters with a magic wand."),
        difficulty = 1,
        icon = "yams_wand.png",
        _yams_sort_key = 81,
    })

    local tmp = awards.registered_awards["yams_awards:wand_combat"]
    tmp.get_progress = function(_, data)
        local player = get_owner_of_data(data)
        local t = get_yams_awards_data(player)
        local count = t.wand or 0

        return {
            current = count,
            target = 10,
            label = S("@1/@2 monsters", count, 10)
        }
    end

    awards.register_award("yams_awards:spear_kill", {
        title = S("Speared!"),
        description = S("Use a thrown spear to slay a mese monster."),
        difficulty = 1,
        icon = "spears_spear_bronze.png",
        _yams_sort_key = 82,
    })

    local tmp = awards.registered_awards["yams_awards:spear_kill"]
    tmp.get_progress = function(_, data)
        -- Only one is required, so just hardcode it
        return {
            current = 0,
            target = 1,
            label = S("@1/@2 monsters", 0, 1)
        }
    end

    awards.register_award("yams_awards:naval_combat", {
        title = S("Naval Combat"),
        description = S("Deal the final blow to 10 monsters while on a boat."),
        difficulty = 1,
        icon = "boats_inventory.png",
        _yams_sort_key = 83,
    })

    local tmp = awards.registered_awards["yams_awards:naval_combat"]
    tmp.get_progress = function(_, data)
        local player = get_owner_of_data(data)
        local t = get_yams_awards_data(player)
        local count = t.naval or 0

        return {
            current = count,
            target = 10,
            label = S("@1/@2 monsters", count, 10)
        }
    end

    awards.register_award("yams_awards:defused", {
        title = S("Defused!"),
        description = S("Defeat 10 explosion masters without making " ..
                        "them explode.") .. mp_mob_desc,
        difficulty = 1,
        icon = "default_tool_diamondsword.png",
        _yams_sort_key = 86,
    })

    local tmp = awards.registered_awards["yams_awards:defused"]
    tmp.get_progress = function(_, data)
        local player = get_owner_of_data(data)
        local t = get_yams_awards_data(player)
        local count = t.defused or 0

        return {
            current = count,
            target = 10,
            label = S("@1/@2 monsters", count, 10)
        }
    end

    -- The player is not necessarily the one that dealt the final blow, as EXP
    -- is awarded to all participants as long as they are nearby at the time of
    -- the mob's death
    -- First parameter is the 'mobs' table but we do not use it here
    function yams.notify_mob_death(_, player, mob)
        assert(yams.is_player(player), "not an actual player")
        assert(mob ~= nil, "mob cannot be nil")
        assert(mob.name ~= nil, "mob is not a Lua entity")

        local name = player:get_player_name()

        if mob.type == "monster" then
            awards.notify_monster_defeat(player, mob.name)

            if mob.cause_of_death and mob.cause_of_death.type == "punch" and
                mob.cause_of_death.puncher == player then
                -- YAMS-TODO: technically you can fire an arrow or orb and then
                -- quickly get on a boat and it will count, but whatever
                local attach = player:get_attach()
                if attach and attach:get_luaentity().name == "boats:boat" then
                    local t = get_yams_awards_data(player)

                    t.naval = t.naval and t.naval + 1 or 1
                    save_yams_awards_data(player, t)

                    if t.naval >= 10 then
                        awards.unlock(name, "yams_awards:naval_combat")
                    end
                end

                local proj = mob.cause_of_death.projectile
                if proj and proj == "yams_weapons:slingshot_projectile" then
                    local t = get_yams_awards_data(player)

                    t.slingshot = t.slingshot and t.slingshot + 1 or 1
                    save_yams_awards_data(player, t)

                    if t.slingshot >= 10 then
                        awards.unlock(name, "yams_awards:slingshot_combat")
                    end
                elseif proj and proj == "yams_weapons:magic_wand_projectile" then
                    local t = get_yams_awards_data(player)

                    t.wand = t.wand and t.wand + 1 or 1
                    save_yams_awards_data(player, t)

                    if t.wand >= 10 then
                        awards.unlock(name, "yams_awards:wand_combat")
                    end
                elseif proj and string.find(proj, "spears:spear_") and
                        mob.name == "mobs_monster:mese_monster" then
                    awards.unlock(name, "yams_awards:spear_kill")
                end
            end
        end

        if mob.name == "yams_mobs:explosion_master" and mob.cause_of_death and
                mob.cause_of_death.type then
            local defused = mob.cause_of_death.type ~= "exploded"

            if defused then
                local t = get_yams_awards_data(player)

                t.defused = t.defused and t.defused + 1 or 1
                save_yams_awards_data(player, t)

                if t.defused >= 10 then
                    awards.unlock(name, "yams_awards:defused")
                end
            end
        end
    end

    mobs.global_after_exp = yams.notify_mob_death
end

if core.get_modpath("nyancat") then
    -- Found a Nyan cat!
    awards.register_award("award_nyanfind", {
        secret = true,
        title = S("A Cat in a Pop-Tart?!"),
        description = S("Mine a nyan cat."),
        icon = "awards_a_cat_in_a_pop_tart.png",
        trigger = {
            type = "dig",
            node = "nyancat:nyancat",
            target = 1
        },
        _yams_sort_key = 10000,
    })
end

awards.register_award("yams_awards:level_one", {
    title = S("Beginner"),
    description = S("Reach level 1."),
    difficulty = 1,
    icon = "awards_ui_icon.png", -- YAMS-TODO: placeholder
    _yams_sort_key = 1,
})

awards.register_award("yams_awards:level_five", {
    title = S("Rookie"),
    description = S("Reach level 5."),
    requires = {"yams_awards:level_one"},
    difficulty = 1,
    icon = "awards_ui_icon.png",
    _yams_sort_key = 2,
})

awards.register_award("yams_awards:level_ten", {
    title = S("Amateur"),
    description = S("Reach level 10."),
    requires = {"yams_awards:level_five"},
    difficulty = 1,
    icon = "awards_ui_icon.png",
    _yams_sort_key = 3,
})

awards.register_award("yams_awards:level_twenty", {
    title = S("Veteran"),
    description = S("Reach level 20."),
    requires = {"yams_awards:level_ten"},
    difficulty = 1,
    icon = "awards_ui_icon.png",
    _yams_sort_key = 4,
})

awards.register_award("yams_awards:level_thirty", {
    title = S("Expert"),
    description = S("Reach level 30."),
    requires = {"yams_awards:level_twenty"},
    difficulty = 1,
    icon = "awards_ui_icon.png",
    _yams_sort_key = 5,
})

awards.register_award("yams_awards:level_forty", {
    title = S("Master"),
    description = S("Reach level 40."),
    requires = {"yams_awards:level_thirty"},
    difficulty = 1,
    icon = "awards_ui_icon.png",
    _yams_sort_key = 6,
})

awards.register_award("yams_awards:level_fifty", {
    title = S("Grand Master"),
    description = S("Reach level 50."),
    requires = {"yams_awards:level_forty"},
    difficulty = 1,
    icon = "awards_ui_icon.png",
    _yams_sort_key = 7,
})

local level_award_mapping = {
    ["yams_awards:level_one"] = 1,
    ["yams_awards:level_five"] = 5,
    ["yams_awards:level_ten"] = 10,
    ["yams_awards:level_twenty"] = 20,
    ["yams_awards:level_thirty"] = 30,
    ["yams_awards:level_forty"] = 40,
    ["yams_awards:level_fifty"] = 50,
}

for key, goal in pairs(level_award_mapping) do
    local award = awards.registered_awards[key]

    award.get_progress = function(_, data)
        local player = get_owner_of_data(data)

        if player then
            local meta = player:get_meta()
            local cur_level = meta:get_int("yams_rpg:level")

            local res = {
                current = cur_level,
                target = goal,
                label = S("@1/@2 levels", cur_level, goal)
            }

            return res
        end
    end
end

local function check_level_up_awards(player, new_level)
    local name = player:get_player_name()

    for key, goal in pairs(level_award_mapping) do
        if new_level >= goal then
            awards.unlock(name, key)
        end
    end
end

yams.register_on_levelup(check_level_up_awards)

local function get_bag_counts(player)
    local meta = player:get_meta()
    local bags_data = core.deserialize(meta:get_string("bags:bags"))
    local bags_count = 0
    local trolleys = 0

    if bags_data then
        for i = 1, 4 do
            if bags_data[i] then
                bags_count = bags_count + 1
                if bags_data[i] == "bags:trolley" then
                    trolleys = trolleys + 1
                end
            end
        end
    end

    return {bags = bags_count, trolleys = trolleys}
end

awards.register_award("yams_awards:four_bags", {
    title = S("Excess Baggage"),
    description = S("Equip four bags of any size at the same time."),
    difficulty = 1,
    icon = "bags_small.png",
    _yams_sort_key = 70,
})

local tmp = awards.registered_awards["yams_awards:four_bags"]
tmp.get_progress = function(_, data)
    local player = get_owner_of_data(data)
    if player then
        local count = get_bag_counts(player).bags

        local res = {
            current = count,
            target = 4,
            label = S("@1/@2 bags", count, 4)
        }

        return res
    end
end

awards.register_award("yams_awards:four_trolleys", {
    title = S("Pocket Wormhole"),
    description = S("Equip four Trolleys of Holding at the same time."),
    requires = {"yams_awards:four_bags"},
    difficulty = 1,
    icon = "bags_trolley.png",
    _yams_sort_key = 71,
})

local tmp = awards.registered_awards["yams_awards:four_trolleys"]
tmp.get_progress = function(_, data)
    local player = get_owner_of_data(data)
    if player then
        local count = get_bag_counts(player).trolleys

        local res = {
            current = count,
            target = 4,
            label = S("@1/@2 trolleys", count, 4)
        }

        return res
    end
end

local function bag_award_check(dtime)
    for _, player in pairs(core.get_connected_players()) do
        local name = player:get_player_name()
        local res = get_bag_counts(player)

        if res.bags == 4 then
            awards.unlock(name, "yams_awards:four_bags")
        end
        if res.trolleys == 4 then
            awards.unlock(name, "yams_awards:four_trolleys")
        end
    end
end

core.register_globalstep(bag_award_check)

if core.get_modpath("3d_armor") then
    local function count_equipped_armor(player)
        local equipped = armor:get_weared_armor_elements(player)
        local count = 0
        for key, item in pairs(equipped) do
            count = count + 1
        end

        return count
    end

    awards.register_award("yams_awards:armor_set", {
        title = S("Geared Up!"),
        description = S("Wear five pieces of armor at the same time."),
        difficulty = 1,
        icon = "3d_armor_inv_chestplate_wood.png",
        _yams_sort_key = 37,
    })

    local tmp = awards.registered_awards["yams_awards:armor_set"]
    tmp.get_progress = function(_, data)
        local player = get_owner_of_data(data)
        if player then
            local count = count_equipped_armor(player)

            return {
                current = count,
                target = 5,
                label = S("@1/@2 pieces", count, 5)
            }
        end
    end

    local function armor_award_check(player, index, stack)
        if count_equipped_armor(player) == 5 then
            awards.unlock(player:get_player_name(), "yams_awards:armor_set")
        end
    end

    armor:register_on_equip(armor_award_check)
end

if core.get_modpath("wooden_bucket") then
    awards.register_award("yams_awards:water_bucket", {
        title = S("Making Buckets"),
        description = S("Fill a wooden bucket with water."),
        difficulty = 1,
        icon = "wooden_bucket.png",
        _yams_sort_key = 38,
    })

    assert(core.registered_items["wooden_bucket:bucket_wood_empty"])

    local def = core.registered_items["wooden_bucket:bucket_wood_empty"]
    local old_on_use = def.on_use

    core.override_item("wooden_bucket:bucket_wood_empty", {
        on_use = function(itemstack, user, pointed_thing)
            -- Mimic some of the bucket's logic and check if the player is
            -- pointing at a water source first
            -- It's easier than checking the resulting ItemStack because
            -- there would be multiple cases to account for, such as the
            -- player holding a stack of wooden buckets, and I did not
            -- want to deal with that
            local getting_liquid = false
            if pointed_thing.type == "node" then
                local node = core.get_node(pointed_thing.under)
                if node.name == "default:water_source" or
                        node.name == "default:river_water_source" then
                    getting_liquid = true
                end
            end

            local res = old_on_use(itemstack, user, pointed_thing)
            -- res can be nil if the node is protected
            if getting_liquid and res and yams.is_player(user) then
                local pname = user:get_player_name()
                awards.unlock(pname, "yams_awards:water_bucket")
            end

            -- Return the result unchanged so that the bucket works normally
            return res
        end
    })
end

awards.register_award("yams_awards:glider", {
    title = S("I Can Fly!"),
    description = S("Glide in the air for one minute without landing or " ..
                    "taking off the glider."),
    difficulty = 1,
    icon = "hangglider_item.png",
    _yams_sort_key = 39,
})

local function glider_award_check(dtime)
    for _, player in pairs(core.get_connected_players()) do
        local gliding = false

        for _, child in pairs(player:get_children()) do
            if child and child:get_luaentity() and
                    child:get_luaentity().name == "hangglider:glider" then
                gliding = true

                local meta = player:get_meta()
                local time = meta:get_float("yams_awards:glider_time")  -- or 0
                time = time + dtime

                if time >= 60.0 then
                    local pname = player:get_player_name()
                    awards.unlock(pname, "yams_awards:glider")
                end

                meta:set_float("yams_awards:glider_time", time)
            end
        end

        if not gliding then
            local meta = player:get_meta()
            meta:set_float("yams_awards:glider_time", 0.0)
        end
    end
end

core.register_globalstep(glider_award_check)

awards.register_award("yams_awards:doc_viewed", {
    title = S("The Friendly Manual"),
    description = S("Read any help page."),
    difficulty = 1,
    icon = "doc_button_icon_hires.png",
    _yams_sort_key = 999,
})

local function doc_award_check(dtime)
    for _, player in pairs(core.get_connected_players()) do
        local pname = player:get_player_name()
        local unused, page = doc.get_selection(pname)

        if page ~= nil then
            awards.unlock(pname, "yams_awards:doc_viewed")
        end
    end
end

core.register_globalstep(doc_award_check)

-- Append awards.clear_player to also clear our own award data
base_awards_clear = awards.clear_player
awards.clear_player = function(name)
    base_awards_clear(name)

    local player = core.get_player_by_name(name)
    if player then
        save_yams_awards_data(player, {})

        -- Prevent crashes when accessing the Awards tab; just setting the
        -- value to 0 would be insufficient
        if sync_item_discovery_award then
            sync_item_discovery_award(player)
        end
    end
end

-- The way that the awards are sorted by default is undesirable so we have to
-- write our own implementation

if core.get_modpath("orienteering") then  -- Set its sort key
    local def = awards.registered_awards["orienteering_quadcorder"]
    def._yams_sort_key = 34
end

awards.get_award_states = function(name)
    local data = awards.player(name)
    local retval = {}

    for _, def in pairs(awards.registered_awards) do
        if def:can_unlock(data) then
            local unlocked = data.unlocked[def.name] and true or false
            local progress = nil
            -- Unlocked awards do not show their progress bar
            if not unlocked then
                progress = def.get_progress and def:get_progress(data)
            end

            retval[#retval + 1] = {
                name = def.name,
                def = def,
                unlocked = unlocked,
                started = progress and progress.current > 0,
                score = def._yams_sort_key or -1,
                progress = progress
            }
        end
    end

    table.sort(retval, function(a, b)
        return a.score < b.score
    end)

    return retval
end


print("yams_awards loaded")
