--[[-- Commands Module - Inventory Search
    - Adds commands that will search all players inventories for an item
    @commands InventorySearch
]]

local Commands = require 'expcore.commands' --- @dep expcore.commands
local format_number = require('util').format_number --- @dep util
local format_chat_player_name = _C.format_chat_player_name --- @dep expcore.common
local format_time = _C.format_time
require 'config.expcore.command_general_parse'

--- Input parse for items by name
local function item_parse(input, _, reject)
    if input == nil then return end
    local lower_input = input:lower():gsub(' ', '-')

    -- Simple Case - internal name is given
    local item = game.item_prototypes[lower_input]
    if item then return item end

    -- Second Case - rich text is given
    local item_name = input:match('%[item=([0-9a-z-]+)%]')
    item = game.item_prototypes[item_name]
    if item then return item end

    -- No item found, we do not attempt to search all prototypes as this will be expensive
    return reject{'expcom-inv-search.reject-item', lower_input}
end

--- Search all players for this item
local function search_players(players, item)
    local head = 1
    local found = {}

    -- Check the item count of all players
    for _, player in pairs(players) do
        local item_count = player.get_item_count(item.name)
        if item_count > 0 then
            -- Add the player to the array as they have the item
            found[head] = { player=player, count=item_count, online_time=player.online_time }
            head = head + 1
        end
    end

    return found
end

--- Custom sort function which only retains 5 greatest values
local function sort_players(players, func)
    local sorted = {}
    local values = {}
    local threshold = nil

    -- Loop over all provided players
    for index, player in ipairs(players) do
        local value = func(player)
        -- Check if the item will make the top 5 elements
        if index <= 5 or value > threshold then
            local inserted = false
            values[player] = value
            -- Find where in the top 5 to insert the element
            for next_index, next_player in ipairs(sorted) do
                if value > values[next_player] then
                    table.insert(sorted, next_index, player)
                    inserted = true
                    break
                end
            end
            -- Insert the element, this can only be called when index <= 5
            if not inserted then
                sorted[#sorted+1] = player
            end
            -- Update the threshold
            if sorted[6] then
                threshold = values[sorted[5]]
                values[sorted[6]] = nil
                sorted[6] = nil
            else
                threshold = values[sorted[#sorted]]
            end
        end
    end

    return sorted
end

--- Display to the player the top players which were found
local function display_players(player, players, item)
    player.print{'expcom-inv-search.results-heading', item.name}
    for index, data in ipairs(players) do
        local player_name_color = format_chat_player_name(data.player)
        local amount = format_number(data.count)
        local time = format_time(data.online_time)
        player.print{'expcom-inv-search.results-item', index, player_name_color, amount, time}
    end
end

--- Return the amount of an item a player has
local function amount_sort(data)
    return data.count
end

--- Get a list of players sorted by the quantity of an item in their inventory
-- @command search-amount
-- @tparam LuaItemPrototype item The item to search for in players inventories
Commands.new_command('search-amount', 'Display players sorted by the quantity of an item held')
:add_alias('ia')
:add_param('item', false, item_parse)
:enable_auto_concat()
:register(function(player, item)
    local players = search_players(game.players, item)
    if #players == 0 then return {'expcom-inv-search.results-none', item.name} end
    local top_players = sort_players(players, amount_sort)
    display_players(player, top_players, item)
end)

--- Return the index of the player, higher means they joined more recently
local function recent_sort(data)
    return data.player.index
end

--- Get a list of players who have the given item, sorted by how recently they joined
-- @command search-recent
-- @tparam LuaItemPrototype item The item to search for in players inventories
Commands.new_command('search-recent', 'Display players who hold an item sorted by join time')
:add_alias('ir')
:add_param('item', false, item_parse)
:enable_auto_concat()
:register(function(player, item)
    local players = search_players(game.players, item)
    if #players == 0 then return {'expcom-inv-search.results-none', item.name} end
    local top_players = sort_players(players, recent_sort)
    display_players(player, top_players, item)
end)

--- Return the the amount of an item a player has divided by their playtime
local function combined_sort(data)
    return data.count/data.online_time
end

--- Get a list of players sorted by quantity held and play time
-- @command search
-- @tparam LuaItemPrototype item The item to search for in players inventories
Commands.new_command('search', 'Display players sorted by the quantity of an item held and playtime')
:add_alias('i')
:add_param('item', false, item_parse)
:enable_auto_concat()
:register(function(player, item)
    local players = search_players(game.players, item)
    if #players == 0 then return {'expcom-inv-search.results-none', item.name} end
    local top_players = sort_players(players, combined_sort)
    display_players(player, top_players, item)
end)

--- Get a list of online players sorted by quantity held and play time
-- @command search-online
-- @tparam LuaItemPrototype item The item to search for in players inventories
Commands.new_command('search-online', 'Display online players sorted by the quantity of an item held and playtime')
:add_alias('io')
:add_param('item', false, item_parse)
:enable_auto_concat()
:register(function(player, item)
    local players = search_players(game.connected_players, item)
    if #players == 0 then return {'expcom-inv-search.results-none', item.name} end
    local top_players = sort_players(players, combined_sort)
    display_players(player, top_players, item)
end)