Modulo:Criteri cancellazione immediata/sandbox

--[[
* Modulo che implementa i template {{Criterio cancellazione immediata}} e
* {{Criteri cancellazione immediata}}.
]]

require('strict')

local cfg = mw.loadJsonData('Modulo:Criteri cancellazione immediata/Configurazione/sandbox.json')
local getArgs = require('Module:Arguments').getArgs

-- =============================================================================
--                           enum NamespaceType
-- =============================================================================
local NamespaceType = setmetatable({
	ANY = {
		value = 'any'
	},
	EVEN = {
		value = 'ns pari'
	},
	ODD = {
		value = 'ns dispari'
	}
}, {
	__index = function (t, key)
		if key == 'findByValue' then
			return function (value)
				for _, ns_type in pairs(t) do
					if ns_type.value == value then
						return ns_type
					end
				end
			end
		end
	end
})

-- =============================================================================
--                           enum Namespace
-- =============================================================================

local Namespace = setmetatable({
	DEFAULT = {
		id = 'default',
		type = NamespaceType.ANY,
		name = 'default'
	},
	PRINCIPALE = {
		id = 0,
		type = NamespaceType.EVEN,
		name = 'Principale'
	}
}, {
	__index = function (t, key)
		if key == 'findById' then
			return function (id)
				for _, ns in pairs(t) do
					if ns.id == tonumber(id) then
						return ns
					end
				end
			end
		elseif key == 'findByName' then
			return function (name)
				for _, ns in pairs(t) do
					if ns.name == name then
						return ns
					end
				end
			end
		end
	end
})

for _, ns in pairs(mw.site.namespaces) do
	if ns.id ~= 0 then
		Namespace[ns.name:gsub(' ', '_'):upper()] = {
			id = ns.id,
			type = ns.id % 2 == 0 and NamespaceType.EVEN or NamespaceType.ODD,
			name = ns.name
		}
	end
end

-- =============================================================================
--                           enum ListType
-- =============================================================================

local ListType = setmetatable({
	DROPDOWN_LIST = {
		arg = 'a discesa',
		prefix = '**',
		section = setmetatable({
			[6] = '* Motivi comuni di cancellazione'
		}, {
			__index = function (t, key)
				return '* Cancellazione immediata'
			end
		})
	},
	BULLETED_LIST = {
		arg = 'puntato',
		prefix = '*',
		section = setmetatable({}, {
			__index = function (t, key)
				return ''
			end
		})
	}
}, {
	__index = function (t, key)
		if key == 'findByArg' then
			return function (arg)
				for _, list_type in pairs(t) do
					if list_type.arg == arg then
						return list_type
					end
				end
			end
		end
	end
})

-- =============================================================================
--                           enum TextType
-- =============================================================================

local TextType = setmetatable({
	SYSTEM_MESSAGE = {
		arg = 'messaggio di sistema',
		get = 'getSystemMessage',
		set = 'setSystemMessage'
	},
	DEFINITION = {
		arg = 'definizione',
		get = 'getDefinition',
		set = 'setDefinition'
	}
}, {
	__index = function (t, key)
		if key == 'findByArg' then
			return function (arg)
				for _, text_type in pairs(t) do
					if text_type.arg == arg then
						return text_type
					end
				end
			end
		end
	end
})

-- =============================================================================
--                           classe Criterion
-- =============================================================================

local Criterion = {}

-- Costruttore della classe Criterion
function Criterion:new(code)
	local self = {}
	setmetatable(self, { __index = Criterion })

	self.code = code
	self.order = { default = 0 }
	self.link = { default = string.format('[[WP:IMMEDIATA|%s]]', code) }
	self.system_message = { default = '' }
	self.definition = { default = '' }
	self.validity = { default = true }

	return self
end

function Criterion:getCode()
	return self.code
end

function Criterion:getLink(ns)
	return self.link[ns.id] or self.link.default
end

function Criterion:getOrder(ns)
	return self.order[ns.id] or self.order.default
end

function Criterion:getSystemMessage(ns, add_link)
	local system_message = self.system_message[ns.id] or self.system_message.default
	local pre = add_link and self:getLink(ns) or self:getCode()

	return string.format('(%s) %s', pre, system_message)
end

function Criterion:getDefinition(ns, add_link)
	local definition = self.definition[ns.id] or self.definition.default
	local pre = add_link and self:getLink(ns) or self:getCode()

	return string.format('(%s) %s', pre, definition)
end

function Criterion:isValid(ns)
	if self.validity[ns.id] ~= nil then
		return self.validity[ns.id]
	elseif self.validity[ns.type.value] ~= nil then
		return self.validity[ns.type.value]
	else
		return self.validity.default
	end
end

function Criterion:setLink(link, ns)
	self.link[ns.id] = link
end

function Criterion:setOrder(order, ns)
	self.order[ns.id] = order
end

function Criterion:setSystemMessage(text, ns)
	self.system_message[ns.id] = text
end

function Criterion:setDefinition(text, ns)
	self.definition[ns.id] = text
end

function Criterion:setValidForNs(valid, ns)
	self.validity[ns.id] = valid
end

function Criterion:setValidForNsType(valid, ns_type)
	self.validity[ns_type.value] = valid
end

-- =============================================================================
--                            Funzioni di utilità
-- =============================================================================

local function to_criterion(key, value)
	local criterion = Criterion:new(key)

	for ns, props in pairs(value) do
		ns = Namespace.findByName(ns)

		if ns and type(props) == 'table' then
			if type(props.ordine) == 'number' then
				criterion:setOrder(props.ordine, ns)
			end

			if type(props.link) == 'string' then
				criterion:setLink(props.link, ns)
			end

			if type(props.testi) == 'table' then
				for k, v in pairs(props.testi) do
					local text_type = TextType.findByArg(k)

					if text_type then
						criterion[text_type.set](criterion, v, ns)
					end
				end
			end

			if ns == Namespace.DEFAULT then
				if type(props['validità']) == 'table' then
					for k, v in pairs(props['validità']) do
						local ns_type = NamespaceType.findByValue(k)

						if ns_type and type(v) == 'boolean' then
							criterion:setValidForNsType(v, ns_type)
						end
					end
				end
			elseif type(props['validità']) == 'boolean' then
				criterion:setValidForNs(props['validità'], ns)
			end
		end
	end

	return criterion
end

local function get_suppressed_criteria(arg)
	local suppressed_criteria = setmetatable({}, {
		__index = function (t, key)
			return false
		end
	})

	if arg then
		for criterion_code in string.gmatch(arg, '[%s,]*([^,]*[^%s,])') do
			suppressed_criteria[criterion_code] = true
		end
	end

	return suppressed_criteria
end

local function get_args(frame)
	local args = getArgs(frame, { parentOnly = true })

	if args[1] and args.criterio == nil then
		args.criterio, args[1] = args[1], nil
	end

	args.ns = Namespace.findByName(args.ns) or Namespace.findById(args.ns) or Namespace.DEFAULT
	args['tipo elenco'] = ListType.findByArg(args['tipo elenco']) or ListType.DROPDOWN_LIST
	args['tipo testo'] = TextType.findByArg(args['tipo testo']) or TextType.SYSTEM_MESSAGE
	args.link = args.link ~= 'no' and true or false
	args.ancore = (args.ancore == 'sì' or args.ancore == 'si') and true or false
	args['escludi criteri'] = get_suppressed_criteria(args['escludi criteri'])

	return args
end

-- =============================================================================
--                            Funzioni esportate
-- =============================================================================

local p = {}

function p.get_criterion(frame)
	local args = get_args(frame)
	local text_type = args['tipo testo']
	local ns, criterion_code, add_link = args.ns, args.criterio, args.link

	if criterion_code == nil or ns == nil then
		return
	end

	for key, value in pairs(cfg) do
		local criterion = to_criterion(key, value)

		if criterion:getCode() == criterion_code and criterion:isValid(ns) then
			return criterion[text_type.get](criterion, ns, add_link)
		end
	end
end

function p.list_criteria(frame)
	local args = get_args(frame)
	local list_type, text_type = args['tipo elenco'], args['tipo testo']
	local ns, add_anchor, add_link = args.ns, args.ancore, args.link
	local suppressed_criteria = args['escludi criteri']

	if ns == nil then
		return
	end

	local list = list_type.section[ns.id]
	local valid_criteria = {}

	for key, value in pairs(cfg) do
		local criterion = to_criterion(key, value)

		if criterion:isValid(ns) and suppressed_criteria[criterion:getCode()] == false then
			table.insert(valid_criteria, criterion)
		end
	end

	local comp = function (a, b)
		if a:getOrder(ns) == b:getOrder(ns) then
			return a:getCode() < b:getCode()
		else
			return a:getOrder(ns) < b:getOrder(ns)
		end
	end

	table.sort(valid_criteria, comp)

	for _, criterion in ipairs(valid_criteria) do
		list = list .. '\n' .. list_type.prefix .. ' '

		if add_anchor then
			list = list .. string.format('<span id=%s></span>', criterion:getCode())
		end

		list = list .. criterion[text_type.get](criterion, ns, add_link)
	end

	return mw.text.trim(list)
end

return p