local nlg = {}

--- Create the intersection of two tag sets
local function intersection( a, b )
	local check = {}
	for _,v in ipairs(a) do
		check[v] = true
	end
	local found = {}
	for _,v in ipairs(b) do
		if check[v] then
			found[1+#found] = v
		end
	end
	return found
end

local classes = {}
-- this should probably in a separate module
local layout = {
	['lead-in'] = 0,
	['history'] = 50,
	['geography'] = 60,
	['politics'] = 70,
	['early-life'] = 30,
	['death'] = 40,
	['legacy'] = 50,
	['bibliography'] = 60,
	['notes'] = 70,
	['footnotes'] = 70,
	['references'] = 80,
	['literature'] = 90,
	['external-links'] = 100,
}

local function createFromClass(t)
	return classes[t.class] and classes[t.class](t)
end

local function createFromWeight(t)
	local max = -100
	local constructor = nil
	local class = nil
	for _,v in pairs(classes) do
		local weight = v.weight(t)
		if weight>max then
			max = weight
			constructor = v
		end
	end
	return constructor and constructor(t)
end

local function create(t)
	return createFromClass(t) or createFromWeight(t)
end

--- Table acting as a baseclass for Plan
local Plan = {}
Plan.__index = Plan

classes.plan = Plan

setmetatable(Plan, {
	__call = function(cls, ...)
		local self = setmetatable({}, cls)
		self:_init(...)
		return self
	end,
})

--- Initialiser for the Plan class
-- @param t table holding additional data
function Plan:_init( t )
	self._data = t
	self._active = true
	self._position = 0
	if self._data.class == nil then
		self._class = self:class()
	end
end

--- Get nudge
-- @return number
function Plan:nudge()
	for k,v in pairs(layout) do
	return self._class or self._data.class or 'plan'
	end
	return
end

--- Get class
-- @return string
function Plan:class()
	return self._class or self._data.class or 'plan'
end

--- Is status active?
-- @return boolean
function Plan:isActive()
	return self._active
end

--- Activate status
-- @return boolean
function Plan:activate()
	self._active = true
	return self._active
end

--- Deactivate status
-- @return boolean
function Plan:deactivate()
	self._active = false
	return self._active
end

--- Weight of the given class viewed as a possible Plan instance
-- @param t table holding data used during test
function Plan.weight( t )
	local weight = 0
	if not t then
		return weight
	end
	weight = weight + (t.class == 'plan' and 100 or 0)
	weight = weight + (t.tags and 10 or 0)
	weight = weight + (t.preconditions and 10 or 0)
	weight = weight + (t.postconditions and 10 or 0)
	return weight
end

--- Get the spawned tags by optionally filtering out a subset
-- @param id(s) from the tagset
-- @return table of Tags, otherwise empty table
function Plan:tags(...)
	local args = {...}
	if #args == 0 then
		return self._data.tags or {}
	else
		return intersection({...}, self._data.tags)
	end
end

--- Get the preconditions
-- @return table of Expressions, otherwise empty table
function Plan:preconditions()
	return self._data.preconditions or {}
end

--- Get the postconditions
-- @return table of Expressions, otherwise empty table
function Plan:postconditions()
	return self._data.postconditions or {}
end

--- Table acting as a subclass for Document
local Document = {}
Document.__index = Document

classes.document = Document

setmetatable(Document, {
  __index = Plan,
  __call = function (cls, ...)
    local self = setmetatable({}, cls)
    self:_init(...)
    return self
  end,
})

--- Initialiser for the Document class
-- @param t table holding additional data
function Document:_init( t )
  Plan._init(self, t)
end

--- Get class
-- @return string
function Document:class()
	return self._class or self._data.class or 'document'
end

--- Weight of the given class viewed as a possible Document instance
-- @param t table holding data used during test
function Document.weight( t )
	local weight = 0
	if not t then
		return weight
	end
	weight = weight + 0.9*Plan.weight(t)
	weight = weight + (t.class == 'document' and 100 or 0)
	weight = weight + (t.constituent and 10 or 0)
	weight = weight + (t.title and 10 or 0)
	return weight
end

--- Get the title
-- @return any if found, otherwise nil
function Document:title()
	return self._data.title
end

--- Get the constituents
-- @return table of Plan, otherwise empty table
function Document:constituents()
	return self._data.constituents or {}
end

--- Get the activeConstituents
-- @return table of Plan, otherwise empty table
function Document:constituents()
	local found = {}
	for _,v in ipairs(self._data.constituents or {}) do
		found[1+#found] = v
	end
	return found
end

--- Table acting as a subclass for Constituent
local Constituent = {}
Constituent.__index = Constituent

classes.constituent = Constituent

setmetatable(Constituent, {
  __index = Plan,
  __call = function (cls, ...)
    local self = setmetatable({}, cls)
    self:_init(...)
    return self
  end,
})

--- Initialiser for the Constituent class
-- @param t table holding additional data
function Constituent:_init( t )
  Plan._init(self, t)
end

--- Get class
-- @return string
function Constituent:class()
	return self._class or self._data.class or 'constituent'
end

--- Weight of the given class viewed as a possible Constituent instance
-- @param t table holding data used during test
function Constituent.weight( t )
	local weight = 0
	if not t then
		return weight
	end
	weight = weight + 0.9*Plan.weight(t)
	weight = weight + (t.class == 'constituent' and 100 or 0)
	weight = weight + (t.nucleus and 10 or 0)
	weight = weight + (t.satelite and 10 or 0)
	weight = weight + (t.constituents and 10 or 0)
	weight = weight + (t.relation and 10 or 0)
	return weight
end

if 1 or _G['_BDD'] then
	nlg['filter'] = filter
	nlg['create'] = create
	nlg['createFromClass'] = createFromClass
	nlg['createFromWeight'] = createFromWeight
	nlg['Plan'] = Plan
	nlg['Document'] = Document
	nlg['Constituent'] = Constituent
end

nlg.load = function(...)
	local constituents = {}
	for _,v in ipairs({...}) do
		local data = nil
		local title = 'Module:NLG/' .. v
		if pcall(function() data = mw.loadData( title ) end) and data then
			for _,w in ipairs(data) do
				constituents[1+#constituents] = create(w)
			end
		end
	end
	local root = Document({
		['class'] = 'document',
		['constituents'] = constituents
	})
	return root
end

return nlg