local function register_chatcommand(cmd, desc, func, param)
    cmdlib.register_chatcommand(
            "cells " .. cmd,
            {
                description = desc,
                params = param,
                func = function(name, params)
                    local arena = arena.get_by_name(name)
                    if not arena then
                        return false, "Not inside exactly one arena"
                    end
                    if not arena:is_owner(name) and not is_cellestial(name) then
                        return false, "Not an owner of the current arena"
                    end
                    return unpack({ func(arena, name, params) })
                end
            }
    )
end
register_chatcommand(
        "clear",
        "Clear the current arena",
        function(arena)
            arena:clear()
            return true, "Arena cleared"
        end
)
register_chatcommand(
        "update",
        "Update the current arena",
        function(arena)
            arena:update()
            return true, "Arena updated"
        end
)
register_chatcommand(
        "randomize",
        "Randomize the current arena",
        function(arena, _, params)
            local threshold = conf.arena_defaults.threshold
            if params.threshold then
                threshold = tonumber(params.threshold)
                if not threshold or threshold < 0 or threshold > 1 then
                    return false, "Threshold needs to be a number from 0 to 1"
                end
            end
            arena:randomize(threshold)
            return true, "Arena randomized"
        end,
        "[threshold]"
)
register_chatcommand(
        "evolve",
        "Evolve/simulate the cells inside the current arena",
        function(arena, _, params)
            local steps = conf.arena_defaults.steps
            if params.steps then
                steps = tonumber(params.steps)
                if not steps or steps <= 0 or steps % 1 ~= 0 then
                    return false, "Steps need to be a positive integer number"
                end
            end
            arena:next_steps(steps)
            return true, "Simulated " .. steps .. " step" .. ((steps > 1 and "s") or "")
        end,
        "[steps]"
)
register_chatcommand(
        "start",
        "Start the simulation",
        function(arena, _, params)
            local steps_per_second = 1
            if params.steps_per_second then
                steps_per_second = tonumber(params.steps_per_second)
                if not steps_per_second or steps_per_second <= 0 then
                    return false, "Steps per second needs to be > 0"
                end
            end
            arena:start(steps_per_second)
            return true, "Started simulation with a speed of " .. steps_per_second .. " steps per second"
        end,
        "[steps_per_second]"
)
register_chatcommand(
        "stop",
        "Stop the simulation",
        function(arena)
            local s = arena:stop()
            if not s then
                return false, "Simulation not running"
            end
            return true, "Simulation stopped"
        end
)
local assign = {
    width_change = "x",
    height_change = "y",
    length_change = "z",
    x_move = "x",
    y_move = "y",
    z_move = "z"
}
local human_names = {
    width_change = "Width Change",
    height_change = "Height Change",
    length_change = "Length Change",
    x_move = "X move",
    y_move = "Y move",
    z_move = "Z move"
}
local dim_human_names = {
    width_change = "Width",
    height_change = "Height",
    length_change = "Length",
    x_move = "X",
    y_move = "Y",
    z_move = "Z"
}
register_chatcommand(
        "resize",
        "Resize arena",
        function(arena, _, params)
            local dimensions = arena:get_dimensions()
            for name, val in pairs(params) do
                val = tonumber(val)
                if not val or val % 1 ~= 0 then
                    return false, human_names[name] .. " needs to be an integer number"
                end
                local new_dim = dimensions[assign[name]] + val
                if new_dim < 3 then
                    return false, dim_human_names[name] .. " needs to be at least 3 (as it includes the borders)"
                end
                dimensions[assign[name]] = new_dim
            end
            local s = arena:set_area(nil, dimensions)
            if s then
                return true, "Arena resized to " .. dimensions.x .. ", " .. dimensions.y .. ", " .. dimensions.z
            end
            return false, "Arena would collide with other arenas if resized"
        end,
        "<width_change> [height_change] [length_change]"
)
register_chatcommand(
        "move",
        "Move arena",
        function(arena, _, params)
            local position = modlib.table.tablecopy(arena.min)
            for name, val in pairs(params) do
                val = tonumber(val)
                if not val or val % 1 ~= 0 then
                    return false, human_names[name] .. " needs to be an integer number"
                end
                local new_dim = position[assign[name]] + val
                position[assign[name]] = new_dim
            end
            local s = arena:set_area(position)
            if s then
                return true, "Arena moved to " .. position.x .. ", " .. position.y .. ", " .. position.z
            end
            return false, "Arena would collide with other arenas if moved"
        end,
        "<x_move> [y_move] [z_move]"
)

local function get_id(name, params)
    local id = tonumber(params.id)
    if not id or id % 1 ~= 0 or id < 0 then
        return false, "ID needs to be a non-negative integer number"
    end
    local arena = arena.get_by_id(id)
    if not arena then
        return false, "No area with the ID #" .. params.id
    end
    return true, arena
end

cmdlib.register_chatcommand(
        "cells get id",
        {
            params = "<id>",
            description = "Get the arena with the corresponding ID",
            func = function(name, params)
                local success, arena = get_id(name, params)
                if success then
                    return success, arena:info()
                end
                return success, arena
            end
        }
)

cmdlib.register_chatcommand(
        "cells teleport id",
        {
            params = "<id>",
            description = "Teleport to the arena with the corresponding ID",
            func = function(name, params)
                local success, arena = get_id(name, params)
                if success then
                    local player = minetest.get_player_by_name(name)
                    if not player then
                        return false, "You need to be online to teleport"
                    end
                    if not arena:is_owner(name) then
                        return false, "Not an owner of the corresponding arena"
                    end
                    arena:teleport(player)
                    return success, "Teleporting to: " .. arena:info()
                end
                return success, arena
            end
        }
)

cmdlib.register_chatcommand(
        "cells get player",
        {
            params = "[name]",
            description = "Get the arena the player is currently in",
            func = function(name, params)
                local arena = arena.get_by_name(params.name or name)
                if not arena then
                    return false, "Not inside an arena"
                end
                return true, arena:info()
            end
        }
)

local function get_pos(params)
    local vector = {}
    for _, param in ipairs({ "x", "y", "z" }) do
        vector[param] = tonumber(params[param])
        if not vector[param] then
            return false, param.upper() .. " needs to be a valid number"
        end
    end
    local arena = arena.get(params)
    if not arena then
        return false, "Not inside an arena"
    end
    return true, arena, vector
end

cmdlib.register_chatcommand(
        "cells get pos",
        {
            params = "<x> <y> <z>",
            description = "Get the arena at position",
            func = function(_, params)
                local success, arena = get_pos(params)
                if success then
                    return success, arena:info()
                end
                return success, arena
            end
        }
)

cmdlib.register_chatcommand(
        "cells teleport pos",
        {
            params = "<x> <y> <z>",
            description = "Teleport to the position",
            func = function(name, params)
                local success, arena, vector = get_pos(params)
                if success then
                    local player = minetest.get_player_by_name(name)
                    if not player then
                        return false, "You need to be online to teleport"
                    end
                    if not arena:get_position(name) then
                        return false, "Not an owner of the arena"
                    end
                    player:set_pos(vector)
                    return success, ("Teleporting to (%s, %s, %s)"):format(vector)
                end
                return success, arena
            end
        }
)

local function create_teleport_request(name, arena, property, value)
    if teleport_requests[name] then
        return false, "You already have a running request"
    end
    if arena:get_position(name) then
        return false, "No need for a teleport request"
    end
    local sent_to = {}
    local timers = {}
    for _, owner in ipairs(arena.meta.owners) do
        owner_ref = minetest.get_player_by_name(owner)
        if owner_ref then
            table.insert(sent_to, owner)
            minetest.chat_send_player(owner, "Player " .. minetest.get_color_escape_sequence(colors.cell.fill) .. name .. " " ..
                    minetest.get_color_escape_sequence("#FFFFFF") .. " requests to teleport to (%s, %s, %s).")
            timers[owner] = hud_timers.add_timer(owner, { name = name .. "'s request", duration = request_duration, color = colors.cell.fill:sub(2) })
        end
    end
    if #sent_to == 0 then
        return false, ("No owner (none of %s) online"):format(arena:owner_info())
    end
    local timer = hud_timers.add_timer(name, { name = "Teleport", duration = request_duration, color = colors.cell.edge:sub(2), on_complete = modlib.func.curry(remove_teleport_request, name)  })
    local request = { timer = timer, timers = timers, [property] = value }
    for _, owner in pairs(sent_to) do
        teleport_requests_last[owner] = request
    end
    teleport_requests[name] = request
    return true, "Teleport request sent to " .. table.concat(sent_to, ", ")
end

request_duration = 30
teleport_requests = {}
teleport_requests_last = {}

local function remove_request_receivers(name)
    local request = teleport_requests[name]
    for owner_name, timer in pairs(request.timers) do
        hud_timers.remove_timer_by_reference(owner_name, timer)
        local last_requests = teleport_requests_last[owner_name]
        local index = modlib.table.find(last_requests, name)
        if index then
            table.remove(last_requests, index)
        end
    end
end

local function remove_teleport_request(name)
    remove_request_receivers(name)
    teleport_requests[name] = nil
end

minetest.register_on_joinplayer(function(player)
    teleport_requests_last[player:get_player_name()] = {}
end)

minetest.register_on_leaveplayer(function(player)
    local name = player:get_player_name()
    local request = teleport_requests[name]
    if request then
        remove_teleport_request(name, request)
    end
    local last_requests = teleport_requests_last[name]
    if last_requests then
        for _, requester_name in pairs(teleport_requests_last) do
            local request = teleport_requests[requester_name]
            if request then
                request.timers[name] = nil
                if modlib.table.is_empty(request.timers) then
                    remove_teleport_request(requester_name)
                end
            end
        end
        teleport_requests_last[name] = nil
    end
end)

cmdlib.register_chatcommand(
        "cells teleport request pos",
        {
            params = "<x> <y> <z>",
            description = "Send teleport request to owners of the arena",
            func = function(name, params)
                local success, arena, vector = get_pos(params)
                if success then
                    local player = minetest.get_player_by_name(name)
                    if not player then
                        return false, "You need to be online to teleport"
                    end
                    return unpack({create_teleport_request(name, arena, "pos", vector)})
                end
                return success, arena
            end
        }
)

cmdlib.register_chatcommand(
        "cells teleport request id",
        {
            params = "<id>",
            description = "Send teleport request to owners of the arena",
            func = function(name, params)
                local id = tonumber(params.id)
                if not id or id % 1 ~= 0 or id < 0 then
                    return false, "ID needs to be a non-negative integer number"
                end
                local arena = arena.get_by_id(id)
                if not arena then
                    return false, "No arena with the ID #"..id
                end
                return unpack({create_teleport_request(name, arena, "id", id)})
            end
        }
)

cmdlib.register_chatcommand(
        "cells teleport request player",
        {
            params = "<name>",
            description = "Send teleport request to player",
            func = function(name, params)
                local player = minetest.get_player_by_name(name)
                if not player then
                    return false, "You need to be online to teleport"
                end
                if teleport_requests[name] then
                    return false, "You already have a running request"
                end
                local target = minetest.get_player_by_name(params.name)
                if not target then
                    return false, "Player "..params.name.." is not online"
                end
                local request = {
                    name = params.name,
                    timer = hud_timers.add_timer(name, { name = "Teleport", duration = request_duration, color = colors.cell.edge:sub(2), on_complete = modlib.func.curry(remove_teleport_request, name) }),
                    timers = { [params.name] = hud_timers.add_timer(params.name, { name = name .. "'s request", duration = request_duration, color = colors.cell.fill:sub(2) }) },
                }
                teleport_requests[name] = request
                table.insert(teleport_requests_last[params.name], name)
            end
        }
)

cmdlib.register_chatcommand(
        "cells teleport accept",
        {
            params = "[name]",
            description = "Accept teleport request of player or last request",
            func = function(name, params)
                local requester_name = params.name
                if not requester_name then
                    requester_name = teleport_requests_last[name]
                    if not requester_name then
                        return false, "No outstanding last request"
                    end
                end
                local request = teleport_requests[params.name]
                if params.name then
                    request = teleport_requests[params.name]
                    if not request or not request.timers[name] then
                        return false, "No outstanding request by player " .. params.name
                    end
                else
                    request = teleport_requests_last[name]
                    if not request then
                        return false, "No outstanding last request"
                    end
                end
                remove_teleport_request(requester_name)
                local pos, message
                if request.pos then
                    pos = request.pos
                    message = ("Player %s teleported to %s, %s, %s"):format(requester_name, tonumber(request.pos.x), tonumber(request.pos.y), tonumber(request.pos.z))
                elseif request.name then
                    pos = minetest.get_player_by_name(request.name):get_pos()
                    message = ("Player %s teleported to %s"):format(requester_name, request.name)
                else
                    arena.get_by_id(request.id):teleport(requester_name)
                    return true, ("Player %s teleported to %s"):format(requester_name, request.name)
                end
                minetest.get_player_by_name(requester_name):set_pos(pos)
                return true, message
            end
        }
)

function create_arena(sendername, params, nums)
    for _, param in ipairs({ "x", "y", "z", "width", "height", "length" }) do
        if not nums[param] then
            local number = tonumber(params[param])
            if not number or number % 1 ~= 0 then
                return false, modlib.text.upper_first(param) .. " needs to be a valid integer number"
            end
            nums[param] = number
        end
    end
    for _, param in ipairs({ "width", "height", "length" }) do
        if nums[param] < 3 then
            return false, modlib.text.upper_first(param) .. " needs to be positive and at least 3"
        end
    end
    local name = params.name
    local owners = params.owners or { sendername }
    for _, owner in ipairs(owners) do
        if not minetest.get_player_by_name(owner) then
            return false, "Player " .. owner .. " is not online. All owners need to be online."
        end
    end
    local min = { x = nums.x, y = nums.y, z = nums.z }
    local max = { x = nums.x + nums.width, y = nums.y + nums.height, z = nums.z + nums.length }
    if arena.overlaps(min, max) then
        return false, "Selected area intersects with existing arenas"
    end
    local arena = arena.new(min, max, { name = name, owners = owners })
    arena:teleport(minetest.get_player_by_name(owners[1]))
    if arena then
        return true, "Arena created"
    end
    return false, "Failed to create arena, would intersect with other arenas"
end

cmdlib.register_chatcommand(
        "cells create there",
        {
            params = "<x> <y> <z> <width> <height> <length> [name] {owners}",
            description = "Create a new arena",
            func = function(sendername, params)
                return unpack({ create_arena(sendername, params, {}) })
            end
        }
)

cmdlib.register_chatcommand(
        "cells create here",
        {
            params = "<width> <height> <length> [name] {owners}",
            description = "Create a new arena",
            func = function(sendername, params)
                local player = minetest.get_player_by_name(sendername)
                if not player then
                    return false, "You need to be online in-game to use the command."
                end
                return unpack({ create_arena(sendername, params, vector.floor(player:get_pos())) })
            end
        }
)

cmdlib.register_chatcommand(
        "cells arenas list",
        {
            params = "[name]",
            description = "Lists all arenas of a player",
            func = function(sendername, params)
                local name = sendername or params.name
                local ids = arena.list_by_name(name)
                if not ids then
                    return false, "Player " .. name .. " is not online."
                end
                if #ids == 0 then
                    return true, "Player " .. name .. " does not have any arenas."
                end
                modlib.table.map(ids, arena.info)
                table.insert(ids, 1, "Player " .. name .. " owns the following arenas:")
                return true, table.concat(ids, "\n")
            end
        }
)

function show_arenas_formspec(sendername, name)
    local ids = arena.list_by_name(name) or {}
    modlib.table.map(ids, arena.formspec_table_info)
    local table_height = math.min(3, #ids * 0.35)
    local message, fs_table
    if #ids == 0 then
        message = "Player " .. name .. " does not have any arenas."
        fs_table = ""
        table_height = 0.25
    else
        message = "Player " .. name .. " owns the following arenas (double-click to teleport):"
        fs_table = ([[
tablecolumns[color;text,align=inline;color;text,align=inline;color;text,align=inline;text,align=inline]
tableoptions[background=#00000000;highlight=#00000000;border=false]
table[0.15,1.6;7.6,%s;arenas;%s]
]]):format(table_height, table.concat(ids, ","))
        table_height = table_height + 0.25
    end
    minetest.show_formspec(sendername, "cellestial:arenas",
            ([[
size[8,%s]
real_coordinates[true]
box[0,0;8,1;%s]
label[0.25,0.5;Arenas of player]
field[2,0.25;2,0.5;player;;%s]
field_close_on_enter[player;false]
button[4.25,0.25;1,0.5;show;Show]
label[0.25,1.35;%s]
%simage_button_exit[7.25,0.25;0.5,0.5;cmdlib_cross.png;close;]
]]):format(table_height + 1.5, colors.cell.fill, minetest.formspec_escape(name), minetest.formspec_escape(message), fs_table))
end

cmdlib.register_chatcommand(
        "cells arenas show",
        {
            params = "[name]",
            description = "Shows all arenas of a player",
            func = function(sendername, params)
                show_arenas_formspec(sendername, params.name or sendername)
                return true
            end
        }
)

modlib.minetest.register_form_listener("cellestial:arenas", function(player, fields)
    if fields.quit then
        return
    end

    local name
    if fields.player then
        -- not using key_enter_field
        name = fields.player
    end
    if not name or name:len() == 0 then
        name = player:get_player_name()
    end
    if fields.arenas then
        local event = minetest.explode_table_event(fields.arenas)
        if event.type == "DCL" then
            local id = arena.list_ids_by_name(name)[event.row]
            if id then
                local arena = arena.get_by_id(id)
                if arena:get_position(player:get_player_name()) then
                    arena:teleport(player)
                else
                    create_teleport_request(player:get_player_name(), arena, "id", id)
                end
            end
        end
    end
    show_arenas_formspec(player:get_player_name(), name)
end)

cmdlib.register_chatcommand(
        "cells help",
        {
            description = "Shows help",
            func = function(name, _)
                show_help(name)
            end
        }
)

register_chatcommand(
        "owner add",
        "Add owner to the current arena",
        function(arena, name, param)
            local param_name = param.name or name
            local namepos = arena:is_owner(name)
            if not namepos and minetest.check_player_privs(name, { cellestial = true }) then
                namepos = 1
            end
            local position
            if param.position then
                position = tonumber(param.position)
                if not position or position % 1 ~= 0 or position < namepos or position > #arena.meta.owners + 1 then
                    return false, "Position needs to be an integer number between " .. namepos .. " (your position) and " .. #arena.meta.owners + 1
                end
            end
            local success = arena:add_owner(name, position)
            if success == false then
                return false, "Player " .. param_name .. " is not online"
            end
            return true, "Added player " .. param_name .. " to arena #" .. arena.id .. ", owners now: " .. table.concat(arena.meta.owners, ", ")
        end,
        "[name] [position]"
)

register_chatcommand(
        "owner remove",
        "Remove owner from current arena",
        function(arena, name, param)
            local param_name = param.name or name
            local namepos = arena:is_owner(name)
            if not namepos and minetest.check_player_privs(name, { cellestial = true }) then
                namepos = 1
            end
            if namepos > arena:is_owner(param_name) then
                return false, "Player " .. param_name .. " is in a higher position"
            end
            local success = arena:remove_owner(param_name)
            if success == false then
                return false, "Player " .. param_name .. " is not online"
            end
            return true, "Removed player " .. param_name .. " from arena #" .. arena.id .. ", owners now: " .. table.concat(arena.meta.owners, ", ")
        end,
        "[name]"
)

register_chatcommand(
        "set_name",
        "Set name of current arena",
        function(arena, name, params)
            local namepos = arena:is_owner(name)
            if namepos > 1 then
                return false, "Only the first owner can change the name."
            end
            local oldname = arena.meta.name
            arena.meta.name = params.name
            arena:store()
            return true, ('Name changed from "%s" to "%s"'):format(oldname, arena.meta.name)
        end,
        "<name>"
)