local X=require(script.Parent.Promise)local Y={}Y.__index=Y function Y.new(X,w)local A=setmetatable({_signal=X,_conn=w,Connected=true},Y)return A end function Y.Disconnect(X)if X._conn then X._conn:Disconnect()X._conn=nil end if not X._signal then return end X.Connected=false local Y=X._signal._connections local w=table.find(Y,X)if w then local X=#Y Y[w]=Y[X]Y[X]=nil end X._signal=nil end function Y.IsConnected(X)if X._conn then return X._conn.Connected end return false end Y.Destroy=Y.Disconnect local w={}w.__index=w function w.new(X)local Y=setmetatable({_bindable=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116");_connections={};_args={},_threads=0,_id=0},w)if X then X:GiveTask(Y)end return Y end function w.Proxy(X,Y)assert(typeof(X)=="\082\066\088\083\099\114\105\112\116\083\105\103\110\097\108","\065\114\103\117\109\101\110\116 \035\049 \109\117\115\116 \098\101 \111\102 \116\121\112\101 \082\066\088\083\099\114\105\112\116\083\105\103\110\097\108")local A=w.new(Y)A:_setProxy(X)return A end function w.Is(X)return type(X)=="\116\097\098\108\101"and getmetatable(X)==w end function w._setProxy(Y,X)assert(typeof(X)=="\082\066\088\083\099\114\105\112\116\083\105\103\110\097\108","\065\114\103\117\109\101\110\116 \035\049 \109\117\115\116 \098\101 \111\102 \116\121\112\101 \082\066\088\083\099\114\105\112\116\083\105\103\110\097\108")Y:_clearProxy()Y._proxyHandle=X:Connect(function(...)Y:Fire(...)end)end function w._clearProxy(X)if X._proxyHandle then X._proxyHandle:Disconnect()X._proxyHandle=nil end end function w.Fire(X,...)local Y=#X._connections+X._threads if Y==0 then return end local w=X._id X._id+=1 X._args[w]={Y;{n=select("\035",...);...}}X._threads=0 X._bindable:Fire(w)end function w.Wait(X)X._threads+=1 local Y=X._bindable.Event:Wait()local w=X._args[Y]w[1]-=1 if w[1]<=0 then X._args[Y]=nil end return table.unpack(w[2],1,w[2].n)end function w.WaitPromise(Y)return X.new(function(X)X(Y:Wait())end)end function w.Connect(w,X)local A=Y.new(w,w._bindable.Event:Connect(function(Y)local A=w._args[Y]A[1]-=1 if A[1]<=0 then w._args[Y]=nil end X(table.unpack(A[2],1,A[2].n))end))table.insert(w._connections,A)return A end function w.DisconnectAll(X)for X,Y in ipairs(X._connections)do if Y._conn then Y._conn:Disconnect()end end X._connections={}X._args={}end function w.Destroy(X)X:DisconnectAll()X:_clearProxy()X._bindable:Destroy()end return w
-- Signal
-- Stephen Leitnick
-- Based off of Anaminus' Signal class: https://gist.github.com/Anaminus/afd813efc819bad8e560caea28942010

--[[

	signal = Signal.new([maid: Maid])
	signal = Signal.Proxy(rbxSignal: RBXScriptSignal [, maid: Maid])

	Signal.Is(object: any): boolean

	signal:Fire(...)
	signal:Wait()
	signal:WaitPromise()
	signal:Destroy()
	signal:DisconnectAll()

	connection = signal:Connect((...) -> void)

	connection:Disconnect()
	connection:IsConnected()

--]]

local Promise = require(script.Parent.Promise)

local Connection = {}
Connection.__index = Connection

function Connection.new(signal, connection)
	local self = setmetatable({
		_signal = signal;
		_conn = connection;
		Connected = true;
	}, Connection)
	return self
end

function Connection:Disconnect()
	if (self._conn) then
		self._conn:Disconnect()
		self._conn = nil
	end
	if (not self._signal) then return end
	self.Connected = false
	local connections = self._signal._connections
	local connectionIndex = table.find(connections, self)
	if (connectionIndex) then
		local n = #connections
		connections[connectionIndex] = connections[n]
		connections[n] = nil
	end
	self._signal = nil
end

function Connection:IsConnected()
	if (self._conn) then
		return self._conn.Connected
	end
	return false
end

Connection.Destroy = Connection.Disconnect

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

local Signal = {}
Signal.__index = Signal


function Signal.new(maid)
	local self = setmetatable({
		_bindable = Instance.new("BindableEvent");
		_connections = {};
		_args = {};
		_threads = 0;
		_id = 0;
	}, Signal)
	if (maid) then
		maid:GiveTask(self)
	end
	return self
end


function Signal.Proxy(rbxScriptSignal, maid)
	assert(typeof(rbxScriptSignal) == "RBXScriptSignal", "Argument #1 must be of type RBXScriptSignal")
	local signal = Signal.new(maid)
	signal:_setProxy(rbxScriptSignal)
	return signal
end


function Signal.Is(obj)
	return (type(obj) == "table" and getmetatable(obj) == Signal)
end


function Signal:_setProxy(rbxScriptSignal)
	assert(typeof(rbxScriptSignal) == "RBXScriptSignal", "Argument #1 must be of type RBXScriptSignal")
	self:_clearProxy()
	self._proxyHandle = rbxScriptSignal:Connect(function(...)
		self:Fire(...)
	end)
end


function Signal:_clearProxy()
	if (self._proxyHandle) then
		self._proxyHandle:Disconnect()
		self._proxyHandle = nil
	end
end


function Signal:Fire(...)
	local totalListeners = (#self._connections + self._threads)
	if (totalListeners == 0) then return end
	local id = self._id
	self._id += 1
	self._args[id] = {totalListeners, {n = select("#", ...), ...}}
	self._threads = 0
	self._bindable:Fire(id)
end


function Signal:Wait()
	self._threads += 1
	local id = self._bindable.Event:Wait()
	local args = self._args[id]
	args[1] -= 1
	if (args[1] <= 0) then
		self._args[id] = nil
	end
	return table.unpack(args[2], 1, args[2].n)
end


function Signal:WaitPromise()
	return Promise.new(function(resolve)
		resolve(self:Wait())
	end)
end


function Signal:Connect(handler)
	local connection = Connection.new(self, self._bindable.Event:Connect(function(id)
		local args = self._args[id]
		args[1] -= 1
		if (args[1] <= 0) then
			self._args[id] = nil
		end
		handler(table.unpack(args[2], 1, args[2].n))
	end))
	table.insert(self._connections, connection)
	return connection
end


function Signal:DisconnectAll()
	for _,c in ipairs(self._connections) do
		if (c._conn) then
			c._conn:Disconnect()
		end
	end
	self._connections = {}
	self._args = {}
end


function Signal:Destroy()
	self:DisconnectAll()
	self:_clearProxy()
	self._bindable:Destroy()
end


return Signal
