--[[ Value packing extension ]]
--

do
	local SendingCache = setmetatable({}, { __index = function(t, i) t[i] = {} return t[i] end, __mode = "k" })
	local ReceivingCache = setmetatable({}, { __index = function(t, i) t[i] = {} return t[i] end, __mode = "k" })
	local MaxStringLength = 64
	local CacheSize = 32 -- must be under 256, keeping it low because adding a new entry goes through the entire cache
	
	local ValidTypes = {
		"number", "string", "boolean", "nil",
		"Vector2", "Vector3", "CFrame",
		"Color3", "BrickColor",
		"UDim2", "UDim"
	}
	
	for i,v in ipairs(ValidTypes) do ValidTypes[v] = true end
	
	local function addEntry(value, client)
		local valueType = typeof(value)
		
		if not ValidTypes[valueType] then
			error(string.format("Invalid value passed to Network:Pack (values of type %s are not supported)", valueType))
		end
		
		if valueType == "boolean" or valueType == "nil" or value == "" then
			return value -- already one-byte
		elseif valueType == "string" and #value > MaxStringLength then
			return "\0" .. value
		end
		
		local cache = SendingCache[client]
		local info = cache[value]
		
		if not info then
			if #cache < CacheSize then
				local index = #cache + 1
				info = { char = string.char(index), value = value, last = 0 }
				
				cache[index] = info
				cache[value] = info
			else
				for i,other in ipairs(cache) do
					if not info or other.last < info.last then
						info = other
					end
				end
				
				cache[info.value] = nil
				cache[value] = info
				
				info.value = value
			end
			
			if IsServer then
				Network:FireClient(client, "SetPackedValue", info.char, info.value)
			else
				Network:FireServer("SetPackedValue", info.char, info.value)
			end
		end
		
		info.last = os.clock()
		
		return info.char
	end
	
	local function getEntry(value, client)
		local valueType = typeof(value)
		if valueType ~= "string" or value == "" then
			return value
		end
		
		local index = string.byte(value, 1)
		if index == 0 then
			return string.sub(value, 2)
		end
		
		return ReceivingCache[client][index]
	end
	
	if IsServer then
		function Network:Pack(value, client)
			assert(typeof(client) == "Instance" and client:IsA("Player"), "client is not a player")
			return addEntry(value, client)
		end
		
		function Network:Unpack(value, client)
			assert(typeof(client) == "Instance" and client:IsA("Player"), "client is not a player")
			return getEntry(value, client)
		end
		
		Network:BindEvents({
			SetPackedValue = function(client, char, value)
				if typeof(char) ~= "string" or #char ~= 1 then
					return client:Kick()
				end
				
				local index = string.byte(char)
				if index < 1 or index > CacheSize then
					return client:Kick()
				end
				
				local valueType = typeof(value)
				if not ValidTypes[valueType] or valueType == "string" and #value > MaxStringLength then
					return client:Kick()
				end
				
				ReceivingCache[client][index] = value
			end
		})
	else
		function Network:Pack(value)
			return addEntry(value, "Server")
		end
		
		function Network:Unpack(value)
			return getEntry(value, "Server")
		end
		
		Network:BindEvents({
			SetPackedValue = function(char, value)
				ReceivingCache.Server[string.byte(char)] = value
			end
		})
	end
end
