-- LUALOCALS < ---------------------------------------------------------
local core, dump, math, pairs, string, table, tonumber, tostring, type,
      unpack
    = core, dump, math, pairs, string, table, tonumber, tostring, type,
      unpack
local math_random, string_format, string_gsub, table_concat
    = math.random, string.format, string.gsub, table.concat
-- LUALOCALS > ---------------------------------------------------------

------------------------------------------------------------------------
-- Low-level layer basic utilities (low dependencies)
------------------------------------------------------------------------

local modname = core.get_current_modname()
local util = {}

------------------------------------------------------------------------
-- Deep Copy

-- Should support aliases and cycles as well
local function deepcopy_core(src, map)
	if type(src) ~= "table" then return src end
	local found = map[src]
	if found then return found end
	local copy = {}
	map[src] = copy
	for k, v in pairs(src) do
		copy[deepcopy_core(k, map)] = deepcopy_core(v, map)
	end
	return copy
end
function util.deepcopy(src) return deepcopy_core(src, {}) end

------------------------------------------------------------------------
-- Event Emitter

-- Create the paired core functions of an event emitter
-- Return the individual functions like a mixin
function util.create_emitter()
	local callbacks = {}
	local function on(event, func)
		local cbs = callbacks[event]
		if not cbs then
			cbs = {}
			callbacks[event] = cbs
		end
		cbs[#cbs + 1] = func
	end
	-- returns truthy if there was a handler, else falsy
	local function emit(event, ...)
		local cbs = callbacks[event]
		if not cbs then return end
		for i = 1, #cbs do
			cbs[i](...)
		end
		if event then emit(false, event, ...) end
		return #cbs > 0 or nil
	end
	return on, emit
end

------------------------------------------------------------------------
-- Logging

-- Standardized logging with structured data support
function util.log(lv, ...)
	local t = {...}
	for i = 1, #t do
		if type(t[i]) == "table" then
			t[i] = string_gsub(dump(t), "\n", " ")
		end
	end
	return core.log(lv, string_gsub(table_concat(t, " "), "%s+", " "))
end

------------------------------------------------------------------------
-- Structured Mod Storage Wrapper

-- local get = util.structstore(modstore[, prefix])
-- local data, save = get(key)
-- save()
function util.structstore(modstore, prefix)
	local cache = {}
	return function(key)
		local fullkey = (prefix or (modname .. "_"
				.. core.get_game_info().id)) .. "_" .. key
		local found = cache[fullkey]
		if found then return unpack(found) end
		local s = modstore:get_string(fullkey)
		local data = s and core.deserialize(s) or {}
		local function save()
			modstore:set_string(fullkey, core.serialize(data))
		end
		cache[fullkey] = {data, save}
		return data, save
	end
end

------------------------------------------------------------------------
-- Generate UUID

-- UUID v4 format using the naive RNG
function util.generate_uuid()
	return string_gsub(string_gsub("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",
			"x", function() return string_format("%x", math_random(0, 15)) end),
		"y", function() return string_format("%x", math_random(0, 15)) end)
end

------------------------------------------------------------------------
-- Convert PrintJSON to Text

-- Lookup building helpers
local function mkidx(tbl, getkey, getvalue, out)
	out = out or {}
	for k, v in pairs(tbl) do
		out[getkey(k, v)] = getvalue(k, v)
	end
	return out
end
local function from_all_games(state, key)
	local out = {}
	local games = state and state.data_package and state.data_package.games
	if not games then return out end
	for gname, gdata in pairs(games) do
		out[gname] = mkidx(gdata[key],
			function(_, v) return v end,
			function(k) return k end)
	end
	return out
end

-- Define lookups by type
local build_lookups = {}
function build_lookups.item_id(state)
	return from_all_games(state, "item_name_to_id")
end
function build_lookups.location_id(state)
	return from_all_games(state, "location_name_to_id")
end
function build_lookups.player_id(state)
	return mkidx(state and state.players or {},
		function(k) return k end,
		function(k, v) return v and v.alias or v.name or k end)
end

-- Convert text using given connection state from
-- PrintJSON command payload data
function util.print_json_convert(state, data)
	local parts = {}
	local lookups = {}
	for i = 1, #data do
		local item = data[i]
		local text = item.text or ""
		local bl = item.type and build_lookups[item.type]
		if bl then
			local lut = lookups[item.type]
			if not lut then
				lut = bl(state)
				lookups[item.type] = lut
			end
			if item.player then
				local slotinfo = state and state.slot_info
				local slot = slotinfo and (slotinfo[item.player]
					or slotinfo[tostring(item.player)])
				local game = slot and slot.game
				lut = game and lut[game] or lut
			end
			local id = tonumber(text)
			text = id and lut[id] or text
		end
		parts[i] = text
	end
	return table_concat(parts)
end

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

return util
