local U=import("\046\047\073\110\115\116\097\110\099\101")local N=import("\046\047\084\097\115\107\083\099\104\101\100\117\108\101\114")local P=import("\046\047\099\114\101\097\116\101\069\110\118\105\114\111\110\109\101\110\116")local F=import("\046\047\102\115")local s=import("\046\047\105\110\115\116\097\110\099\101\115\047\071\097\109\101")local o=import("\046\047\118\097\108\105\100\097\116\101\084\121\112\101")local l=import("\046\047\097\115\115\105\103\110")local x={loadInitModules=true}local E={}E.__index=E function E.new(U)local F={game=s:new(),taskScheduler=N.new();settings=U or{},environment=nil}setmetatable(F,E)F.environment=P(F)return F end function E.loadFromFs(s,N,P)o("\112\097\116\104",N,"\115\116\114\105\110\103")if P~=nil then o("\112\097\115\115\101\100\079\112\116\105\111\110\115",P,"\116\097\098\108\101")end local G=l({},x,P)if F.isFile(N)then if N:find("\037\046\108\117\097\036")then local P=U.new("\077\111\100\117\108\101\083\099\114\105\112\116")local s=assert(F.read(N))P.Name=N:match("\040\091\094\047\093-\041\037\046\108\117\097\036")P.Source=s;(getmetatable(P)).instance.modulePath=N return P end return elseif F.isDirectory(N)then local s=U.new("\070\111\108\100\101\114")s.Name=N:match("\040\091\094\047\093-\041\036")for U in F.dir(N)do if U~="\046"and U~="\046\046"then local F=N..("\047"..U)local o=E:loadFromFs(F,P)if o~=nil then o.Parent=s end end end if G.loadInitModules then local U=s:FindFirstChild("\105\110\105\116")if U~=nil then for N,P in ipairs(s:GetChildren())do if P~=U then P.Parent=U end end U.Name=s.Name U.Parent=nil s:Destroy()s=U end end return s end error(("\108\111\097\100\070\114\111\109\070\115 \102\097\105\108\101\100\058 \080\097\116\104 \037\115 \100\105\100 \110\111\116 \101\120\105\115\116\046"):format(N),2)end function E.require(N,U)o("\105\110\115\116\097\110\099\101",U,"\073\110\115\116\097\110\099\101")if not U:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then local N=("\065\116\116\101\109\112\116\101\100 \116\111 \114\101\113\117\105\114\101 \110\111\110-\077\111\100\117\108\101\083\099\114\105\112\116 \111\098\106\101\099\116 \037\113 \040\037\115\041"):format(U.Name,U.ClassName)error(N,2)end local P=(getmetatable(U)).instance if P.moduleLoaded then return P.moduleResult end local F=assert(loadstring(U.Source,"\064"..P.modulePath))local s=l({},N.environment,{script=U})setfenv(F,s)local x=F()P.moduleLoaded=true P.moduleResult=x return x end return E
--[[
	A Lemur Habitat is an instance of an emulated Roblox environment.

	It is the root instance of the emulated hierarchy.
]]

local Instance = import("./Instance")
local TaskScheduler = import("./TaskScheduler")
local createEnvironment = import("./createEnvironment")
local fs = import("./fs")
local Game = import("./instances/Game")
local validateType = import("./validateType")
local assign = import("./assign")

local defaultLoadFromFsOptions = {
	loadInitModules = true,
}

local Habitat = {}
Habitat.__index = Habitat

function Habitat.new(settings)
	local habitat = {
		game = Game:new(),
		taskScheduler = TaskScheduler.new(),
		settings = settings or {},
		environment = nil,
	}

	setmetatable(habitat, Habitat)

	habitat.environment = createEnvironment(habitat)

	return habitat
end

function Habitat:loadFromFs(path, passedOptions)
	validateType("path", path, "string")

	if passedOptions ~= nil then
		validateType("passedOptions", passedOptions, "table")
	end

	local options = assign({}, defaultLoadFromFsOptions, passedOptions)

	if fs.isFile(path) then
		if path:find("%.lua$") then
			local instance = Instance.new("ModuleScript")
			local contents = assert(fs.read(path))

			instance.Name = path:match("([^/]-)%.lua$")
			instance.Source = contents

			getmetatable(instance).instance.modulePath = path

			return instance
		end
		-- Ignore non-lua files
		return
	elseif fs.isDirectory(path) then
		local instance = Instance.new("Folder")
		instance.Name = path:match("([^/]-)$")

		for name in fs.dir(path) do
			-- Why are these even in the iterator?
			if name ~= "." and name ~= ".." then
				local childPath = path .. "/" .. name

				local childInstance = Habitat:loadFromFs(childPath, passedOptions)
				if childInstance ~= nil then
					childInstance.Parent = instance
				end
			end
		end

		if options.loadInitModules then
			local init = instance:FindFirstChild("init")

			if init ~= nil then
				for _, child in ipairs(instance:GetChildren()) do
					if child ~= init then
						child.Parent = init
					end
				end

				init.Name = instance.Name
				init.Parent = nil

				instance:Destroy()

				instance = init
			end
		end

		return instance
	end

	error(("loadFromFs failed: Path %s did not exist."):format(path), 2)
end

--[[
	Equivalent to Roblox's 'require', called on an emulated Roblox instance.
]]
function Habitat:require(instance)
	validateType("instance", instance, "Instance")

	if not instance:IsA("ModuleScript") then
		local message = ("Attempted to require non-ModuleScript object %q (%s)"):format(
			instance.Name,
			instance.ClassName
		)
		error(message, 2)
	end

	local internalInstance = getmetatable(instance).instance
	if internalInstance.moduleLoaded then
		return internalInstance.moduleResult
	end

	local chunk = assert(loadstring(instance.Source, "@" .. internalInstance.modulePath))

	local environment = assign({}, self.environment, { script = instance })
	setfenv(chunk, environment)

	local result = chunk()

	internalInstance.moduleLoaded = true
	internalInstance.moduleResult = result

	return result
end

return Habitat