PACKETS = {
	IMMEDIATE = 0,
	HIGH = 1,
	MEDIUM = 2,
	LOW = 3,
	UNRELIABLE = 0,
	UNRELIABLE_SEQUENCED = 1,
	RELIABLE = 2,
	RELIABLE_ORDERED = 3,
	RELIABLE_SEQUENCED = 4,
}

packets = {}

packets.baseIndex = raknet.getBasePacketId() or 0

packets.readers = {}
-- Int
packets.readers["i"] = function(device)
	return device:readInt()
end
-- Int table
packets.readers["it"] = function(device)
	local num = device:readInt()
	local tab = {}
	for i=1,num do
		local val = device:readInt()
		table.insert(tab, val)
	end
	return tab
end
-- table table
packets.readers["tt"] = function(device, callback)
	local num = device:readInt()
	local tab = {}
	for i=1,num do
		local val = packets.readers["t"](device, callback)
		table.insert(tab, val)
	end
	return tab
end

-- New parameters table
packets.readers["npt"] = function(device, callback)
	local num = device:readInt()
	for i=1,num do
		packets.readers["np"](device, callback)
	end
end
-- New parameters
packets.readers["np"] = function(device, callback)
	local num = device:readInt()
	local classRef = packets.readers["c"](device)

	local eid = device:readInt()

	local structure = classRef:getVariablesStructure("new")
	
	local params = {}
	if structure then
		for i=1,math.min(num, #structure) do
			local ty = structure[i]
			local param = packets.readers[ty](device, callback)
			table.insert(params, param)
		end
	else
		print("missing newStructure in ", getClassName(classRef))
	end

	if callback and callback.createEnt then
		callback:createEnt(eid, classRef, unpack(params))
	end
end

-- New parameters table
packets.readers["esynct"] = function(device, callback)
	local num = device:readInt()
	for i=1,num do
		packets.readers["esync"](device, callback)
	end
end
-- entity variables
packets.readers["esync"] = function(device, callback)
	local ent = packets.readers["e"](device, callback)
	if ent then
		local num = device:readInt()
		local structure = ent:getVariablesStructure("sync")
		local vars = ent:getVariables("sync")
		if structure then
			if num > #structure then
				print("structure mismatch to num diffs in ", getClassName(classRef))
			else
				local num = math.min(num, #structure)
				for i=1,num do
					local ty = structure[i]
					local read = packets.readers[ty](device, callback)
					ent[vars[i]] = read

				end
			end
		else
			print("missing getVariablesStructure in ", getClassName(classRef))
		end
		if callback and callback.onEntChanged then
			callback:onEntChanged(ent)
		end
	end
end

-- Float
packets.readers["f"] = function(device)
	return device:readFloat()
end

-- Angle
packets.readers["r"] = function(device)
	return device:readFloat()
end

-- Guid
packets.readers["g"] = function(device)
	return device:readGuid()
end
-- String
packets.readers["s"] = function(device)
	return device:readString()
end
-- Boolean
packets.readers["b"] = function(device)
	return device:readBoolean()
end
-- class
packets.readers["c"] = function(device)
	local id = device:readInt()
	local classRef = Network.getClassFromId(id)
	if classRef then
		return classRef
	else
		print("cannot read unregistered class ", id)
	end
end

-- assigned entity table
packets.readers["aet"] = function(device, callback)
	local num = device:readInt()
	local tab = {}
	for i=1,num do
		local val = packets.readers["ae"](device, callback)
		table.insert(tab, val)
	end
	return tab
end

packets.readers["ae"] = function(device, callback)
	local assignment = {}
	assignment.parent = packets.readers["e"](device, callback)
	local isNumber = device:readBoolean()
	if isNumber then
		assignment.index = packets.readers["i"](device, callback)
	else
		assignment.index = packets.readers["s"](device, callback)
	end
	local hasChild = device:readBoolean()
	if hasChild then
		assignment.child = packets.readers["e"](device, callback)
	else
		assignment.child = false
	end
	return assignment
end


-- entity
packets.readers["e"] = function(device, callback)
	local id = device:readInt()
	if callback and callback.getEnt then
		return callback:getEnt(id)
	else
		print("cannot get entity missing callback getEnt support ")
	end
end
-- Table
packets.readers["t"] = function(device, callback)
	
	local tableLayout = packets.readers["s"](device, callback)
	local indexTypes = packets.readers["s"](device, callback)
	
	local t = {}
	for i = 1, #tableLayout do
		local c = tableLayout:sub(i,i)
		local reader = packets.readers[c]
		local n = indexTypes:sub(i,i)
		local indexReader = packets.readers[n]
		
		if reader then
			local key = indexReader(device, callback)
			t[key] = reader(device, callback)
		end
	end
	return t
end

-- Specified Type
packets.readers["*"] = function(device)
	local valType = packets.readers["s"](device)
	return packets.readers[valType](device)
end

packets.writers = {}
-- Int
packets.writers["i"] = function(device, data)
	return device:writeInt(data)
end
-- Int table
packets.writers["it"] = function(device, data)
	device:writeInt(#data)
	for index, val in ipairs(data) do
		device:writeInt(val)
	end
end

-- table table
packets.writers["tt"] = function(device, data, callback)
	device:writeInt(#data)
	for index, val in ipairs(data) do
		packets.writers["t"](device, val, callback)
	end
	return tab
end

-- New parameters table
packets.writers["npt"] = function(device, data, callback)
	local num = device:writeInt(#data)
	for index, val in ipairs(data) do
		packets.writers["np"](device, val, callback)
	end
end
-- New parameters
packets.writers["np"] = function(device, data, callback)
	device:writeInt(#data)
	local classRef = data[1]

	packets.writers["c"](device, classRef, callback)
	
	device:writeInt(data[2])

	local structure = classRef:getVariablesStructure("new")
	if structure then
		if #structure < #data-2 then
			print("structure not matching to data in ", getClassName(classRef))
		else
			for i=3,#data do
				local ty = structure[i-2]
				packets.writers[ty](device, data[i], callback)
			end
		end
	else
		print("missing newStructure in ", getClassName(classRef))
	end
end

-- entity variables table
packets.writers["esynct"] = function(device, data, callback)
	local num = device:writeInt(#data)
	for index, val in ipairs(data) do
		packets.writers["esync"](device, val, callback)
	end
end
-- entity variables
packets.writers["esync"] = function(device, data, callback)
	packets.writers["e"](device, data, callback)
	local diffs = data:getDiffingAmount("sync")
	local structure = data:getVariablesStructure("sync")
	local vars = data:getVariables("sync")
	if structure then
		if diffs > #structure then
			print("structure diffs number mismatch ", getClassName(classRef))
		else
			local num = math.min(diffs, #structure)
			device:writeInt(num)
			for i=1,num do
				local ty = structure[i]
				packets.writers[ty](device, data[vars[i]], callback)
			end
		end
	else
		device:writeInt(0)
		print("missing getVariablesStructure in ", getClassName(classRef))
	end
end

-- Float
packets.writers["f"] = function(device, data)
	return device:writeFloat(data)
end

-- Angle
packets.writers["r"] = function(device, data)
	return device:writeFloat(data)
end
-- Guid
packets.writers["g"] = function(device, data)
	return device:writeGuid(data)
end
-- String
packets.writers["s"] = function(device, data)
	return device:writeString(data)
end
-- Boolean
packets.writers["b"] = function(device, data)
	return device:writeBoolean(data or false)
end
-- class
packets.writers["c"] = function(device, data)
	local id = Network.getIdFromClass(data)
	if id then
		device:writeInt(id)
	else
		print("cannot send unregistered class ", getClassName(data))
	end
end
-- entity
packets.writers["e"] = function(device, data, callback)
	if callback and callback.getEntId then
		device:writeInt(callback:getEntId(data))
	else
		print("cannot write entity missing callback getEntId support")
	end
end

-- assigned entity table
packets.writers["aet"] = function(device, data, callback)
	device:writeInt(#data)
	for index, val in ipairs(data) do
		packets.writers["ae"](device, val, callback)
	end
end

packets.writers["ae"] = function(device, data, callback)
	packets.writers["e"](device, data.parent, callback)
	if type(data.index) == "number" then
		device:writeBoolean(true)
		packets.writers["i"](device, data.index, callback)
	else
		device:writeBoolean(false)
		packets.writers["s"](device, data.index, callback)
	end
	if data.child then
		device:writeBoolean(true)
		packets.writers["e"](device, data.child, callback)
	else
		device:writeBoolean(false)
	end
end

-- Table
packets.writers["t"] = function(device, data, callback)

	if type(data) ~= "table" then
		return "data is not a table"
	end
	local indexstring = ""
	local typestring = ""
	
	for k,v in pairs(data) do
		if v ~= data then
			local valType = type(v)
			local indexType = type(k)
			if valType == "string" then
				typestring = typestring .. "s"
			elseif valType == "number" then
				typestring = typestring .. "f"
			elseif valType == "boolean" then
				typestring = typestring .. "b"
			elseif valType == "table" then
				if Network.isRegistered(v) then
					typestring = typestring .. "e"
				else
					typestring = typestring .. "t"
				end
			end
			
			if indexType == "string" then
				indexstring = indexstring .. "s"
			elseif indexType == "number" then
				indexstring = indexstring .. "i"
			end
		end
	end
	
	packets.writers["s"](device, typestring)
	packets.writers["s"](device, indexstring)
	
	local index = 1
	for k,v in pairs(data) do
		if v ~= data then
			local valType = typestring:sub(index,index)
			local indexType = indexstring:sub(index,index)
			packets.writers[indexType](device, k, callback)
			packets.writers[valType](device, v, callback)
			index = index +1
		end
	end
end
-- Specified Type
packets.writers["*"] = function(device, data)

	local valType = type(data)
	
	if valType == "string" then
		packets.writers["s"](device,"s")
		packets.writers["s"](device,data)
	elseif valType == "number" then
		packets.writers["s"](device,"f")
		packets.writers["f"](device,data)
	elseif valType == "table" then
		packets.writers["s"](device,"t")
		packets.writers["t"](device,data)
	elseif valType == "boolean" then
		packets.writers["s"](device,"b")
		packets.writers["b"](device,data)
	elseif valType == "nil" then
		packets.writers["s"](device,"i")
		packets.writers["b"](device,nil)
	else
		return "NETWORK ERROR: Passed unsupported value type " .. valType
	end
	
end