--[[
Questo modulo è in appoggio al template Fumetto e animazione per gestirne
le funzioni di categorizzazione automatica
]]
local cfg = mw.loadData("Modulo:Fumetto e animazione/Configurazione/sandbox")
local getArgs = require('Module:Arguments').getArgs
local error_category = 'Errori di compilazione del template Fumetto e animazione'

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

-- Restituisce i valori di una tabella di alias nella configurazione
local function build_reverse_alias(table_name)
	local reverse_alias = {}
	for alias, name in pairs(cfg['alias_' .. table_name]) do
		if not reverse_alias[name] then reverse_alias[name] = {} end
		table.insert(reverse_alias[name], alias)
	end
	return reverse_alias
end

-- Restituisce un valore senza eventuale wikilink a inizio stringa
local function delink(value)
	if not value then return end
	local pattern
	if value:find('|') then
		pattern = "^'*%[%[ *([^%|%[%]]-) *%|.-%]%]"
	else
		pattern = "^'*%[%[ *([^%[%]]-) *%]%]"
	end
	return value:match(pattern) or value
end

-- Restituisce liste di valori uno alla volta
local function extract_values(args, base_name)
	local index = 0
	return function()
			while true do
				index = index + 1
				local value = args[base_name .. ' ' .. tostring(index)]
				if not value and index == 1 then
					value = args[base_name]
				end
				if not value then break end
				return delink(value)
			end
		end
end

-- Parsifica i parametri rimuovendo le stringhe vuote
local function parse_args(args)
	for key, value in pairs(args) do
		if value and mw.text.trim(value) == '' then
			args[key] = nil
		end
	end
	return args
end

-- Sostituisce le parentesi in una stringa coi corrispondenti codici ascii
local function replace_braces(s)
	local new_s = mw.ustring.gsub(s, "%[", "[")
	new_s = mw.ustring.gsub(new_s, "%]", "]")
	new_s = mw.ustring.gsub(new_s, "%(", "(")
	new_s = mw.ustring.gsub(new_s, "%)", ")")
	new_s = mw.ustring.gsub(new_s, " ", " ")
	return new_s
end

-- =============================================================================
--                           classe Media
-- =============================================================================

local Media = {}

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

	self.args = args
	self.tipo = self:_getType()
	self.sottotipo = self:_getSubtype()
	self.name = self:_getName()
	self.alias = self:_getAlias()

	return self
end

-- Verifica se il medium corrisponde a uno di quelli indicati
function Media:inArray(...)
	for _, value in ipairs({ ... }) do
		if self.tipo == value or self.sottotipo == value or self.name == value then
			return true
		end
	end
	return false
end

-- Ottiene l'alias più generico del medium per le categorie
function Media:_getAlias()
	return self:_getValue('alias_tipo', 'alias_sottotipo')
end

-- Ottiene il nome più preciso del medium per le categorie
function Media:_getName()
	return self:_getValue('tipo', 'sottotipo')
end

-- Verifica che il sottotipo di medium sia riconosciuto
function Media:_getSubtype()
	if cfg.sottotipo[self.tipo] and cfg.sottotipo[self.tipo][self.args.sottotipo] then
		return self.args.sottotipo
	end
end

-- Verifica che il tipo di medium sia riconosciuto
function Media:_getType()
	if cfg.tipo[self.args.tipo] or cfg.sottotipo[self.args.tipo] then
		return self.args.tipo
	end
end

-- Cerca il nome o l'alias del medium nella configurazione
function Media:_getValue(key_tipo, key_sottotipo)
	if cfg[key_sottotipo][self.tipo] then
		return cfg[key_sottotipo][self.tipo][self.sottotipo]
	else
		return cfg[key_tipo][self.tipo]
	end
end

-- =============================================================================
--                           classe CategoryManager
-- =============================================================================

local CategoryManager = {}

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

	self.args = parse_args(args)
	self.media = Media:new(self.args)
	self.year = self:_getYear()
	self.categories = {}
	self.tables_matched = {}

	self:_addCategories(self.args.categorie == 'no')

	return self
end

-- Restituisce l'elenco di categorie
function CategoryManager:listCategories()
	return table.concat(self.categories)
end

-- Avvia la categorizzazione automatica dell'opera
function CategoryManager:_addCategories(error_only)
	if (self.args['data inizio'] or self.args['data fine']) and not self.year then
		self:_addCategory(error_category)
	end

	if error_only or self.media.name == nil then return end

	if self.media.name == "serie televisive d'animazione" then
		self
			:_addCategoriesBy('studio_cartoneTV')
			:_addCategoriesBy('paese', 'fp', self._categorizeTVSeriesByCountry)
	elseif self.media.name == "film d'animazione direct-to-video" then
		self:_addCategoriesBy('paese', 'mp', self._categorizeDTVFilmByCountry)
	elseif self.media.name == "webserie d'animazione" then
		self:_addCategoriesBy('paese', 'fp', self._categorizeWebseriesByCountry)
	elseif self.media.name == "film d'animazione per la televisione" then
		self:_addCategoriesBy('paese', 'mp', self._categorizeTVFilmByCountry)
	elseif self.media.tipo == 'manga' then
		self
			:_addCategoriesBy('editore_manga')
			:_addCategoriesBy('editore')
			:_addCategoriesBy('target', nil, self._ucfirst)
	elseif self.media:inArray('manhua', 'manhwa') then
		self:_addCategoriesBy('editore')
	elseif self.media.tipo == 'light novel' then
		self
			:_addCategory(self.media.tipo)
			:_addCategory('Romanzi in giapponese')
			:_addCategoriesBy('etichetta')
			:_addCategoriesBy('editore')
	elseif self.media.tipo == 'fumetto' and self.media.sottotipo then
		self
			:_addCategoriesBy('etichetta_sottotipo', nil, self._prependSubtype)
			:_addCategoriesBy('etichetta')
			:_addCategoriesBy('editore_sottotipo', nil, self._prependSubtype)
			:_addCategoriesBy('editore')
			:_addCategoriesBy('paese', 'mp', self._categorizeComicByCountry)
		if not self.tables_matched['etichetta_sottotipo'] and
				not self.tables_matched['editore_sottotipo'] then
			self:_addCategory(self.media.name)
		end
	elseif self.media.tipo == 'fumetto' then
		self
			:_addCategoriesBy('etichetta')
			:_addCategoriesBy('editore')
			:_addCategoriesBy('paese', 'mp', self._categorizeComicByCountry)
	end

	if self.media:inArray('anime', 'cartone') then
		self:_addCategoriesBy('studio')
		local broadcaster
		if self.media:inArray('film TV', 'serie TV') then
			local network = self.args['rete 1'] or self.args.rete
			broadcaster = self:_getCategory(delink(network), 'rete')
		end
		if not broadcaster and self.media:inArray('film TV', 'ONA', 'serie TV', 'webserie') then
			local platform = self.args['streaming 1'] or self.args.streaming
			broadcaster = self:_getCategory(delink(platform), 'streaming')
		end
		if broadcaster then
			self:_addCategory(self.media.alias .. ' ' .. broadcaster)
		end
	end

	-- categorizzazione per tipo delle opere non suddivise per anno
	if self.media:inArray('film direct-to-video', 'manhua', 'manhwa', 'ONA', 'webserie') then
		self:_addCategory(self.media.name)
	-- categorizzazione delle opere suddivise per anno
	elseif self.year then
		local media = self.media.sottotipo ~= 'serie TV' and
			self.media.alias or self.media.name
		self:_addCategory(string.format('%s del %s', media, self.year))
	end
end

-- Aggiunge categorie per una lista di valori
function CategoryManager:_addCategoriesBy(table_name, gender, callback)
	if self:_isCategoryRedundant(table_name) == false then
		local base_name = table_name:match('[^_]+')
		for value in extract_values(self.args, base_name) do
			if table_name == 'paese' then
				self:_getCategoryByCountry(value, gender, callback)
			else
				if callback then
					value = callback(self, value)
				end
				local category = self:_getCategory(value, table_name)
				if category then
					self.tables_matched[table_name] = true
					self:_addCategory(category)
				end
			end
		end
	end
	return self
end

-- Formatta e aggiunge una categoria
function CategoryManager:_addCategory(category)
	table.insert(self.categories, string.format('[[Categoria:%s]]', category))
	return self
end

-- Categorizza un fumetto per Paese
function CategoryManager:_categorizeComicByCountry(adj)
	if adj == 'giapponesi' or adj == 'cinesi' or adj == 'coreani' then
		return
	elseif adj == 'belgi' or adj == 'francesi' then
		adj = 'franco-belgi'
	end
	self:_addCategory('Fumetti ' .. adj)
end

-- Categorizza una serie direct-to-video per Paese
function CategoryManager:_categorizeDTVFilmByCountry(adj)
	self:_addCategory("Film d'animazione " .. adj)
	if self.year then
		self:_addCategory(string.format('Film %s del %s', adj, self.year))
	end
end

-- Categorizza un film televisivo per Paese
function CategoryManager:_categorizeTVFilmByCountry(adj)
	self:_addCategory(string.format("Film d'animazione %s per la televisione", adj))
end

-- Categorizza una serie TV per Paese
function CategoryManager:_categorizeTVSeriesByCountry(adj)
	if adj ~= 'giapponesi' or self.args['paese 2'] then
		self:_addCategory("Serie televisive d'animazione " .. adj)
	end
end

-- Categorizza una webserie per Paese
function CategoryManager:_categorizeWebseriesByCountry(adj)
	self:_addCategory('Webserie ' .. adj)
end

-- Cerca il nome di una categoria nella configurazione
function CategoryManager:_getCategory(key, table_name)
	key = cfg['alias_' .. table_name] and cfg['alias_' .. table_name][key] or key
	return cfg[table_name][key]
end

-- Restituisce la categoria per Paese dopo aver individuato l'aggettivo
-- maschile o femminile, singolare o plurale adeguato
function CategoryManager:_getCategoryByCountry(country, gender, callback)
	local frame = mw.getCurrentFrame()
	local success, adj = pcall(frame.expandTemplate, frame, {
		title = 'Template:AggNaz/' .. country,
		args = { gender }
	})
	if success then
		return callback(self, adj)
	end
end

-- Ricava l'anno dell'opera dalle date di inizio e fine
function CategoryManager:_getYear()
	local start_date = self.args['data inizio'] or ''
	local end_date = self.args['data fine'] or ''
	return (start_date .. end_date):match('%d%d%d%d')
end

-- Verifica che non sia già stata aggiunta una sottocategoria
function CategoryManager:_isCategoryRedundant(table_name)
	if cfg.sottocategorie[table_name] then
		for _, value in ipairs(cfg.sottocategorie[table_name]) do
			if self.tables_matched[value] then
				return true
			end
		end
	end
	return false
end

-- Antepone il sottotipo dell'opera a una stringa
function CategoryManager:_prependSubtype(value)
	return self.media.sottotipo .. ' ' .. value
end

-- Converte la lettera iniziale di una stringa in maiuscola
function CategoryManager:_ucfirst(str)
	return str:gsub('^%l', string.upper)
end

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

local p = {}

-- Funzione per il template FeA, categorizza tranne che per genere
function p.categorie(frame)
	if mw.title.getCurrentTitle().namespace ~= 0 then return end
	return CategoryManager:new(frame:getParent().args):listCategories()
end

-- Funzione per il template FeA, elabora categorie e wikilink dei generi
function p.generi(frame)
	local args = getArgs(frame, {parentOnly = not frame.args[1]})
	local genre = frame.args[1]
	if genre == '' then return end
	local current_page = mw.title.getCurrentTitle()
	local current_namespace = current_page.namespace
	local category
	local result = {}
	for name, alias_name in pairs(cfg['alias_genere']) do
		local piped = mw.ustring.match(genre, '%[%[[^%[]*|%s*' .. name .. '%s*%]%]')
		local pattern = (piped and '%[%[[^%[]*|%s*' or '%[%[%s*') .. name .. '%s*%]%]%a*'
		genre = mw.ustring.gsub( genre, pattern, '[[' .. alias_name .. ']]' )
	end
	for name, correct_name in pairs(cfg['genere']) do
		if current_namespace == 0 and args.categorie ~= 'no' then
			local tipo = {
				['anime'] = correct_name[1],
				['fumetto'] = correct_name[2],
				['light novel'] = correct_name[3],
				['manga'] = correct_name[1],
				['manhua'] = correct_name[2],
				['manhwa'] = correct_name[2]
			}
			tipo = tipo[args.tipo]
			tipo = tipo and tipo[1] and tipo[1] .. ']][[Categoria:' .. tipo[2] or tipo
			category = tipo and tipo ~= '' and '[[Categoria:' .. tipo .. ']]'
		end
		local piped = mw.ustring.match(genre, '%[%[[^%[]*|%s*' .. name .. '%s*%]%]')
		local pattern = (piped and '%[%[[^%[]*|%s*' or '%[%[%s*') .. name .. '%s*%]%]%a*'
		genre = mw.ustring.gsub( genre, pattern, correct_name[4] .. (category or '') )
		-- Categoria d'errore in caso di genere senza corrispondenza
		local list = mw.text.split( genre, ',' )
		for i = 1, #list do
			local s = replace_braces(list[i])
			local p = replace_braces(correct_name[4])
			if mw.ustring.match( s, p ) then
				result[i] = ''
			elseif result[i] == nil and current_namespace == 0 then
				result[i] = '[[Categoria:' .. error_category .. ']]'
			end
		end
	end
	genre = genre .. table.concat(result)
	return genre
end

-- Funzione per il manuale, restituisce la configurazione per aziende e target
function p.tabella_configurazione(frame)
	local args = getArgs(frame)
	local table_name = args[1]
	if not(table_name) then return '' end
	local reverse_alias = build_reverse_alias(table_name)
	local root = mw.html.create('table')
	root
		:addClass('wikitable mw-collapsible mw-collapsed sortable')
		:tag('tr')
			:tag('th'):wikitext(table_name):done()
			:tag('th'):wikitext('Alias'):done()
			:tag('th'):wikitext('Categoria'):done()
	for name, cat_name in pairs(cfg[table_name]) do
		local name_code = table_name == 'genere' and
				replace_braces(cat_name[4]) or
				'[[' .. name .. ']]'
		local cat_code = {}
		if cat_name[1] then
			for i, cat in ipairs(cat_name) do
				if i > 3 then break end
				cat = cat[1] and
						cat[1] .. ']]<br />[[:Categoria:' .. cat[2] or cat
				if cat ~= '' then
					cat_code[#cat_code+1] = '[[:Categoria:' .. cat .. ']]'
				end
			end
			cat_code = table.concat(cat_code, '<br />')
		else
			cat_code = '[[:Categoria:' .. cat_name .. ']]'
		end
		local alias_code = '&nbsp;'
		if reverse_alias[name] then
			for i, alias in ipairs(reverse_alias[name]) do
				reverse_alias[name][i] = '&#91;&#91;' .. alias .. '&#93;&#93;'
			end
			alias_code = table.concat(reverse_alias[name], '<br />')
		end
		root:tag('tr')
			:tag('td'):wikitext(name_code):done()
			:tag('td'):wikitext(alias_code):done()
			:tag('td'):wikitext(cat_code):done()
	end
	return tostring(root)
end

return p