local h={}h.Version=script.Parent.Version.Value h.Services={}h.Util=script.Parent.Util local e=Instance.new("\070\111\108\100\101\114")e.Name="\083\101\114\118\105\099\101\115"local A=require(h.Util.Promise)local q=require(h.Util.Signal)local E=require(h.Util.Loader)local o=require(h.Util.Ser)local J=require(h.Util.Remote.RemoteSignal)local R=require(h.Util.Remote.RemoteProperty)local V=require(h.Util.TableUtil)local y=false local r=false local u=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")local function N(h)local e=Instance.new("\070\111\108\100\101\114")e.Name=h return e end local function D(h,e)local A=h:FindFirstChild(e)if not A then A=Instance.new("\070\111\108\100\101\114")A.Name=e A.Parent=h end return A end local function F(h,A,q)if q then A.Parent=D(h._knit_rep_folder,q)elseif A:IsA("\082\101\109\111\116\101\070\117\110\099\116\105\111\110")then A.Parent=D(h._knit_rep_folder,"\082\070")elseif A:IsA("\082\101\109\111\116\101\069\118\101\110\116")then A.Parent=D(h._knit_rep_folder,"\082\069")elseif A:IsA("\086\097\108\117\101\066\097\115\101")then A.Parent=D(h._knit_rep_folder,"\082\080")else error("\073\110\118\097\108\105\100 \114\101\112 \111\098\106\101\099\116\058 "..A.ClassName)end if not h._knit_rep_folder.Parent then h._knit_rep_folder.Parent=e end end function h.IsService(h)return type(h)=="\116\097\098\108\101"and h._knit_is_service==true end function h.CreateService(e)assert(type(e)=="\116\097\098\108\101","\083\101\114\118\105\099\101 \109\117\115\116 \098\101 \097 \116\097\098\108\101\059 \103\111\116 "..type(e))assert(type(e.Name)=="\115\116\114\105\110\103","\083\101\114\118\105\099\101\046\078\097\109\101 \109\117\115\116 \098\101 \097 \115\116\114\105\110\103\059 \103\111\116 "..type(e.Name))assert(#e.Name>0,"\083\101\114\118\105\099\101\046\078\097\109\101 \109\117\115\116 \098\101 \097 \110\111\110-\101\109\112\116\121 \115\116\114\105\110\103")assert(h.Services[e.Name]==nil,"\083\101\114\118\105\099\101 \""..(e.Name.."\" \097\108\114\101\097\100\121 \101\120\105\115\116\115"))e=V.Assign(e,{_knit_is_service=true,_knit_rf={},_knit_re={},_knit_rp={},_knit_rep_folder=N(e.Name)})if type(e.Client)~="\116\097\098\108\101"then e.Client={Server=e}else if e.Client.Server~=e then e.Client.Server=e end end h.Services[e.Name]=e return e end function h.AddServices(h)return E.LoadChildren(h)end function h.AddServicesDeep(h)return E.LoadDescendants(h)end function h.GetService(e)assert(type(e)=="\115\116\114\105\110\103","\083\101\114\118\105\099\101\078\097\109\101 \109\117\115\116 \098\101 \097 \115\116\114\105\110\103\059 \103\111\116 "..type(e))return assert(h.Services[e],"\067\111\117\108\100 \110\111\116 \102\105\110\100 \115\101\114\118\105\099\101 \""..(e.."\""))end function h.BindRemoteEvent(h,e,A)assert(h._knit_re[e]==nil,"\082\101\109\111\116\101\069\118\101\110\116 \""..(e.."\" \097\108\114\101\097\100\121 \101\120\105\115\116\115"))local q=A._remote q.Name=e h._knit_re[e]=q F(h,q)end function h.BindRemoteFunction(h,e,A)assert(h._knit_rf[e]==nil,"\082\101\109\111\116\101\070\117\110\099\116\105\111\110 \""..(e.."\" \097\108\114\101\097\100\121 \101\120\105\115\116\115"))local q=Instance.new("\082\101\109\111\116\101\070\117\110\099\116\105\111\110")q.Name=e h._knit_rf[e]=q F(h,q)function q.OnServerInvoke(...)return o.SerializeArgsAndUnpack(A(h.Client,o.DeserializeArgsAndUnpack(...)))end end function h.BindRemoteProperty(h,e,A)assert(h._knit_rp[e]==nil,"\082\101\109\111\116\101\080\114\111\112\101\114\116\121 \""..(e.."\" \097\108\114\101\097\100\121 \101\120\105\115\116\115"))A._object.Name=e h._knit_rp[e]=A F(h,A._object,"\082\080")end function h.Start()if y then return A.Reject("\075\110\105\116 \097\108\114\101\097\100\121 \115\116\097\114\116\101\100")end y=true local E=h.Services return(A.new(function(e)for e,A in pairs(E)do for e,E in pairs(A.Client)do if type(E)=="\102\117\110\099\116\105\111\110"then h.BindRemoteFunction(A,e,E)elseif J.Is(E)then h.BindRemoteEvent(A,e,E)elseif R.Is(E)then h.BindRemoteProperty(A,e,E)elseif q.Is(E)then warn("\070\111\117\110\100 \083\105\103\110\097\108 \105\110\115\116\101\097\100 \111\102 \082\101\109\111\116\101\083\105\103\110\097\108 \040\075\110\105\116\046\085\116\105\108\046\082\101\109\111\116\101\083\105\103\110\097\108\041\046 \080\108\101\097\115\101 \099\104\097\110\103\101 \116\111 \082\101\109\111\116\101\083\105\103\110\097\108\046 \091"..(A.Name..("\046\067\108\105\101\110\116\046"..(e.."\093"))))end end end local o={}for h,e in pairs(E)do if type(e.KnitInit)=="\102\117\110\099\116\105\111\110"then table.insert(o,A.new(function(h)e:KnitInit()h()end))end end e(A.All(o))end)):Then(function()for h,e in pairs(E)do if type(e.KnitStart)=="\102\117\110\099\116\105\111\110"then task.spawn(e.KnitStart,e)end end r=true u:Fire()task.defer(function()u:Destroy()end)e.Parent=script.Parent end)end function h.OnStart()if r then return A.Resolve()else return A.FromEvent(u.Event)end end return h



local KnitServer = {}

KnitServer.Version = script.Parent.Version.Value
KnitServer.Services = {}
KnitServer.Util = script.Parent.Util


local knitRepServiceFolder = Instance.new("Folder")
knitRepServiceFolder.Name = "Services"

local Promise = require(KnitServer.Util.Promise)
local Signal = require(KnitServer.Util.Signal)
local Loader = require(KnitServer.Util.Loader)
local Ser = require(KnitServer.Util.Ser)
local RemoteSignal = require(KnitServer.Util.Remote.RemoteSignal)
local RemoteProperty = require(KnitServer.Util.Remote.RemoteProperty)
local TableUtil = require(KnitServer.Util.TableUtil)

local started = false
local startedComplete = false
local onStartedComplete = Instance.new("BindableEvent")


local function CreateRepFolder(serviceName)
	local folder = Instance.new("Folder")
	folder.Name = serviceName
	return folder
end


local function GetFolderOrCreate(parent, name)
	local f = parent:FindFirstChild(name)
	if (not f) then
		f = Instance.new("Folder")
		f.Name = name
		f.Parent = parent
	end
	return f
end


local function AddToRepFolder(service, remoteObj, folderOverride)
	if (folderOverride) then
		remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, folderOverride)
	elseif (remoteObj:IsA("RemoteFunction")) then
		remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, "RF")
	elseif (remoteObj:IsA("RemoteEvent")) then
		remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, "RE")
	elseif (remoteObj:IsA("ValueBase")) then
		remoteObj.Parent = GetFolderOrCreate(service._knit_rep_folder, "RP")
	else
		error("Invalid rep object: " .. remoteObj.ClassName)
	end
	if (not service._knit_rep_folder.Parent) then
		service._knit_rep_folder.Parent = knitRepServiceFolder
	end
end


function KnitServer.IsService(object)
	return type(object) == "table" and object._knit_is_service == true
end


function KnitServer.CreateService(service)
	assert(type(service) == "table", "Service must be a table; got " .. type(service))
	assert(type(service.Name) == "string", "Service.Name must be a string; got " .. type(service.Name))
	assert(#service.Name > 0, "Service.Name must be a non-empty string")
	assert(KnitServer.Services[service.Name] == nil, "Service \"" .. service.Name .. "\" already exists")
	service = TableUtil.Assign(service, {
		_knit_is_service = true;
		_knit_rf = {};
		_knit_re = {};
		_knit_rp = {};
		_knit_rep_folder = CreateRepFolder(service.Name);
	})
	if (type(service.Client) ~= "table") then
		service.Client = {Server = service}
	else
		if (service.Client.Server ~= service) then
			service.Client.Server = service
		end
	end
	KnitServer.Services[service.Name] = service
	return service
end


function KnitServer.AddServices(folder)
	return Loader.LoadChildren(folder)
end


function KnitServer.AddServicesDeep(folder)
	return Loader.LoadDescendants(folder)
end


function KnitServer.GetService(serviceName)
	assert(type(serviceName) == "string", "ServiceName must be a string; got " .. type(serviceName))
	return assert(KnitServer.Services[serviceName], "Could not find service \"" .. serviceName .. "\"")
end


function KnitServer.BindRemoteEvent(service, eventName, remoteEvent)
	assert(service._knit_re[eventName] == nil, "RemoteEvent \"" .. eventName .. "\" already exists")
	local re = remoteEvent._remote
	re.Name = eventName
	service._knit_re[eventName] = re
	AddToRepFolder(service, re)
end


function KnitServer.BindRemoteFunction(service, funcName, func)
	assert(service._knit_rf[funcName] == nil, "RemoteFunction \"" .. funcName .. "\" already exists")
	local rf = Instance.new("RemoteFunction")
	rf.Name = funcName
	service._knit_rf[funcName] = rf
	AddToRepFolder(service, rf)
	function rf.OnServerInvoke(...)
		return Ser.SerializeArgsAndUnpack(func(service.Client, Ser.DeserializeArgsAndUnpack(...)))
	end
end


function KnitServer.BindRemoteProperty(service, propName, prop)
	assert(service._knit_rp[propName] == nil, "RemoteProperty \"" .. propName .. "\" already exists")
	prop._object.Name = propName
	service._knit_rp[propName] = prop
	AddToRepFolder(service, prop._object, "RP")
end


function KnitServer.Start()

	if (started) then
		return Promise.Reject("Knit already started")
	end

	started = true

	local services = KnitServer.Services

	return Promise.new(function(resolve)

		-- Bind remotes:
		for _,service in pairs(services) do
			for k,v in pairs(service.Client) do
				if (type(v) == "function") then
					KnitServer.BindRemoteFunction(service, k, v)
				elseif (RemoteSignal.Is(v)) then
					KnitServer.BindRemoteEvent(service, k, v)
				elseif (RemoteProperty.Is(v)) then
					KnitServer.BindRemoteProperty(service, k, v)
				elseif (Signal.Is(v)) then
					warn("Found Signal instead of RemoteSignal (Knit.Util.RemoteSignal). Please change to RemoteSignal. [" .. service.Name .. ".Client." .. k .. "]")
				end
			end
		end

		-- Init:
		local promisesInitServices = {}
		for _,service in pairs(services) do
			if (type(service.KnitInit) == "function") then
				table.insert(promisesInitServices, Promise.new(function(r)
					service:KnitInit()
					r()
				end))
			end
		end

		resolve(Promise.All(promisesInitServices))

	end):Then(function()

		-- Start:
		for _,service in pairs(services) do
			if (type(service.KnitStart) == "function") then
				task.spawn(service.KnitStart, service)
			end
		end

		startedComplete = true
		onStartedComplete:Fire()

		task.defer(function()
			onStartedComplete:Destroy()
		end)

		-- Expose service remotes to everyone:
		knitRepServiceFolder.Parent = script.Parent

	end)

end


function KnitServer.OnStart()
	if (startedComplete) then
		return Promise.Resolve()
	else
		return Promise.FromEvent(onStartedComplete.Event)
	end
end


return KnitServer
