--Modulo per implementare le funzionalità di template:Interprogetto
require('Module:No globals')
local p = {} -- per l'esportazione delle funzioni del modulo

local cfg = mw.loadData( 'Modulo:Interprogetto/Configurazione' );
local root -- radice del markup html
local debug= {} -- per debug
local categories = {} -- categorie di errore da aggiungere
local errors_msg = {} -- messaggi di errore da aggiungere 

local function dump(t, ...)
    local args = {...}
    for _, s in ipairs(args) do
        table.insert(t, s)
    end
end

-- ============================================================================================================
-- Aggiunge uno spazio alla stringa se non termina per "'"
-- ============================================================================================================
local function add_space(s)
    --ritorna uno spazio se l'ultimo carattere non è un accento
    if s == nil then return '' end
    if mw.ustring.sub(s, -1) == "'" then
        return s 
    else
        return s .. ' '
    end
end

-- ============================================================================================================
-- ritorna la stringa se è definita e diversa da stringa nulla, altrimenti nil
-- ============================================================================================================
local function is_defined(s)
    if s and s ~= '' then return s end
    return nil
end

-- ============================================================================================================
-- Aggiunge category alla lista di categorie da aggiungere alla voce
-- ============================================================================================================
local function add_category(category)
    if category ~= nil then
        categories[category] = true
    end
end

-- ============================================================================================================
-- Aggiunge un messaggio di errore alla lista di messaggi di errore
-- ============================================================================================================
local function add_error(error_msg)
    if errors_msg ~= nil then
        errors_msg[#errors_msg+1] = error_msg
        add_category('Errori di compilazione del template Interprogetto')
    end
end

-- ============================================================================================================
-- Ritorna true se la voce è una pagina di disambiguazione
-- controllando se il valore della proprietà P31 (wikidata property "Istance of", https://www.wikidata.org/wiki/Property:P31)
--- è uguale a "4167410"  (wikidata item 'Wikimedia disambiguation page': https://www.wikidata.org/wiki/Q4167410)
-- ============================================================================================================
local function check_is_disambigua(entity)
    local istance_of = 'P31'  -- 
    if  entity and entity.claims and entity.claims[istance_of]
        and entity.claims[istance_of][1]
        and entity.claims[istance_of][1].mainsnak
        and entity.claims[istance_of][1].mainsnak.datavalue
        and entity.claims[istance_of][1].mainsnak.datavalue.type == 'wikibase-entityid'
        and entity.claims[istance_of][1].mainsnak.datavalue.value
        and entity.claims[istance_of][1].mainsnak.datavalue.value['entity-type'] == 'item'
        and entity.claims[istance_of][1].mainsnak.datavalue.value['numeric-id'] == 4167410 then
            return true
    end
    return false
end

-- ============================================================================================================
-- Ritorna una lista di progetti linkati da wikidata e un elenco di eventuali badge 
-- controllando i progetti elencati in cfg.automatic_link
-- ============================================================================================================
local function get_wikidata_links(entity)
    local wikidata_links = {}
    local badge_links = {}
    if entity == nil then return wikidata_links, badge_links end 
    for key_progetto, progetto in pairs(cfg.automatic_link) do
        if entity.sitelinks and entity.sitelinks[progetto.interwiki] then
            wikidata_links[key_progetto] = entity.sitelinks[progetto.interwiki].title
            if  entity.sitelinks[progetto.interwiki].badges then
                local badge_list = {}
                for _, badge_quality in ipairs(entity.sitelinks[progetto.interwiki].badges) do
                    if cfg.badges[badge_quality] then
                        badge_list[#badge_list+1] = cfg.badges[badge_quality]
                    end
                end
                if #badge_list > 0 then
                    table.sort(badge_list, sort_by_ordine)
                    badge_links[key_progetto] = table.concat(badge_list, ' ')
                end
            end
        elseif progetto.property_category and  entity.claims then
            local property_id =progetto.property_category
            if entity.claims[property_id]
                and entity.claims[property_id][1]
                and entity.claims[property_id][1].mainsnak
                and entity.claims[property_id][1].mainsnak.datavalue
                and entity.claims[property_id][1].mainsnak.datavalue.type == 'string' then
                    wikidata_links[key_progetto] = 'Category:' .. entity.claims[property_id][1].mainsnak.datavalue.value
            end
        end
    end
    return wikidata_links, badge_links
end

-- ============================================================================================================
-- Ritorna un collegamento di default dato il nome progetto 
-- ============================================================================================================
local function get_default_collegamento(key_progetto, fullpagename)
    if cfg.parameters[key_progetto] then
        if cfg.parameters[key_progetto]['collegamento_non_esistente'] then
            add_error(cfg.parameters[key_progetto]['collegamento_non_esistente'])
            return ''
        elseif cfg.parameters[key_progetto]['collegamento_default_minuscolo'] then
            return mw.ustring.gsub(fullpagename, "^%u", string.lower)
        else
            return fullpagename
        end
    end
    return ''
end

-- ===============================================================================================
-- Classe per gestire i collegamenti interprogetto
-- ===============================================================================================
local Collegamento = {}

function Collegamento:new(key_progetto, args, badge_links, default)
    -- Crea un collegamento a un progetto, riceve il nome del progetto, gli argomenti da usare per determinare
    -- i valori dei vari parametri del collegamento. Si appoggia alla tabella esterna cfg.parameters per i 
    -- valori di default del collegamento e all'array globale defaults per i valor di default generali

    local self = {}
    setmetatable(self, { __index = Collegamento,
                         __tostring = function(t) return self:__tostring() end })
    local default_progetto = cfg.parameters[key_progetto]
    if default_progetto == nil then return nil end
    self.ordine = default_progetto.ordine
    self.key_progetto = key_progetto
    self.badge_leftbar =  badge_links[key_progetto] or ''
    self.collegamento = args[key_progetto]
    self.etichetta =  is_defined(args[key_progetto .. '_etichetta']) or (default_progetto.etichetta_lower and default.etichetta_lower) or default.etichetta
    self.oggetto  = args[key_progetto .. '_oggetto'] or default.oggetto or default_progetto.oggetto
    if default_progetto.preposizione then
        self.preposizione = args[key_progetto .. '_preposizione'] or default.preposizione or default_progetto.preposizione
    else
        self.preposizione = ''
    end
    if key_progetto=='notizia' and is_defined(args.data)  then
            self.testo_dopo = table.concat({' <small>', args.data, '</small>'})
        else
            self.testo_dopo = default_progetto.testo_dopo
    end
    if default_progetto.lingua and args[key_progetto ..'_lingua'] then
        self.lingua = ' in lingua ' .. args[key_progetto ..'_lingua']
    else
        self.lingua = ''
    end
    return self
end

function Collegamento:Link()
    if cfg.parameters[self.key_progetto].link == 'Link' then return self:Link_text() end
    if cfg.parameters[self.key_progetto].link == 'LinkWithLanguage' then return self:Link_language() end
    if cfg.parameters[self.key_progetto].link == 'LinkRicette' then return self:Link_ricette() end
    if cfg.parameters[self.key_progetto].link == 'LinkIncubator' then return self:Link_incubator() end
    add_error('Errore interno modulo Interprogetto:'.. self.key_progetto)
    return ''
end

function Collegamento:Link_lb()
    if cfg.parameters[self.key_progetto].link == 'Link' then return self:Link_text_lb() end
    if cfg.parameters[self.key_progetto].link == 'LinkWithLanguage' then return self:Link_language_lb() end
    if cfg.parameters[self.key_progetto].link == 'LinkRicette' then return self:Link_ricette_lb() end
    if cfg.parameters[self.key_progetto].link == 'LinkIncubator' then return self:Link_incubator_lb() end
    add_error('Errore interno modulo Interprogetto:' .. self.key_progetto)
    return ''
end

function Collegamento:Link_text()
    local default_progetto = cfg.parameters[self.key_progetto]
    return mw.message.newRawMessage("* [[File:$1|link=$2|$3|Collabora a $4]] '''[[$6|$6]]''' contiene $7 $8 $9$10'''[[$11$12|$13]]'''$14",
                                    {default_progetto.icona, default_progetto.prefix, default_progetto.dimensione_icona, default_progetto.nome_progetto,
                                    -- 1                            2                       3                                 4
                                     default_progetto.prefix, default_progetto.nome_progetto,
                                    --                   5           6       
                                     self.oggetto, self.lingua,  add_space(self.preposizione), default_progetto.testo_prima,
                                    -- 7                8           9                                   10
                                     default_progetto.prefix , self.collegamento, self.etichetta , self.testo_dopo }):plain()
                                    -- 11                          12              13              14
end

function Collegamento:Link_text_lb()
    local default_progetto = cfg.parameters[self.key_progetto]
    return mw.message.newRawMessage("[[$1$2|$3]]", {default_progetto.prefix, self.collegamento, default_progetto.nome_leftbar or default_progetto.nome_progetto}):plain()
end

function Collegamento:Link_ricette()
    local default_progetto = cfg.parameters[self.key_progetto]
    return mw.message.newRawMessage("* [[File:$1|link=$2|$3|Collabora a $4]] Il ''[[b:Libro di cucina|Libro di cucina]]'' di '''[[b:|Wikibooks]]''' contiene [[$5$6|ricette]] relative a questo argomento.",
                                    { default_progetto.icona, default_progetto.prefix, default_progetto.dimensione_icona, default_progetto.nome_progetto, default_progetto.prefix, self.collegamento} ):plain()
end

function Collegamento:Link_language()
    local default_progetto = cfg.parameters[self.key_progetto]
    local main_page_link=tostring(mw.uri.fullUrl(default_progetto.prefix .. self.collegamento, {uselang='it'}))
    return mw.message.newRawMessage("* [[File:$1|link=$2|$3|Collabora a $4]] '''<span class=\"plainlinks\">[$5 $6]</span>''' contiene $7 $8$9'''<span class=\"plainlinks\">[$10 $11]</span>'''$12",
                                    { default_progetto.icona, main_page_link, default_progetto.dimensione_icona, default_progetto.nome_progetto,
                                        --1             2                3                 4
                                      main_page_link, default_progetto.nome_progetto, self.oggetto, add_space(self.preposizione),
                                        -- 5                -6                  -7          8
                                      default_progetto.testo_prima, tostring(mw.uri.fullUrl(default_progetto.prefix, self.collegamento, {uselang='it'})), 
                                                --9                 10                                      
                                      self.etichetta, self.testo_dopo} ):plain()
                                                --11            12-
end

function Collegamento:Link_language_lb()
    local default_progetto = cfg.parameters[self.key_progetto]
    return mw.message.newRawMessage("<span class=\"plainlinks\" title=\"$1\">[$2 $3]</span>",
                                    self.etichetta, tostring(mw.uri.fullUrl(default_progetto.prefix .. self.collegamento, {uselang='it'})),
                                    default_progetto.nome_leftbar or default_progetto.nome_progetto):plain()
end

function Collegamento:Link_incubator()
    local default_progetto = cfg.parameters[self.key_progetto]
    local oggetto = self.oggetto
    if not cfg.prefix_incubator[oggetto] then oggetto='wikipedia' end
    local collegamento = tostring(mw.uri.fullUrl(table.concat({'incubator:', prefix_incubator[oggetto],
                                        '/', self.collegamento}), {uselang='it'}))
    local main_page_incubator=tostring(mw.uri.fullUrl(':incubator:Main_Page/it', {uselang='it'}))
    local main_page_progetto = ''
    if oggetto == 'wikipedia' then
        main_page_progetto = '[[Wikipedia]]'
    else
        main_page_progetto = table.concat({'[[', oggetto, ':|', oggetto:gsub("^%l", string.upper), ']]'})
    end    
    return  mw.message.newRawMessage("* [[File:$1|link=$2|$3|Collabora a Incubator '''<span class=\"plainlinks\">[$4 Incubator]</span>''' contiene un test su $5 $6<span class=\"plainlinks\">[$8 $9]</span>",
                                        {default_progetto.icona, main_page_incubator, default_progetto.dimensione_icona, main_page_incubator, main_page_progetto,
                                         add_space(self.preposizione), collegamento, self.etichetta }):plain()
end

function Collegamento:Link_incubator_lb()
    local oggetto = self.oggetto
    if not cfg.prefix_incubator[oggetto] then oggetto='wikipedia' end
    local collegamento = tostring(mw.uri.fullUrl(table.concat({'incubator:', prefix_incubator[oggetto],
                                        '/', self.collegamento}), {uselang='it'}))
    return mw.message.newRawMessage("<span class=\"plainlinks\" title=\"$1\">[$2 Incubator]<span>", {self.etichetta, collegamento}):plain()
end
-- ===============================================================================================
-- Fine definizione della classe
-- ===============================================================================================

-- ============================================================================================================
-- Funzione per ordinare una tabella in funzione della chiave "ordine" degli elementi della tabella
-- ============================================================================================================
local function sort_by_ordine(t1, t2)
    if t1.ordine < t2.ordine then
        return true
    end
end

local function RenderLeftBar(progetti)
    -- Crea la barra di sinistra con i link interprogetto
    -- costruisce le righe per la barra di sinistra come un elenco puntato
    -- Apertura del tag div id="interProject" (vedi [[Commons:MediaWiki:InterProject.js]] incluso da [[Mediawiki:Common.js]])

    local leftbar = mw.html.create('div'):attr('title', 'Collegamenti verso gli altri progetti Wikimedia')
    for _,progetto in ipairs(progetti) do
        leftbar:newline()
        leftbar:wikitext('<li class=\"', progetto.badge_leftbar, '\">')
        leftbar:wikitext(progetto:Link_lb())
        if cfg.parameters[progetto.key_progetto].nome_leftbar then
            leftbar:wikitext('<br />(', cfg.parameters[progetto.key_progetto].nome_leftbar, ')')
        end
        leftbar:wikitext('</li>')
    end
    root:tag('div')
        :attr('id', 'interProject')
        :cssText('display: none; clear: both; border-width: 2px 0; border-style: dotted; border-color: #AAAAAA; margin-top: 2em')
        :tag('p')
            :attr('id', 'sisterProjects')
            :cssText('background-color: #efefef; font-weight: bold; margin: 0 0 -0.2em')
            :tag('span')
                :wikitext('Altri progetti')
                :done()
            :done()
        :node(leftbar)
end


local function RenderLinksInText(progetti)
    -- Scandisce la tabella progetti e produce il codice html per l'elenco puntato
    for _,progetto in ipairs(progetti)  do
        root:newline()
        root:wikitext(progetto:Link())
    end
end

function p.interprogetto(frame)

    local origArgs
    -- Se chiamata mediante  #invoke, usa gli argomenti passati al template invocante.
    -- Altrimenti a scopo di test assume che gli argomenti siano passati direttamente
    if frame == mw.getCurrentFrame() then
        origArgs = frame:getParent().args
    else
        origArgs = frame.args
    end
    local default = {} -- tabella con i parametri di default valorizzati
    -- Carico i parametri
    local current_page = mw.title.getCurrentTitle()
    local current_namespace = current_page.namespace
    local current_pagename = current_page.text
    -- Per i namespace uso il nome canonico (inglese) per non avere differenze tra progetti:
    -- esempio: Utente/User, Categoria/Category, ma anche Wikiquote/Wikipedia (Project), ecc.
    if current_namespace ~= 0 then
        default.current_fullpagename = mw.site.namespaces[current_namespace].canonicalName .. ':' .. current_pagename
    else
        default.current_fullpagename = current_pagename
    end
    -- carico i dati da wikidata se esistono
    local entity = nil
    if mw.wikibase then
        entity = mw.wikibase.getEntityObject()
    end
    -- Calcolo l'etichetta di default per i collegamenti, in ordine di priorità:
    -- 1) Se è definita l'etichetta in lingua italiana su wikidata uso questa (elimiando un eventuale 'Categoria:' di fronte alla voce)
    --2) Altrimenti uso il nome della pagina corrente, eliminando eventuale disambigue in coda alla voce 
    --    e definisco etichetta_lower come il nome della voce con iniziale minuscola
    -- 
    if is_defined(origArgs.etichetta) then
        default.etichetta = origArgs.etichetta
    else
        if entity and entity.labels and entity.labels.it and entity.labels.it.value ~= '' then
            default.etichetta = mw.ustring.gsub(entity.labels.it.value, '^Categoria:', '') -- Elimino un eventuale "Categoria:" in fronte del nome
        else
            default.etichetta = mw.ustring.gsub(current_pagename, ' %(.*%)$', '')   -- Elimino un'eventuale disambigua dal nome
            if current_namespace == 0 then
                default.etichetta_lower = mw.ustring.gsub(default.etichetta, "^%l", string.lower)
            end 
        end
    end
    -- se etichetta_lower non è definito lo rendo uguale ad etichetta
    default.etichetta_lower = default.etichetta_lower or default.etichetta
    local is_disambigua = check_is_disambigua(entity)
    if current_namespace ~= 14 then
        default.preposizione = origArgs.preposizione
        default.oggetto = origArgs.oggetto
    else
        default.preposizione = preposizione or "sull'argomento"
        default.oggetto = oggetto or "una categoria"
    end
    local notizia_presente = false
    -- Copio i parametri in una nuova tabella, creando una coppia progetto/collegamento per i parametri posizionali
    -- e controllando per parametri duplicati e nomi di progetto non conosciuti
    local newArgs = {}
    for key, value in pairs(origArgs) do
        if tonumber(key) then
            local key_progetto = mw.text.trim(value)
            if cfg.parameters[key_progetto] then
                if origArgs[key_progetto] then
                    add_error('Collegamento a "' ..  value .. '" inserito sia come parametro posizionale che nominale')
                else
                    newArgs[key_progetto] = get_default_collegamento(key_progetto, default.current_fullpagename)
                end
            else
                if key == 1 and key_progetto == 'nolink' then
                    newArgs[1] = 'nolink'
                else
                    add_error('Il parametro "' .. value .. '" non corrisponde a nessun progetto riconosciuto dal template')
                end
            end
        else
            newArgs[key] = value
        end
    end
    -- carico i links e i badge proposti da wikidata
    local wikidata_links = {}
    local badge_links = {}
    if entity and not(is_disambigua) then
        wikidata_links, badge_links = get_wikidata_links(entity)
    end
    -- Controllo se i collegamenti inseriti manualmente sono presenti in wikidata
    for key_progetto, collegamento in pairs(newArgs) do
        if cfg.parameters[key_progetto] ~= nil and wikidata_links[key_progetto] == nil then
            add_category(cfg.automatic_link['category_wikidata_missing'])
        end
    end
    -- Aggiungo i collegamenti da wikidata alla tabella se non già presenti
    -- TODO: controllo per collegamenti su wikidata diversi da quelli manuali ?
    for key_progetto, collegamento in pairs(wikidata_links) do     
        if newArgs[key_progetto]==nil and not (cfg.automatic_link[key_progetto]['alias'] and newArgs[cfg.automatic_link[key_progetto]['alias']]) then
            newArgs[key_progetto] = collegamento
        end
    end 
    -- Costruisco una lista di progetti da visualizzare e la ordino
    local progetti = {}
    for key_progetto, collegamento in pairs(newArgs) do
        if cfg.parameters[key_progetto] then
        -- Salto i link a wikidata per le voci  in ns0 eccetto che per la Pagina principale
            if not(key_progetto == 'wikidata') or current_namespace ~= 0 or current_pagename == 'Pagina principale' then
                -- Se è disambigua registro solo i valori per i link al wikizionario
                if cfg.parameters[key_progetto]['abilita_in_disambigua'] or not(is_disambigua) then
                     progetti[#progetti+1] = Collegamento:new(key_progetto, newArgs, badge_links, default)
                end
            end
        end
    end
    root = mw.html.create('')
    if newArgs['notizia'] then -- gestisco il parametro "notizia" a parte
        if #progetti>1 then
            add_error('Errore: il parametro "notizia" se presente deve essere unico')
        else
            root:wikitext('* [[File:', progetti.icona[1], '|link=', progetti.prefix[1], '|',
                         progetti.dimensione_icona[1],'|Collabora a ', progetti.nome_progetto[1],
                        "]]", " Articolo su '''[[", progetti.prefix[1], progetti.nome_progetto[1], '|',
                        progetti.nome_progetto[1], "]]''': ", "'''[[",  progetti.prefix[1],
                        progetti.collegamento[1], '|', progetti.collegamento[1], "]]'''", progetti.testo_dopo[1] )
        end
    else
        table.sort(progetti, sort_by_ordine)
        RenderLeftBar(progetti) 
        if not(newArgs[1]) or mw.text.trim(newArgs[1])~= 'nolink'  then
            RenderLinksInText(progetti)
        end
    end
    if #progetti == 0 then
        add_category('Template interprogetto vuoti')
    end
    for category,_ in pairs(categories) do
        root:wikitext('[[Categoria:' .. category .. ']]')
    end
    if #errors_msg > 0 then
        if #progetti > 0 then 
            root:wikitext('\n')
        end
        root:wikitext('<strong class="error">' .. table.concat(errors_msg, '; ') .. '</strong>')
    end
    return  tostring(root)
end

return p