local module = {}
setmetatable(module, {__index=_G})
setfenv(1, module)


--===== filters =====--


Filter = {}

-- base filter creation
function Filter.new(_, operation, tbl)
	local self = {}
	self.keys = {}
	self.filters = {}
	self.op = operation

	for _, v in ipairs(tbl) do
		if type(v) == "function" then
			table.insert(self.filters, v)
		elseif type(v) == "string" then
			table.insert(self.keys, v)
		end
	end

	return function(entity, _self) 
		local entity = _self or entity -- able to call as . or :
		return Filter.check(self, entity)
	end
end
setmetatable(Filter, {__call=Filter.new})


-- base filter checking
function Filter.check(self, entity)
	local funcname = "check" .. self.op
	return Filter[funcname](self, entity)
end


-- AND filter (all keys and subfilters must match)
function Filter.AND(tbl)
	return Filter("AND", tbl)
end
function Filter.checkAND(self, entity)
	for _, subfilter in ipairs(self.filters) do
		if not subfilter(entity) then return false end
	end
	for _, key in ipairs(self.keys) do
		if entity[key] == nil then return false end
	end
	return true
end


-- OR filter (at least one key or subfilter must match)
function Filter.OR(tbl)
	return Filter("OR", tbl)
end
function Filter.checkOR(self, entity)
	for _, subfilter in ipairs(self.filters) do
		if subfilter(entity) then return true end
	end
	for _, key in ipairs(self.keys) do
		if entity[key] ~= nil then return true end
	end
	return false
end


-- NAND filter (at least one key or subfilter must NOT match)
function Filter.NAND(tbl)
	return Filter("NAND", tbl)
end
function Filter.checkNAND(self, entity)
	for _, subfilter in ipairs(self.filters) do
		if not subfilter(entity) then return true end
	end
	for _, key in ipairs(self.keys) do
		if entity[key] == nil then return true end
	end
	return false
end


-- NOR filter (no keys or subfilters may match)
function Filter.NOR(tbl)
	return Filter("NOR", tbl)
end
function Filter.checkNOR(self, entity)
	for _, subfilter in ipairs(self.filters) do
		if subfilter(entity) then return false end
	end
	for _, key in ipairs(self.keys) do
		if entity[key] ~= nil then return false end
	end
	return true
end


--===== levels =====--

Level = {}
Level.__index = Level

function Level.new(_)
	local self = {}
	self.systems = {}
	self.entities = {}
	self.nextId = 1
	setmetatable(self, Level)
	return self
end
setmetatable(Level, {__call=Level.new})


local function systemLt(a, b)
	return (a.priority or 50) < (b.priority or 50)
end
function Level.addSystem(self, system)
	assert(system.update, "systems must have an 'update' key")
	assert(system.filter, "systems must have a 'filter' key")
	system.entities = {}
	table.insert(self.systems, system)
	table.sort(self.systems, systemLt)
	if system.setup then
		system.setup(system)
	end
end


local function addEntityToSystem(system, id, entity)
	-- check if entity is already present
	if system.entities[id] then return end

	if system.onAddEntity then
		system:onAddEntity(id, entity)
	end
	system.entities[id] = true
end


local function removeEntityFromSystem(system, id, entity)
	-- check if entity is already absent
	if not system.entities[id] then return end

	if system.onRemoveEntity then
		system:onRemoveEntity(id, entity)
	end
	system.entities[id] = nil
end


function Level.addEntity(self, entity)
	local id = self.nextId
	self.entities[id] = entity
	self.nextId = id + 1

	for _, system in ipairs(self.systems) do
		if system.filter(entity) then
			addEntityToSystem(system, id, entity)
		end
	end

	return id
end


function Level.reconfigureEntity(self, id)
	local entity = self.entities[id]
	
	for _, system in ipairs(self.systems) do
		if system.filter(entity) then
			addEntityToSystem(system, id, entity)
		else
			removeEntityFromSystem(system, id, entity)
		end
	end
end


function Level.removeEntity(self, id)
	local entity = self.entities[id]
	if not entity then error("bad id: "..tostring(id)) end
	for _, system in ipairs(self.systems) do
		removeEntityFromSystem(system, id, entity)
	end
	self.entities[id] = nil
end


function Level.reconfigureAllEntities(self)
	for id in ipairs(self.entities) do
		self:reconfigureEntity(id)
	end
end


function Level.update(self, dt, paused)
	local paused = paused or false
	for _, system in ipairs(self.systems) do
		if (not paused) or (paused and system.nopause) then
			if system.preUpdate then
				system:preUpdate()
			end
			if system.prepareEntity then
				for id in pairs(system.entities) do
					local entity = self.entities[id]
					if entity then
						system:prepareEntity(entity)
					end
				end
			end
			for id in pairs(system.entities) do
				local entity = self.entities[id]
				if entity then
					system:update(entity, dt)
				end
			end
			if system.postUpdate then
				system:postUpdate()
			end
		end
	end
end


return module