Modulo:Sandbox/M.casanova/Mappa
require('strict')
local getArgs = require('Module:Arguments').getArgs
local mWikidata = require('Module:Wikidata')
local errorCategory = '[[Categoria:Voci con errori del modulo Mapframe]]'

local p = {}

-- Error handler per xpcall, formatta l'errore.
local function errhandler(msg)
	local cat = mw.title.getCurrentTitle().namespace == 0 and errorCategory or ''
	return string.format('<span class="error">%s</span>%s', msg, cat)
end

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

function p._mappa(args)
	local points = {}
	local geo = args.dati or -1
	local sinottico = tonumber(args.sinottico) or 0
	local colore  = args.colore or '#b80000' --colore
	local simbolo = args.simbolo or (sinottico ~= 1 and '-number' or nil) --simbolo
	local gruppo = args.gruppo or ''
	local autozoom = args.autozoom or 's'
	local dsimbolo = 'medium'
	--if sinottico == 1 then dsimbolo = 'small' end
	local cornice
	if args.cornice then cornice = tonumber(args.cornice) or 1 else cornice = 1 end
	local lat1, lat2, lon1, lon2 = 400, -400, 400, -400
	local elenco = tonumber(args.elenco) or 0
	local elenco_dati = {}
	local elenco_num = 1
	local elenco_lett = 'A'

	if args.didascalia then agg(elenco_dati, args.didascalia) end

	if geo == -1 then
		local num = 1
		if sinottico == 1 and args.lat1 == nil and args.lon1 == nil then
			args.lat1 = mWikidata._getProperty({ 'P625', coord = 'latitude', n = 1 })
			args.lon1 = mWikidata._getProperty({ 'P625', coord = 'longitude', n = 1 })
		end
		while args['nome' .. num] do
			points[num] = {
				nome = args['nome' .. num], -- nome del punto
				desc = args['desc' .. num], -- descrizione  del punto
				lat  = tonumber(args['lat' .. num]) or -400, --latitudine
				lon = tonumber(args['lon' .. num]) or -400, --longitudine
				col  = args['colore' .. num] or colore, -- colore del punto
				simb = args['simbolo' .. num] or simbolo, --simbolo da mostrare
				dsimb = args['dim_simbolo' .. num] or dsimbolo, --dimensione del simbolo da mostrare
			}
			if points[num].simb == '-number' and gruppo ~= '' then
				points[num].simb = '-number-' .. gruppo
			end
			if points[num].lat > 85 or points[num].lat < -85 then
				error(string.format('latitudine non valida per il punto %d', num), 2)
			elseif points[num].lon > 180 or points[num].lon < -180 then
				error(string.format('longitudine non valida per il punto %d', num), 2)
			else
				lat1 = math.min(lat1, points[num].lat)
				lat2 = math.max(lat2, points[num].lat)
				lon1 = math.min(lon1, points[num].lon)
				lon2 = math.max(lon2, points[num].lon)
			end
			num = num + 1
		end
		if elenco == 2 then
			agg(elenco_dati,'\n{| width="100%" style="font-size:90%"\n|-\n|width=50% valign=top|')
			for i=1,#points do
				agg(elenco_dati,string.format('<p style="border-left:4px solid %s">',points[i].col))
				if points[i].simb:find('-number') == 1 then
					agg(elenco_dati, string.format('&nbsp;<b>%d</b>)',elenco_num))
					elenco_num = elenco_num+1
				elseif points[i].simb:find('-letter') == 1 then
					agg(elenco_dati, string.format('&nbsp;<b>%s</b>)',elenco_lett))
					elenco_lett = ('ABCDEFGHIJKLMNOPQRSTUVWXYZA'):match(elenco_lett..'(.)')
				elseif points[i].simb == '' then
				else
					agg(elenco_dati, string.format('[[File:Maki2-%s-18.svg|18px]])',points[i].simb))
				end
				agg(elenco_dati,string.format(' %s</p>',points[i].nome))
				if (i == math.floor(#points/2 +0.5)) then agg(elenco_dati,'\n|width=50% valign=top|') end
			end
			agg(elenco_dati,'\n|}')
		elseif elenco == 1 then
			for i=1,#points do
				agg(elenco_dati,string.format('<p style="font-size:90%%;border-left:4px solid %s">',points[i].col))
				if points[i].simb:find('-number') == 1 then
					agg(elenco_dati, string.format('&nbsp;<b>%d</b>)',elenco_num))
					elenco_num = elenco_num+1
				elseif points[i].simb:find('-letter') == 1 then
					agg(elenco_dati, string.format('&nbsp;<b>%s</b>)',elenco_lett))
					elenco_lett = ('ABCDEFGHIJKLMNOPQRSTUVWXYZA'):match(elenco_lett..'(.)')
				elseif points[i].simb == '' then
				else
					agg(elenco_dati, string.format('[[File:Maki2-%s-18.svg|18px]]',points[i].simb))
				end
				agg(elenco_dati,string.format(' %s</p>',points[i].nome))
			end
		end
	end

	local m_args = {}

	-- valori predefiniti
	m_args.width = 350
	m_args.height = 300
	m_args.align = 'right'
	-- valori per sinottico
	if sinottico == 1 then
		m_args.width = 260
		m_args.height = 260
		m_args.align = 'center'
	end
	-- valori inseriti
	m_args.width = args.larghezza or m_args.width
	m_args.height = args.altezza or m_args.height
	m_args.align = args.allinea or m_args.align
	
	-- elimina cornice per cornice = 0 e per sinottico = 1
	if cornice ~= 1 or sinottico == 1 then m_args.frameless = 1 end

	-- valore ingrandimento
	if args.zoom then
		m_args.zoom = tonumber(args.zoom)
	elseif geo == -1 then
		if #points == 1 then
			m_args.zoom = 10 -- valore predefinito per singolo punto
			if sinottico == 1 then
				m_args.zoom = 13 -- valore predefinito per sinottico con singolo punto
			end
		elseif autozoom == 's' then -- calcola zoom automaticamente
			local dx = 1.1 * (lon2 - lon1) / 360
			local dy = 1.1 * (math.log(math.tan(math.pi * (1 + lat2 / 90) / 4)) - math.log(math.tan( math.pi * (1 + lat1 / 90) / 4))) / (2 * math.pi)

			local scalax, scalay
			if dx == 0 then scalax = 18 else scalax = math.floor(-math.log(dx) / math.log(2)) end
			if dy == 0 then scalay = 18 else scalay = math.floor(-math.log(dy) / math.log(2)) end
			if dx == 0 and dy == 0 then
				m_args.zoom = 10 -- valore default per singolo punto
	   		else
   				m_args.zoom = math.max(0, math.min(18, scalax, scalay))
			end
		end
	end
	-- esclude didascalia per cornice = 0 e per sinottico = 1
	if cornice == 1 and sinottico ~= 1 then
		m_args.text = mw.getCurrentFrame():preprocess(table.concat(elenco_dati))
	end

	if args.centro_lon then
		m_args.longitude = tonumber(args.centro_lon)
	elseif geo == -1 then
		if #points == 1 then
			m_args.longitude = points[1].lon
		else
			m_args.longitude = (lon1 + lon2) / 2
		end
	end

	if args.centro_lat then
		m_args.latitude = tonumber(args.centro_lat)
	elseif geo == -1 then
		if #points == 1 then
			m_args.latitude = points[1].lat
		else
			local l1 = 1 - math.log(math.tan(math.pi * (1 + lat1 / 90) / 4)) / math.pi
			local l2 = 1 - math.log(math.tan(math.pi * (1 + lat2 / 90) / 4)) / math.pi
			local centroy = (l1 + l2) / 2
			centroy = (math.atan(math.exp(math.pi * (1 - centroy))) - math.pi / 4) * 360 / math.pi
			m_args.latitude = centroy
		end
	end

	local m_dati
	if geo == -1 then
		m_dati = { type = 'FeatureCollection', features = {} }
		for i = 1, #points do
			m_dati.features[i] = {
				type = 'Feature',
				properties = {},
				geometry = {
					type = 'Point',
					coordinates = { points[i].lon, points[i].lat }
				}
			}
			m_dati.features[i].properties['marker-symbol'] = points[i].simb
			m_dati.features[i].properties['marker-color'] = points[i].col
			m_dati.features[i].properties['marker-size'] = points[i].dsimb
			m_dati.features[i].properties.title = points[i].nome
			m_dati.features[i].properties.description = points[i].desc
		end
		m_dati = mw.text.jsonEncode(m_dati)
	else
		m_dati = mw.getCurrentFrame():preprocess(geo)
	end

	if args.debug then
		local mdebug = mw.text.jsonEncode(m_args)
		--return string.format('<pre>%s\n\n%s</pre>', mdebug, m_dati)
		return table.concat(elenco_dati)
	else
		return mw.getCurrentFrame():extensionTag('mapframe', m_dati, m_args)
	end
end

-- Funzione per il template {{Mappa OSM}}.
function p.mappa(frame)
	return select(2, xpcall(function()
		return p._mappa(getArgs(frame, { parentOnly = true }))
	end, errhandler))
end

return p