local l=import("\046\046\047\097\115\115\105\103\110")local u=import("\046\046\047\073\110\115\116\097\110\099\101\080\114\111\112\101\114\116\121")local k=import("\046\046\047\083\105\103\110\097\108")local b=import("\046\046\047\116\121\112\101\075\101\121")local function o(l)local u=getmetatable(l)return u and u.instance~=nil end local X={}X.options={creatable=false}X.name="\073\110\115\116\097\110\099\101"X.properties={}X.properties.Name=u.normal({getDefault=function(l)return(getmetatable(l)).class.name end})X.properties.ClassName=u.readOnly({getDefault=function(l)return(getmetatable(l)).class.name end})X.properties.AncestryChanged=u.readOnly({getDefault=function()return k.new()end})X.properties.Changed=u.readOnly({getDefault=function()return k.new()end})X.properties.ChildAdded=u.readOnly({getDefault=k.new})X.properties.ChildRemoved=u.readOnly({getDefault=k.new})X.properties.Parent=u.normal({set=function(l,u,k)local b=(getmetatable(l)).instance if b.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 b.properties.Parent==k then return end if k~=nil and not o(k)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(k))end if b.properties.Parent~=nil then(getmetatable(b.properties.Parent)).instance.children[l]=nil b.properties.Parent.ChildRemoved:Fire(l)end b.properties.Parent=k if k~=nil then(getmetatable(k)).instance.children[l]=true k.ChildAdded:Fire(l)end l:_PropagateAncestryChanged(l,k)end})X.prototype={}function X.prototype.ClearAllChildren(l)local u=(getmetatable(l)).instance.children for l in pairs(u)do l:Destroy()end end function X.prototype.FindFirstAncestor(u,l)local k=u.Parent while k do if k.Name==l then return k end k=k.Parent end end function X.prototype.FindFirstAncestorOfClass(u,l)local k=u.Parent while k do if k.ClassName==l then return k end k=k.Parent end end function X.prototype.FindFirstAncestorWhichIsA(u,l)local k=u.Parent while k do if k:IsA(l)then return k end k=k.Parent end end function X.prototype.FindFirstChild(u,l)local k=(getmetatable(u)).instance.children for u in pairs(k)do if u.Name==l then return u end end return nil end function X.prototype.FindFirstChildOfClass(u,l)local k=(getmetatable(u)).instance.children for u in pairs(k)do if u.ClassName==l then return u end end return nil end function X.prototype.FindFirstChildWhichIsA(u,l)local k=(getmetatable(u)).instance.children for u in pairs(k)do if u:IsA(l)then return u end end return nil end function X.prototype.GetChildren(l)local u=(getmetatable(l)).instance.children local k={}for l in pairs(u)do table.insert(k,l)end return k end function X.prototype.GetDescendants(l)local u={}local k={}local b=l while b do local l=b:GetChildren()for l,b in pairs(l)do k[#k+1]=b u[#u+1]=b end b=u[#u]u[#u]=nil end return k end function X.prototype.IsA(u,l)local k=(getmetatable(u)).class while k~=nil do if k.name==l then return true end k=k.super end return false end function X.prototype.IsDescendantOf(u,l)local k=u repeat k=k.Parent until k==nil or k==l return k==l end function X.prototype.Destroy(l)l:ClearAllChildren()if l.Parent~=nil then l.Parent=nil end l:_DisconnectAllChangedListeners();(getmetatable(l)).instance.destroyed=true end function X.prototype.GetPropertyChangedSignal(u,l)local b=(getmetatable(u)).class.properties local o=(getmetatable(u)).instance.propertySignals local X=o[l]if not X then assert(b[l],l.." \105\115 \110\111\116 \097 \118\097\108\105\100 \112\114\111\112\101\114\116\121 \110\097\109\101\046")X=k.new()o[l]=X end return X end function X.prototype.GetFullName(l)local u=l.Name local k=l.Parent while k and(getmetatable(k)).class.name~="\068\097\116\097\077\111\100\101\108"do u=k.Name..("\046"..u)k=k.Parent end return u end function X.prototype.WaitForChild(k,l,u)return k:FindFirstChild(l)end function X.prototype._DisconnectAllChangedListeners(l)local u=(getmetatable(l)).instance.propertySignals for l,u in pairs(u)do u:_DisconnectAllListeners()end l.Changed:_DisconnectAllListeners()end function X.prototype._PropagateAncestryChanged(k,l,u)k.AncestryChanged:Fire(l,u)local b=(getmetatable(k)).instance.children for k in pairs(b)do k:_PropagateAncestryChanged(l,u)end end X.metatable={}X.metatable[b]="\073\110\115\116\097\110\099\101"function X.metatable.__index(l,u)local k=(getmetatable(l)).class if k.properties[u]then return k.properties[u].get(l,u)end if k.prototype[u]then return k.prototype[u]end local b=l:FindFirstChild(u)if b then return b 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(u),l.ClassName),2)end function X.metatable.__newindex(l,u,k)local b=(getmetatable(l)).class if b.properties[u]then b.properties[u].set(l,u,k)l.Changed:Fire(u)local o=(getmetatable(l)).instance.propertySignals[u]if o then o: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(u),l.ClassName),2)end function X.metatable.__tostring(l)return l.Name end function X.new(u,...)local k={destroyed=false,properties={},propertySignals={},children={}}local b=newproxy(true)l(getmetatable(b),u.metatable);(getmetatable(b)).instance=k;(getmetatable(b)).class=u for l,u in pairs(u.properties)do k.properties[l]=u.getDefault(b)end u:init(b,...)return b end function X.init(u,l,...) end function X.extend(b,u,k)assert(type(u)=="\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(k)=="\116\097\098\108\101"or k==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 o=l({},b)o.name=u o.super=b o.properties=l({},b.properties)o.prototype=l({},b.prototype)o.metatable=l({},b.metatable)o.options=l({},b.options,k or{})return o end return X
--[[
	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