--[[
	MockDataStoreUtils.lua
	Contains helper and utility functions used by other classes.

	This module is licensed under APLv2, refer to the LICENSE file or:
	buildthomas/MockDataStoreService/blob/master/LICENSE
]]

local MockDataStoreUtils = {}

local Constants = require(script.Parent.MockDataStoreConstants)
local HttpService = game:GetService("HttpService") -- for json encode/decode
local RunService = game:GetService("RunService")

local rand = Random.new()

local function shorten(s, num)
	if #s > num then
		return s:sub(1,num-2) .. ".."
	end
	return s
end

--[[
	[DataStore] [Name/Scope] [GetAsync] KEY
	[DataStore] [Name/Scope] [UpdateAsync] KEY => VALUE
	[DataStore] [Name/Scope] [SetAsync] KEY => VALUE
	[DataStore] [Name/Scope] [IncrementAsync] KEY by INCR => VALUE
	[DataStore] [Name/Scope] [RemoveAsync] KEY =/> VALUE
	[DataStore] [Name/Scope] [OnUpdate] KEY
	[DataStore] [Name/Scope] [GetSortedAsync]

	[OrderedDataStore] [Name/Scope] [GetAsync] KEY
	[OrderedDataStore] [Name/Scope] [UpdateAsync] KEY => VALUE
	[OrderedDataStore] [Name/Scope] [SetAsync] KEY => VALUE
	[OrderedDataStore] [Name/Scope] [IncrementAsync] KEY + INCR => VALUE
	[OrderedDataStore] [Name/Scope] [RemoveAsync] KEY =/> VALUE
	[OrderedDataStore] [Name/Scope] [OnUpdate] KEY
	[OrderedDataStore] [Name/Scope] [GetSortedAsync]

	[OrderedDataStore] [Name/Scope] [AdvanceToNextPageAsync]
]]

local function logMethod(self, method, key, value, increment)
	if not Constants.LOGGING_ENABLED or type(Constants.LOGGING_FUNCTION) ~= "function" then
		return
	end

	local name = self.__name
	local scope = self.__scope

	local prefix
	if not name then
		prefix = ("[GlobalDataStore] [%s]"):format(method)
	elseif not scope then
		prefix = ("[%s] [%s] [%s]"):format(self.__type, shorten(name, 20), method)
	else
		prefix = ("[%s] [%s/%s] [%s]"):format(self.__type, shorten(name, 15), shorten(scope, 15), method)
	end

	local message
	if value and increment then
		message = key .. " + " .. tostring(increment) .. " => " .. tostring(value)
	elseif increment then
		message = key .. " + " .. tostring(increment)
	elseif value then
		if method == "RemoveAsync" then
			message = key .. " =/> " .. tostring(value)
		else
			message = key .. " => " .. tostring(value)
		end
	else
		message = "key"
	end

	Constants.LOGGING_FUNCTION(prefix .. " " .. message)

end

local function deepcopy(t)
	if type(t) == "table" then
		local n = {}
		for i,v in pairs(t) do
			n[i] = deepcopy(v)
		end
		return n
	else
		return t
	end
end

local function scanValidity(tbl, passed, path) -- Credit to Corecii (edited)
	if type(tbl) ~= "table" then
		return scanValidity({input = tbl}, {}, {})
	end
	passed, path = passed or {}, path or {"root"}
	passed[tbl] = true
	local tblType
	do
		local key = next(tbl)
		if type(key) == "number" then
			tblType = "Array"
		else
			tblType = "Dictionary"
		end
	end
	local last = 0
	for key, value in next, tbl do
		path[#path + 1] = tostring(key)
		if type(key) == "number" then
			if tblType == "Dictionary" then
				return false, path, "cannot store mixed tables"
			elseif key % 1 ~= 0 then
				return false, path, "cannot store tables with non-integer indices"
			elseif key == math.huge or key == -math.huge then
				return false, path, "cannot store tables with (-)infinity indices"
			end
		elseif type(key) ~= "string" then
			return false, path, "dictionaries cannot have keys of type " .. typeof(key)
		elseif tblType == "Array" then
			return false, path, "cannot store mixed tables"
		elseif not utf8.len(key) then
			return false, path, "dictionary has key that is invalid UTF-8"
		end
		if tblType == "Array" then
			if last ~= key - 1 then
				return false, path, "array has non-sequential indices"
			end
			last = key
		end
		if type(value) == "userdata" or type(value) == "function" or type(value) == "thread" then
			return false, path, "cannot store value '" .. tostring(value) .. "' of type " .. typeof(value)
		elseif type(value) == "string" and not utf8.len(value) then
			return false, path, "cannot store strings that are invalid UTF-8"
		end
		if type(value) == "table" then
			if passed[value] then
				return false, path, "cannot store cyclic tables"
			end
			local isValid, keyPath, reason = scanValidity(value, passed, path)
			if not isValid then
				return isValid, keyPath, reason
			end
		end
		path[#path] = nil
	end
	passed[tbl] = nil
	return true
end

local function getStringPath(path)
	return table.concat(path, '.')
end

-- Import into a single datastore:
local function importPairsFromTable(origin, destination, interface, warnFunc, methodName, prefix, isOrdered)
	for key, value in pairs(origin) do
		if type(key) ~= "string" then
			warnFunc(("%s: ignored %s > '%s' (key is not a string, but a %s)")
				:format(methodName, prefix, tostring(key), typeof(key)))
		elseif not utf8.len(key) then
			warnFunc(("%s: ignored %s > '%s' (key is not valid UTF-8)")
				:format(methodName, prefix, tostring(key)))
		elseif #key > Constants.MAX_LENGTH_KEY then
			warnFunc(("%s: ignored %s > '%s' (key exceeds %d character limit)")
				:format(methodName, prefix, key, Constants.MAX_LENGTH_KEY))
		elseif type(value) == "string" and #value > Constants.MAX_LENGTH_DATA then
			warnFunc(("%s: ignored %s > '%s' (length of value exceeds %d character limit)")
				:format(methodName, prefix, key, Constants.MAX_LENGTH_DATA))
		elseif type(value) == "table" and #HttpService:JSONEncode(value) > Constants.MAX_LENGTH_DATA then
			warnFunc(("%s: ignored %s > '%s' (length of encoded value exceeds %d character limit)")
				:format(methodName, prefix, key, Constants.MAX_LENGTH_DATA))
		elseif type(value) == "function" or type(value) == "userdata" or type(value) == "thread" then
			warnFunc(("%s: ignored %s > '%s' (cannot store value '%s' of type %s)")
				:format(methodName, prefix, key, tostring(value), type(value)))
		elseif isOrdered and type(value) ~= "number" then
			warnFunc(("%s: ignored %s > '%s' (cannot store value '%s' of type %s in OrderedDataStore)")
				:format(methodName, prefix, key, tostring(value), type(value)))
		elseif isOrdered and value % 1 ~= 0 then
			warnFunc(("%s: ignored %s > '%s' (cannot store non-integer value '%s' in OrderedDataStore)")
				:format(methodName, prefix, key, tostring(value)))
		elseif type(value) == "string" and not utf8.len(value) then
			warnFunc(("%s: ignored %s > '%s' (string value is not valid UTF-8)")
				:format(methodName, prefix, key, tostring(value), type(value)))
		else
			local isValid = true
			local keyPath, reason
			if type(value) == "table" then
				isValid, keyPath, reason = scanValidity(value)
			end
			if isOrdered then
				value = math.floor(value + .5)
			end
			if isValid then
				local old = destination[key]
				destination[key] = value
				if interface and old ~= value then -- hacky block to fire OnUpdate signals
					if isOrdered and interface then -- hacky block to populate internal structures for OrderedDataStores
						if interface.__ref[key] then
							interface.__ref[key].Value = value
							interface.__changed = true
						else
							interface.__ref[key] = {Key = key, Value = interface.__data[key]}
							table.insert(interface.__sorted, interface.__ref[key])
							interface.__changed = true
						end
					end
					interface.__event:Fire(key, value)
				end
			else
				warnFunc(("%s: ignored %s > '%s' (table has invalid entry at <%s>: %s)")
					:format(methodName, prefix, key, getStringPath(keyPath), reason))
			end
		end
	end
end

-- Trim empty datastores and scopes from an entire datastore type:
local function prepareDataStoresForExport(origin)
	local dataPrepared = {}

	for name, scopes in pairs(origin) do
		local exportScopes = {}
		for scope, data in pairs(scopes) do
			local exportData = table.clone(data)
			if next(exportData) ~= nil then -- Only export datastore when non-empty
				exportScopes[scope] = exportData
			end
		end
		if next(exportScopes) ~= nil then -- Only export scope list when non-empty
			dataPrepared[name] = exportScopes
		end
	end

	if next(dataPrepared) ~= nil then -- Only return datastore type when non-empty
		return dataPrepared
	end
end

local function preprocessKey(key)
	if type(key) == "number" then
		if key ~= key then
			return "NAN"
		elseif key >= math.huge then
			return "INF"
		elseif key <= -math.huge then
			return "-INF"
		end
		return tostring(key)
	end
	return key
end

local function simulateYield()
	if Constants.YIELD_TIME_MAX > 0 then
		task.wait(rand:NextNumber(Constants.YIELD_TIME_MIN, Constants.YIELD_TIME_MAX))
	end
end

local function simulateErrorCheck(method)
	if Constants.SIMULATE_ERROR_RATE > 0 and rand:NextNumber() <= Constants.SIMULATE_ERROR_RATE then
		simulateYield()
		error(method .. " rejected with error (simulated error)", 3)
	end
end

-- Setting these here so the functions above can self-reference just by name:
MockDataStoreUtils.logMethod = logMethod
MockDataStoreUtils.deepcopy = deepcopy
MockDataStoreUtils.scanValidity = scanValidity
MockDataStoreUtils.getStringPath = getStringPath
MockDataStoreUtils.importPairsFromTable = importPairsFromTable
MockDataStoreUtils.prepareDataStoresForExport = prepareDataStoresForExport
MockDataStoreUtils.preprocessKey = preprocessKey
MockDataStoreUtils.simulateYield = simulateYield
MockDataStoreUtils.simulateErrorCheck = simulateErrorCheck

return MockDataStoreUtils


--[[
	MockGlobalDataStore.lua
	This module implements the API and functionality of Roblox's GlobalDataStore class.

	This module is licensed under APLv2, refer to the LICENSE file or:
	buildthomas/MockDataStoreService/blob/master/LICENSE
]]

local MockGlobalDataStore = {}
MockGlobalDataStore.__index = MockGlobalDataStore

local MockDataStoreManager = require(script.Parent.MockDataStoreManager)
local Utils = require(script.Parent.MockDataStoreUtils)
local Constants = require(script.Parent.MockDataStoreConstants)
local HttpService = game:GetService("HttpService") -- for json encode/decode

local rand = Random.new()

function MockGlobalDataStore:OnUpdate(key, callback)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'OnUpdate' (string expected, got %s)"):format(typeof(key)), 2)
	elseif type(callback) ~= "function" then
		error(("bad argument #2 to 'OnUpdate' (function expected, got %s)"):format(typeof(callback)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'OnUpdate' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'OnUpdate' (key name exceeds %d character limit)"):format(Constants.MAX_LENGTH_KEY), 2)
	end

	Utils.simulateErrorCheck("OnUpdate")

	local success = MockDataStoreManager.YieldForBudget(
		function()
			warn(("OnUpdate request was throttled due to lack of budget. Try sending fewer requests. Key = %s"):format(key))
		end,
		{Enum.DataStoreRequestType.OnUpdate}
	)

	if not success then
		error("OnUpdate rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	Utils.logMethod(self, "OnUpdate", key)

	return self.__event.Event:Connect(function(k, v)
		if k == key then
			if Constants.YIELD_TIME_UPDATE_MAX > 0 then
				task.wait(rand:NextNumber(Constants.YIELD_TIME_UPDATE_MIN, Constants.YIELD_TIME_UPDATE_MAX))
			end
			callback(v) -- v was implicitly deep-copied
		end
	end)
end

function MockGlobalDataStore:GetAsync(key)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'GetAsync' (string expected, got %s)"):format(typeof(key)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'GetAsync' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'GetAsync' (key name exceeds %d character limit)"):format(Constants.MAX_LENGTH_KEY), 2)
	end

	if self.__getCache[key] and tick() - self.__getCache[key] < Constants.GET_COOLDOWN then
		return Utils.deepcopy(self.__data[key])
	end

	Utils.simulateErrorCheck("GetAsync")

	local success = MockDataStoreManager.YieldForBudget(
		function()
			warn(("GetAsync request was throttled due to lack of budget. Try sending fewer requests. Key = %s"):format(key))
		end,
		{Enum.DataStoreRequestType.GetAsync}
	)

	if not success then
		error("GetAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	self.__getCache[key] = tick()

	local retValue = Utils.deepcopy(self.__data[key])

	Utils.simulateYield()

	Utils.logMethod(self, "GetAsync", key)

	return retValue
end

function MockGlobalDataStore:IncrementAsync(key, delta)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'IncrementAsync' (string expected, got %s)"):format(typeof(key)), 2)
	elseif delta ~= nil and type(delta) ~= "number" then
		error(("bad argument #2 to 'IncrementAsync' (number expected, got %s)"):format(typeof(delta)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'IncrementAsync' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'IncrementAsync' (key name exceeds %d character limit)")
			:format(Constants.MAX_LENGTH_KEY), 2)
	end

	Utils.simulateErrorCheck("IncrementAsync")

	local success

	if self.__writeLock[key] or tick() - (self.__writeCache[key] or 0) < Constants.WRITE_COOLDOWN then
		success = MockDataStoreManager.YieldForWriteLockAndBudget(
			function()
				warn(("IncrementAsync request was throttled, a key can only be written to once every %d seconds. Key = %s")
					:format(Constants.WRITE_COOLDOWN, key))
			end,
			key,
			self.__writeLock,
			self.__writeCache,
			{Enum.DataStoreRequestType.SetIncrementAsync}
		)
	else
		self.__writeLock[key] = true
		success = MockDataStoreManager.YieldForBudget(
			function()
				warn(("IncrementAsync request was throttled due to lack of budget. Try sending fewer requests. Key = %s")
					:format(key))
			end,
			{Enum.DataStoreRequestType.SetIncrementAsync}
		)
		self.__writeLock[key] = nil
	end

	if not success then
		error("IncrementAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	local old = self.__data[key]

	if old ~= nil and (type(old) ~= "number" or old % 1 ~= 0) then
		Utils.simulateYield()
		error("IncrementAsync rejected with error (cannot increment non-integer value)", 2)
	end

	self.__writeLock[key] = true

	delta = delta and math.floor(delta + .5) or 1

	self.__data[key] = (old or 0) + delta

	if old == nil or delta ~= 0 then
		self.__event:Fire(key, self.__data[key])
	end

	local retValue = self.__data[key]

	Utils.simulateYield()

	self.__writeLock[key] = nil
	self.__writeCache[key] = tick()

	self.__getCache[key] = tick()

	Utils.logMethod(self, "IncrementAsync", key, retValue, delta)

	return retValue
end

function MockGlobalDataStore:RemoveAsync(key)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'RemoveAsync' (string expected, got %s)"):format(typeof(key)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'RemoveAsync' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'RemoveAsync' (key name exceeds %d character limit)"):format(Constants.MAX_LENGTH_KEY), 2)
	end

	Utils.simulateErrorCheck("RemoveAsync")

	local success

	if self.__writeLock[key] or tick() - (self.__writeCache[key] or 0) < Constants.WRITE_COOLDOWN then
		success = MockDataStoreManager.YieldForWriteLockAndBudget(
			function()
				warn(("RemoveAsync request was throttled, a key can only be written to once every %d seconds. Key = %s")
					:format(Constants.WRITE_COOLDOWN, key))
			end,
			key,
			self.__writeLock,
			self.__writeCache,
			{Enum.DataStoreRequestType.SetIncrementAsync}
		)
	else
		self.__writeLock[key] = true
		success = MockDataStoreManager.YieldForBudget(
			function()
				warn(("RemoveAsync request was throttled due to lack of budget. Try sending fewer requests. Key = %s")
					:format(key))
			end,
			{Enum.DataStoreRequestType.SetIncrementAsync}
		)
		self.__writeLock[key] = nil
	end

	if not success then
		error("RemoveAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	self.__writeLock[key] = true

	local value = Utils.deepcopy(self.__data[key])
	self.__data[key] = nil

	if value ~= nil then
		self.__event:Fire(key, nil)
	end

	Utils.simulateYield()

	self.__writeLock[key] = nil
	self.__writeCache[key] = tick()

	Utils.logMethod(self, "RemoveAsync", key, value)

	return value
end

function MockGlobalDataStore:SetAsync(key, value)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'SetAsync' (string expected, got %s)"):format(typeof(key)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'SetAsync' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'SetAsync' (key name exceeds %d character limit)"):format(Constants.MAX_LENGTH_KEY), 2)
	elseif value == nil or type(value) == "function" or type(value) == "userdata" or type(value) == "thread" then
		error(("bad argument #2 to 'SetAsync' (cannot store value '%s' of type %s)")
			:format(tostring(value), typeof(value)), 2)
	end

	if type(value) == "table" then
		local isValid, keyPath, reason = Utils.scanValidity(value)
		if not isValid then
			error(("bad argument #2 to 'SetAsync' (table has invalid entry at <%s>: %s)")
				:format(Utils.getStringPath(keyPath), reason), 2)
		end
		local pass, content = pcall(function() return HttpService:JSONEncode(value) end)
		if not pass then
			error("bad argument #2 to 'SetAsync' (table could not be encoded to json)", 2)
		elseif #content > Constants.MAX_LENGTH_DATA then
			error(("bad argument #2 to 'SetAsync' (encoded data length exceeds %d character limit)")
				:format(Constants.MAX_LENGTH_DATA), 2)
		end
	elseif type(value) == "string" then
		if #value > Constants.MAX_LENGTH_DATA then
			error(("bad argument #2 to 'SetAsync' (data length exceeds %d character limit)")
				:format(Constants.MAX_LENGTH_DATA), 2)
		elseif not utf8.len(value) then
			error("bad argument #2 to 'SetAsync' (string value is not valid UTF-8)", 2)
		end
	end

	Utils.simulateErrorCheck("SetAsync")

	local success

	if self.__writeLock[key] or tick() - (self.__writeCache[key] or 0) < Constants.WRITE_COOLDOWN then
		success = MockDataStoreManager.YieldForWriteLockAndBudget(
			function()
				warn(("SetAsync request was throttled, a key can only be written to once every %d seconds. Key = %s")
					:format(Constants.WRITE_COOLDOWN, key))
			end,
			key,
			self.__writeLock,
			self.__writeCache,
			{Enum.DataStoreRequestType.SetIncrementAsync}
		)
	else
		self.__writeLock[key] = true
		success = MockDataStoreManager.YieldForBudget(
			function()
				warn(("SetAsync request was throttled due to lack of budget. Try sending fewer requests. Key = %s")
					:format(key))
			end,
			{Enum.DataStoreRequestType.SetIncrementAsync}
		)
		self.__writeLock[key] = nil
	end

	if not success then
		error("SetAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	self.__writeLock[key] = true

	if type(value) == "table" or value ~= self.__data[key] then
		self.__data[key] = Utils.deepcopy(value)
		self.__event:Fire(key, self.__data[key])
	end

	Utils.simulateYield()

	self.__writeLock[key] = nil
	self.__writeCache[key] = tick()

	Utils.logMethod(self, "SetAsync", key, self.__data[key])

end

function MockGlobalDataStore:UpdateAsync(key, transformFunction)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'UpdateAsync' (string expected, got %s)"):format(typeof(key)), 2)
	elseif type(transformFunction) ~= "function" then
		error(("bad argument #2 to 'UpdateAsync' (function expected, got %s)"):format(typeof(transformFunction)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'UpdateAsync' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'UpdateAsync' (key name exceeds %d character limit)"):format(Constants.MAX_LENGTH_KEY), 2)
	end

	Utils.simulateErrorCheck("UpdateAsync")

	local success

	if self.__writeLock[key] or tick() - (self.__writeCache[key] or 0) < Constants.WRITE_COOLDOWN then
		success = MockDataStoreManager.YieldForWriteLockAndBudget(
			function()
				warn(("UpdateAsync request was throttled, a key can only be written to once every %d seconds. Key = %s")
					:format(Constants.WRITE_COOLDOWN, key))
			end,
			key,
			self.__writeLock,
			self.__writeCache,
			{Enum.DataStoreRequestType.SetIncrementAsync}
		)
	else
		self.__writeLock[key] = true
		local budget
		if self.__getCache[key] and tick() - self.__getCache[key] < Constants.GET_COOLDOWN then
			budget = {Enum.DataStoreRequestType.SetIncrementAsync}
		else
			budget = {Enum.DataStoreRequestType.GetAsync, Enum.DataStoreRequestType.SetIncrementAsync}
		end
		success = MockDataStoreManager.YieldForBudget(
			function()
				warn(("UpdateAsync request was throttled due to lack of budget. Try sending fewer requests. Key = %s")
					:format(key))
			end,
			budget
		)
		self.__writeLock[key] = nil
	end

	if not success then
		error("UpdateAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	local value = transformFunction(Utils.deepcopy(self.__data[key]))

	if value == nil then -- cancel update after remote call
		Utils.simulateYield()
		return nil -- this is what datastores do even though it should be old value
	end

	if type(value) == "function" or type(value) == "userdata" or type(value) == "thread" then
		error(("UpdateAsync rejected with error (resulting value '%s' is of type %s that cannot be stored)")
			:format(tostring(value), typeof(value)), 2)
	end

	if type(value) == "table" then
		local isValid, keyPath, reason = Utils.scanValidity(value)
		if not isValid then
			error(("UpdateAsync rejected with error (resulting table has invalid entry at <%s>: %s)")
				:format(Utils.getStringPath(keyPath), reason), 2)
		end
		local pass, content = pcall(function() return HttpService:JSONEncode(value) end)
		if not pass then
			error("UpdateAsync rejected with error (resulting table could not be encoded to json)", 2)
		elseif #content > Constants.MAX_LENGTH_DATA then
			error(("UpdateAsync rejected with error (resulting encoded data length exceeds %d character limit)")
				:format(Constants.MAX_LENGTH_DATA), 2)
		end
	elseif type(value) == "string" then
		if #value > Constants.MAX_LENGTH_DATA then
			error(("UpdateAsync rejected with error (resulting data length exceeds %d character limit)")
				:format(Constants.MAX_LENGTH_DATA), 2)
		elseif not utf8.len(value) then
			error("UpdateAsync rejected with error (string value is not valid UTF-8)", 2)
		end
	end

	self.__writeLock[key] = true

	if type(value) == "table" or value ~= self.__data[key] then
		self.__data[key] = Utils.deepcopy(value)
		self.__event:Fire(key, self.__data[key])
	end

	local retValue = Utils.deepcopy(value)

	self.__writeLock[key] = nil
	self.__writeCache[key] = tick()

	self.__getCache[key] = tick()

	Utils.logMethod(self, "UpdateAsync", key, retValue)

	return retValue
end

function MockGlobalDataStore:ExportToJSON()
	return HttpService:JSONEncode(self.__data)
end

function MockGlobalDataStore:ImportFromJSON(json, verbose)
	local content
	if type(json) == "string" then
		local parsed, value = pcall(function() return HttpService:JSONDecode(json) end)
		if not parsed then
			error("bad argument #1 to 'ImportFromJSON' (string is not valid json)", 2)
		end
		content = value
	elseif type(json) == "table" then
		content = Utils.deepcopy(json)
	else
		error(("bad argument #1 to 'ImportFromJSON' (string or table expected, got %s)"):format(typeof(json)), 2)
	end

	if verbose ~= nil and type(verbose) ~= "boolean" then
		error(("bad argument #2 to 'ImportFromJSON' (boolean expected, got %s)"):format(typeof(verbose)), 2)
	end

	Utils.importPairsFromTable(
		content,
		self.__data,
		MockDataStoreManager.GetDataInterface(self.__data),
		(verbose == false and function() end or warn),
		"ImportFromJSON",
		((type(self.__name) == "string" and type(self.__scope) == "string")
			and ("DataStore > %s > %s"):format(self.__name, self.__scope)
			or "GlobalDataStore"),
		false
	)
end

return MockGlobalDataStore


--[[
	MockOrderedDataStore.lua
	This module implements the API and functionality of Roblox's OrderedDataStore class.

	This module is licensed under APLv2, refer to the LICENSE file or:
	buildthomas/MockDataStoreService/blob/master/LICENSE
]]

local MockOrderedDataStore = {}
MockOrderedDataStore.__index = MockOrderedDataStore

local MockDataStoreManager = require(script.Parent.MockDataStoreManager)
local MockDataStorePages = require(script.Parent.MockDataStorePages)
local Utils = require(script.Parent.MockDataStoreUtils)
local Constants = require(script.Parent.MockDataStoreConstants)
local HttpService = game:GetService("HttpService") -- for json encode/decode

local rand = Random.new()

function MockOrderedDataStore:OnUpdate(key, callback)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'OnUpdate' (string expected, got %s)"):format(typeof(key)), 2)
	elseif type(callback) ~= "function" then
		error(("bad argument #2 to 'OnUpdate' (function expected, got %s)"):format(typeof(callback)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'OnUpdate' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'OnUpdate' (key name exceeds %d character limit)"):format(Constants.MAX_LENGTH_KEY), 2)
	end

	Utils.simulateErrorCheck("OnUpdate")

	local success = MockDataStoreManager.YieldForBudget(
		function()
			warn(("OnUpdate request was throttled due to lack of budget. Try sending fewer requests. Key = %s"):format(key))
		end,
		{Enum.DataStoreRequestType.OnUpdate}
	)

	if not success then
		error("OnUpdate rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	Utils.logMethod(self, "OnUpdate", key)

	return self.__event.Event:Connect(function(k, v)
		if k == key then
			if Constants.YIELD_TIME_UPDATE_MAX > 0 then
				task.wait(rand:NextNumber(Constants.YIELD_TIME_UPDATE_MIN, Constants.YIELD_TIME_UPDATE_MAX))
			end
			callback(v) -- v was implicitly deep-copied
		end
	end)
end

function MockOrderedDataStore:GetAsync(key)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'GetAsync' (string expected, got %s)"):format(typeof(key)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'GetAsync' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'GetAsync' (key name exceeds %d character limit)"):format(Constants.MAX_LENGTH_KEY), 2)
	end

	if self.__getCache[key] and tick() - self.__getCache[key] < Constants.GET_COOLDOWN then
		return self.__data[key]
	end

	Utils.simulateErrorCheck("GetAsync")

	local success = MockDataStoreManager.YieldForBudget(
		function()
			warn(("GetAsync request was throttled due to lack of budget. Try sending fewer requests. Key = %s"):format(key))
		end,
		{Enum.DataStoreRequestType.GetAsync}
	)

	if not success then
		error("GetAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	local retValue = self.__data[key]

	Utils.simulateYield()

	self.__getCache[key] = tick()

	Utils.logMethod(self, "GetAsync", key)

	return retValue
end

function MockOrderedDataStore:IncrementAsync(key, delta)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'IncrementAsync' (string expected, got %s)"):format(typeof(key)), 2)
	elseif delta ~= nil and type(delta) ~= "number" then
		error(("bad argument #2 to 'IncrementAsync' (number expected, got %s)"):format(typeof(delta)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'IncrementAsync' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'IncrementAsync' (key name exceeds %d character limit)")
			:format(Constants.MAX_LENGTH_KEY), 2)
	end

	Utils.simulateErrorCheck("IncrementAsync")

	local success

	if self.__writeLock[key] or tick() - (self.__writeCache[key] or 0) < Constants.WRITE_COOLDOWN then
		success = MockDataStoreManager.YieldForWriteLockAndBudget(
			function()
				warn(("IncrementAsync request was throttled, a key can only be written to once every %d seconds. Key = %s")
					:format(Constants.WRITE_COOLDOWN, key))
			end,
			key,
			self.__writeLock,
			self.__writeCache,
			{Enum.DataStoreRequestType.SetIncrementSortedAsync}
		)
	else
		self.__writeLock[key] = true
		success = MockDataStoreManager.YieldForBudget(
			function()
				warn(("IncrementAsync request was throttled due to lack of budget. Try sending fewer requests. Key = %s")
					:format(key))
			end,
			{Enum.DataStoreRequestType.SetIncrementSortedAsync}
		)
		self.__writeLock[key] = nil
	end

	if not success then
		error("IncrementAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	local old = self.__data[key]

	if old ~= nil and (type(old) ~= "number" or old % 1 ~= 0) then
		Utils.simulateYield()
		error("IncrementAsync rejected with error (cannot increment non-integer value)", 2)
	end

	self.__writeLock[key] = true

	delta = delta and math.floor(delta + .5) or 1

	if old == nil then
		self.__data[key] = delta
		self.__ref[key] = {Key = key, Value = self.__data[key]}
		table.insert(self.__sorted, self.__ref[key])
		self.__changed = true
		self.__event:Fire(key, self.__data[key])
	elseif delta ~= 0 then
		self.__data[key] = self.__data[key] + delta
		self.__ref[key].Value = self.__data[key]
		self.__changed = true
		self.__event:Fire(key, self.__data[key])
	end

	local retValue = self.__data[key]

	Utils.simulateYield()

	self.__writeLock[key] = nil
	self.__writeCache[key] = tick()

	self.__getCache[key] = tick()

	Utils.logMethod(self, "IncrementAsync", key, retValue, delta)

	return retValue
end

function MockOrderedDataStore:RemoveAsync(key)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'RemoveAsync' (string expected, got %s)"):format(type(key)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'RemoveAsync' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'RemoveAsync' (key name exceeds %d character limit)"):format(Constants.MAX_LENGTH_KEY), 2)
	end

	Utils.simulateErrorCheck("RemoveAsync")

	local success

	if self.__writeLock[key] or tick() - (self.__writeCache[key] or 0) < Constants.WRITE_COOLDOWN then
		success = MockDataStoreManager.YieldForWriteLockAndBudget(
			function()
				warn(("RemoveAsync request was throttled, a key can only be written to once every %d seconds. Key = %s")
					:format(Constants.WRITE_COOLDOWN, key))
			end,
			key,
			self.__writeLock,
			self.__writeCache,
			{Enum.DataStoreRequestType.SetIncrementSortedAsync}
		)
	else
		self.__writeLock[key] = true
		success = MockDataStoreManager.YieldForBudget(
			function()
				warn(("RemoveAsync request was throttled due to lack of budget. Try sending fewer requests. Key = %s")
					:format(key))
			end,
			{Enum.DataStoreRequestType.SetIncrementSortedAsync}
		)
		self.__writeLock[key] = nil
	end

	if not success then
		error("RemoveAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	self.__writeLock[key] = true

	local value = self.__data[key]

	if value ~= nil then
		self.__data[key] = nil
		self.__ref[key] = nil
		for i,v in pairs(self.__sorted) do
			if v.Key == key then
				table.remove(self.__sorted, i)
				break
			end
		end
		self.__event:Fire(key, nil)
	end

	Utils.simulateYield()

	self.__writeLock[key] = nil
	self.__writeCache[key] = tick()

	Utils.logMethod(self, "RemoveAsync", key, value)

	return value
end

function MockOrderedDataStore:SetAsync(key, value)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'SetAsync' (string expected, got %s)"):format(typeof(key)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'SetAsync' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'SetAsync' (key name exceeds %d character limit)"):format(Constants.MAX_LENGTH_KEY), 2)
	elseif type(value) ~= "number" then
		error(("bad argument #2 to 'SetAsync' (number expected, got %s)"):format(typeof(value)), 2)
	elseif value % 1 ~= 0 then
		error("bad argument #2 to 'SetAsync' (cannot store non-integer values in OrderedDataStore)", 2)
	end

	Utils.simulateErrorCheck("SetAsync")

	local success

	if self.__writeLock[key] or tick() - (self.__writeCache[key] or 0) < Constants.WRITE_COOLDOWN then
		success = MockDataStoreManager.YieldForWriteLockAndBudget(
			function()
				warn(("SetAsync request was throttled, a key can only be written to once every %d seconds. Key = %s")
					:format(Constants.WRITE_COOLDOWN, key))
			end,
			key,
			self.__writeLock,
			self.__writeCache,
			{Enum.DataStoreRequestType.SetIncrementSortedAsync}
		)
	else
		self.__writeLock[key] = true
		success = MockDataStoreManager.YieldForBudget(
			function()
				warn(("SetAsync request was throttled due to lack of budget. Try sending fewer requests. Key = %s")
					:format(key))
			end,
			{Enum.DataStoreRequestType.SetIncrementSortedAsync}
		)
		self.__writeLock[key] = nil
	end

	if not success then
		error("SetAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	self.__writeLock[key] = true

	local old = self.__data[key]

	if old == nil then
		self.__data[key] = value
		self.__ref[key] = {Key = key, Value = value}
		table.insert(self.__sorted, self.__ref[key])
		self.__changed = true
		self.__event:Fire(key, self.__data[key])
	elseif old ~= value then
		self.__data[key] = value
		self.__ref[key].Value = value
		self.__changed = true
		self.__event:Fire(key, self.__data[key])
	end

	Utils.simulateYield()

	self.__writeLock[key] = nil
	self.__writeCache[key] = tick()

	Utils.logMethod(self, "SetAsync", key, self.__data[key])

	return value
end

function MockOrderedDataStore:UpdateAsync(key, transformFunction)
	key = Utils.preprocessKey(key)
	if type(key) ~= "string" then
		error(("bad argument #1 to 'UpdateAsync' (string expected, got %s)"):format(typeof(key)), 2)
	elseif type(transformFunction) ~= "function" then
		error(("bad argument #2 to 'UpdateAsync' (function expected, got %s)"):format(typeof(transformFunction)), 2)
	elseif #key == 0 then
		error("bad argument #1 to 'UpdateAsync' (key name can't be empty)", 2)
	elseif #key > Constants.MAX_LENGTH_KEY then
		error(("bad argument #1 to 'UpdateAsync' (key name exceeds %d character limit)"):format(Constants.MAX_LENGTH_KEY), 2)
	end

	Utils.simulateErrorCheck("UpdateAsync")

	local success

	if self.__writeLock[key] or tick() - (self.__writeCache[key] or 0) < Constants.WRITE_COOLDOWN then
		success = MockDataStoreManager.YieldForWriteLockAndBudget(
			function()
				warn(("UpdateAsync request was throttled, a key can only be written to once every %d seconds. Key = %s")
					:format(Constants.WRITE_COOLDOWN, key))
			end,
			key,
			self.__writeLock,
			self.__writeCache,
			{Enum.DataStoreRequestType.SetIncrementSortedAsync}
		)
	else
		self.__writeLock[key] = true
		local budget
		if self.__getCache[key] and tick() - self.__getCache[key] < Constants.GET_COOLDOWN then
			budget = {Enum.DataStoreRequestType.SetIncrementSortedAsync}
		else
			budget = {Enum.DataStoreRequestType.GetAsync, Enum.DataStoreRequestType.SetIncrementSortedAsync}
		end
		success = MockDataStoreManager.YieldForBudget(
			function()
				warn(("UpdateAsync request was throttled due to lack of budget. Try sending fewer requests. Key = %s")
					:format(key))
			end,
			budget
		)
		self.__writeLock[key] = nil
	end

	if not success then
		error("UpdateAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	local value = transformFunction(self.__data[key])

	if value == nil then -- cancel update after remote call
		Utils.simulateYield()
		return nil -- this is what datastores do even though it should be old value
	end

	if type(value) ~= "number" or value % 1 ~= 0 then
		error("UpdateAsync rejected with error (resulting non-integer value can't be stored in OrderedDataStore)", 2)
	end

	self.__writeLock[key] = true

	local old = self.__data[key]

	if old == nil then
		self.__data[key] = value
		self.__ref[key] = {Key = key, Value = value}
		table.insert(self.__sorted, self.__ref[key])
		self.__changed = true
		self.__event:Fire(key, self.__data[key])
	elseif old ~= value then
		self.__data[key] = value
		self.__ref[key].Value = value
		self.__changed = true
		self.__event:Fire(key, self.__data[key])
	end

	Utils.simulateYield()

	self.__writeLock[key] = nil
	self.__writeCache[key] = tick()

	self.__getCache[key] = tick()

	Utils.logMethod(self, "UpdateAsync", key, value)

	return value
end

function MockOrderedDataStore:GetSortedAsync(ascending, pageSize, minValue, maxValue)
	if type(ascending) ~= "boolean" then
		error(("bad argument #1 to 'GetSortedAsync' (boolean expected, got %s)"):format(typeof(ascending)), 2)
	elseif type(pageSize) ~= "number" then
		error(("bad argument #2 to 'GetSortedAsync' (number expected, got %s)"):format(typeof(pageSize)), 2)
	end

	pageSize = math.floor(pageSize + .5)
	if pageSize <= 0 or pageSize > Constants.MAX_PAGE_SIZE then
		error(("bad argument #2 to 'GetSortedAsync' (page size must be an integer above 0 and below or equal to %d)")
			:format(Constants.MAX_PAGE_SIZE), 2)
	end

	if minValue ~= nil then
		if type(minValue) ~= "number" then
			error(("bad argument #3 to 'GetSortedAsync' (number expected, got %s)"):format(typeof(minValue)), 2)
		elseif minValue % 1 ~= 0 then
			error("bad argument #3 to 'GetSortedAsync' (minimum threshold must be an integer)", 2)
		end
	else
		minValue = -math.huge
	end

	if maxValue ~= nil then
		if type(maxValue) ~= "number" then
			error(("bad argument #4 to 'GetSortedAsync' (number expected, got %s)"):format(typeof(maxValue)), 2)
		elseif maxValue % 1 ~= 0 then
			error("bad argument #4 to 'GetSortedAsync' (maximum threshold must be an integer)", 2)
		end
	else
		maxValue = math.huge
	end

	Utils.simulateErrorCheck("GetSortedAsync")

	local success = MockDataStoreManager.YieldForBudget(
		function()
			warn("GetSortedAsync request was throttled due to lack of budget. Try sending fewer requests.")
		end,
		{Enum.DataStoreRequestType.GetSortedAsync}
	)

	if not success then
		error("GetSortedAsync rejected with error (request was throttled, but throttled queue was full)", 2)
	end

	if minValue > maxValue then
		Utils.simulateYield()
		error("GetSortedAsync rejected with error (minimum threshold is higher than maximum threshold)", 2)
	end

	if self.__changed then
		table.sort(self.__sorted, function(a,b) return a.Value < b.Value end)
		self.__changed = false
	end

	local results = {}

	if ascending then
		local i = 1
		while self.__sorted[i] and self.__sorted[i].Value < minValue do
			i = i + 1
		end
		while self.__sorted[i] and self.__sorted[i].Value <= maxValue do
			table.insert(results, {key = self.__sorted[i].Key, value = self.__sorted[i].Value})
			i = i + 1
		end
	else
		local i = #self.__sorted
		while i > 0 and self.__sorted[i].Value > maxValue do
			i = i - 1
		end
		while i > 0 and self.__sorted[i].Value >= minValue do
			table.insert(results, {key = self.__sorted[i].Key, value = self.__sorted[i].Value})
			i = i - 1
		end
	end

	Utils.simulateYield()

	Utils.logMethod(self, "GetSortedAsync")

	return setmetatable({
		__datastore = self;
		__currentPage = 1;
		__pageSize = pageSize;
		__results = results;
		IsFinished = (#results <= pageSize);
	}, MockDataStorePages)
end

function MockOrderedDataStore:ExportToJSON()
	return HttpService:JSONEncode(self.__data)
end

function MockOrderedDataStore:ImportFromJSON(json, verbose)
	local content
	if type(json) == "string" then
		local parsed, value = pcall(function() return HttpService:JSONDecode(json) end)
		if not parsed then
			error("bad argument #1 to 'ImportFromJSON' (string is not valid json)", 2)
		end
		content = value
	elseif type(json) == "table" then
		content = json -- No need to deepcopy, OrderedDataStore only contains numbers which are passed by value
	else
		error(("bad argument #1 to 'ImportFromJSON' (string or table expected, got %s)"):format(typeof(json)), 2)
	end

	if verbose ~= nil and type(verbose) ~= "boolean" then
		error(("bad argument #2 to 'ImportFromJSON' (boolean expected, got %s)"):format(typeof(verbose)), 2)
	end

	Utils.importPairsFromTable(
		content,
		self.__data,
		MockDataStoreManager.GetDataInterface(self.__data),
		(verbose == false and function() end or warn),
		"ImportFromJSON",
		("OrderedDataStore > %s > %s"):format(self.__name, self.__scope),
		true
	)
end

return MockOrderedDataStore


--[[
	DataStoreService.lua
	This module decides whether to use actual datastores or mock datastores depending on the environment.

	This module is licensed under APLv2, refer to the LICENSE file or:
	buildthomas/MockDataStoreService/blob/master/LICENSE
]]

local MockDataStoreServiceModule = script.MockDataStoreService

local shouldUseMock = false
if game.GameId == 0 then -- Local place file
	shouldUseMock = true
elseif game:GetService("RunService"):IsStudio() then -- Published file in Studio
	local status, message = pcall(function()
		-- This will error if current instance has no Studio API access:
		game:GetService("DataStoreService"):GetDataStore("__TEST"):SetAsync("__TEST", "__TEST_" .. os.time())
	end)
	if not status and message:find("403", 1, true) then -- HACK
		-- Can connect to datastores, but no API access
		shouldUseMock = true
	end
end

return function(forceMockDatastore)
	-- Return the mock or actual service depending on environment:
	if shouldUseMock or forceMockDatastore then
		warn(":: Adonis :: Using MockDataStoreService instead of DataStoreService")
		return require(MockDataStoreServiceModule)
	else
		return game:GetService("DataStoreService")
	end
end


--[[
	A way to dynamically fetch the F3X module with require()'s ModuleScript sandboxing.
]]

script:Destroy()
-- selene: allow(incorrect_standard_library_use)
script = nil

return select(2, pcall(function()
	return require(580330877)()
end)) or true


--# selene: allow(incorrect_standard_library_use, multiple_statements, shadowing, unused_variable, empty_if, divide_by_zero, unbalanced_assignments)
--[[--------------------------------------------------------------------

  ldump.lua
  Save precompiled Lua chunks
  This file is part of Yueliang.

  Copyright (c) 2006 Kein-Hong Man <khman@users.sf.net>
  The COPYRIGHT file describes the conditions
  under which this software may be distributed.

  See the ChangeLog for more information.

----------------------------------------------------------------------]]

--[[--------------------------------------------------------------------
-- Notes:
-- * WARNING! byte order (little endian) and data type sizes for header
--   signature values hard-coded; see luaU:header
-- * chunk writer generators are included, see below
-- * one significant difference is that instructions are still in table
--   form (with OP/A/B/C/Bx fields) and luaP:Instruction() is needed to
--   convert them into 4-char strings
--
-- Not implemented:
-- * DumpVar, DumpMem has been removed
-- * DumpVector folded into folded into DumpDebug, DumpCode
--
-- Added:
-- * for convenience, the following two functions have been added:
--   luaU:make_setS: create a chunk writer that writes to a string
--   luaU:make_setF: create a chunk writer that writes to a file
--   (lua.h contains a typedef for lua_Writer/lua_Chunkwriter, and
--    a Lua-based implementation exists, writer() in lstrlib.c)
-- * luaU:ttype(o) (from lobject.h)
-- * for converting number types to its binary equivalent:
--   luaU:from_double(x): encode double value for writing
--   luaU:from_int(x): encode integer value for writing
--     (error checking is limited for these conversion functions)
--     (double conversion does not support denormals or NaNs)
--
-- Changed in 5.1.x:
-- * the dumper was mostly rewritten in Lua 5.1.x, so notes on the
--   differences between 5.0.x and 5.1.x is limited
-- * LUAC_VERSION bumped to 0x51, LUAC_FORMAT added
-- * developer is expected to adjust LUAC_FORMAT in order to identify
--   non-standard binary chunk formats
-- * header signature code is smaller, has been simplified, and is
--   tested as a single unit; its logic is shared with the undumper
-- * no more endian conversion, invalid endianness mean rejection
-- * opcode field sizes are no longer exposed in the header
-- * code moved to front of a prototype, followed by constants
-- * debug information moved to the end of the binary chunk, and the
--   relevant functions folded into a single function
-- * luaU:dump returns a writer status code
-- * chunk writer now implements status code because dumper uses it
-- * luaU:endianness removed
----------------------------------------------------------------------]]

--requires luaP
local luaU = {}
local luaP = require(script.Parent.LuaP)

-- mark for precompiled code ('<esc>Lua') (from lua.h)
luaU.LUA_SIGNATURE = "\27Lua"

-- constants used by dumper (from lua.h)
luaU.LUA_TNUMBER  = 3
luaU.LUA_TSTRING  = 4
luaU.LUA_TNIL     = 0
luaU.LUA_TBOOLEAN = 1
luaU.LUA_TNONE    = -1

-- constants for header of binary files (from lundump.h)
luaU.LUAC_VERSION    = 0x51     -- this is Lua 5.1
luaU.LUAC_FORMAT     = 0        -- this is the official format
luaU.LUAC_HEADERSIZE = 12       -- size of header of binary files

--[[--------------------------------------------------------------------
-- Additional functions to handle chunk writing
-- * to use make_setS and make_setF, see test_ldump.lua elsewhere
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- create a chunk writer that writes to a string
-- * returns the writer function and a table containing the string
-- * to get the final result, look in buff.data
------------------------------------------------------------------------
function luaU:make_setS()
  local buff = {}
        buff.data = ""
  local writer =
    function(s, buff)  -- chunk writer
      if not s then return 0 end
      buff.data = buff.data..s
      return 0
    end
  return writer, buff
end

------------------------------------------------------------------------
-- create a chunk writer that writes to a file
-- * returns the writer function and a table containing the file handle
-- * if a nil is passed, then writer should close the open file
------------------------------------------------------------------------

--[[
function luaU:make_setF(filename)
  local buff = {}
        buff.h = io.open(filename, "wb")
  if not buff.h then return nil end
  local writer =
    function(s, buff)  -- chunk writer
      if not buff.h then return 0 end
      if not s then
        if buff.h:close() then return 0 end
      else
        if buff.h:write(s) then return 0 end
      end
      return 1
    end
  return writer, buff
end--]]

------------------------------------------------------------------------
-- works like the lobject.h version except that TObject used in these
-- scripts only has a 'value' field, no 'tt' field (native types used)
------------------------------------------------------------------------
function luaU:ttype(o)
  local tt = type(o.value)
  if tt == "number" then return self.LUA_TNUMBER
  elseif tt == "string" then return self.LUA_TSTRING
  elseif tt == "nil" then return self.LUA_TNIL
  elseif tt == "boolean" then return self.LUA_TBOOLEAN
  else
    return self.LUA_TNONE  -- the rest should not appear
  end
end

-----------------------------------------------------------------------
-- converts a IEEE754 double number to an 8-byte little-endian string
-- * luaU:from_double() and luaU:from_int() are adapted from ChunkBake
-- * supports +/- Infinity, but not denormals or NaNs
-----------------------------------------------------------------------
function luaU:from_double(x)
  local function grab_byte(v)
    local c = v % 256
    return (v - c) / 256, string.char(c)
  end
  local sign = 0
  if x < 0 then sign = 1; x = -x end
  local mantissa, exponent = math.frexp(x)
  if x == 0 then -- zero
    mantissa, exponent = 0, 0
  elseif x == 1/0 then
    mantissa, exponent = 0, 2047
  else
    mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53)
    exponent = exponent + 1022
  end
  local v, byte = "" -- convert to bytes
  x = math.floor(mantissa)
  for i = 1,6 do
    x, byte = grab_byte(x); v = v..byte -- 47:0
  end
  x, byte = grab_byte(exponent * 16 + x); v = v..byte -- 55:48
  x, byte = grab_byte(sign * 128 + x); v = v..byte -- 63:56
  return v
end

-----------------------------------------------------------------------
-- converts a number to a little-endian 32-bit integer string
-- * input value assumed to not overflow, can be signed/unsigned
-----------------------------------------------------------------------
function luaU:from_int(x)
  local v = ""
  x = math.floor(x)
  if x < 0 then x = 4294967296 + x end  -- ULONG_MAX+1
  for i = 1, 4 do
    local c = x % 256
    v = v..string.char(c); x = math.floor(x / 256)
  end
  return v
end

--[[--------------------------------------------------------------------
-- Functions to make a binary chunk
-- * many functions have the size parameter removed, since output is
--   in the form of a string and some sizes are implicit or hard-coded
----------------------------------------------------------------------]]

--[[--------------------------------------------------------------------
-- struct DumpState:
--   L  -- lua_State (not used in this script)
--   writer  -- lua_Writer (chunk writer function)
--   data  -- void* (chunk writer context or data already written)
--   strip  -- if true, don't write any debug information
--   status  -- if non-zero, an error has occured
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- dumps a block of bytes
-- * lua_unlock(D.L), lua_lock(D.L) unused
------------------------------------------------------------------------
function luaU:DumpBlock(b, D)
  if D.status == 0 then
    -- lua_unlock(D->L);
    D.status = D.write(b, D.data)
    -- lua_lock(D->L);
  end
end

------------------------------------------------------------------------
-- dumps a char
------------------------------------------------------------------------
function luaU:DumpChar(y, D)
  self:DumpBlock(string.char(y), D)
end

------------------------------------------------------------------------
-- dumps a 32-bit signed or unsigned integer (for int) (hard-coded)
------------------------------------------------------------------------
function luaU:DumpInt(x, D)
  self:DumpBlock(self:from_int(x), D)
end

------------------------------------------------------------------------
-- dumps a lua_Number (hard-coded as a double)
------------------------------------------------------------------------
function luaU:DumpNumber(x, D)
  self:DumpBlock(self:from_double(x), D)
end

------------------------------------------------------------------------
-- dumps a Lua string (size type is hard-coded)
------------------------------------------------------------------------
function luaU:DumpString(s, D)
  if s == nil then
    self:DumpInt(0, D)
  else
    s = s.."\0"  -- include trailing '\0'
    self:DumpInt(#s, D)
    self:DumpBlock(s, D)
  end
end

------------------------------------------------------------------------
-- dumps instruction block from function prototype
------------------------------------------------------------------------
function luaU:DumpCode(f, D)
  local n = f.sizecode
  --was DumpVector
  self:DumpInt(n, D)
  for i = 0, n - 1 do
    self:DumpBlock(luaP:Instruction(f.code[i]), D)
  end
end

------------------------------------------------------------------------
-- dump constant pool from function prototype
-- * bvalue(o), nvalue(o) and rawtsvalue(o) macros removed
------------------------------------------------------------------------
function luaU:DumpConstants(f, D)
  local n = f.sizek
  self:DumpInt(n, D)
  for i = 0, n - 1 do
    local o = f.k[i]  -- TValue
    local tt = self:ttype(o)
    self:DumpChar(tt, D)
    if tt == self.LUA_TNIL then
    elseif tt == self.LUA_TBOOLEAN then
      self:DumpChar(o.value and 1 or 0, D)
    elseif tt == self.LUA_TNUMBER then
      self:DumpNumber(o.value, D)
    elseif tt == self.LUA_TSTRING then
      self:DumpString(o.value, D)
    else
      --lua_assert(0)  -- cannot happen
    end
  end
  n = f.sizep
  self:DumpInt(n, D)
  for i = 0, n - 1 do
    self:DumpFunction(f.p[i], f.source, D)
  end
end

------------------------------------------------------------------------
-- dump debug information
------------------------------------------------------------------------
function luaU:DumpDebug(f, D)
  local n
  n = D.strip and 0 or f.sizelineinfo           -- dump line information
  --was DumpVector
  self:DumpInt(n, D)
  for i = 0, n - 1 do
    self:DumpInt(f.lineinfo[i], D)
  end
  n = D.strip and 0 or f.sizelocvars            -- dump local information
  self:DumpInt(n, D)
  for i = 0, n - 1 do
    self:DumpString(f.locvars[i].varname, D)
    self:DumpInt(f.locvars[i].startpc, D)
    self:DumpInt(f.locvars[i].endpc, D)
  end
  n = D.strip and 0 or f.sizeupvalues           -- dump upvalue information
  self:DumpInt(n, D)
  for i = 0, n - 1 do
    self:DumpString(f.upvalues[i], D)
  end
end

------------------------------------------------------------------------
-- dump child function prototypes from function prototype
------------------------------------------------------------------------
function luaU:DumpFunction(f, p, D)
  local source = f.source
  if source == p or D.strip then source = nil end
  self:DumpString(source, D)
  self:DumpInt(f.lineDefined, D)
  self:DumpInt(f.lastlinedefined, D)
  self:DumpChar(f.nups, D)
  self:DumpChar(f.numparams, D)
  self:DumpChar(f.is_vararg, D)
  self:DumpChar(f.maxstacksize, D)
  self:DumpCode(f, D)
  self:DumpConstants(f, D)
  self:DumpDebug(f, D)
end

------------------------------------------------------------------------
-- dump Lua header section (some sizes hard-coded)
------------------------------------------------------------------------
function luaU:DumpHeader(D)
  local h = self:header()
  assert(#h == self.LUAC_HEADERSIZE) -- fixed buffer now an assert
  self:DumpBlock(h, D)
end

------------------------------------------------------------------------
-- make header (from lundump.c)
-- returns the header string
------------------------------------------------------------------------
function luaU:header()
 local x = 1
 return self.LUA_SIGNATURE..
        string.char(
          self.LUAC_VERSION,
          self.LUAC_FORMAT,
          x,                    -- endianness (1=little)
          4,                    -- sizeof(int)
          4,                    -- sizeof(size_t)
          4,                    -- sizeof(Instruction)
          8,                    -- sizeof(lua_Number)
          0)                    -- is lua_Number integral?
end

------------------------------------------------------------------------
-- dump Lua function as precompiled chunk
-- (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip)
-- * w, data are created from make_setS, make_setF
------------------------------------------------------------------------
function luaU:dump(L, f, w, data, strip)
  local D = {}  -- DumpState
  D.L = L
  D.write = w
  D.data = data
  D.strip = strip
  D.status = 0
  self:DumpHeader(D)
  self:DumpFunction(f, nil, D)
  -- added: for a chunk writer writing to a file, this final call with
  -- nil data is to indicate to the writer to close the file
  D.write(nil, D.data)
  return D.status
end

return luaU


--# selene: allow(incorrect_standard_library_use, multiple_statements, shadowing, unused_variable, empty_if, divide_by_zero, unbalanced_assignments)
--[[--------------------------------------------------------------------

  llex.lua
  Lua lexical analyzer in Lua
  This file is part of Yueliang.

  Copyright (c) 2005-2006 Kein-Hong Man <khman@users.sf.net>
  The COPYRIGHT file describes the conditions
  under which this software may be distributed.

  See the ChangeLog for more information.

----------------------------------------------------------------------]]

--[[--------------------------------------------------------------------
-- Notes:
-- * intended to 'imitate' llex.c code; performance is not a concern
-- * tokens are strings; code structure largely retained
-- * deleted stuff (compared to llex.c) are noted, comments retained
-- * nextc() returns the currently read character to simplify coding
--   here; next() in llex.c does not return anything
-- * compatibility code is marked with "--#" comments
--
-- Added:
-- * luaX:chunkid (function luaO_chunkid from lobject.c)
-- * luaX:str2d (function luaO_str2d from lobject.c)
-- * luaX.LUA_QS used in luaX:lexerror (from luaconf.h)
-- * luaX.LUA_COMPAT_LSTR in luaX:read_long_string (from luaconf.h)
-- * luaX.MAX_INT used in luaX:inclinenumber (from llimits.h)
-- Luau specific additions by ccuser44:
--   * Added Luau number handling
--   * Added post 5.1 string handling
--   * Added env localisation aiding for parser
--   * Made lexer pre-lex lex tree instead of using on-the-fly lexing.
--     Necessary for env localisation and more efficient than building
--     a parse tree (which would require full pre-lexing as well)
--
-- To use the lexer:
-- (1) luaX:init() to initialize the lexer
-- (2) luaX:setinput() to set the input stream to lex
-- (3) call luaX:next() or luaX:luaX:lookahead() to get tokens,
--     until "TK_EOS": luaX:next()
-- * since EOZ is returned as a string, be careful when regexp testing
--
-- Not implemented:
-- * luaX_newstring: not required by this Lua implementation
-- * buffer MAX_SIZET size limit (from llimits.h) test not implemented
--   in the interest of performance
-- * locale-aware number handling is largely redundant as Lua's
--   tonumber() function is already capable of this
--
-- Changed in 5.1.x:
-- * TK_NAME token order moved down
-- * string representation for TK_NAME, TK_NUMBER, TK_STRING changed
-- * token struct renamed to lower case (LS -> ls)
-- * LexState struct: removed nestlevel, added decpoint
-- * error message functions have been greatly simplified
-- * token2string renamed to luaX_tokens, exposed in llex.h
-- * lexer now handles all kinds of newlines, including CRLF
-- * shbang first line handling removed from luaX:setinput;
--   it is now done in lauxlib.c (luaL_loadfile)
-- * next(ls) macro renamed to nextc(ls) due to new luaX_next function
-- * EXTRABUFF and MAXNOCHECK removed due to lexer changes
-- * checkbuffer(ls, len) macro deleted
-- * luaX:read_numeral now has 3 support functions: luaX:trydecpoint,
--   luaX:buffreplace and (luaO_str2d from lobject.c) luaX:str2d
-- * luaX:read_numeral is now more promiscuous in slurping characters;
--   hexadecimal numbers was added, locale-aware decimal points too
-- * luaX:skip_sep is new; used by luaX:read_long_string
-- * luaX:read_long_string handles new-style long blocks, with some
--   optional compatibility code
-- * luaX:llex: parts changed to support new-style long blocks
-- * luaX:llex: readname functionality has been folded in
-- * luaX:llex: removed test for control characters
--
--------------------------------------------------------------------]]

local luaZ = require(script.Parent.LuaZ)

local luaX = {}

-- FIRST_RESERVED is not required as tokens are manipulated as strings
-- TOKEN_LEN deleted; maximum length of a reserved word not needed

------------------------------------------------------------------------
-- "ORDER RESERVED" deleted; enumeration in one place: luaX.RESERVED
------------------------------------------------------------------------

-- terminal symbols denoted by reserved words: TK_AND to TK_WHILE
-- other terminal symbols: TK_NAME to TK_EOS
luaX.RESERVED = [[
TK_AND and
TK_BREAK break
TK_CONTINUE continue
TK_DO do
TK_ELSE else
TK_ELSEIF elseif
TK_END end
TK_FALSE false
TK_FOR for
TK_FUNCTION function
TK_IF if
TK_IN in
TK_LOCAL local
TK_NIL nil
TK_NOT not
TK_OR or
TK_REPEAT repeat
TK_RETURN return
TK_THEN then
TK_TRUE true
TK_UNTIL until
TK_WHILE while
TK_ASSIGN_ADD +=
TK_ASSIGN_SUB -=
TK_ASSIGN_MUL *=
TK_ASSIGN_DIV /=
TK_ASSIGN_MOD %=
TK_ASSIGN_POW ^=
TK_ASSIGN_CONCAT ..=
TK_CONCAT ..
TK_DOTS ...
TK_EQ ==
TK_GE >=
TK_LE <=
TK_NE ~=
TK_NAME <name>
TK_NUMBER <number>
TK_STRING <string>
TK_EOS <eof>]]

-- NUM_RESERVED is not required; number of reserved words

--[[--------------------------------------------------------------------
-- Instead of passing seminfo, the Token struct (e.g. ls.t) is passed
-- so that lexer functions can use its table element, ls.t.seminfo
--
-- SemInfo (struct no longer needed, a mixed-type value is used)
--
-- Token (struct of ls.t and ls.lookahead):
--   token  -- token symbol
--   seminfo  -- semantics information
--
-- LexState (struct of ls; ls is initialized by luaX:setinput):
--   current  -- current character (charint)
--   linenumber  -- input line counter
--   lastline  -- line of last token 'consumed'
--   t  -- current token (table: struct Token)
--   lookahead  -- look ahead token (table: struct Token)
--   fs  -- 'FuncState' is private to the parser
--   L -- LuaState
--   z  -- input stream
--   buff  -- buffer for tokens
--   source  -- current source name
--   decpoint -- locale decimal point
--   nestlevel  -- level of nested non-terminals
----------------------------------------------------------------------]]

-- luaX.tokens (was luaX_tokens) is now a hash; see luaX:init

luaX.MAXSRC = 80
luaX.MAX_INT = 2147483645       -- constants from elsewhere (see above)
luaX.LUA_QS = "'%s'"
luaX.LUA_COMPAT_LSTR = 1
--luaX.MAX_SIZET = 4294967293

------------------------------------------------------------------------
-- LuaU specific constants
-- * luaX.DEOP: Tokens that cause env deoptimisation
-- * luaX.GLOBALVARS: Tokens that are globals in the env
------------------------------------------------------------------------
luaX.DEOP = {
	["getf".."env"] = true, ["setf".."env"] = true, ["_".."ENV"] = true
}
luaX.GLOBALVARS = [[
_G _VERSION

assert collectgarbage dofile error getmetatable ipairs
load loadfile module next pairs pcall
print rawequal rawget rawset require select
setmetatable tonumber tostring type unpack xpcall
newproxy gcinfo warn

game plugin script shared workspace

coroutine debug io math os package
string table bit32 utf8

DebuggerManager delay PluginManager settings spawn tick
time UserSettings wait

task

Axes BrickColor CatalogSearchParams CFrame Color3 ColorSequence
ColorSequenceKeypoint DateTime DockWidgetPluginGuiInfo Enum Faces FloatCurveKey
Font Instance NumberRange NumberSequence NumberSequenceKeypoint OverlapParams
PathWaypoint PhysicalProperties Random Ray RaycastParams Rect
Region3 Region3int16 RotationCurveKey SharedTable TweenInfo UDim
UDim2 Vector2 Vector2int16 Vector3 Vector3int16
]]

------------------------------------------------------------------------
-- initialize lexer
-- * original luaX_init has code to create and register token strings
-- * luaX.tokens: TK_* -> token
-- * luaX.enums:  token -> TK_* (used in luaX:llex)
------------------------------------------------------------------------
function luaX:init()
  local tokens, enums, globalvars = {}, {}, {}
  for v in string.gmatch(self.RESERVED, "[^\n]+") do
    local _, _, tok, str = string.find(v, "(%S+)%s+(%S+)")
    tokens[tok] = str
    enums[str] = tok
  end
  for v in string.gmatch(self.GLOBALVARS, "[^%s]+") do
    globalvars[v] = true
  end
  self.tokens = tokens
  self.enums = enums
  self.globalvars = globalvars
end

------------------------------------------------------------------------
-- returns a suitably-formatted chunk name or id
-- * from lobject.c, used in llex.c and ldebug.c
-- * the result, out, is returned (was first argument)
------------------------------------------------------------------------
function luaX:chunkid(source, bufflen)
  local out
  local first = string.sub(source, 1, 1)
  if first == "=" then
    out = string.sub(source, 2, bufflen)  -- remove first char
  else  -- out = "source", or "...source"
    if first == "@" then
      source = string.sub(source, 2)  -- skip the '@'
      bufflen = bufflen - #" '...' "
      local l = #source
      out = ""
      if l > bufflen then
        source = string.sub(source, 1 + l - bufflen)  -- get last part of file name
        out = out.."..."
      end
      out = out..source
    else  -- out = [string "string"]
      local len = string.find(source, "[\n\r]")  -- stop at first newline
      len = len and (len - 1) or #source
      bufflen = bufflen - #(" [string \"...\"] ")
      if len > bufflen then len = bufflen end
      out = "[string \""
      if len < #source then  -- must truncate?
        out = out..string.sub(source, 1, len).."..."
      else
        out = out..source
      end
      out = out.."\"]"
    end
  end
  return out
end

--[[--------------------------------------------------------------------
-- Support functions for lexer
-- * all lexer errors eventually reaches lexerror:
     syntaxerror -> lexerror
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- look up token and return keyword if found (also called by parser)
------------------------------------------------------------------------
function luaX:token2str(ls, token)
  if string.sub(token, 1, 3) ~= "TK_" then
    if string.find(token, "%c") then
      return string.format("char(%d)", string.byte(token))
    end
    return token
  else
  end
    return self.tokens[token]
end

------------------------------------------------------------------------
-- throws a lexer error
-- * txtToken has been made local to luaX:lexerror
-- * can't communicate LUA_ERRSYNTAX, so it is unimplemented
------------------------------------------------------------------------
function luaX:lexerror(ls, msg, token)
  local function txtToken(ls, token)
    if token == "TK_NAME" or
       token == "TK_STRING" or
       token == "TK_NUMBER" then
      return ls.buff
    else
      return self:token2str(ls, token)
    end
  end
  local buff = self:chunkid(ls.source, self.MAXSRC)
  local msg = string.format("%s:%d: %s", buff, ls.linenumber, msg)
  if token then
    msg = string.format("%s near "..self.LUA_QS, msg, txtToken(ls, token))
  end
  -- luaD_throw(ls->L, LUA_ERRSYNTAX)
  error(msg)
end

------------------------------------------------------------------------
-- throws a syntax error (mainly called by parser)
-- * ls.t.token has to be set by the function calling luaX:llex
--   (see luaX:next and luaX:lookahead elsewhere in this file)
------------------------------------------------------------------------
function luaX:syntaxerror(ls, msg)
  self:lexerror(ls, msg, ls.t.token)
end

------------------------------------------------------------------------
-- move on to next line
------------------------------------------------------------------------
function luaX:currIsNewline(ls)
  return ls.current == "\n" or ls.current == "\r"
end

function luaX:inclinenumber(ls)
  local old = ls.current
  -- lua_assert(currIsNewline(ls))
  self:nextc(ls)  -- skip '\n' or '\r'
  if self:currIsNewline(ls) and ls.current ~= old then
    self:nextc(ls)  -- skip '\n\r' or '\r\n'
  end
  ls.linenumber = ls.linenumber + 1
  if ls.linenumber >= self.MAX_INT then
    self:syntaxerror(ls, "chunk has too many lines")
  end
end

------------------------------------------------------------------------
-- initializes an input stream for lexing
-- * if ls (the lexer state) is passed as a table, then it is filled in,
--   otherwise it has to be retrieved as a return value
-- * LUA_MINBUFFER not used; buffer handling not required any more
------------------------------------------------------------------------
function luaX:setinput(L, ls, z, source)
  if not ls then ls = {} end  -- create struct
  if not ls.lookahead then ls.lookahead = {} end
  if not ls.t then ls.t = {} end
  ls.decpoint = "."
  ls.L = L
  ls.lookahead.token = "TK_EOS"  -- no look-ahead token
  ls.z = z
  ls.fs = nil
  ls.linenumber = 1
  ls.lastline = 1
  ls.source = source
  ls.safeenv = true
  ls.usedglobals = {}
  self:nextc(ls)  -- read first char
end

--[[--------------------------------------------------------------------
-- LEXICAL ANALYZER
----------------------------------------------------------------------]]

------------------------------------------------------------------------
-- checks if current character read is found in the set 'set'
------------------------------------------------------------------------
function luaX:check_next(ls, set)
  if not string.find(set, ls.current, 1, 1) then
    return false
  end
  self:save_and_next(ls)
  return true
end

------------------------------------------------------------------------
-- retrieve next token, checking the lookahead buffer if necessary
-- * note that the macro next(ls) in llex.c is now luaX:nextc
-- * utilized used in lparser.c (various places)
------------------------------------------------------------------------
function luaX:next(ls)
  ls.lastline = ls.linenumber
  if ls.lookahead.token ~= "TK_EOS" then  -- is there a look-ahead token?
    -- this must be copy-by-value
    ls.t.seminfo = ls.lookahead.seminfo  -- use this one
    ls.t.token = ls.lookahead.token
    ls.lookahead.token = "TK_EOS"  -- and discharge it
  else
    ls.t.token, ls.t.seminfo, ls.linenumber = self:poptk(ls)  -- read next token
  end
end

------------------------------------------------------------------------
-- fill in the lookahead buffer
-- * utilized used in lparser.c:constructor
-- * Does currently nothing because of lexer caching
------------------------------------------------------------------------
function luaX:lookahead(ls)
  -- lua_assert(ls.lookahead.token == "TK_EOS")
  ls.lookahead.token, ls.lookahead.seminfo, ls.lookahead.linenumber = self:poptk(ls)--self:llex(ls, ls.lookahead)
end

------------------------------------------------------------------------
-- gets the next character and returns it
-- * this is the next() macro in llex.c; see notes at the beginning
------------------------------------------------------------------------
function luaX:nextc(ls)
  local c = luaZ:zgetc(ls.z)
  ls.current = c
  return c
end

------------------------------------------------------------------------
-- saves the given character into the token buffer
-- * buffer handling code removed, not used in this implementation
-- * test for maximum token buffer length not used, makes things faster
------------------------------------------------------------------------

function luaX:save(ls, c)
  local buff = ls.buff
  -- if you want to use this, please uncomment luaX.MAX_SIZET further up
  --if #buff > self.MAX_SIZET then
  --  self:lexerror(ls, "lexical element too long")
  --end
  ls.buff = buff..c
end

------------------------------------------------------------------------
-- save current character into token buffer, grabs next character
-- * like luaX:nextc, returns the character read for convenience
------------------------------------------------------------------------
function luaX:save_and_next(ls)
  self:save(ls, ls.current)
  return self:nextc(ls)
end

------------------------------------------------------------------------
-- LUA_NUMBER
-- * luaX:read_numeral is the main lexer function to read a number
-- * luaX:str2d, luaX:buffreplace, luaX:trydecpoint are support functions
------------------------------------------------------------------------

------------------------------------------------------------------------
-- string to number converter (was luaO_str2d from lobject.c)
-- * returns the number, nil if fails (originally returns a boolean)
-- * conversion function originally lua_str2number(s,p), a macro which
--   maps to the strtod() function by default (from luaconf.h)
-- * ccuser44 was here to add support for binary intiger constants and
--   intiger decimal seperators
------------------------------------------------------------------------
function luaX:str2d(s)
	-- Support for Luau decimal seperators for integer literals
	if string.match(string.lower(s), "[^b%da-f_]_") or string.match(string.lower(s), "_[^%da-f_]") then
		return nil
	end
	s = string.gsub(s, "_", "")

	local result = tonumber(s)
	if result then return result end
	-- conversion failed

	if string.lower(string.sub(s, 1, 2)) == "0x" then  -- maybe an hexadecimal constant?
		result = tonumber(s, 16)
		if result then return result end  -- most common case
		-- Was: invalid trailing characters?
		-- In C, this function then skips over trailing spaces.
		-- true is returned if nothing else is found except for spaces.
		-- If there is still something else, then it returns a false.
		-- All this is not necessary using Lua's tonumber.
	elseif string.lower(string.sub(s, 1, 2)) == "0b" then  -- binary intiger constants
		if string.match(string.sub(s, 3), "[^01]") then
			return nil
		end

		local bin = string.reverse(string.sub(s, 3))
		local sum = 0

		for i = 1, string.len(bin) do
			sum = sum + bit32.lshift(string.sub(bin, i, i) == "1" and 1 or 0, i - 1)
		end

		return sum
	end
	return nil
end

------------------------------------------------------------------------
-- single-character replacement, for locale-aware decimal points
------------------------------------------------------------------------
function luaX:buffreplace(ls, from, to)
  local result, buff = "", ls.buff
  for p = 1, #buff do
    local c = string.sub(buff, p, p)
    if c == from then c = to end
    result = result..c
  end
  ls.buff = result
end

------------------------------------------------------------------------
-- Attempt to convert a number by translating '.' decimal points to
-- the decimal point character used by the current locale. This is not
-- needed in Yueliang as Lua's tonumber() is already locale-aware.
-- Instead, the code is here in case the user implements localeconv().
------------------------------------------------------------------------
function luaX:trydecpoint(ls, Token)
  -- format error: try to update decimal point separator
  local old = ls.decpoint
  -- translate the following to Lua if you implement localeconv():
  -- struct lconv *cv = localeconv();
  -- ls->decpoint = (cv ? cv->decimal_point[0] : '.');
  self:buffreplace(ls, old, ls.decpoint)  -- try updated decimal separator
  local seminfo = self:str2d(ls.buff)
  Token.seminfo = seminfo
  if not seminfo then
    -- format error with correct decimal point: no more options
    self:buffreplace(ls, ls.decpoint, ".")  -- undo change (for error message)
    self:lexerror(ls, "malformed number", "TK_NUMBER")
  end
end

------------------------------------------------------------------------
-- main number conversion function
-- * "^%w$" needed in the scan in order to detect "EOZ"
------------------------------------------------------------------------
function luaX:read_numeral(ls, Token)
  -- lua_assert(string.find(ls.current, "%d"))
  repeat
    self:save_and_next(ls)
  until string.find(ls.current, "%D") and ls.current ~= "."
  if self:check_next(ls, "Ee") then  -- 'E'?
    self:check_next(ls, "+-")  -- optional exponent sign
  end
  while string.find(ls.current, "^%w$") or ls.current == "_" do
    self:save_and_next(ls)
  end
  self:buffreplace(ls, ".", ls.decpoint)  -- follow locale for decimal point
  local seminfo = self:str2d(ls.buff)
  Token.seminfo = seminfo
  if not seminfo then  -- format error?
    self:trydecpoint(ls, Token) -- try to update decimal point separator
  end
end

------------------------------------------------------------------------
-- count separators ("=") in a long string delimiter
-- * used by luaX:read_long_string
------------------------------------------------------------------------
function luaX:skip_sep(ls)
  local count = 0
  local s = ls.current
  -- lua_assert(s == "[" or s == "]")
  self:save_and_next(ls)
  while ls.current == "=" do
    self:save_and_next(ls)
    count = count + 1
  end
  return (ls.current == s) and count or (-count) - 1
end

------------------------------------------------------------------------
-- reads a long string or long comment
------------------------------------------------------------------------
function luaX:read_long_string(ls, Token, sep)
  local cont = 0
  self:save_and_next(ls)  -- skip 2nd '['
  if self:currIsNewline(ls) then  -- string starts with a newline?
    self:inclinenumber(ls)  -- skip it
  end
  while true do
    local c = ls.current
    if c == "EOZ" then
      self:lexerror(ls, Token and "unfinished long string" or
                    "unfinished long comment", "TK_EOS")
    elseif c == "[" then
      --# compatibility code start
      if self.LUA_COMPAT_LSTR then
        if self:skip_sep(ls) == sep then
          self:save_and_next(ls)  -- skip 2nd '['
          cont = cont + 1
          --# compatibility code start
          if self.LUA_COMPAT_LSTR == 1 then
            if sep == 0 then
              self:lexerror(ls, "nesting of [[...]] is deprecated", "[")
            end
          end
          --# compatibility code end
        end
      end
      --# compatibility code end
    elseif c == "]" then
      if self:skip_sep(ls) == sep then
        self:save_and_next(ls)  -- skip 2nd ']'
        --# compatibility code start
        if self.LUA_COMPAT_LSTR and self.LUA_COMPAT_LSTR == 2 then
          cont = cont - 1
          if sep == 0 and cont >= 0 then break end
        end
        --# compatibility code end
        break
      end
    elseif self:currIsNewline(ls) then
      self:save(ls, "\n")
      self:inclinenumber(ls)
      if not Token then ls.buff = "" end -- avoid wasting space
    else  -- default
      if Token then
        self:save_and_next(ls)
      else
        self:nextc(ls)
      end
    end--if c
  end--while
  if Token then
    local p = 3 + sep
    Token.seminfo = string.sub(ls.buff, p, -p)
  end
end

------------------------------------------------------------------------
-- reads a string
-- * has been restructured significantly compared to the original C code
-- * ccuser44 was here to add support for UTF8 string literals,
-- hex numerical string literals and the \z string literal
------------------------------------------------------------------------

function luaX:read_string(ls, del, Token)
	self:save_and_next(ls)
	while ls.current ~= del do
		local c = ls.current
		if c == "EOZ" then
			self:lexerror(ls, "unfinished string", "TK_EOS")
		elseif self:currIsNewline(ls) then
			self:lexerror(ls, "unfinished string", "TK_STRING")
		elseif c == "\\" then
			c = self:nextc(ls)  -- do not save the '\'
			if self:currIsNewline(ls) then  -- go through
				self:save(ls, "\n")
				self:inclinenumber(ls)
			elseif c ~= "EOZ" then -- will raise an error next loop
				-- escapes handling greatly simplified here:
				local i = string.find("abfnrtv", c, 1, 1)
				if i then
					self:save(ls, string.sub("\a\b\f\n\r\t\v", i, i))
					self:nextc(ls)
				elseif c == "u" then -- UTF8 string literal
					assert(utf8 and utf8.char, "No utf8 library found! Cannot decode UTF8 string literal!")

					if self:nextc(ls) ~= "{" then
						self:lexerror("Sounds like a skill issue", "TK_STRING")
					end

					local unicodeCharacter = ""

					while true do
						c = self:nextc(ls)

						if c == "}" then
							break
						elseif string.match(c, "%x") then
							unicodeCharacter = unicodeCharacter .. c
						else
							self:lexerror(string.format("Invalid unicode character sequence. Expected alphanumeric character, got %s. Did you forget to close the code sequence with a curly bracket?", c), "TK_STRING")
						end
					end

					if not tonumber(unicodeCharacter, 16) or not utf8.char(tonumber(unicodeCharacter, 16)) then
						self:lexerror(string.format("Invalid UTF8 char %s. Expected a valid UTF8 character code", unicodeCharacter), "TK_STRING")
					else
						self:save(ls, utf8.char(tonumber(unicodeCharacter)))
					end
				elseif string.lower(c) == "x" then -- Hex numeral literal
					local hexNum = self:nextc(ls)..self:nextc(ls)

					if not string.match(string.upper(hexNum), "%x") then
						self:lexerror(string.format("Invalid hex string literal. Expected valid string literal, got %s", hexNum), "TK_STRING")
					else
						self:save(ls, string.char(tonumber(hexNum, 16)))
					end
				elseif string.lower(c) == "z" then -- Support \z string literal. I'm not sure why you would want to use this
					local c = luaX:nextc(ls)

					if c == del then
						break
					else
						self:save(ls, c)
					end
				elseif not string.find(c, "%d") then
					self:save_and_next(ls)  -- handles \\, \", \', and \?
				else  -- \xxx
					c, i = 0, 0
					repeat
						c = 10 * c + ls.current
						self:nextc(ls)
						i = i + 1
					until i >= 3 or not string.find(ls.current, "%d")
					if c > 255 then  -- UCHAR_MAX
						self:lexerror(ls, "escape sequence too large", "TK_STRING")
					end
					self:save(ls, string.char(c))
				end
			end
		else
			self:save_and_next(ls)
		end--if c
	end--while
	self:save_and_next(ls)  -- skip delimiter
	Token.seminfo = string.sub(ls.buff, 2, -2)
end

------------------------------------------------------------------------
-- lexer cache
-- * Pre-lex the whole lex tree and then cache it
--   Allow global optimisation by scanning all lex tokens before parsing
------------------------------------------------------------------------

function luaX:poptk(ls)
	if ls.lexercache then
		local tkdata = ls.lexercache
		local data = tkdata[tkdata.n]
		tkdata.n, tkdata[tkdata.n] = tkdata.n - 1, nil

		return data.type or "TK_EOS", data.seminfo or "", data.line
	end

	-- Generate lex tree stack
	local tkdata = {}
	ls.lexercache = tkdata
	while true do
		local type = luaX:llex(ls, ls.t)

		table.insert(tkdata, {
			type = type,
			seminfo = ls.t.seminfo,
			line = ls.linenumber,
		})

		if type == "TK_EOS" then
			break
		end
	end

	-- Reverse lex tree stack
	local len = #tkdata
	tkdata.n = len
	ls.linenumber, ls.lastline, ls.lookahead.token = 1, 1, "TK_EOS"
	for i = 1, math.floor(len/2) do
		local j = len - i + 1
		tkdata[i], tkdata[j] = tkdata[j], tkdata[i]
	end

	-- Ugly hack to support global localisation without parser logic
	-- TODO: Switch to parser logic LOCALEXP(name) = GLOBALEXP(name), ...
	local usedglobals = ls.usedglobals
	if ls.safeenv and #usedglobals > 0 then
		for i = #usedglobals, 1, -1 do
			tkdata.n = tkdata.n + 2
			tkdata[tkdata.n - 1], tkdata[tkdata.n] = {
				type = "TK_NAME",
				seminfo = usedglobals[i],
				line = 1
			}, {
				type = ",",
				seminfo = ",",
				line = 1
			}
		end
		tkdata[tkdata.n] = {
			type = "=",
			seminfo = "=",
			line = 1
		}
		for i = #usedglobals, 1, -1 do
			tkdata.n = tkdata.n + 2
			tkdata[tkdata.n - 1], tkdata[tkdata.n] = {
				type = "TK_NAME",
				seminfo = usedglobals[i],
				line = 1
			}, {
				type = ",",
				seminfo = ",",
				line = 1
			}
		end
		tkdata[tkdata.n] = {
			type = "TK_LOCAL",
			seminfo = "local",
			line = 1
		}
	end

	return self:poptk(ls)
end

------------------------------------------------------------------------
-- main lexer function
------------------------------------------------------------------------
function luaX:llex(ls, Token)
  ls.buff = ""
  while true do
    local c = ls.current
    ----------------------------------------------------------------
    if self:currIsNewline(ls) then
      self:inclinenumber(ls)
    ----------------------------------------------------------------
    elseif c == "-" then
      c = self:nextc(ls)
      if c == "=" then self:nextc(ls); return "TK_ASSIGN_SUB" -- Luau Compound
      elseif c ~= "-" then return "-" end
      -- else is a comment
      local sep = -1
      if self:nextc(ls) == '[' then
        sep = self:skip_sep(ls)
        ls.buff = ""  -- 'skip_sep' may dirty the buffer
      end
      if sep >= 0 then
        self:read_long_string(ls, nil, sep)  -- long comment
        ls.buff = ""
      else  -- else short comment
        while not self:currIsNewline(ls) and ls.current ~= "EOZ" do
          self:nextc(ls)
        end
      end
    ----------------------------------------------------------------
    elseif c == "[" then
      local sep = self:skip_sep(ls)
      if sep >= 0 then
        self:read_long_string(ls, Token, sep)
        return "TK_STRING"
      elseif sep == -1 then
        return "["
      else
        self:lexerror(ls, "invalid long string delimiter", "TK_STRING")
      end
    ---------------------Luau Compound Start------------------------
    elseif c == "+" then
      c = self:nextc(ls)
      if c ~= "=" then return "+"
      else self:nextc(ls); return "TK_ASSIGN_ADD" end
    ----------------------------------------------------------------
    elseif c == "*" then
      c = self:nextc(ls)
      if c ~= "=" then return "*"
      else self:nextc(ls); return "TK_ASSIGN_MUL" end
    ----------------------------------------------------------------
    elseif c == "/" then
      c = self:nextc(ls)
      if c ~= "=" then return "/"
      else self:nextc(ls); return "TK_ASSIGN_DIV" end
    ----------------------------------------------------------------
    elseif c == "%" then
      c = self:nextc(ls)
      if c ~= "=" then return "%"
      else self:nextc(ls); return "TK_ASSIGN_MOD" end
    ----------------------------------------------------------------
    elseif c == "^" then
      c = self:nextc(ls)
      if c ~= "=" then return "^"
      else self:nextc(ls); return "TK_ASSIGN_POW" end
    ----------------------------------------------------------------
    --             TODO: TK_ASSIGN_CONCAT support                 --
    ----------------------Luau Compound End-------------------------
    elseif c == "=" then
      c = self:nextc(ls)
      if c ~= "=" then return "="
      else self:nextc(ls); return "TK_EQ" end
    ----------------------------------------------------------------
    elseif c == "<" then
      c = self:nextc(ls)
      if c ~= "=" then return "<"
      else self:nextc(ls); return "TK_LE" end
    ----------------------------------------------------------------
    elseif c == ">" then
      c = self:nextc(ls)
      if c ~= "=" then return ">"
      else self:nextc(ls); return "TK_GE" end
    ----------------------------------------------------------------
    elseif c == "~" then
      c = self:nextc(ls)
      if c ~= "=" then return "~"
      else self:nextc(ls); return "TK_NE" end
    ----------------------------------------------------------------
    elseif c == "\"" or c == "'" then
      self:read_string(ls, c, Token)
      return "TK_STRING"
    ----------------------------------------------------------------
    elseif c == "." then
      c = self:save_and_next(ls)
      if self:check_next(ls, ".") then
        if self:check_next(ls, ".") then
          return "TK_DOTS"   -- ...
        else return "TK_CONCAT"   -- ..
        end
      elseif not string.find(c, "%d") then
        return "."
      else
        self:read_numeral(ls, Token)
        return "TK_NUMBER"
      end
    ----------------------------------------------------------------
    elseif c == "EOZ" then
      return "TK_EOS"
    ----------------------------------------------------------------
    else  -- default
      if string.find(c, "%s") then
        -- lua_assert(self:currIsNewline(ls))
        self:nextc(ls)
      elseif string.find(c, "%d") then
        self:read_numeral(ls, Token)
        return "TK_NUMBER"
      elseif string.find(c, "[_%a]") then
        -- identifier or reserved word
        repeat
          c = self:save_and_next(ls)
        until c == "EOZ" or not string.find(c, "[_%w]")
        local ts = ls.buff
        local tok = self.enums[ts]
        if tok then return tok end  -- reserved word?

		-- Global optimisation helper
		if self.DEOP[ts] then
		  ls.safeenv = false
		elseif self.globalvars[ts] and not table.find(ls.usedglobals, ts) then
		  table.insert(ls.usedglobals, ts)
		end

        Token.seminfo = ts
        return "TK_NAME"
      else
        self:nextc(ls)
        return c  -- single-char tokens (+ - / ...)
      end
    ----------------------------------------------------------------
    end--if c
  end--while
end

return luaX


--# selene: allow(incorrect_standard_library_use, multiple_statements, shadowing, unused_variable, empty_if, divide_by_zero, unbalanced_assignments)
--[[--------------------------------------------------------------------

  lzio.lua
  Lua buffered streams in Lua
  This file is part of Yueliang.

  Copyright (c) 2005-2006 Kein-Hong Man <khman@users.sf.net>
  The COPYRIGHT file describes the conditions
  under which this software may be distributed.

  See the ChangeLog for more information.

----------------------------------------------------------------------]]

--[[--------------------------------------------------------------------
-- Notes:
-- * EOZ is implemented as a string, "EOZ"
-- * Format of z structure (ZIO)
--     z.n       -- bytes still unread
--     z.p       -- last read position position in buffer
--     z.reader  -- chunk reader function
--     z.data    -- additional data
-- * Current position, p, is now last read index instead of a pointer
--
-- Not implemented:
-- * luaZ_lookahead: used only in lapi.c:lua_load to detect binary chunk
-- * luaZ_read: used only in lundump.c:ezread to read +1 bytes
-- * luaZ_openspace: dropped; let Lua handle buffers as strings (used in
--   lundump.c:LoadString & lvm.c:luaV_concat)
-- * luaZ buffer macros: dropped; buffers are handled as strings
-- * lauxlib.c:getF reader implementation has an extraline flag to
--   skip over a shbang (#!) line, this is not implemented here
--
-- Added:
-- (both of the following are vaguely adapted from lauxlib.c)
-- * luaZ:make_getS: create Reader from a string
-- * luaZ:make_getF: create Reader that reads from a file
--
-- Changed in 5.1.x:
-- * Chunkreader renamed to Reader (ditto with Chunkwriter)
-- * Zio struct: no more name string, added Lua state for reader
--   (however, Yueliang readers do not require a Lua state)
----------------------------------------------------------------------]]

local luaZ = {}

------------------------------------------------------------------------
-- * reader() should return a string, or nil if nothing else to parse.
--   Additional data can be set only during stream initialization
-- * Readers are handled in lauxlib.c, see luaL_load(file|buffer|string)
-- * LUAL_BUFFERSIZE=BUFSIZ=512 in make_getF() (located in luaconf.h)
-- * Original Reader typedef:
--   const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz);
-- * This Lua chunk reader implementation:
--   returns string or nil, no arguments to function
------------------------------------------------------------------------

------------------------------------------------------------------------
-- create a chunk reader from a source string
------------------------------------------------------------------------
function luaZ:make_getS(buff)
  local b = buff
  return function() -- chunk reader anonymous function here
    if not b then return nil end
    local data = b
    b = nil
    return data
  end
end

------------------------------------------------------------------------
-- create a chunk reader from a source file
------------------------------------------------------------------------
--[[
function luaZ:make_getF(filename)
  local LUAL_BUFFERSIZE = 512
  local h = io.open(filename, "r")
  if not h then return nil end
  return function() -- chunk reader anonymous function here
    if not h or io.type(h) == "closed file" then return nil end
    local buff = h:read(LUAL_BUFFERSIZE)
    if not buff then h:close(); h = nil end
    return buff
  end
end
--]]
------------------------------------------------------------------------
-- creates a zio input stream
-- returns the ZIO structure, z
------------------------------------------------------------------------
function luaZ:init(reader, data, name)
  if not reader then return end
  local z = {}
  z.reader = reader
  z.data = data or ""
  z.name = name
  -- set up additional data for reading
  if not data or data == "" then z.n = 0 else z.n = #data end
  z.p = 0
  return z
end

------------------------------------------------------------------------
-- fill up input buffer
------------------------------------------------------------------------
function luaZ:fill(z)
  local buff = z.reader()
  z.data = buff
  if not buff or buff == "" then return "EOZ" end
  z.n, z.p = #buff - 1, 1
  return string.sub(buff, 1, 1)
end

------------------------------------------------------------------------
-- get next character from the input stream
-- * local n, p are used to optimize code generation
------------------------------------------------------------------------
function luaZ:zgetc(z)
  local n, p = z.n, z.p + 1
  if n > 0 then
    z.n, z.p = n - 1, p
    return string.sub(z.data, p, p)
  else
    return self:fill(z)
  end
end

return luaZ


--# selene: allow(incorrect_standard_library_use)
--[[
	Credit to einsteinK.
	Credit to Stravant for LBI.
	Credit to ccuser44 for proto conversion.

	Credit to the creators of all the other modules used in this.

	Sceleratis was here and decided modify some things.

	einsteinK was here again to fix a bug in LBI for if-statements
--]]

local PROTO_CONVERT = true -- If proto conversion is used instead of serialise->deserialise
local DEPENDENCIES = {
	"FiOne";
	"LuaK";
	"LuaP";
	"LuaU";
	"LuaX";
	"LuaY";
	"LuaZ";
	"VirtualEnv";
}

for _, v in ipairs(DEPENDENCIES) do
	script:WaitForChild(v)
end

local luaX = require(script.LuaX)
local luaY = require(script.LuaY)
local luaZ = require(script.LuaZ)
local luaU = require(script.LuaU)
local fiOne = require(script.FiOne)
local getvenv = require(script.VirtualEnv)

local function to1BasedIndex(tbl)
	local tbl = table.move(tbl, 0, #tbl + (tbl[0] and 1 or 0), 1)
	tbl[0] = nil

	return tbl
end

local function protoConvert(proto, opRemap, opType, opMode)
	local const = table.create(#proto.k + 1)
	proto.code, proto.lines, proto.subs = to1BasedIndex(proto.code), to1BasedIndex(proto.lineinfo), to1BasedIndex(proto.p)
	proto.lineinfo, proto.p = nil, nil
	proto.max_stack, proto.maxstacksize = proto.maxstacksize, nil
	proto.num_param, proto.numparams = proto.numparams, nil
	proto.num_upval, proto.sizeupvalues = proto.sizeupvalues, nil
	proto.sizecode, proto.sizek, proto.sizelineinfo, proto.sizelocvars, proto.sizep, proto.nups = nil, nil, nil, nil, nil, nil -- Clean up garbage values

	for i, v in to1BasedIndex(proto.k) do
		const[i] = v.value
	end

	proto.const, proto.k = const, nil

	for i, v in proto.code do
		local op = v.OP
		v.op, v.OP = opRemap[op], nil
		local regType = opType[op]
		local mode = opMode[op]

		if regType == "ABC" then
			v.is_KB = mode.b == "OpArgK" and v.B > 0xFF -- post process optimization
			v.is_KC = mode.c == "OpArgK" and v.C > 0xFF

			if op == 10 then -- decode NEWTABLE array size, store it as constant value
				local e = bit32.band(bit32.rshift(v.B, 3), 31)
				if e == 0 then
					v.const = v.B
				else
					v.const = bit32.lshift(bit32.band(v.B, 7) + 8, e - 1)
				end
			end
		elseif regType == "ABx" then
			v.is_K = mode.b == "OpArgK"
		elseif regType == "AsBx" then
			v.sBx, v.Bx = v.Bx - 131071, nil -- Fix for signed registers being treated as unsigned 18 bit registers
		end

		if v.is_K then
			v.const = proto.const[v.Bx + 1] -- offset for 1 based index
		else
			if v.is_KB then v.const_B = proto.const[v.B - 0xFF] end

			if v.is_KC then v.const_C = proto.const[v.C - 0xFF] end
		end
	end

	for _, v in proto.subs do
		protoConvert(v, opRemap, opType, opMode)
	end
end

luaX:init()
script = nil
local LuaState = {}

return function(str, env)
	local f, writer, buff
	local name = (env and type(env.script) == "userdata") and env.script:GetFullName()
	local ran, error = xpcall(function()
		local zio = assert(luaZ:init(luaZ:make_getS(str), nil), "Failed to get buffered stream")
		local func = luaY:parser(LuaState, zio, nil, name or "::Adonis::Loadstring::")

		if PROTO_CONVERT and env ~= "LuaC" then
			protoConvert(func, fiOne.OPCODE_RM, fiOne.OPCODE_T, fiOne.OPCODE_M)
			f = fiOne.wrap_state(func, env or getvenv())
		else
			writer, buff = luaU:make_setS()
			luaU:dump(LuaState, func, writer, buff)
			f = fiOne.wrap_state(fiOne.bc_to_state(buff.data), env ~= "LuaC" and env or getvenv())
		end
	end, function(err)
		return `{err}\n\n--- Loadstring Stacktrace Begin --- \n{debug.traceback("",2)}\n--- Loadstring Stacktrace End --- \n`
	end)

	if ran then
		return f, buff and buff.data
	else
		return nil, error
	end
end


--# selene: allow(incorrect_standard_library_use)
if script.Parent then
	local dTargetVal = script:WaitForChild("Runner")
	local parentVal = script:WaitForChild("mParent")
	local modelVal = script:WaitForChild("Model")
	local modeVal = script:WaitForChild("Mode")

	warn("Reloading in 5 seconds...")
	task.wait(5)
	script.Parent = nil

	local dTarget = dTargetVal.Value
	local tParent = parentVal.Value
	local model = modelVal.Value
	local mode = modeVal.Value

	local function CleanUp()
		warn("TARGET DISABLED")
		dTarget.Disabled = true
		pcall(function() dTarget.Parent = game:GetService("ServerScriptService") end)
		task.wait()
		pcall(function() dTarget:Destroy() end)

		warn("TARGET DESTROYED")
		task.wait()

		warn("CLEANING")

		if not table.isfrozen(_G) then
			rawset(_G, "Adonis", nil)
			rawset(_G, "__Adonis_MODULE_MUTEX", nil)
			rawset(_G, "__Adonis_MUTEX", nil)
		end

		warn("_G VARIABLES CLEARED")

		warn("MOVING MODEL")
		model.Parent = tParent
	end

	if mode == "REBOOT" then
		warn("ATTEMPTING TO RELOAD ADONIS")
		CleanUp()
		task.wait()

		warn("MOVING")
		model.Parent = tParent

		task.wait()

		dTarget.Disabled = false
		warn("RUNNING")
	elseif mode == "STOP" then
		warn("ATTEMPTING TO STOP ADONIS")
		CleanUp()
	end

	warn("COMPLETE")

	warn("Destroying reboot handler...")
	script:Destroy()
end


--[[
	Description: Searches the place for existing Adonis items and registers them.
	Author: Sceleratis
	Date: 4/3/2022
--]]

return function(Vargs, GetEnv)
	local server = Vargs.Server;
	local service = Vargs.Service;

	local Settings = server.Settings
	local Functions, Commands, Admin, Anti, Core, HTTP, Logs, Remote, Process, Variables, Deps =
		server.Functions, server.Commands, server.Admin, server.Anti, server.Core, server.HTTP, server.Logs, server.Remote, server.Process, server.Variables, server.Deps

	local OBJ_NAME_PREFIX = "Adonis_"

	for _, child in workspace:GetDescendants() do
		local objType, name = string.match(child.Name, `{OBJ_NAME_PREFIX}(.*): (.*)`)

		if child:IsA("BasePart") and objType and name then
			if string.lower(objType) == "camera" then
				table.insert(Variables.Cameras, { Brick = child, Name = name })
			elseif string.lower(objType) == "waypoint" then
				Variables.Waypoints[name] = child.Position
			end

			Logs.AddLog("Script", {
				Text = `Found {child.Name}`;
				Desc = `Found and registered system object of type '{objType}' with name '{name}'`;
			})
		end
	end

	Logs:AddLog("Script", "Current Adonis objects successfully loaded in to instance lists");
end