----- Private functions -----

local function GetWriteLibFunctionsRecursive(list_table, pointer, name_stack)
	for key, value in pairs(pointer) do
		if type(value) == "table" then
			GetWriteLibFunctionsRecursive(list_table, value, name_stack .. key .. ".")
		elseif type(value) == "function" then
			table.insert(list_table, {name_stack .. key, value})
		else
			error("[ReplicaController]: Invalid write function value \"" .. tostring(value) .. "\" (" .. typeof(value) .. "); name_stack = \"" .. name_stack .. "\"")
		end
	end
end

local function LoadWriteLib(write_lib_module)
	local get_write_lib_pack = LoadedWriteLibPacks[write_lib_module]
	if get_write_lib_pack ~= nil then
		return get_write_lib_pack
	end

	local write_lib_raw = require(write_lib_module)

	local function_list = {} -- func_id = {func_name, func}

	GetWriteLibFunctionsRecursive(function_list, write_lib_raw, "")
	table.sort(function_list, function(item1, item2)
		return item1[1] < item2[1] -- Sort functions by their names - this creates a consistent indexing on server and client-side
	end)

	local write_lib = {} -- {[func_id] = function, ...}
	local write_lib_dictionary = {} -- {["function_name"] = func_id, ...}

	for func_id, func_params in ipairs(function_list) do
		write_lib[func_id] = func_params[2]
		write_lib_dictionary[func_params[1]] = func_id
	end

	local write_lib_pack = {write_lib, write_lib_dictionary}

	LoadedWriteLibPacks[write_lib_module] = write_lib_pack

	return write_lib_pack
end

local function StringPathToArray(path)
	local path_array = {}
	if path ~= "" then
		for s in string.gmatch(path, "[^%.]+") do
			table.insert(path_array, s)
		end
	end
	return path_array
end

local function DestroyReplicaAndDescendantsRecursive(replica, not_first_in_stack)
	-- Scan children replicas:
	for _, child in ipairs(replica.Children) do
		DestroyReplicaAndDescendantsRecursive(child, true)
	end

	local id = replica.Id
	-- Clear replica entry:
	Replicas[id] = nil
	-- Cleanup:
	replica._maid:Cleanup()
	-- Clear from children table of top parent replica:
	if not_first_in_stack ~= true then
		if replica.Parent ~= nil then
			local children = replica.Parent.Children
			table.remove(children, table.find(children, replica))
		end
	end
	-- Clear child listeners:
	ChildListeners[id] = nil
end

local function CreateTableListenerPathIndex(replica, path_array, listener_type)
	-- Getting listener table:
	local listeners = replica._table_listeners
	-- Getting and or creating the structure nescessary to index the path for the listened key:
	for i = 1, #path_array do
		local key_listeners = listeners[1][path_array[i]]
		if key_listeners == nil then
			key_listeners = {[1] = {}}
			listeners[1][path_array[i]] = key_listeners
		end
		listeners = key_listeners
	end

	local listener_type_table = listeners[listener_type]
	if listener_type_table == nil then
		listener_type_table = {}
		listeners[listener_type] = listener_type_table
	end
	return listener_type_table
end

local function CleanTableListenerTable(disconnect_param)
	local table_listeners = disconnect_param[1]
	local path_array = disconnect_param[2]
	local pointer = table_listeners
	local pointer_stack = {pointer}
	for i = 1, #path_array do
		pointer = pointer[1][path_array[i]]
		table.insert(pointer_stack, pointer)
	end
	for i = #pointer_stack, 2, -1 do
		local listeners = pointer_stack[i]
		if next(listeners[1]) ~= nil then
			return -- Lower branches exist for this branch - this branch will not need further cleanup
		end
		for k = 2, 6 do
			if listeners[k] ~= nil then
				if #listeners[k] > 0 then
					return -- A listener exists - this branch will not need further cleanup
				end
			end
		end
		pointer_stack[i - 1][1][path_array[i - 1]] = nil -- Clearing listeners table for this branch
	end
end

local function CreateReplicaBranch(replica_entries, created_replicas) --> created_replicas: {replica, ...}
	-- Sorting replica entries:
	-- replica_entries = {[replica_id_string] = {replica_class, replica_tags, data_table, parent_id / 0, write_lib_module / nil}, ...}
	local sorted_replica_entries = {} -- {replica_class, replica_tags, data_table, parent_id / 0, write_lib_module / nil, replica_id}, ...}
	for replica_id_string, replica_entry in pairs(replica_entries) do
		replica_entry[6] = tonumber(replica_id_string)
		table.insert(sorted_replica_entries, replica_entry)
	end
	table.sort(sorted_replica_entries, function(a, b)
		return a[6] < b[6]
	end)
	local waiting_for_parent = {} -- [parent_replica_id] = {replica, ...}
	created_replicas = created_replicas or {}
	for _, replica_entry in ipairs(sorted_replica_entries) do
		local replica_id = replica_entry[6]
		-- Fetching replica parent:
		local parent_id = replica_entry[4]
		local parent = nil
		local wait_for_parent = false
		if parent_id ~= 0 then
			parent = Replicas[parent_id]
			if parent == nil then
				wait_for_parent = true
			end
		end
		-- Fetching replica write_lib:
		local write_lib, write_lib_dictionary
		if replica_entry[5] ~= nil then
			local write_lib_pack = LoadWriteLib(replica_entry[5])
			write_lib = write_lib_pack[1]
			write_lib_dictionary = write_lib_pack[2]
		end
		-- New Replica object table:
		local replica = {
			Data = replica_entry[3],
			Id = replica_id,
			Class = replica_entry[1],
			Tags = replica_entry[2],

			Parent = parent,
			Children = {},

			_write_lib = write_lib,
			_write_lib_dictionary = write_lib_dictionary,

			_table_listeners = {[1] = {}},
			_function_listeners = {},
			_raw_listeners = {},

			_signal_listeners = {},
			_maid = MadworkMaid.NewMaid(),
		}
		setmetatable(replica, Replica)
		-- Setting as child to parent:
		if parent ~= nil then
			table.insert(parent.Children, replica)
		elseif wait_for_parent == true then
			local wait_table = waiting_for_parent[parent_id]
			if wait_table == nil then
				wait_table = {}
				waiting_for_parent[parent_id] = wait_table
			end
			table.insert(wait_table, replica)
		end
		-- Adding replica to replica list:
		Replicas[replica_id] = replica
		table.insert(created_replicas, replica)
		-- Checking replicas waiting for parent:
		local children_waiting = waiting_for_parent[replica_id]
		if children_waiting ~= nil then
			waiting_for_parent[replica_id] = nil
			for _, child_replica in ipairs(children_waiting) do
				child_replica.Parent = replica
				table.insert(replica.Children, child_replica)
			end
		end
	end
	if next(waiting_for_parent) ~= nil then
		-- An error occured while replicating an replica branch.
		local error_string = "[ReplicaService]: BRANCH REPLICATION ERROR - Missing parents: "
		for parent_replica_id, child_replicas in pairs(waiting_for_parent) do
			error_string = error_string .. "[" .. tostring(parent_replica_id) .. "]: {"
			for k, replica in ipairs(child_replicas) do
				error_string = error_string .. (k == 1 and "" or ", ") .. replica:Identify()
			end
			error_string = error_string .. "}; "
		end
		error(error_string)
	end
	return created_replicas
end