features/redmew_commands.lua
local Game = require 'utils.game'
local Timestamp = require 'utils.timestamp'
local Command = require 'utils.command'
local Utils = require 'utils.core'
local Server = require 'features.server'
local Rank = require 'features.rank_system'
local Donator = require 'features.donator'
local Color = require 'resources.color_presets'
local ScoreTracker = require 'utils.score_tracker'
local format_number = require 'util'.format_number
local player_data_to_show = storage.config.redmew_commands.whois.player_data_to_show
local print_to_player = Game.player_print
local concat = table.concat
local tostring = tostring
local tonumber = tonumber
local pairs = pairs
local floor = math.floor
local Ranks = require 'resources.ranks'
local ClearCorpses = require 'features.clear_corpses'

--- Informs the actor that there is no target. Acts as a central place where this message can be changed.
local function print_no_target(target_name)
    print_to_player({ 'common.fail_no_target', target_name }, Color.fail)
end

--- Kill a player with fish as the cause of death.
local function do_fish_kill(player, suicide)
    local c = player.character
    if not c then
        return false
    end

    local e = player.physical_surface.create_entity { name = 'fish', position = player.physical_position }
    c.die(player.force, e)

    -- Don't want people killing themselves for free fish.
    if suicide then
        e.destroy()
    end

    return true
end

--- Kill a player: admins and the server can kill others, non-admins can only kill themselves
local function kill(args, player)
    local target_ident = args.player
    local target, target_name = Utils.validate_player(target_ident)
    if target_ident and not target then
        if not target then
            print_no_target(target_ident)
            return
        end
    end

    if player then
        if not target or target == player then -- player suicide
            if not do_fish_kill(player, true) then
                print_to_player({ 'redmew_commands.kill_fail_suicide_no_character' })
            end
        elseif target and player.admin then -- admin killing target
            if not do_fish_kill(target) then
                print_to_player({ 'redmew_commands.kill_fail_target_no_character' }, target_name)
            end
        else -- player failing to kill target
            print_to_player({ 'redmew_commands.kill_fail_no_perm' })
        end
    elseif target then -- server killing target
        if not do_fish_kill(target) then
            print_to_player({ 'redmew_commands.kill_fail_target_no_character' }, target_name)
        end
    end
end

--- Check players' afk times
local function afk()
    local count = 0
    for _, v in pairs(game.players) do
        local afk_time = v.afk_time
        if afk_time > 300 then
            count = count + 1
            local time = ' '
            if afk_time > 21600 then
                time = time .. floor(afk_time / 216000) .. ' hours '
            end
            if afk_time > 3600 then
                time = time .. floor(afk_time / 3600) % 60 .. ' minutes and '
            end
            time = time .. floor(v.afk_time / 60) % 60 .. ' seconds.'
            print_to_player(v.name .. ' has been afk for' .. time)
        end
    end
    if count == 0 then
        print_to_player({ 'redmew_commands.afk_no_afk' })
    end
end

--- Lets a player set their zoom level
local function zoom(args, player)
    local zoom_val = tonumber(args.zoom)
    if zoom_val then
        player.zoom = zoom_val
    else
        print_to_player({ 'redmew_commands.zoom_fail' })
    end
end

--- Creates an alert for the player at the location of their target
local function find_player(args, player)
    local target_ident = args.player
    local target, target_name = Utils.validate_player(target_ident)

    if not target then
        print_no_target(target_ident)
        return
    end

    target = target.character
    if not target or not target.valid then
        print_to_player({ 'redmew_commands.find_player_fail_no_character', target_name })
        return
    end

    player.add_custom_alert(target, { type = 'virtual', name = 'signal-F' }, target_name, true)
end

--- Turns on rail block visualization for player
local function show_rail_block(_, player)
    local vs = player.game_view_settings
    local show = not vs.show_rail_block_visualisation
    vs.show_rail_block_visualisation = show

    print_to_player({ 'redmew_commands.show_rail_block_success', tostring(show) })
end

--- Provides the time on the server
local function server_time(_, player)
    local p
    if not player then
        p = print
    elseif player.valid then
        p = player.print
    else
        return
    end

    local secs = Server.get_current_time()
    if secs == nil then
        p({ 'redmew_commands.server_time_fail' })
    else
        p(Timestamp.to_string(secs))
    end
end

local function list_seeds()
    local seeds = {}
    local count_of_seeds = 0
    for _, surface in pairs(game.surfaces) do
        seeds[count_of_seeds + 1] = surface.name
        seeds[count_of_seeds + 2] = ': '
        seeds[count_of_seeds + 3] = tostring(surface.map_gen_settings.seed)
        count_of_seeds = count_of_seeds + 4
        seeds[count_of_seeds] = ', '
    end

    seeds[#seeds] = nil
    seeds = concat(seeds)
    print_to_player(seeds)
end

local function print_version()
    local version_str
    if storage.redmew_version then
        version_str = storage.redmew_version
    else
        version_str = { 'redmew_commands.print_version_from_source' }
    end
    print_to_player(version_str)
end

--- Prints information about the target player
local function print_player_info(args, player)
    local target_ident = args.player
    local target, target_name, player_index = Utils.validate_player(target_ident)

    if not target then
        print_no_target(target_ident)
        return
    end

    local sep = ': '
    print_to_player({ '', { 'common.player_name' }, sep, target_name })
    print_to_player({ '', { 'common.connection_status' }, sep,
        { target.connected and 'common.online' or 'common.offline' } })
    print_to_player({ '', { 'common.player_index' }, sep, target.index })
    print_to_player({ '', { 'common.player_rank' }, sep, Rank.get_player_rank_name(target_name) })
    print_to_player({ '', { 'ranks.donator' }, sep, { Donator.is_donator(target.name) and 'common.yes' or 'common.no' } })
    print_to_player({ '', { 'common.time_played' }, sep, Utils.format_time(target.online_time) })
    print_to_player({ '', { 'common.afk_time' }, sep, Utils.format_time(target.afk_time or 0) })
    print_to_player({ '', { 'common.current_force' }, sep, target.force.name })
    print_to_player({ '', { 'common.current_surface' }, sep, target.surface.name })
    print_to_player({ '', { 'common.player_tag' }, sep, target.tag })

    local scores = ScoreTracker.get_player_scores_with_metadata(player_index, player_data_to_show)

    for i = 1, #scores do
        local score_data = scores[i]
        print_to_player({ '', score_data.locale_string, sep, format_number(score_data.value, true) })
    end

    if (not player or player.admin) and args.inventory then
        local m_inventory = target.get_inventory(defines.inventory.character_main)
        m_inventory = m_inventory.get_contents()
        print_to_player('Main and hotbar inventories: ')
        print_to_player(serpent.line(m_inventory))
    end
end

local function clear_corpses(args, player)
    ClearCorpses.clear_corpses(player, args)
end

-- Command registrations

Command.add(
    'kill',
    {
        description = { 'command_description.kill' },
        arguments = { 'player' },
        default_values = { player = false },
        allowed_by_server = true
    },
    kill
)

Command.add(
    'afk',
    {
        description = { 'command_description.afk' },
        allowed_by_server = true
    },
    afk
)

Command.add(
    'zoom',
    {
        description = { 'command_description.zoom' },
        arguments = { 'zoom' },
        required_rank = Ranks.admin
    },
    zoom
)

Command.add(
    'find',
    {
        description = { 'command_description.find' },
        arguments = { 'player' }
    },
    find_player
)

Command.add(
    'show-rail-block',
    {
        description = { 'command_description.show_rail_block' }
    },
    show_rail_block
)

Command.add(
    'server-time',
    {
        description = { 'command_description.server_time' },
        allowed_by_server = true
    },
    server_time
)

Command.add(
    'seeds',
    {
        description = { 'command_description.seeds' },
        allowed_by_server = true
    },
    list_seeds
)

Command.add(
    'redmew-version',
    {
        description = { 'command_description.redmew_version' },
        allowed_by_server = true
    },
    print_version
)

Command.add(
    'whois',
    {
        description = { 'command_description.whois' },
        arguments = { 'player', 'inventory' },
        default_values = { inventory = false },
        allowed_by_server = true
    },
    print_player_info
)

Command.add(
    'corpses',
    {
        description = { 'command_description.clear_corpses' },
        arguments = { 'surface' },
        default_values = { surface = '' },
        required_rank = Ranks.regular
    },
    clear_corpses
)
