-- original author @centauri
local bt
do
	local FAILURE = 0
	local SUCCESS = 1
	local RUNNING = 2

	local function SEQUENCE(nodes)
		return function(...)
			for _, node in nodes do
				local status = node(...)
				if status == FAILURE or status == RUNNING then
					return status
				end
			end
			return SUCCESS
		end
	end
	local function FALLBACK(nodes)
		return function(...)
			for _, node in nodes do
				local status = node(...)
				if status == SUCCESS or status == RUNNING then
					return status
				end
			end
			return FAILURE
		end
	end
	bt = {
		SEQUENCE = SEQUENCE,
		FALLBACK = FALLBACK,
		RUNNING = RUNNING,
		SUCCESS = SUCCESS,
		FAILURE = FAILURE,
	}
end

local SEQUENCE, FALLBACK = bt.SEQUENCE, bt.FALLBACK
local RUNNING, SUCCESS, FAILURE = bt.FAILURE, bt.SUCCESS, bt.FAILURE

local btree = FALLBACK({
	SEQUENCE({
		function()
			return 1
		end,

		function()
			return 0
		end,
	}),
	SEQUENCE({
		function()
			print(3)
			local start = os.clock()
			local now = os.clock()
			while os.clock() - now < 4 do
				print("yielding")
				coroutine.yield()
			end
			return 0
		end,
	}),
	function()
		return 1
	end,
})

function wait(seconds)
	local start = os.clock()
	while os.clock() - start < seconds do
	end
	return os.clock() - start
end

local function panic(str)
	-- We don't want to interrupt the loop when we error
	coroutine.resume(coroutine.create(function()
		error(str)
	end))
end

local jecs = require("@jecs")
local world = jecs.World.new()

local function Scheduler(world, ...)
	local systems = { ... }
	local systemsNames = {}
	local N = #systems
	local system
	local dt

	for i, module in systems do
		local sys = if typeof(module) == "function" then module else require(module)
		systems[i] = sys
		local file, line = debug.info(2, "sl")
		systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}`
	end

	local function run()
		local name = systemsNames[system]

		--debug.profilebegin(name)
		--debug.setmemorycategory(name)
		system(world, dt)
		--debug.profileend()
	end

	local function loop(sinceLastFrame)
		--debug.profilebegin("loop()")
		local start = os.clock()
		for i = N, 1, -1 do
			system = systems[i]

			dt = sinceLastFrame

			local didNotYield, why = xpcall(function()
				for _ in run do
				end
			end, debug.traceback)

			if didNotYield then
				continue
			end

			if string.find(why, "thread is not yieldable") then
				N -= 1
				local name = table.remove(systems, i)
				panic("Not allowed to yield in the systems." .. "\n" .. `System: {name} has been ejected`)
			else
				panic(why)
			end
		end

		--debug.profileend()
		--debug.resetmemorycategory()
		return os.clock() - start
	end

	return loop
end

local co = coroutine.create(btree)
local function ai(world, dt)
	coroutine.resume(co)
end

local loop = Scheduler(world, ai)

while wait(0.2) do
	print("frame time: ", loop(0.2))
end
