local config = {nb_user_keepers=1}
local lanes = require "lanes".configure(config)

-- set TEST1, PREFILL1, FILL1, TEST2, PREFILL2, FILL2 from the command line

-- Lua 5.1/5.2 compatibility
local table_unpack = unpack or table.unpack

local finalizer = function(err, stk)
	if err == lanes.cancel_error then
		-- note that we don't get the cancel_error when running wrapped inside a protected call if it doesn't rethrow it
		print("			laneBody after cancel" )
	elseif err then
		print("			laneBody error: "..tostring(err))
	else
		print("			laneBody finalized")
	end
end

-- #################################################################################################
if true then
	do
		print "############################################ tests get/set"
		-- linda:get throughput
		local l = lanes.linda{name = "get/set", group = 1}
		local batch = {}
		for i = 1,1000 do
			table.insert(batch, i)
		end
		for _,size in ipairs{1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987 } do
			l:set("<->", table_unpack(batch))
			local count = math.floor(20000/math.sqrt(size))
			print("START", "get("..size..") " .. count, " times")
			local t1 = lanes.now_secs()
			for i = 1, count do
				assert (l:get("<->", size) == size)
			end
			print("DURATION = " .. lanes.now_secs() - t1 .. "\n")
		end
	end
	collectgarbage()
end

-- #################################################################################################

-- this lane eats items in the linda one by one
local eater = function( l, loop)
	set_finalizer(finalizer)
	-- wait for start signal
	l:receive( "go")
	-- eat data one by one
	for i = 1, loop do
		local key, val = l:receive( "key")
		-- print("eater:", val)
	end
	-- print "loop is over"
	key, val = l:receive( "done")
	print("eater: done ("..val..")")
	return "ate everything"
end

-- #################################################################################################

-- this lane eats items in the linda in batches
local gobbler = function( l, loop, batch)
	set_finalizer(finalizer)
	-- wait for start signal
	l:receive( "go")
	-- eat data in batches
	for i = 1, loop/batch do
		l:receive_batched("key", batch)
		-- print("gobbler:", batch)
	end
	print "loop is over"
	key, val = l:receive( "done")
	print("gobbler: done ("..val..")")
	return "gobbled everything"
end

-- #################################################################################################

local lane_eater_gen = lanes.gen( "*", { name = 'auto', priority = 3 }, eater)
local lane_gobbler_gen = lanes.gen( "*", { name = 'auto', priority = 3 }, gobbler)

-- #################################################################################################

local group_uid = 1

-- main thread writes data while a lane reads it
local function ziva1( preloop, loop, batch)
	-- prefill the linda a bit to increase fifo stress
	local top = math.max( preloop, loop)
	local l = lanes.linda{name = "ziva1("..preloop..":"..loop..":"..batch..")", group = group_uid}
	group_uid = (group_uid % config.nb_user_keepers) + 1
	local t1 = lanes.now_secs()
	for i = 1, preloop do
		l:send( "key", i)
	end
	print( "stored " .. l:count( "key") .. " items in the linda before starting consumer lane")
	local lane
	if batch > 0 then
		lane = lane_gobbler_gen( l, top, batch)
	else
		lane = lane_eater_gen( l, top)
	end
	-- tell the consumer lane it can start eating data
	l:send( "go", true)
	-- send the remainder of the elements while they are consumed
	-- create a function that can send several values in one shot
	batch = math.max( batch, 1)
	local batch_values = {}
	for i = 1, batch do
		table.insert( batch_values, i)
	end
	local batch_send_log = "main: sending "..batch.." values"
	local batch_send = function()
		-- print(batch_send_log)
		l:send( "key", table_unpack( batch_values))
	end
	if loop > preloop then
		for i = preloop + 1, loop, batch do
			batch_send()
		end
	end
	l:send( "done" ,"are you happy?")
	local r, ret = lane:join()
	assert(r == true and type(ret) == "string", "got " .. tostring(r) .. " " .. tostring(ret))
	return lanes.now_secs() - t1
end

-- #################################################################################################

if true then
	do
		TEST1 = TEST1 or 1000 -- how many tests do we run?
		PREFILL1 = PREFILL1 or 10000
		FILL1 = FILL1 or 2000000

		local tests1 =
		{
			{ PREFILL1, FILL1, 0},
			{ PREFILL1, FILL1, 1},
			{ PREFILL1, FILL1, 2},
			{ PREFILL1, FILL1, 3},
			{ PREFILL1, FILL1, 5},
			{ PREFILL1, FILL1, 8},
			{ PREFILL1, FILL1, 13},
			{ PREFILL1, FILL1, 21},
			{ PREFILL1, FILL1, 34},
			{ PREFILL1, FILL1, 55},
			{ PREFILL1, FILL1, 89},
		}
		print "############################################ tests #1"
		for i, v in ipairs( tests1) do
			if i > TEST1 then break end
			local pre, loop, batch = v[1], v[2], v[3]
			print("-------------------------------------------------\n")
			print("START", "prefill="..pre, "fill="..loop, "batch="..batch)
			print("DURATION = " .. ziva1( pre, loop, batch) .. "\n")
		end
	end
	collectgarbage()
end

-- #################################################################################################

-- sequential write/read (no parallelization involved)
local function ziva2( preloop, loop, batch)
	local l = lanes.linda{name = "ziva2("..preloop..":"..loop..":"..tostring(batch)..")", group = group_uid}
	group_uid = (group_uid % config.nb_user_keepers) + 1
	-- prefill the linda a bit to increase fifo stress
	local top, step = math.max( preloop, loop), batch or 1
	local batch_send, batch_read
	if batch then
		local batch_values = {}
		for i = 1, batch do
			table.insert( batch_values, i)
		end
		-- create a function that can send several values in one shot
		batch_send = function()
			l:send( "key", table_unpack( batch_values))
		end
		batch_read = function()
			l:receive_batched("key", batch)
		end
	else -- not batched
		batch_send = function()
			l:send( "key", top)
		end
		batch_read = function()
			l:receive( "key")
		end
	end
	local t1 = lanes.now_secs()
	-- first, prime the linda with some data
	for i = 1, preloop, step do
		batch_send()
	end
	print( "stored " .. (l:count( "key") or 0) .. " items in the linda before starting the alternating reads and writes")
	-- loop that alternatively sends and reads data off the linda
	if loop > preloop then
		for i = preloop + 1, loop, step do
			batch_send()
			batch_read()
		end
	end
	-- here, we have preloop elements still waiting inside the linda
	for i = 1, preloop, step do
		batch_read()
	end
	return lanes.now_secs() - t1
end

-- #################################################################################################

if true then
	do
		TEST2 = TEST2 or 1000 -- how many tests do we run?
		PREFILL2 = PREFILL2 or 0
		FILL2 = FILL2 or 4000000

		local tests2 =
		{
			{ PREFILL2, FILL2},
			{ PREFILL2, FILL2, 1},
			{ PREFILL2, FILL2, 2},
			{ PREFILL2, FILL2, 3},
			{ PREFILL2, FILL2, 5},
			{ PREFILL2, FILL2, 8},
			{ PREFILL2, FILL2, 13},
			{ PREFILL2, FILL2, 21},
			{ PREFILL2, FILL2, 34},
			{ PREFILL2, FILL2, 55},
			{ PREFILL2, FILL2, 89},
		}

		print "############################################ tests #2"
		for i, v in ipairs( tests2) do
			if i > TEST2 then break end
			local pre, loop, batch = v[1], v[2], v[3]
			print("-------------------------------------------------\n")
			print("START", "prefill="..pre, "fill="..loop, "batch="..(batch or "no"))
			print("DURATION = " .. ziva2( pre, loop, batch) .. "\n")
		end
	end
	collectgarbage()
end

print "############################################"
print "THE END"