local p=import("\046\047\073\110\115\116\097\110\099\101")local g=import("\046\047\084\097\115\107\083\099\104\101\100\117\108\101\114")local Q=import("\046\047\099\114\101\097\116\101\069\110\118\105\114\111\110\109\101\110\116")local c=import("\046\047\102\115")local V=import("\046\047\105\110\115\116\097\110\099\101\115\047\071\097\109\101")local w=import("\046\047\118\097\108\105\100\097\116\101\084\121\112\101")local h=import("\046\047\097\115\115\105\103\110")local D={loadInitModules=true}local U={}U.__index=U function U.new(p)local c={game=V:new();taskScheduler=g.new(),settings=p or{},environment=nil}setmetatable(c,U)c.environment=Q(c)return c end function U.loadFromFs(V,g,Q)w("\112\097\116\104",g,"\115\116\114\105\110\103")if Q~=nil then w("\112\097\115\115\101\100\079\112\116\105\111\110\115",Q,"\116\097\098\108\101")end local Y=h({},D,Q)if c.isFile(g)then if g:find("\037\046\108\117\097\036")then local Q=p.new("\077\111\100\117\108\101\083\099\114\105\112\116")local V=assert(c.read(g))Q.Name=g:match("\040\091\094\047\093-\041\037\046\108\117\097\036")Q.Source=V;(getmetatable(Q)).instance.modulePath=g return Q end return elseif c.isDirectory(g)then local V=p.new("\070\111\108\100\101\114")V.Name=g:match("\040\091\094\047\093-\041\036")for p in c.dir(g)do if p~="\046"and p~="\046\046"then local c=g..("\047"..p)local w=U:loadFromFs(c,Q)if w~=nil then w.Parent=V end end end if Y.loadInitModules then local p=V:FindFirstChild("\105\110\105\116")if p~=nil then for g,Q in ipairs(V:GetChildren())do if Q~=p then Q.Parent=p end end p.Name=V.Name p.Parent=nil V:Destroy()V=p end end return V 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(g),2)end function U.require(g,p)w("\105\110\115\116\097\110\099\101",p,"\073\110\115\116\097\110\099\101")if not p:IsA("\077\111\100\117\108\101\083\099\114\105\112\116")then local g=("\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(p.Name,p.ClassName)error(g,2)end local Q=(getmetatable(p)).instance if Q.moduleLoaded then return Q.moduleResult end local c=assert(loadstring(p.Source,"\064"..Q.modulePath))local V=h({},g.environment,{script=p})setfenv(c,V)local D=c()Q.moduleLoaded=true Q.moduleResult=D return D end return U
--[[
	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