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


--===== worlds =====--

World = {}
World.__index = World

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


local function systemLt(a, b)
	return (a.priority or 50) < (b.priority or 50)
end
function World.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)
end


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

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


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

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


function World.addEntity(self, entity)
	self.entities[entity] = true
	for _, system in ipairs(self.systems) do
		if system.filter(entity) then
			addEntityToSystem(system, entity)
		end
	end
end


function World.reconfigureEntity(self, entity)
	for _, system in ipairs(self.systems) do
		if system.filter(entity) then
			addEntityToSystem(system, entity)
		else
			removeEntityFromSystem(system, entity)
		end
	end
end


function World.removeEntity(self, entity)
	self.entities[entity] = nil
	for _, system in ipairs(self.systems) do
		removeEntityFromSystem(system, entity)
	end
end


function World.reconfigureAllEntities(self)
	for entity in pairs(self.entities) do
		self:reconfigureEntity(entity)
	end
end


function World.update(self, dt)
	for _, system in ipairs(self.systems) do
		for entity in pairs(system.entities) do
			system.update(entity, dt)
		end
	end
end


return module