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