local S = minetest.get_translator("lzr_level_select")
local F = minetest.formspec_escape
local FS = function(...) return minetest.formspec_escape(S(...)) end

-- Directory separator
local DIR_DELIM = "/"

lzr_level_select = {}

local current_core_level_selection = nil
local current_custom_level_selection = nil
local current_custom_level_pack_selection = "__singleton"
local current_stats_level_pack_selection = "__core"
local custom_levels
local custom_level_packs = {}

local message_form = function(player, message)
	return "formspec_version[7]size[8,3.5]"..
	"textarea[0.5,0.5;7,1.8;;;"..F(message).."]"..
	"button_exit[3,2.5;2,0.6;okay;"..FS("OK").."]"
end

local update_custom_level_packs = function(skip_core, level_pack_selection)
	local packnames = lzr_levels.get_level_pack_names()
	local mypacks = {}
	custom_level_packs = {}
	table.insert(custom_level_packs, "__singleton")
	table.insert(mypacks, FS("Single levels"))
	local dropdown_idx
	local pi = 1 -- counted pack index
	for p=1, #packnames do
		local packname = packnames[p]
		if (not skip_core) or packname ~= "__core" then
			pi = pi + 1
			if packname == level_pack_selection then
				dropdown_idx = pi
			end
			table.insert(custom_level_packs, packname)
			local pack = lzr_levels.get_level_pack(packname)
			local title = pack.title or packname
			table.insert(mypacks, minetest.formspec_escape(title))
		end
	end
	if level_pack_selection == "__singleton" then
		dropdown_idx = 1
	end
	if not dropdown_idx then
		dropdown_idx = 1
	end
	return mypacks, dropdown_idx
end

lzr_level_select.open_dialog = function(player, level_set)
	local caption
	if level_set == "core" then
		caption = S("Select level:")
	elseif level_set == "custom" then
		caption = S("Select level pack:")
	else
		minetest.log("error", "[lzr_level_select] open_dialog called with unknown level_set!")
		return false
	end
	local form = "formspec_version[7]size[6,10]"..
		"label[0.5,0.4;"..F(caption).."]"..
		"button_exit[1.5,8.5;3,1;okay;"..FS("Start").."]"

	local mypacks
	local form_levellist
	if level_set == "custom" then
		local mypacks, dropdown_idx = update_custom_level_packs(true, current_custom_level_pack_selection)
		local mypacks_str = table.concat(mypacks, ",")
		form = form ..
		"dropdown[0.5,0.7;5,0.6;levelpack;"..mypacks_str..";"..dropdown_idx..";true]"

		form_levellist =
		"label[0.5,1.65;"..FS("Select level:").."]"..
		"tablecolumns[color;text]"..
		"table[0.5,2;5,5.9;levellist;"
	else
		form_levellist = "tablecolumns[color;text]"..
		"table[0.5,0.8;5,7.5;levellist;"
	end
	local list = {}
	local entry_header = ""
	local first_uncompleted_level = nil
	if level_set == "core" then
		-- Built-in level pack
		local level_data = lzr_levels.get_level_pack("__core")
		local completed_levels = lzr_levels.get_completed_levels(level_data)
		for l=1, #level_data do
			local filename = level_data[l].filename
			local level_id = string.sub(filename, 1, -5)
			if completed_levels[level_id] then
				entry_header = "#00FF00"
			else
				if not first_uncompleted_level then
					first_uncompleted_level = l
				end
				entry_header  = ""
			end
			table.insert(list, entry_header..","..F(lzr_levels.get_level_name(l, level_data, true)))
		end
		if not first_uncompleted_level then
			first_uncompleted_level = 1
		end
		current_core_level_selection = first_uncompleted_level
	else
		if current_custom_level_pack_selection == "__singleton" then
			-- Pseudo-levelpack containing single unsorted levels (editor levels)
			local list_levels = lzr_editor.get_custom_levels()
			custom_levels = {}
			for l=1, #list_levels do
				local levelname = list_levels[l]
				-- Hide the autosave level because it's not meant
				-- for playing
				if levelname ~= lzr_globals.AUTOSAVE_NAME then
					table.insert(custom_levels, levelname)

					local proper_name = lzr_editor.get_custom_level_name(levelname, true)
					table.insert(list, entry_header..","..F(proper_name))
				end
			end
			first_uncompleted_level = 1
			current_custom_level_selection = 1
		else
			-- Custom level pack
			local level_data = lzr_levels.get_level_pack(current_custom_level_pack_selection)
			local completed_levels = lzr_levels.get_completed_levels(level_data)
			for l=1, #level_data do
				local filename = level_data[l].filename
				local level_id = string.sub(filename, 1, -5)
				if completed_levels[level_id] then
					entry_header = "#00FF00"
				else
					if not first_uncompleted_level then
						first_uncompleted_level = l
					end
					entry_header  = ""
				end
				table.insert(list, entry_header..","..F(lzr_levels.get_level_name(l, level_data, true)))
			end
			if not first_uncompleted_level then
				first_uncompleted_level = 1
			end
			current_custom_level_selection = first_uncompleted_level
		end
	end
	if not first_uncompleted_level then
		first_uncompleted_level = 1
	end
	local list_str = table.concat(list, ",")
	form_levellist = form_levellist .. list_str .. ";"..first_uncompleted_level.."]"
	local formname
	if level_set == "core" then
		if #list == 0 then
			form = message_form(player, S("There are no levels."))
		else
			form = form .. form_levellist
		end
		formname = "lzr_level_select:levellist"
	else
		if #list == 0 then
			local level_packs = lzr_levels.get_level_pack_names()
			if #level_packs <= 1 then
				form = message_form(player,
					S("There are no level packs or custom single levels.").."\n"..
					S("Install a level pack or use the level editor to build your own levels."))
			elseif current_custom_level_pack_selection == "__singleton" then
				form = form .. "textarea[0.5,1.7;5,5;;;"..
				FS("There are no custom single levels.").."\n"..
				FS("Install one from your friends or use the level editor to build your own levels.").."]"
			else
				form = form .. "textarea[0.5,1.7;5,5;;;"..
				FS("This level pack is empty.").."]"
			end
		else
			form = form .. form_levellist
		end
		formname = "lzr_level_select:levellist_custom"
	end
	minetest.show_formspec(player:get_player_name(), formname, form)
end

local load_custom_level = function(level, player)
	minetest.log("action", "[lzr_level_select] Player selects custom level: "..level)
	local csv = minetest.get_worldpath().."/levels/"..level..".csv"
	local levels_path = minetest.get_worldpath().."/levels"
	local pname = player:get_player_name()

	local custom_level_data, error_type, error_msg, error_detail = lzr_levels.analyze_levels(csv, levels_path)
	if custom_level_data then
		custom_level_data.is_singleton = true
		-- Load level with metadata (preferred)
		lzr_levels.start_level(1, custom_level_data)
	elseif error_type == "load_error" then
		-- Fallback when CSV file could not be read
		custom_level_data = lzr_levels.create_fallback_level_data(level, levels_path)
		if not custom_level_data then
			local form = message_form(player, S("Level could not be loaded. No level CSV file was found, and the fallback mechanism failed as well."))
			minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
			minetest.log("action", "[lzr_level_select] Player tried to load broken custom level: "..tostring(level)..". Missing CSV file, and the fallback, failed as well.")
		else
			custom_level_data.is_singleton = true
			lzr_levels.start_level(1, custom_level_data)
			minetest.log("action", "[lzr_level_select] No CSV file found for custom level: "..tostring(level)..". Using fallback.")
		end
	elseif error_type == "csv_error" then
		local form = message_form(player, S("The level could not be loaded. Invalid syntax of level CSV file."))
		minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
		minetest.log("action", "[lzr_level_select] Player tried to load broken custom level "..tostring(level)..". Invalid CSV syntax.")
	elseif error_type == "bad_schematic" then
		local error_append = ""
		if error_detail then
			local reason
			if error_detail == "file_nonexistant" then
				reason = S("The level schematic file could not be found.")
			elseif error_detail == "no_teleporter" then
				reason = S("There’s no teleporter for the player to start on.")
			elseif error_detail == "too_many_teleporters" then
				reason = S("There’s more than one teleporter.")
			elseif error_detail == "too_many_parrot_spawners" then
				reason = S("There’s more than one Goldie the Parrot.")
			elseif error_detail == "barriers" then
				reason = S("There’s a barrier or barrier-like node in the level.")
			elseif error_detail == "gold_block" then
				reason = S("There’s a bare gold block in the level.")
			elseif error_detail == "plant_on_ground" then
				reason = S("There’s a rooted plant in the level.")
			elseif error_detail == "schematic_load_error" then
				reason = S("Error while loading schematic file.")
			else
				reason = error_detail
			end
			--~ Reason shown on level loading error
			error_append = "\n\n" .. S("Reason: @1", reason)
		end
		local form = message_form(player, S("This level is unplayable.")..error_append)
		minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
		minetest.log("action", "[lzr_level_select] Player tried to load broken custom level "..tostring(level)..". Bad schematic.")
	else
		local form = message_form(player, S("The level could not be loaded due to an unknown error."))
		minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
		minetest.log("error", "[lzr_level_select] Unknown error while loading custom level: "..tostring(level)..". Aborting.")
	end
end

lzr_level_select.open_stats_dialog = function(player)
	local level_packs_names = lzr_levels.get_level_pack_names()

	local mypacks, dropdown_idx = update_custom_level_packs(false, current_stats_level_pack_selection)
	local mypacks_str = table.concat(mypacks, ",")

	local form = "formspec_version[7]size[6,10]"..
		"box[0,0;6,0.8;#00000080]"..
		"label[0.5,0.4;"..FS("Level packs").."]"..
		"label[0.5,1.3;"..FS("Select level pack:").."]"..
		"dropdown[0.5,1.5;5,0.6;levelpack;"..mypacks_str..";"..dropdown_idx..";true]"..
		"button_exit[1.5,8.5;3,1;okay;"..FS("Okay").."]"..
		"box[0.5,2.3;5,5.9;#00000020]"..
		"hypertext[0.5,2.3;5,5.9;info;"

	local info
	local color = "#00ff00"
	local H = minetest.hypertext_escape
	if current_stats_level_pack_selection == "__singleton" then
		local count = #lzr_editor.get_custom_levels()
		--~ path of a Luanti world directory
		local path = "<"..S("WORLD PATH")..">"..DIR_DELIM.."levels"

		info =
			"<bigger>"..H(S("Single levels")).."</bigger>\n"..
			"<big>"..H(S("Description")).."</big>\n"..
				H(S("Single, unsorted levels that don’t belong to any level pack.")).."\n"..
			"<big>"..H(S("Stats")).."</big>\n"..
				--~ @1 = number of levels
				H(S("• Levels: @1", count)).."\n\n"..

			"<big>"..H(S("File location")).."</big>\n"..
				H(S("Single levels are stored in:")).."\n"..
				"<mono>"..H(path).."</mono>"
	else
		local level_data = lzr_levels.get_level_pack(current_stats_level_pack_selection)
		local treasures_total = lzr_levels.count_total_treasures(level_data)
		local treasures_found = lzr_levels.count_total_collected_treasures(level_data)
		local treasures_percent = string.format("%d", (treasures_found / treasures_total) * 100)

		local levels_total = #level_data
		local levels_completed = lzr_levels.count_completed_levels(level_data)

		local levels_percent = string.format("%d", (levels_completed / levels_total) * 100)

		local title = level_data.title or level_data.name
		local pack_description = level_data.description or S("(no description provided)")

		info =
			"<bigger>"..H(title).."</bigger>\n"..
			"<big>"..H(S("Description")).."</big>\n"..
				H(pack_description).."\n"..
			"<big>"..H(S("Stats")).."</big>\n"..
				--~ @1 = total number of levels, @2 = number of completed levels, @3 percentage of level completion
				H(S("• Levels: @1 (@2 completed, @3%)", levels_total, levels_completed, levels_percent)).."\n"..
				--~ @1 = total number of gold blocks, @2 = number of found gold blocks, @3 percentage of found gold blocks
				H(S("• Gold blocks: @1 (@2 found, @3%)", treasures_total, treasures_found, treasures_percent)).."\n\n"..

			"<big>"..H(S("Mod")).."</big>\n"..
				"<mono>"..H(level_data.mod_origin).."</mono>"

		local privs = minetest.get_player_privs(player:get_player_name())
		if minetest.settings:get_bool("lzr_debug", true) or privs.server or privs.debug then
			info = info .."\n"..
				--~ For the technical level pack identifier
				"<big>"..H(S("Level pack ID")).."</big>\n"..
				"<mono>"..H(level_data.name).."</mono>"
		end
	end

	form = form .. F(info) .. "]"

	minetest.show_formspec(player:get_player_name(), "lzr_level_select:stats", form)
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname == "lzr_level_select:levellist" then
		if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
			return
		end
		local level_data = lzr_levels.get_level_pack("__core")
		if fields.okay then
			if current_core_level_selection then
				lzr_levels.start_level(current_core_level_selection, level_data)
			end
		elseif fields.levellist then
			local expl = minetest.explode_table_event(fields.levellist)
			if expl.type == "CHG" then
				current_core_level_selection = expl.row
			elseif expl.type == "DCL" then
				current_core_level_selection = expl.row
				lzr_levels.start_level(current_core_level_selection, level_data)
				minetest.close_formspec(player:get_player_name(), "lzr_level_select:levellist")
			elseif expl.type == "INV" then
				current_core_level_selection = nil
			end
		end
	elseif formname == "lzr_level_select:levellist_custom" then
		if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
			return
		end
		if fields.okay then
			if custom_levels and current_custom_level_selection then
				if current_custom_level_pack_selection == "__singleton" then
					local level = custom_levels[current_custom_level_selection]
					if level then
						load_custom_level(level, player)
					end
				else
					local level_data = lzr_levels.get_level_pack(current_custom_level_pack_selection)
					lzr_levels.start_level(current_custom_level_selection, level_data)
				end
			end
		elseif fields.levellist then
			local expl = minetest.explode_table_event(fields.levellist)
			if expl.type == "CHG" then
				current_custom_level_selection = expl.row
			elseif expl.type == "DCL" then
				current_custom_level_selection = expl.row
				if current_custom_level_pack_selection == "__singleton" then
					if custom_levels then
						local level = custom_levels[current_custom_level_selection]
						if level then
							load_custom_level(level, player)
						end
					end
				else
					local level_data = lzr_levels.get_level_pack(current_custom_level_pack_selection)
					lzr_levels.start_level(current_custom_level_selection, level_data)
				end
				minetest.close_formspec(player:get_player_name(), "lzr_level_select:levellist_custom")
			elseif expl.type == "INV" then
				current_custom_level_selection = nil
			end
		elseif fields.levelpack then
			local idx = tonumber(fields.levelpack)
			current_custom_level_pack_selection = custom_level_packs[idx]
			lzr_level_select.open_dialog(player, "custom")
		end
	elseif formname == "lzr_level_select:stats" then
		if fields.levelpack then
			local idx = tonumber(fields.levelpack)
			current_stats_level_pack_selection = custom_level_packs[idx]
			if not fields.quit then
				lzr_level_select.open_stats_dialog(player)
			end
		end
	end
end)

minetest.register_chatcommand("level", {
	privs = { server = true },
	description = S("Go to level"),
	params = S("[<level pack>] <level number>"),
	func = function(name, param)
		if lzr_gamestate.get_state() == lzr_gamestate.LEVEL_TEST then
			return false, S("Not possible during the level solution test!")
		end
		if lzr_gamestate.is_loading() then
			return false, S("Can’t start a level while loading!")
		end
		-- Parse arguments. level argument may be relative (tilde notation)
		local pack, level = string.match(param, "([a-zA-Z0-9_]+) (~?-?[0-9]*)")

		local current_data = lzr_levels.get_current_level_data()
		local current_pack
		if current_data then
			current_pack = current_data.name
		end
		-- Select level pack. Defaults to the current level pack
		-- if playing one or __core if playing no level pack
		-- (singleton levels don't count)
		if not pack then
			if current_pack then
				pack = current_pack
			else
				pack = "__core"
			end
			level = string.match(param, "(~?-?[0-9]*)")
		end
		if not level then
			return false
		end
		-- Handle relative level argument (e.g. "~1" to move to the next level)
		local is_relative_level = string.sub(level, 1, 1) == "~"
		if is_relative_level then
			local current_level = lzr_levels.get_current_level()
			-- Relative level argument is only allowed if playing in a level
			-- and the chosen level pack is the same as the current one.
			if not current_level then
				return false, S("Level argument can’t be relative when not playing in a level.")
			end
			if current_pack ~= pack then
				return false, S("Level argument can’t be relative when choosing a different level pack.")
			end
			level = minetest.parse_relative_number(level, current_level)
		else
			level = tonumber(level)
		end
		if not level then
			return false
		end

		local level_data = lzr_levels.get_level_pack(pack)
		if not level_data then
			return false, S("Level pack “@1” doesn’t exist.", pack)
		end
		if level < 1 or level > #level_data then
			--~ @1, @2, and @3 are level numbers
			return false, S("Level @1 does not exist in this level pack. Available levels range from @2 to @3.", level, 1, #level_data)
		end
		lzr_levels.start_level(level, level_data)
		return true
	end,

})

minetest.register_chatcommand("level_packs", {
	description = S("Display information and stats about the installed level packs"),
	params = "",
	func = function(name, param)
		local player = minetest.get_player_by_name(name)
		if not player then
			return false, S("No player.")
		end
		lzr_level_select.open_stats_dialog(player)
		return true
	end,
})
