local previous_player_transfer_distance

pvpplus.tournament = {
	engaging_players = false,
	engagement_position = nil,
	running_tournament = false,
	teleport_immediately = false,
	engaged_players = {},
	previous_pvp_states = {},
	players = {},
	sent_damages = {},
	received_damages = {},
	kills = {},
	sound_handles = {},
	starting_infos = {starter = nil, open_time = nil, start_time = nil, initially_engaged_players = nil},
	broadcast_messages = false,
	damage_motionless = false,
	motion_table = {},
}

local tournament = pvpplus.tournament -- Shortcut reference

function pvpplus.engage_player(player_name)
	if not tournament.engaging_players then
		return false, "There is no open tournament."
	end
	tournament.engaged_players[player_name] = true
	minetest.chat_send_player(player_name, "You have been engaged for a PvP tournament!")

	if tournament.engagement_position and tournament.teleport_immediately then
		local player_obj = minetest.get_player_by_name(player_name)
		player_obj:moveto(tournament.engagement_position)
		-- Send a chat message
		minetest.chat_send_player(player_name, "You've been teleported to the tournament position.")
	end
	return true
end

function pvpplus.disengage_player(player_name)
	if not pvpplus.is_engaged(player_name) then
		return false, "This player is not engaged"
	end
	tournament.engaged_players[player_name] = nil
	return true
end

function pvpplus.is_engaged(player_name)
	if tournament.engaged_players[player_name] then
		return true
	else
		return false
	end
end

function pvpplus.is_engaging_players()
	return tournament.engaging_players
end

function pvpplus.get_score(name)
	if not pvpplus.is_running_tournament() then
		return false, "There is no running tournament."
	end
	if not tournament.sent_damages[name] then
		return false, "Player " .. name .. " did not play in the current tournament."
	end
	return tournament.sent_damages[name] - tournament.received_damages[name] + tournament.kills[name] * 20
end

function pvpplus.chat_send_tournament(msg, allow_broadcast)
	if tournament.broadcast_messages and allow_broadcast then
		minetest.chat_send_all(msg)
		return false
	elseif pvpplus.is_running_tournament() then
		for name, _ in pairs(tournament.sent_damages) do
			minetest.chat_send_player(name, msg)
		end
		return true
	elseif pvpplus.is_engaging_players() then
		for name, _ in pairs(tournament.engaged_players) do
			minetest.chat_send_player(name, msg)
		end
		return true
	else
		return false
	end
end

function pvpplus.start_tournament()
	if tournament.running_tournament then
		return false, "There is already a running tournament."
	end

	local count = 0
	for _, _ in pairs(tournament.engaged_players) do
		count = count + 1
	end
	if count <= 1 and not pvpplus.debug then
		pvpplus.chat_send_tournament("There are not enough engaged players to start a tournament.", true)
		tournament.engaged_players = {}
		tournament.engagement_position = nil
		tournament.teleport_immediately = false
		tournament.engaging_players = false
		return false
	end

	-- Stop engaging
	tournament.engaging_players = false

	local chat_message = "PVP TOURNAMENT BEGINS! Started by " .. (tournament.starting_infos.starter or "[unknown player]") .. "\nEngaged players: "
	tournament.starting_infos.initially_engaged_players = {}

	for player, _ in pairs(tournament.engaged_players) do
		tournament.starting_infos.initially_engaged_players[player] = true

		-- Enable PvPs
		tournament.previous_pvp_states[player] = pvpplus.is_pvp(player)
		pvpplus.pvp_enable(player)
		-- Move to the playing table
		tournament.players[player] = true
		tournament.sent_damages[player] = 0
		tournament.received_damages[player] = 0
		tournament.kills[player] = 0
		tournament.motion_table[player] = {pos = minetest.get_player_by_name(player):get_pos(), time = os.time()}

		chat_message = chat_message .. player .. ", "

		-- Play the sounds
		minetest.sound_play("pvpplus_tournament_start", {
			to_player = player,
			gain = 1.0,
		})
		if minetest.settings:get_bool("pvpplus.enable_sound_loop") ~= false then -- If it's true or nil (unset)
			minetest.after(10, function(name)
				tournament.sound_handles[name] = minetest.sound_play("pvpplus_tournament_loop", {
					to_player = name,
					gain = 1.0,
					loop = true,
				})
			end, player)
		end
	end
	chat_message = chat_message:sub(0, -3) -- Remove the last ', '
	tournament.engaged_players = {}
	tournament.engagement_position = nil
	tournament.teleport_immediately = false

	-- Set the player transfer distance
	previous_player_transfer_distance = minetest.settings:get("player_transfer_distance")
	minetest.settings:set("player_transfer_distance", 0) -- 0 = unlimited

	-- Send the final chat message
	pvpplus.chat_send_tournament(chat_message, true)

	-- Set the tournament flag
	tournament.running_tournament = true

	-- Update HUDs
	pvpplus.tournament_hud_update_all()
end

function pvpplus.start_global_tournament(starter_name)
	local players = minetest.get_connected_players()

	-- Engage all connected players
	pvpplus.allow_engaging(starter_name)
	for _, player in ipairs(players) do
		local player_name = player:get_player_name()
		if minetest.get_player_privs(player_name).interact then -- Well, don't engage players who don't have interact.
			pvpplus.engage_player(player_name)
		end
	end

	-- Start the tournament
	return pvpplus.start_tournament()
end

function pvpplus.stop_tournament()
	if not pvpplus.is_running_tournament() then
		return false
	end

	pvpplus.chat_send_tournament("END OF TOURNAMENT!", true)

	-- Calculate rating
	local rating = {}
	for name, _ in pairs(tournament.sent_damages) do
		table.insert(rating, {
			name = name,
			score = pvpplus.get_score(name)
		})

		if tournament.sound_handles[name] then
			minetest.sound_stop(tournament.sound_handles[name])
		end
		minetest.sound_play("pvpplus_tournament_end", {
			to_player = name,
			gain = 1.0,
		})

		-- Set PvP to the state it had before the tournament
		if tournament.previous_pvp_states[name] then
			pvpplus.pvp_set(name, tournament.previous_pvp_states[name])
		end

		local player = minetest.get_player_by_name(name)
		if player then
			pvpplus.tournament_hud_clear(player)
		end
	end
	table.sort(rating, function(a, b) return a.score > b.score end)

	-- Print it
	-- It won't look good if the font used in the chat and the formspec textarea isn't monospace.
	local str = "***************************** TOURNAMENT RANKING ****************************\n" ..
	            "+--------------+-------+--------+---------------+-------------------+-------+\n" ..
		    "| player       | rank  | score  | sent damages  | received damages  | kills |\n" ..
	            "+--------------+-------+--------+---------------+-------------------+-------+"
	for i, v in ipairs(rating) do
		local player = v.name
		local rank = tostring(i)
		local score = tostring(v.score)
		local sent_damages = tostring(tournament.sent_damages[v.name])
		local received_damages = tostring(tournament.received_damages[v.name])
		local kills = tostring(tournament.kills[v.name])

		local substr = "| "

		local function cat_str(value, len)
			substr = substr .. value
			if #substr > len then
				substr = substr:sub(1, len-1) .. "+"
			end
			for n = #substr, len do
				substr = substr .. " "
			end
			substr = substr .. "| "
		end

		cat_str(player, 14)
		cat_str(rank, 22)
		cat_str(score, 31)
		cat_str(sent_damages, 47)
		cat_str(received_damages, 67)
		cat_str(kills, 75)

		str = str .. "\n" .. substr
	end
	str = str .. "\n+--------------+-------+--------+---------------+-------------------+-------+"

	pvpplus.chat_send_tournament(str, true)

	local formspec = "size[10,8.5]textarea[0.5,0.5;9.5,8;ranking;Ranking;"..str.."]button_exit[4,7.5;2,1;exit;Ok]"
	for name, _ in pairs(tournament.sent_damages) do
		minetest.show_formspec(name, "pvpplus:ranking", formspec)
	end

	-- Clean tables
	tournament = {
		engaging_players = false,
		running_tournament = false,
		engaged_players = {},
		previous_pvp_states = {},
		players = {},
		sent_damages = {},
		received_damages = {},
		kills = {},
		sound_handles = {},
		starting_infos = {starter = nil, open_time = nil, start_time = nil, initially_engaged_players = nil},
		broadcast_messages = false,
		damage_motionless = false,
		motion_table = {},
	}

	-- Change the player transfer distance back
	minetest.settings:set("player_transfer_distance", previous_player_transfer_distance)
end

function pvpplus.allow_engaging(starter_name, teleport)
	if tournament.engaging_players then
		return false, "There is already an open tournament."
	end
	if tournament.is_running_tournament then
		return false, "There is already a running tournament."
	end
	tournament.engaging_players = true
	minetest.chat_send_all(starter_name .. " opened a tournament! Type /engage to engage yourself in the tournament!")
	if teleport then
		tournament.engagement_position = minetest.get_player_by_name(starter_name):get_pos()
	end
	minetest.sound_play("pvpplus_tournament_start", {
		gain = 1.0,
	})
	return true
end

function pvpplus.teleport_engaged_players()
	if not tournament.engagement_position then
		return false, "No engagement position."
	end
	for player, _ in pairs(tournament.engaged_players) do
		-- Teleport player to the starter
		if tournament.engagement_position then
			local player_obj = minetest.get_player_by_name(player)
			player_obj:moveto(tournament.engagement_position)
		end
		-- Send a chat message
		minetest.chat_send_player(player, "You've been teleported to the tournament position.")
	end
	tournament.teleport_immediately = true
	return true
end

function pvpplus.remove_from_tournament(player_name)
	-- Remove from players table
	tournament.players[player_name] = nil

	-- Send a chat message
	if minetest.get_player_by_name(player_name) then
		minetest.chat_send_player(player_name, "You are no longer playing the tournament.")
	end
	pvpplus.chat_send_tournament("Player "..player_name.." is no longer playing the tournament.")

	-- Change their PvP state
	pvpplus.pvp_set(player_name, tournament.previous_pvp_states[player_name])
	tournament.previous_pvp_states[player_name] = nil

	-- Check if the tournament is ended
	local count = 0
	for _, _ in pairs(tournament.players) do count = count + 1 end

	if count <= 1 then -- 1 or less remaining players
		pvpplus.stop_tournament()
	else
		pvpplus.tournament_hud_update_all()
	end
end

function pvpplus.add_to_tournament(player_name)
	local player = minetest.get_player_by_name(player_name)
	if not pvpplus.is_running_tournament() or pvpplus.is_playing_tournament(player_name) or not player then
		return false
	end

	-- Add to tables
	tournament.players[player_name] = true
	tournament.sent_damages[player_name] = 0
	tournament.received_damages[player_name] = 0
	tournament.kills[player_name] = 0
	tournament.previous_pvp_states[player_name] = pvpplus.is_pvp(player_name)
	tournament.motion_table[player] = {pos = minetest.get_player_by_name(player):get_pos(), time = os.time()}

	-- Enable PvP
	pvpplus.pvp_enable(player_name)

	-- Send a chat message
	minetest.chat_send_player(player_name, "You joined the current tournament!")
	pvpplus.chat_send_tournament("Player "..player_name.." joined the current tournament!")

	pvpplus.tournament_hud_update_all()
end

function pvpplus.is_playing_tournament(player_name)
	if tournament.players[player_name] then
		return true
	else
		return false
	end
end

function pvpplus.get_tournament_players()
	local t = {}
	if pvpplus.is_running_tournament() then
		for name, _ in pairs(tournament.players) do
			table.insert(t, minetest.get_player_by_name(name))
		end
	elseif pvpplus.is_engaging_players() then
		for name, _ in pairs(tournament.engaged_players) do
			table.insert(t, minetest.get_player_by_name(name))
		end
	else
		t = nil
	end

	return t
end

function pvpplus.is_running_tournament()
	return tournament.running_tournament
end

function pvpplus.tournament_on_punchplayer(player, hitter, damage)
	local player_name = player:get_player_name()
	local hitter_name = hitter:get_player_name()

	if not (pvpplus.is_running_tournament()
	    and pvpplus.is_playing_tournament(player_name)
	    and pvpplus.is_playing_tournament(hitter_name)) then
		return false
	end

	tournament.received_damages[player_name] = tournament.received_damages[player_name] + damage
	tournament.sent_damages[hitter_name] = tournament.sent_damages[hitter_name] + damage

	if player:get_hp() - damage <= 0 then -- Killed
		tournament.kills[hitter_name] = tournament.kills[hitter_name] + 1
		minetest.chat_send_player(player_name, "You have been killed by " .. hitter_name)
		-- Removing the player from the tournament and updating HUDs is done by the on_dieplayer callback.
	else
		pvpplus.tournament_hud_update_all()
	end
end

minetest.register_on_leaveplayer(function(player)
	local player_name = player:get_player_name()
	if pvpplus.is_playing_tournament(player_name) then
		pvpplus.remove_from_tournament(player_name)
	end
	if pvpplus.is_engaged(player_name) then
		pvpplus.disengage_player(player_name)
	end
end)

minetest.register_on_dieplayer(function(player)
	local player_name = player:get_player_name()
	if not pvpplus.is_playing_tournament(player_name) then
		return
	end

	pvpplus.remove_from_tournament(player_name)

	local pos = vector.round(player:getpos())
	if minetest.get_modpath("bones") and minetest.get_node(pos).name == "bones:bones" then
		local bones_inv = minetest.get_inventory({type="node", pos=pos})
		local player_inv = player:get_inventory()
		if not player_inv:is_empty("main") then
			minetest.chat_send_player(player_name, "Cannot give you back your stuff because your inventory is not empty. Your stuff is still is your bones.")
			return
		end
		player_inv:set_list("main", bones_inv:get_list("main"))
		minetest.remove_node(pos)
	end
end)

dofile(minetest.get_modpath(minetest.get_current_modname()).."/tournament_commands.lua")
dofile(minetest.get_modpath(minetest.get_current_modname()).."/tournament_hud.lua")
dofile(minetest.get_modpath(minetest.get_current_modname()).."/tournament_motionless.lua")
