-- LUALOCALS < ---------------------------------------------------------
local core, pairs, table, tostring, type
    = core, pairs, table, tostring, type
local table_concat
    = table.concat
-- LUALOCALS > ---------------------------------------------------------

------------------------------------------------------------------------
-- High-level UI/UX helpers for presenting standard interfaces and
-- interactions to a user
------------------------------------------------------------------------

local env = ...
local include = env.include
local S = env.S

------------------------------------------------------------------------

local modname = core.get_current_modname()
local util = include("util")

local ux = {}

------------------------------------------------------------------------
-- Connection Config UI

-- Get stored per-game connection config defaults, and save any changes.
function ux.config_defaults(set)
	local gameid = core.get_game_info().id
	local defaults = {
		url_target = "wss://archipelago.gg:54321",
		url_proxy = "http://localhost:9839",
		login_slotname = core.settings:get("name") .. "-" .. gameid,
		login_password = "",
	}
	local settings = core.settings
	local dirty
	for k in pairs(defaults) do
		local sk = modname .. "_" .. gameid .. "_" .. k
		if set and set[k] then
			defaults[k] = set[k]
			settings:set(sk, set[k])
			dirty = true
		elseif settings:has(sk) then
			defaults[k] = settings:get(sk)
		end
	end
	if dirty then settings:write() end
	return defaults
end

-- ux.configure(options) - access stored configuration
-- modstore - load/save options from this mod storage
-- prefix - override default mod storage key prefix
-- manager - arclib manager to configure
-- player - show formspec to this player (nil to only load)
do
	-- Fields that are allowed to be edited in config
	local okfields = {
		url_target = true,
		url_proxy = true,
		login_slotname = true,
		login_password = true,
		enabled = true
	}

	-- Apply config immediately to both manager and storage
	-- (with changes for edits, without just to rehydrate)
	local function applyconfig(options, changes)
		local conf, save = options.store("config")
		if changes then
			for k, v in pairs(changes) do conf[k] = v end
			save()
		end
		local manager = options.manager
		local dirty
		for k, v in pairs(conf) do
			if okfields[k] then
				dirty = dirty or manager[k] ~= v
				manager[k] = v
			end
		end
		if dirty then manager:close() end
		if conf.enabled then manager:open() end
		return conf
	end

	-- UI form management
	local hooked
	local sendform
	local function form_received(_, formname, fields)
		if not fields.enable and not fields.disable then return end
		local found = hooked[formname]
		if not found then return end
		hooked[formname] = nil
		fields.enabled = fields.enable and true or false
		return sendform(found, applyconfig(found, fields))
	end
	local fsesc = core.formspec_escape
	sendform = function(options, conf)
		if not options.player then return end

		local formname = table_concat({
				modname,
				core.get_us_time(),
				util.generate_uuid()
			}, "_")
		local parts = {}
		local function fsS(...) return fsesc(S(...)) end
		local function addpart(fieldtype, ...)
			local params = {...}
			for i = 1, #params do
				local v = params[i]
				if type(v) == "table" then v = table_concat(v, ",") end
				params[i] = tostring(v)
			end
			parts[#parts + 1] = fieldtype .. "[" .. table_concat(params, ";") .. "]"
		end
		local function addfield(pos, size, field, content)
			-- Use labels as "disabled" fields when connection is active
			-- (must disconnect to edit). Tweak positions to line content
			-- text up as closely as possible visually.
			if conf.enabled then
				pos[1] = pos[1] + 0.05
				pos[2] = pos[2] + 0.15
				return addpart("label", pos, content)
			else
				pos[2] = pos[2] - 0.25
				return addpart("field", pos, size, field, "", content)
			end
		end
		local values = ux.config_defaults(conf)
		addpart("formspec_version", 3)
		addpart("size", {8, 8.5})
		addpart("real_coordinates", true)
		addpart("label", {0.5, 0.5}, fsS("Archipelago Server URL"))
		addfield({0.5, 1}, {7, 0.8}, "url_target",
			fsesc(values.url_target))
		addpart("label", {0.5, 2}, fsS("Player Name"))
		addfield({0.5, 2.5}, {7, 0.8}, "login_slotname",
			fsesc(values.login_slotname))
		addpart("label", {0.5, 3.5}, fsS("Password"))
		addfield({0.5, 4}, {7, 0.8}, "login_password",
			conf.enabled and ((conf.login_password or "") ~= "" and " *** " or "")
			or fsesc(values.login_password))
		addpart("label", {0.5, 5}, fsS("WebSockProxy URL"))
		addfield({0.5, 5.5}, {7, 0.8}, "url_proxy",
			fsesc(values.url_proxy))
		addpart("button", {1.5, 7.5}, {3, 0.8},
			conf.enabled and "disable" or "enable",
			fsS(conf.enabled and "Disconnect" or "Save & Connect"))
		addpart("button_exit", {4.5, 7.5}, {3, 0.8}, "cancel", fsS("Cancel"))
		local formspec = table_concat(parts, "")
		core.show_formspec(options.player, formname, formspec)
		if not hooked then
			hooked = {}
			core.register_on_player_receive_fields(form_received)
		end
		hooked[formname] = options
	end

	-- Exported API
	function ux.configure(options)
		local manager = options.manager
		options = util.deepcopy(options)
		options.manager = manager

		options.store = util.structstore(options.modstore, options.prefix)

		options.player = options.player and type(options.player) ~= "string"
		and options.player:get_player_name() or options.player

		return sendform(options, applyconfig(options))
	end
end

------------------------------------------------------------------------

return ux
