--[[----------------------------------------------------------------------------

	MessagePack encoder / decoder written in pure Lua 5.3 / Lua 5.4
	written by Sebastian Steinhauer <s.steinhauer@yahoo.de>

	This is free and unencumbered software released into the public domain.

	Anyone is free to copy, modify, publish, use, compile, sell, or
	distribute this software, either in source code form or as a compiled
	binary, for any purpose, commercial or non-commercial, and by any
	means.

	In jurisdictions that recognize copyright laws, the author or authors
	of this software dedicate any and all copyright interest in the
	software to the public domain. We make this dedication for the benefit
	of the public at large and to the detriment of our heirs and
	successors. We intend this dedication to be an overt act of
	relinquishment in perpetuity of all present and future rights to this
	software under copyright law.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
	IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
	OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
	OTHER DEALINGS IN THE SOFTWARE.

	For more information, please refer to <http://unlicense.org/>

--]]----------------------------------------------------------------------------
local pack, unpack = string.pack, string.unpack
local mtype, utf8len = math.type, utf8.len
local tconcat, tunpack = table.concat, table.unpack
local ssub = string.sub
local type, pcall, pairs, select = type, pcall, pairs, select


--[[----------------------------------------------------------------------------

		Encoder

--]]----------------------------------------------------------------------------
local encode_value -- forward declaration

local function is_an_array(value)
	local expected = 1
	for k in pairs(value) do
		if k ~= expected then
			return false
		end
		expected = expected + 1
	end
	return true
end

local encoder_functions = {
	['nil'] = function()
		return pack('B', 0xc0)
	end,
	['boolean'] = function(value)
		if value then
			return pack('B', 0xc3)
		else
			return pack('B', 0xc2)
		end
	end,
	['number'] = function(value)
		if mtype(value) == 'integer' then
			if value >= 0 then
				if value < 128 then
					return pack('B', value)
				elseif value <= 0xff then
					return pack('BB', 0xcc, value)
				elseif value <= 0xffff  then
					return pack('>BI2', 0xcd, value)
				elseif value <= 0xffffffff then
					return pack('>BI4', 0xce, value)
				else
					return pack('>BI8', 0xcf, value)
				end
			else
				if value >= -32 then
					return pack('B', 0xe0 + (value + 32))
				elseif value >= -128 then
					return pack('Bb', 0xd0, value)
				elseif value >= -32768 then
					return pack('>Bi2', 0xd1, value)
				elseif value >= -2147483648 then
					return pack('>Bi4', 0xd2, value)
				else
					return pack('>Bi8', 0xd3, value)
				end
			end
		else
			local test = unpack('f', pack('f', value))
			if test == value then -- check if we can use float
				return pack('>Bf', 0xca, value)
			else
				return pack('>Bd', 0xcb, value)
			end
		end
	end,
	['string'] = function(value)
		local len = #value
		if utf8len(value) then -- check if it is a real utf8 string or just byte junk
			if len < 32 then
				return pack('B', 0xa0 + len) .. value
			elseif len < 256 then
				return pack('>Bs1', 0xd9, value)
			elseif len < 65536 then
				return pack('>Bs2', 0xda, value)
			else
				return pack('>Bs4', 0xdb, value)
			end
		else -- encode it as byte-junk :)
			if len < 256 then
				return pack('>Bs1', 0xc4, value)
			elseif len < 65536 then
				return pack('>Bs2', 0xc5, value)
			else
				return pack('>Bs4', 0xc6, value)
			end
		end
	end,
	['table'] = function(value)
		if is_an_array(value) then -- it seems to be a proper Lua array
			local elements = {}
			for i, v in sort_pairs(value) do
				elements[i] = encode_value(v)
			end

			local length = #elements
			if length < 16 then
				return pack('>B', 0x90 + length) .. tconcat(elements)
			elseif length < 65536 then
				return pack('>BI2', 0xdc, length) .. tconcat(elements)
			else
				return pack('>BI4', 0xdd, length) .. tconcat(elements)
			end
		else -- encode as a map
			local elements = {}
			for k, v in sort_pairs(value) do
				elements[#elements + 1] = encode_value(k)
				elements[#elements + 1] = encode_value(v)
			end

			local length = #elements // 2
			if length < 16 then
				return pack('>B', 0x80 + length) .. tconcat(elements)
			elseif length < 65536 then
				return pack('>BI2', 0xde, length) .. tconcat(elements)
			else
				return pack('>BI4', 0xdf, length) .. tconcat(elements)
			end
		end
	end,
}

encode_value = function(value)
	return encoder_functions[type(value)](value)
end

local function encode(...)
	local data = {}
	for i = 1, select('#', ...) do
		data[#data + 1] = encode_value(select(i, ...))
	end
	return tconcat(data)
end


--[[----------------------------------------------------------------------------

		Decoder

--]]----------------------------------------------------------------------------
local decode_value -- forward declaration

local function decode_array(data, position, length)
	local elements, value = {}
	for i = 1, length do
		value, position = decode_value(data, position)
		elements[i] = value
	end
	return elements, position
end

local function decode_map(data, position, length)
	local elements, key, value = {}
	for i = 1, length do
		key, position = decode_value(data, position)
		value, position = decode_value(data, position)
		elements[key] = value
	end
	return elements, position
end

local decoder_functions = {
	[0xc0] = function(data, position)
		return nil, position
	end,
	[0xc2] = function(data, position)
		return false, position
	end,
	[0xc3] = function(data, position)
		return true, position
	end,
	[0xc4] = function(data, position)
		return unpack('>s1', data, position)
	end,
	[0xc5] = function(data, position)
		return unpack('>s2', data, position)
	end,
	[0xc6] = function(data, position)
		return unpack('>s4', data, position)
	end,
	-- 0xc7 - 0xc9 for userdata
	[0xca] = function(data, position)
		return unpack('>f', data, position)
	end,
	[0xcb] = function(data, position)
		return unpack('>d', data, position)
	end,
	[0xcc] = function(data, position)
		return unpack('>B', data, position)
	end,
	[0xcd] = function(data, position)
		return unpack('>I2', data, position)
	end,
	[0xce] = function(data, position)
		return unpack('>I4', data, position)
	end,
	[0xcf] = function(data, position)
		return unpack('>I8', data, position)
	end,
	[0xd0] = function(data, position)
		return unpack('>b', data, position)
	end,
	[0xd1] = function(data, position)
		return unpack('>i2', data, position)
	end,
	[0xd2] = function(data, position)
		return unpack('>i4', data, position)
	end,
	[0xd3] = function(data, position)
		return unpack('>i8', data, position)
	end,
	-- 0xd4 - 0xd8 for userdata
	[0xd9] = function(data, position)
		return unpack('>s1', data, position)
	end,
	[0xda] = function(data, position)
		return unpack('>s2', data, position)
	end,
	[0xdb] = function(data, position)
		return unpack('>s4', data, position)
	end,
	[0xdc] = function(data, position)
		local length
		length, position = unpack('>I2', data, position)
		return decode_array(data, position, length)
	end,
	[0xdd] = function(data, position)
		local length
		length, position = unpack('>I4', data, position)
		return decode_array(data, position, length)
	end,
	[0xde] = function(data, position)
		local length
		length, position = unpack('>I2', data, position)
		return decode_map(data, position, length)
	end,
	[0xdf] = function(data, position)
		local length
		length, position = unpack('>I4', data, position)
		return decode_map(data, position, length)
	end,
}

-- add fix-array, fix-map, fix-string, fix-int stuff
for i = 0x00, 0x7f do
	decoder_functions[i] = function(data, position)
		return i, position
	end
end
for i = 0x80, 0x8f do
	decoder_functions[i] = function(data, position)
		return decode_map(data, position, i - 0x80)
	end
end
for i = 0x90, 0x9f do
	decoder_functions[i] = function(data, position)
		return decode_array(data, position, i - 0x90)
	end
end
for i = 0xa0, 0xbf do
	decoder_functions[i] = function(data, position)
		local length = i - 0xa0
		return ssub(data, position, position + length - 1), position + length
	end
end
for i = 0xe0, 0xff do
	decoder_functions[i] = function(data, position)
		return -32 + (i - 0xe0), position
	end
end

decode_value = function(data, position)
	local byte, value
	byte, position = unpack('B', data, position)
	value, position = decoder_functions[byte](data, position)
	return value, position
end


--[[----------------------------------------------------------------------------

		Interface

--]]----------------------------------------------------------------------------
return {
	_AUTHOR = 'Sebastian Steinhauer <s.steinhauer@yahoo.de>',
	_VERSION = '0.6.1',

	encoder_functions = encoder_functions,
	decoder_functions = decoder_functions,

	-- primary encode function
	encode = function(...)
		local data, ok = {}
		for i = 1, select('#', ...) do
			ok, data[i] = pcall(encode_value, select(i, ...))
			if not ok then
				return nil, 'cannot encode MessagePack'
			end
		end
		return tconcat(data)
	end,

	-- encode just one value
	encode_one = function(value)
		local ok, data = pcall(encode_value, value)
		if ok then
			return data
		else
			return nil, 'cannot encode MessagePack'
		end
	end,

	-- primary decode function
	decode = function(data, position)
		local values, value, ok = {}
		position = position or 1
		while position <= #data do
			ok, value, position = pcall(decode_value, data, position)
			if ok then
				values[#values + 1] = value
			else
				return nil, 'cannot decode MessagePack'
			end
		end
		return tunpack(values)
	end,

	-- decode just one value
	decode_one = function(data, position)
		local value, ok
		ok, value, position = pcall(decode_value, data, position or 1)
		if ok then
			return value, position
		else
			return nil, 'cannot decode MessagePack'
		end
	end,
}

--[[----------------------------------------------------------------------------
--]]----------------------------------------------------------------------------
