local found_bitser, bitser = pcall(require, 'bitser')
local found_binser, binser = pcall(require, 'binser')
local found_ser, ser = pcall(require, 'ser')
local found_serpent, serpent = pcall(require, 'serpent')
local found_smallfolk, smallfolk = pcall(require, 'smallfolk')
local found_msgpack, msgpack = pcall(require, 'MessagePack')

local cases
local selected_case = 1

local sers = {}
local desers = {}

if found_bitser then
	sers.bitser = bitser.dumps
	desers.bitser = bitser.loads
	bitser.reserveBuffer(1024 * 1024)
end

if found_binser then
	sers.binser = binser.s
	desers.binser = binser.d
end

if found_ser then
	sers.ser = ser
	desers.ser = loadstring
end

if found_serpent then
	sers.serpent = serpent.dump
	desers.serpent = loadstring
end

if found_smallfolk then
	sers.smallfolk = smallfolk.dumps
	desers.smallfolk = smallfolk.loads
end

if found_msgpack then
	sers.msgpack = msgpack.pack
	desers.msgpack = msgpack.unpack
end

local view_absolute = true
local resultname = "serialisation time in seconds"

function love.load()
	cases = love.filesystem.getDirectoryItems("cases")
	state = 'select_case'
	love.graphics.setBackgroundColor(1, 230/256, 220/256)
	love.graphics.setColor(40/256, 30/256, 0/256)
	love.window.setTitle("Select a benchmark testcase")
end

function love.keypressed(key)
	if state == 'select_case' then
		if key == 'up' then
			selected_case = (selected_case - 2) % #cases + 1
		elseif key == 'down' then
			selected_case = selected_case % #cases + 1
		elseif key == 'return' then
			state = 'calculate_results'
			love.window.setTitle("Running benchmark...")
		end
	elseif state == 'results' then
		if key == 'r' then
			view_absolute = not view_absolute
		elseif key == 'right' then
			if results == results_ser then
				results = results_deser
				resultname = "deserialisation time in seconds"
			elseif results == results_deser then
				results = results_size
				resultname = "size of output in bytes"
			elseif results == results_size then
				results = results_ser
				resultname = "serialisation time in seconds"
			end
		elseif key == 'left' then
			if results == results_ser then
				results = results_size
				resultname = "size of output in bytes"
			elseif results == results_deser then
				results = results_ser
				resultname = "serialisation time in seconds"
			elseif results == results_size then
				results = results_deser
				resultname = "deserialisation time in seconds"
			end
		elseif key == 'escape' then
			state = 'select_case'
			love.window.setTitle("Select a benchmark testcase")
		end
	end
end

function love.draw()
	if state == 'select_case' then
		for i, case in ipairs(cases) do
			love.graphics.print(case, selected_case == i and 60 or 20, i * 20)
		end
		local i = 2
		love.graphics.print('serialisation libraries installed:', 200, 20)
		for sername in pairs(sers) do
			love.graphics.print(sername, 200, i * 20)
			i = i + 1
		end
	elseif state == 'calculate_results' then
		love.graphics.print("Running benchmark...", 20, 20)
		love.graphics.print("This may take a while", 20, 40)
		state = 'calculate_results_2'
	elseif state == 'calculate_results_2' then
		local data, iters, tries = love.filesystem.load("cases/" .. cases[selected_case])()
		results_ser = {}
		results = results_ser
		resultname = "serialisation time in seconds"
		results_size = {}
		results_deser = {}
		errors = {}
		for sername, serializer in pairs(sers) do
			results_ser[sername] = math.huge
			results_deser[sername] = math.huge
		end
		local outputs = {}
		for try = 1, tries do
			for sername, serializer in pairs(sers) do
				local output
				local success, diff = pcall(function()
					local t = os.clock()
					for i = 1, iters do
						output = serializer(data)
					end
					return os.clock() - t
				end)
				if not success and not errors[sername] then
					errors[sername] = diff
				elseif success and diff < results_ser[sername] then
					results_ser[sername] = diff
				end
				if try == 1 then
					outputs[sername] = output
					results_size[sername] = output and #output or math.huge
				end
			end
		end
		for try = 1, tries do
			for sername, deserializer in pairs(desers) do
				local input = outputs[sername]
				local success, diff = pcall(function()
					local t = os.clock()
					for i = 1, iters / 10 do
						deserializer(input)
					end
					return os.clock() - t
				end)
				if not success and not errors[sername] then
					errors[sername] = diff
				elseif success and diff < results_deser[sername] then
					results_deser[sername] = diff
				end
			end
		end
		state = 'results'
		love.window.setTitle("Results for " .. cases[selected_case])
	elseif state == 'results' then
		local results_min = math.huge
		local results_max = -math.huge
		for sername, result in pairs(results) do
			if result < results_min then
				results_min = result
			end
			if result > results_max and result < math.huge then
				results_max = result
			end
		end
		if view_absolute then results_min = 0 end
		local i = 1
		for sername, result in pairs(results) do
			love.graphics.print(sername, 20, i * 20)
			if result == math.huge then
				love.graphics.setColor(220/256, 30/256, 0)
				love.graphics.rectangle('fill', 100, i * 20, 780 - 100, 18)
				love.graphics.setColor(40/256, 30/256, 0)
				love.graphics.print(errors[sername], 102, i * 20 + 2)
			else
				love.graphics.rectangle('fill', 100, i * 20, (780 - 100) * (result - results_min) / (results_max - results_min), 18)
			end
			i = i + 1
		end
		love.graphics.print(results_min, 100, i * 20)
		love.graphics.print(results_max, 780 - love.graphics.getFont():getWidth(results_max), i * 20)
		love.graphics.print(resultname .." (smaller is better; try left, right, R, escape)", 100, i * 20 + 20)
	end
end
