local o=import("\046\046\047\097\115\115\105\103\110")local N=import("\046\046\047\073\110\115\116\097\110\099\101\080\114\111\112\101\114\116\121")local a=import("\046\046\047\083\105\103\110\097\108")local G=import("\046\046\047\116\121\112\101\075\101\121")local function i(o)local N=getmetatable(o)return N and N.instance~=nil end local w={}w.options={creatable=false}w.name="\073\110\115\116\097\110\099\101"w.properties={}w.properties.Name=N.normal({getDefault=function(o)return(getmetatable(o)).class.name end})w.properties.ClassName=N.readOnly({getDefault=function(o)return(getmetatable(o)).class.name end})w.properties.AncestryChanged=N.readOnly({getDefault=function()return a.new()end})w.properties.Changed=N.readOnly({getDefault=function()return a.new()end})w.properties.ChildAdded=N.readOnly({getDefault=a.new})w.properties.ChildRemoved=N.readOnly({getDefault=a.new})w.properties.Parent=N.normal({set=function(o,N,a)local G=(getmetatable(o)).instance if G.destroyed then error("\065\116\116\101\109\112\116 \116\111 \115\101\116 \112\097\114\101\110\116 \097\102\116\101\114 \098\101\105\110\103 \100\101\115\116\114\111\121\101\100\033")end if G.properties.Parent==a then return end if a~=nil and not i(a)then error(string.format("\067\097\110\'\116 \115\101\116 \080\097\114\101\110\116 \116\111 \037\113\059 \080\097\114\101\110\116 \109\117\115\116 \098\101 \097\110 \073\110\115\116\097\110\099\101\033"),tostring(a))end if G.properties.Parent~=nil then(getmetatable(G.properties.Parent)).instance.children[o]=nil G.properties.Parent.ChildRemoved:Fire(o)end G.properties.Parent=a if a~=nil then(getmetatable(a)).instance.children[o]=true a.ChildAdded:Fire(o)end o:_PropagateAncestryChanged(o,a)end})w.prototype={}function w.prototype.ClearAllChildren(o)local N=(getmetatable(o)).instance.children for o in pairs(N)do o:Destroy()end end function w.prototype.FindFirstAncestor(N,o)local a=N.Parent while a do if a.Name==o then return a end a=a.Parent end end function w.prototype.FindFirstAncestorOfClass(N,o)local a=N.Parent while a do if a.ClassName==o then return a end a=a.Parent end end function w.prototype.FindFirstAncestorWhichIsA(N,o)local a=N.Parent while a do if a:IsA(o)then return a end a=a.Parent end end function w.prototype.FindFirstChild(N,o)local a=(getmetatable(N)).instance.children for N in pairs(a)do if N.Name==o then return N end end return nil end function w.prototype.FindFirstChildOfClass(N,o)local a=(getmetatable(N)).instance.children for N in pairs(a)do if N.ClassName==o then return N end end return nil end function w.prototype.FindFirstChildWhichIsA(N,o)local a=(getmetatable(N)).instance.children for N in pairs(a)do if N:IsA(o)then return N end end return nil end function w.prototype.GetChildren(o)local N=(getmetatable(o)).instance.children local a={}for o in pairs(N)do table.insert(a,o)end return a end function w.prototype.GetDescendants(o)local N={}local a={}local G=o while G do local o=G:GetChildren()for o,G in pairs(o)do a[#a+1]=G N[#N+1]=G end G=N[#N]N[#N]=nil end return a end function w.prototype.IsA(N,o)local a=(getmetatable(N)).class while a~=nil do if a.name==o then return true end a=a.super end return false end function w.prototype.IsDescendantOf(N,o)local a=N repeat a=a.Parent until a==nil or a==o return a==o end function w.prototype.Destroy(o)o:ClearAllChildren()if o.Parent~=nil then o.Parent=nil end o:_DisconnectAllChangedListeners();(getmetatable(o)).instance.destroyed=true end function w.prototype.GetPropertyChangedSignal(N,o)local G=(getmetatable(N)).class.properties local i=(getmetatable(N)).instance.propertySignals local w=i[o]if not w then assert(G[o],o.." \105\115 \110\111\116 \097 \118\097\108\105\100 \112\114\111\112\101\114\116\121 \110\097\109\101\046")w=a.new()i[o]=w end return w end function w.prototype.GetFullName(o)local N=o.Name local a=o.Parent while a and(getmetatable(a)).class.name~="\068\097\116\097\077\111\100\101\108"do N=a.Name..("\046"..N)a=a.Parent end return N end function w.prototype.WaitForChild(a,o,N)return a:FindFirstChild(o)end function w.prototype._DisconnectAllChangedListeners(o)local N=(getmetatable(o)).instance.propertySignals for o,N in pairs(N)do N:_DisconnectAllListeners()end o.Changed:_DisconnectAllListeners()end function w.prototype._PropagateAncestryChanged(a,o,N)a.AncestryChanged:Fire(o,N)local G=(getmetatable(a)).instance.children for a in pairs(G)do a:_PropagateAncestryChanged(o,N)end end w.metatable={}w.metatable[G]="\073\110\115\116\097\110\099\101"function w.metatable.__index(o,N)local a=(getmetatable(o)).class if a.properties[N]then return a.properties[N].get(o,N)end if a.prototype[N]then return a.prototype[N]end local G=o:FindFirstChild(N)if G then return G end error(string.format("\037\113 \105\115 \110\111\116 \097 \118\097\108\105\100 \109\101\109\098\101\114 \111\102 \037\115",tostring(N),o.ClassName),2)end function w.metatable.__newindex(o,N,a)local G=(getmetatable(o)).class if G.properties[N]then G.properties[N].set(o,N,a)o.Changed:Fire(N)local i=(getmetatable(o)).instance.propertySignals[N]if i then i:Fire()end return end error(string.format("\037\113 \105\115 \110\111\116 \097 \118\097\108\105\100 \109\101\109\098\101\114 \111\102 \037\115",tostring(N),o.ClassName),2)end function w.metatable.__tostring(o)return o.Name end function w.new(N,...)local a={destroyed=false;properties={};propertySignals={};children={}}local G=newproxy(true)o(getmetatable(G),N.metatable);(getmetatable(G)).instance=a;(getmetatable(G)).class=N for o,N in pairs(N.properties)do a.properties[o]=N.getDefault(G)end N:init(G,...)return G end function w.init(N,o,...) end function w.extend(G,N,a)assert(type(N)=="\115\116\114\105\110\103","\069\120\112\101\099\116\101\100 \115\116\114\105\110\103 \'\110\097\109\101\' \097\115 \097\114\103\117\109\101\110\116 \035\049\046")assert(type(a)=="\116\097\098\108\101"or a==nil,"\069\120\112\101\099\116\101\100 \111\112\116\105\111\110\097\108 \116\097\098\108\101 \'\111\112\116\105\111\110\115\' \097\115 \097\114\103\117\109\101\110\116 \035\050\046")local i=o({},G)i.name=N i.super=G i.properties=o({},G.properties)i.prototype=o({},G.prototype)i.metatable=o({},G.metatable)i.options=o({},G.options,a or{})return i end return w
--[[
	Provides a base implementation for all Instances in Lemur.

	When adding a new instance, you can define:
	* properties, using helpers in InstanceProperty
	* prototype, used for defining methods and static values
	* init, called by the class's constructor
]]

local assign = import("../assign")
local InstanceProperty = import("../InstanceProperty")
local Signal = import("../Signal")
local typeKey = import("../typeKey")

local function isInstance(value)
	local metatable = getmetatable(value)

	return metatable and metatable.instance ~= nil
end

local BaseInstance = {}

BaseInstance.options = {
	creatable = false,
}

BaseInstance.name = "Instance"

BaseInstance.properties = {}

BaseInstance.properties.Name = InstanceProperty.normal({
	getDefault = function(self)
		return getmetatable(self).class.name
	end,
})

BaseInstance.properties.ClassName = InstanceProperty.readOnly({
	getDefault = function(self)
		return getmetatable(self).class.name
	end,
})

BaseInstance.properties.AncestryChanged = InstanceProperty.readOnly({
	getDefault = function()
		return Signal.new()
	end,
})

BaseInstance.properties.Changed = InstanceProperty.readOnly({
	getDefault = function()
		return Signal.new()
	end,
})

BaseInstance.properties.ChildAdded = InstanceProperty.readOnly({
	getDefault = Signal.new
})

BaseInstance.properties.ChildRemoved = InstanceProperty.readOnly({
	getDefault = Signal.new
})

BaseInstance.properties.Parent = InstanceProperty.normal({
	set = function(self, key, value)
		local instance = getmetatable(self).instance

		if instance.destroyed then
			error("Attempt to set parent after being destroyed!")
		end

		if instance.properties.Parent == value then
			return
		end

		if value ~= nil and not isInstance(value) then
			error(string.format("Can't set Parent to %q; Parent must be an Instance!"), tostring(value))
		end

		if instance.properties.Parent ~= nil then
			getmetatable(instance.properties.Parent).instance.children[self] = nil
			instance.properties.Parent.ChildRemoved:Fire(self)
		end

		instance.properties.Parent = value

		if value ~= nil then
			getmetatable(value).instance.children[self] = true
			value.ChildAdded:Fire(self)
		end

		self:_PropagateAncestryChanged(self, value)
	end,
})

BaseInstance.prototype = {}

function BaseInstance.prototype:ClearAllChildren()
	local children = getmetatable(self).instance.children

	for child in pairs(children) do
		child:Destroy()
	end
end

function BaseInstance.prototype:FindFirstAncestor(name)
	local level = self.Parent

	while level do
		if level.Name == name then
			return level
		end

		level = level.Parent
	end
end

function BaseInstance.prototype:FindFirstAncestorOfClass(name)
	local level = self.Parent

	while level do
		if level.ClassName == name then
			return level
		end

		level = level.Parent
	end
end

function BaseInstance.prototype:FindFirstAncestorWhichIsA(className)
	local level = self.Parent

	while level do
		if level:IsA(className) then
			return level
		end

		level = level.Parent
	end
end

function BaseInstance.prototype:FindFirstChild(name)
	local children = getmetatable(self).instance.children

	-- Search for existing children
	-- This is a set stored by child instead of by name, since names are not unique.
	for child in pairs(children) do
		if child.Name == name then
			return child
		end
	end

	return nil
end

function BaseInstance.prototype:FindFirstChildOfClass(className)
	local children = getmetatable(self).instance.children

	-- Search for existing children
	-- This is a set stored by child instead of by name, since names are not unique.
	for child in pairs(children) do
		if child.ClassName == className then
			return child
		end
	end

	return nil
end

function BaseInstance.prototype:FindFirstChildWhichIsA(className)
	local children = getmetatable(self).instance.children

	-- Search for existing children
	-- This is a set stored by child instead of by name, since names are not unique.
	for child in pairs(children) do
		if child:IsA(className) then
			return child
		end
	end

	return nil
end

function BaseInstance.prototype:GetChildren()
	local children = getmetatable(self).instance.children
	local result = {}

	for child in pairs(children) do
		table.insert(result, child)
	end

	return result
end

function BaseInstance.prototype:GetDescendants()
	local stack = {}
	local descendants = {}
	local current = self

	while current do
		local children = current:GetChildren()

		for _, child in pairs(children) do
			descendants[#descendants + 1] = child
			stack[#stack + 1] = child
		end

		current = stack[#stack]
		stack[#stack] = nil
	end

	return descendants
end

function BaseInstance.prototype:IsA(className)
	local currentClass = getmetatable(self).class

	while currentClass ~= nil do
		if currentClass.name == className then
			return true
		end

		currentClass = currentClass.super
	end

	return false
end

function BaseInstance.prototype:IsDescendantOf(object)
	local parent = self

	repeat
		parent = parent.Parent
	until parent == nil or parent == object

	return parent == object
end

function BaseInstance.prototype:Destroy()
	self:ClearAllChildren()

	if self.Parent ~= nil then
		self.Parent = nil
	end

	self:_DisconnectAllChangedListeners()

	getmetatable(self).instance.destroyed = true
end

function BaseInstance.prototype:GetPropertyChangedSignal(key)
	local properties = getmetatable(self).class.properties
	local propertySignals = getmetatable(self).instance.propertySignals

	local listener = propertySignals[key]

	if not listener then
		assert(properties[key], key .. " is not a valid property name.")

		listener = Signal.new()
		propertySignals[key] = listener
	end

	return listener
end

function BaseInstance.prototype:GetFullName()
	local name = self.Name
	local level = self.Parent

	while level and getmetatable(level).class.name ~= "DataModel" do
		name = level.Name .. "." .. name
		level = level.Parent
	end

	return name
end

function BaseInstance.prototype:WaitForChild(name, delay)
	return self:FindFirstChild(name)
end

function BaseInstance.prototype:_DisconnectAllChangedListeners()
	local propertySignals = getmetatable(self).instance.propertySignals

	for _, signal in pairs(propertySignals) do
		signal:_DisconnectAllListeners()
	end

	self.Changed:_DisconnectAllListeners()
end

function BaseInstance.prototype:_PropagateAncestryChanged(instance, parent)
	self.AncestryChanged:Fire(instance, parent)

	local children = getmetatable(self).instance.children

	for child in pairs(children) do
		child:_PropagateAncestryChanged(instance, parent)
	end
end

BaseInstance.metatable = {}
BaseInstance.metatable[typeKey] = "Instance"

function BaseInstance.metatable.__index(self, key)
	local class = getmetatable(self).class

	if class.properties[key] then
		return class.properties[key].get(self, key)
	end

	if class.prototype[key] then
		return class.prototype[key]
	end

	local object = self:FindFirstChild(key)
	if object then
		return object
	end

	error(string.format("%q is not a valid member of %s", tostring(key), self.ClassName), 2)
end

function BaseInstance.metatable.__newindex(self, key, value)
	local class = getmetatable(self).class

	if class.properties[key] then
		class.properties[key].set(self, key, value)

		self.Changed:Fire(key)

		local propertyChangedSignal = getmetatable(self).instance.propertySignals[key]

		if propertyChangedSignal then
			propertyChangedSignal:Fire()
		end

		return
	end

	error(string.format("%q is not a valid member of %s", tostring(key), self.ClassName), 2)
end

function BaseInstance.metatable:__tostring()
	return self.Name
end

function BaseInstance:new(...)
	local internalInstance = {
		destroyed = false,
		properties = {},
		propertySignals = {},
		children = {},
	}

	local instance = newproxy(true)

	-- Because userdata have a fixed metatable, merge values onto it.
	assign(getmetatable(instance), self.metatable)
	getmetatable(instance).instance = internalInstance
	getmetatable(instance).class = self

	for key, property in pairs(self.properties) do
		internalInstance.properties[key] = property.getDefault(instance)
	end

	self:init(instance, ...)

	return instance
end

function BaseInstance:init(instance, ...)
end

--[[
	Create a new instance class with the given name.
]]
function BaseInstance:extend(name, options)
	assert(type(name) == "string", "Expected string 'name' as argument #1.")
	assert(type(options) == "table" or options == nil, "Expected optional table 'options' as argument #2.")

	local newClass = assign({}, self)

	newClass.name = name
	newClass.super = self

	newClass.properties = assign({}, self.properties)
	newClass.prototype = assign({}, self.prototype)
	newClass.metatable = assign({}, self.metatable)
	newClass.options = assign({}, self.options, options or {})

	return newClass
end

return BaseInstance