function RSC.Log(level, msg, ...)
	level = level or "info"
	msg = msg or ""
	MsgN("[RSC] ", level:upper(), " - ", msg:format(...))
end

if CLIENT then
	RSC.AccessRequests = {}
end

function RSC.HasAccess(ply, cb)
	if SERVER and not IsValid(ply) then return cb(false) end

	-- Clients need to ask server about access
	if CLIENT then
		table.insert(RSC.AccessRequests, cb)

		if #RSC.AccessRequests == 1 then
			net.Start("RSC.NetworkV2")
				net.WriteUInt(RSC.NET_OP_CHECK_ACCESS, 4)
			net.SendToServer()
		end
	return end

	-- CAMI access system
	if CAMI then
		CAMI.PlayerHasAccess(ply, "ulx screengrab", cb)
	return end

	-- Fallback access system
	cb(ply:IsSuperAdmin())
end

function RSC.AsyncHasAccess(ply)
	return promise.New(function(resolve)
		RSC.HasAccess(ply, resolve)
	end)
end

function RSC.CanScreengrab(receiver, victim, cb)
	if CLIENT and RSC.LocalPlayer then receiver = RSC.LocalPlayer end

	RSC.HasAccess(receiver, function(can)
		if not can or not IsValid(victim) or victim:IsBot() then return cb(false) end

		local receiverRank, victimRank = receiver:GetUserGroup(), victim:GetUserGroup()
		if receiver == victim or receiverRank == victimRank then return cb(true) end

		if CAMI then
			cb( not CAMI.UsergroupInherits(victim:GetUserGroup(), receiver:GetUserGroup()) )
		return end

		cb(false)
	end)
end

function RSC.AsyncCanScreengrab(receiver, victim)
	return promise.New(function(resolve)
		RSC.CanScreengrab(receiver, victim, resolve)
	end)
end

-- Returns image format and quality
function RSC.ParseQuality(quality)
	return quality == 2 and "png" or "jpeg", quality == 0 and 60 or 80
end

-- Returns player if found by a search string
function RSC.FindPlayer(str)
	if not isstring(str) then return end
	if str:match("^STEAM_%d:[01]:%d+$") then -- Find by SteamID
		local ply = player.GetBySteamID(str)
		if ply then return ply end
	end
	if str:match("^7%d+$") then -- Find by SteamID64
		local ply = player.GetBySteamID64(str)
		if ply then return ply end
	end

	-- Find by name
	str = str:lower()
	for _, ply in ipairs( player.GetHumans() ) do
		if ply:GetName():lower():match(str) then
			return ply
		end

		if DarkRP and ply:SteamName():lower():match(str) then
			return ply
		end
	end
end

-- Generates unique id in base64url encoding
function RSC.UniqueID(len)
	local chars = {}
	for i = 1, len or 4 do
		table.insert(chars, string.char(math.random(0, 255)))
	end

	local hash = table.concat(chars)
	hash = util.Base64Encode(hash, true)
	hash = hash:gsub("[%=%+%/]", "")
	return hash
end

-- Returns players steamname
function RSC.SteamName(ply)
	if not IsValid(ply) then return end
	return DarkRP and ply:SteamName() or ply:GetName()
end

