-- LUALOCALS < ---------------------------------------------------------
local ipairs, math, minetest, pairs, piredo_api, string, type
    = ipairs, math, minetest, pairs, piredo_api, string, type
local math_floor, string_format
    = math.floor, string.format
-- LUALOCALS > ---------------------------------------------------------

local api = piredo_api.get_mod_api()
local modstore = minetest.get_mod_storage()

function api.timefmt(n)
	local s = string_format("%0.3f", n % 60)
	if n < 60 then return s end
	if (n % 60) < 10 then s = "0" .. s end
	local m = math_floor(n / 60)
	if m < 60 then return m .. ":" .. s end
	local h = math_floor(m / 60)
	m = m % 60
	m = (m < 10) and ("0" .. m) or ("" .. m)
	return h .. ":" .. m .. ":" .. s
end

function api.create_player_data()
	local pdata = {}
	minetest.register_on_leaveplayer(function(player)
			pdata[player:get_player_name()] = nil
		end)
	return function(p)
		if type(p) ~= "string" then p = p:get_player_name() end
		local found = pdata[p]
		if not found then
			found = {}
			pdata[p] = found
		end
		return found
	end
end

function api.register_playerstep(func)
	minetest.register_globalstep(function(dtime)
			local player, pname = api.mainplayer()
			return player and func(player, dtime, pname)
		end)
end

minetest.register_privilege("latent_interact", {
		description = "Allowed to automatically gain interact privs as the primary player",
		give_to_singleplayer = false,
		give_to_admin = false
	})

-- Main player's location and inventory are stored in mod storage, so that
-- any other player who "takes over" being the "main player" will get these.
api.register_playerstep(function(player, _, pname)
		modstore:set_string("mainplayer", pname)
		local pos = player:get_pos()
		pos.h = player:get_look_horizontal()
		pos.v = player:get_look_vertical()
		modstore:set_string("playerpos", minetest.serialize(pos))
		local inv = player:get_inventory():get_list("main")
		for k, v in pairs(inv) do inv[k] = v:to_string() end
		modstore:set_string("playerinv", minetest.serialize(inv))
	end)

local playerswaps = {}
function api.register_on_playerswap(func) playerswaps[#playerswaps + 1] = func end

-- The "main player" is any arbitrarily-selected player (who retains this status
-- until they disconnect or become no longer eligible). All other players will
-- have their interact privs revoked automatically and replaced with "latent"
-- privs, while the main player will have "latent" privs upgraded if necessary.
do
	local mainpname = ""
	local function checkmain(players)
		local mainprivs = mainpname and players[mainpname]
		if mainprivs and mainprivs.interact then return mainpname end
		if mainprivs and mainprivs.latent_interact then
			mainprivs.interact = true
			minetest.set_player_privs(mainpname, mainprivs)
			return mainpname
		end
		for pname, privs in pairs(players) do
			if privs.interact then return pname end
		end
		for pname, privs in pairs(players) do
			if privs.latent_interact then return pname end
		end
	end
	local function applymain()
		local oldpname = modstore:get_string("mainplayer")
		if oldpname == "" or oldpname == mainpname then return end
		local player = minetest.get_player_by_name(mainpname)
		local pos = minetest.deserialize(modstore:get_string("playerpos"))
		player:set_pos(pos)
		player:set_look_horizontal(pos.h)
		player:set_look_vertical(pos.v)
		player:get_inventory():set_list("main", minetest.deserialize(
				modstore:get_string("playerinv")))
		modstore:set_string("mainplayer", mainpname)
	end
	function api.mainplayer()
		local oldmain = mainpname
		local players = {}
		for _, player in ipairs(minetest.get_connected_players()) do
			local pname = player:get_player_name()
			players[pname] = minetest.get_player_privs(pname)
		end
		mainpname = checkmain(players)
		if mainpname then applymain() end
		if mainpname ~= oldmain then
			local mainp = mainpname and minetest.get_player_by_name(mainpname)
			local oldp = oldmain and minetest.get_player_by_name(oldmain)
			for _, v in pairs(playerswaps) do
				v(mainp, oldp, mainpname, oldmain)
			end
		end
		if not mainpname then return end
		for pname, privs in pairs(players) do
			if pname ~= mainpname and privs.interact then
				privs.interact = nil
				privs.latent_interact = true
				minetest.set_player_privs(pname, privs)
			end
		end
		return minetest.get_player_by_name(mainpname), mainpname
	end
end

minetest.register_on_joinplayer(function() api.mainplayer() end)
minetest.register_on_leaveplayer(function() minetest.after(0, api.mainplayer) end)

-- For first-time initialization of the single player experience shared
-- by all players, not each time any new player account is created that
-- might "take over" the existing one.
function api.register_player_init(func)
	minetest.register_on_newplayer(function(player)
			if player == api.mainplayer()
			and modstore:get_string("mainplayer") == "" then
				return func(player)
			end
		end)
end
