require('Module:No globals')
local getArgs = require('Module:Arguments').getArgs
local errorCategory = '[[Categoria:Errori di compilazione del template Discendenza]]'

local p = {}
local pers = {}
local tabella = {}
local txt = {}

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)
	table.insert(txt,t)
end

local function dividi(dati)
	local n = 1
	local resto = 0
	local nx,px
	-- conta elementi
	while (dati[n]) do n = n+1 end
	n = n-1
	-- crea pers dai dati
	for m=4,n,4 do
		nx = tonumber(dati[m-3])
		px = tonumber(dati[m-2])
		if (nx) then
			if (px) then
				if (pers[nx]) then
					error(string.format('Inserito id = %d per più elementi',nx))
				else
					pers[nx] = { padre = px, testo = dati[m-1], nota = dati[m], id = -1, x = -1, y = -1, sp = 0, figli = {} }
				end
			else
				error(string.format('Inserito id genitore = %s non numerico (id = %d)',dati[m-2],nx))
			end
		else
			error(string.format('Inserito id = %s non numerico',dati[m-3]))
		end
		resto = n-m
	end
	if (resto > 0) then
		error(string.format('Numero di dati %d non valido (elementi in più: %d)',n,resto))
	end
end

local function organizza(pid, y)
	local nn = 1
	pers[pid].y = y
	if (not tabella[y]) then tabella[y] = {} end
	table.insert(tabella[y], pid)
	for i, v in pairs(pers[pid].figli) do
		pers[v].id = i
		nn = nn + organizza(v, y+1)
	end
	return nn
end

local function limSx(pid, delta, dt)
	if (dt[pers[pid].y]) then
		dt[pers[pid].y] = math.min(dt[pers[pid].y], pers[pid].x+delta)
	else
		dt[pers[pid].y] = pers[pid].x + delta
	end
	for _, v in pairs(pers[pid].figli) do
		dt = limSx(v, delta+pers[pid].sp, dt)
	end
	return dt
end

local function limDx(pid, delta, dt)
	if (dt[pers[pid].y]) then
		dt[pers[pid].y] = math.max(dt[pers[pid].y], pers[pid].x+delta)
	else
		dt[pers[pid].y] = pers[pid].x + delta
	end
	for _, v in pairs(pers[pid].figli) do
		dt = limDx(v, delta+pers[pid].sp, dt)
	end
	return dt
end

local function riallinea(pid2, n1, n2)
	local distanza = n2 - n1
	local vrf = 0
	local pos, inizio, passo
	if (distanza > 1) then
		inizio = pers[pers[pid2].figli[n1]].x
		passo = (pers[pers[pid2].figli[n2]].x - inizio)/distanza
		for cc=1,(distanza-1) do
			pos = inizio + math.floor(cc*passo)
			if (pos - pers[pers[pid2].figli[n1+cc]].x > 0) then
				pers[pers[pid2].figli[n1+cc]].x = pos
				pers[pers[pid2].figli[n1+cc]].sp = pos
			end
		end
		vrf = 1
	end
	return vrf
end

local function verifica(pid)
	local tSx
	local tDx
	local sposta = 0

	local fine = pers[pid].id
	local frt2, n
	
	for frt=1,(fine-1) do
		frt2 = pers[pers[pid].padre].figli[frt]
		tDx = limDx(frt2, 0, {})
		tSx = limSx(pid, 0, {})
		n = pers[pid].y
		while ((tSx[n]) and (tDx[n])) do
			if (tSx[n] - tDx[n] + sposta < 2) then
				sposta = 2 + tDx[n] - tSx[n]
			end
			n = n + 1
		end
		if  (sposta > 0) then
			pers[pid].x = pers[pid].x + sposta
			pers[pid].sp = pers[pid].sp + sposta
			if (riallinea(pers[pid].padre, frt, fine) == 1) then verifica(pid) end
			sposta = 0
		end
	end
end

local function calcolaX1(pid)
	for _, v in pairs(pers[pid].figli) do
		calcolaX1(v)
	end
	local tt = #pers[pid].figli
	if (tt == 0) then
		if (pers[pid].padre == -1 or pers[pid].id == 1) then
			pers[pid].x = 0
		else
			pers[pid].x = pers[pers[pers[pid].padre].figli[pers[pid].id - 1]].x + 2
		end
	elseif (tt == 1) then
		if (pers[pid].padre == -1 or pers[pid].id == 1) then
			pers[pid].x = pers[pers[pid].figli[1]].x
		else
			pers[pid].x = pers[pers[pers[pid].padre].figli[pers[pid].id - 1]].x + 2
			pers[pid].sp = pers[pid].x - pers[pers[pid].figli[1]].x
			verifica(pid)
		end
	else
		local media = math.floor((pers[pers[pid].figli[1]].x + pers[pers[pid].figli[tt]].x)/2)
		if (pers[pid].padre == -1 or pers[pid].id == 1) then
			pers[pid].x = media
		else
			pers[pid].x = pers[pers[pers[pid].padre].figli[pers[pid].id - 1]].x + 2
			pers[pid].sp = pers[pid].x - media
			verifica(pid)
		end
	end
end

local function calcolaX2(pid)
	local sposta = 0
	local tt = limSx(pid, 0, {})
	for _, v in pairs(tt) do
		if (v+sposta<0) then
			sposta = -v
		end
	end
	if (sposta > 0) then
		pers[pid].x = pers[pid].x + sposta
		pers[pid].sp = pers[pid].sp + sposta
	end
end

local function calcolaX3(pid, sposta)
	pers[pid].x = pers[pid].x + sposta
	for _, v in pairs(pers[pid].figli) do
		calcolaX3(v, sposta + pers[pid].sp)
	end
end

local function massimoXY(pid, t)
	if (pers[pid].x > t[1]) then t[1] = pers[pid].x end
	if (pers[pid].y > t[2]) then t[2] = pers[pid].y end
	for _, v in pairs(pers[pid].figli) do
		t = massimoXY(v,t)
	end
	return t
end

local function mostraX(pid,allinea,largo,dida)
	local xy = massimoXY(pid, {0, 0})
	local posx = {}
	local n1
	local stx
	local riga = {}
	local xx, xp
	if (allinea == 'destra') then
		agg(string.format('<div class="floatright" style="width:%dpx;padding:3px;background:#fff;border:1px solid #c8ccd1">', largo))
		agg('<table cellpadding="1" cellspacing="0" border=0 style="border-collapse:separate;text-align:center;font-size:95%;line-height:105%;margin:0px auto">')
	else
		agg('<table cellpadding="1" cellspacing="0" border=0 style="border-collapse:separate;text-align:center;font-size:95%;line-height:105%;margin:10px auto">')
	end
	local lg = math.floor(100/(xy[1]+2))
	if (lg == 0) then lg = 1 end
	for n=1,xy[2] do
		riga[1] = {}; riga[2] = {}; riga[3] = {}
		posx[1] = 0; posx[2] = 0; posx[3] = 0
		n1 = 0
		table.insert(riga[1],string.format('<tr%s>',n>1 and ' style="line-height:8px"' or ''))
		table.insert(riga[2],'<tr>')
		table.insert(riga[3],string.format('<tr%s>',n<xy[2] and ' style="line-height:8px"' or ''))
		for _, v in pairs(tabella[n]) do
			xx = pers[v].x
			xp = pers[v].padre
			if (n==1) then
				for m=1,(xy[1]+2) do table.insert(riga[1],string.format('<td width=%d%%></td>',lg)) end
			else
				stx = string.format('<td style="border-right:1px solid #000%s%s">&nbsp;</td>',
					n1==xp and ';border-top:1px solid #000' or '',
					xx-posx[1]>0 and '" colspan="'..(xx+1-posx[1]) or ''
				)
				n1 = xp
				posx[1] = xx + 1
				table.insert(riga[1],stx)
			end

			if (xx-posx[2]>0) then
				table.insert(riga[2],string.format('<td%s>&nbsp;</td>',
					xx-posx[2]>1 and ' colspan="'..(xx-posx[2])..'"' or ''))
			end
			table.insert(riga[2],'<td colspan=2>'..pers[v].testo)
			if not (pers[v].nota == '-') then
				table.insert(riga[2],string.format('<br/><span style="font-size:90%%"><i>%s</i></span>',pers[v].nota))
			end
			table.insert(riga[2],'</td>')
			posx[2] = xx + 2

			if (n<xy[2]) then
				if (#pers[v].figli > 0) then
					table.insert(riga[3],string.format('<td style="border-right: 1px solid #000%s">&nbsp;</td>',
						xx-posx[3]>0 and '" colspan="'..(xx+1-posx[3]) or '')
					)
					posx[3] = xx + 1
				end
			end
		end

		table.insert(riga[1],'</tr>')
		table.insert(riga[2],'</tr>')
		if (n<xy[2]) then table.insert(riga[3],'</tr>') end

		agg(table.concat(riga[1]))
		agg(table.concat(riga[2]))
		agg(table.concat(riga[3]))
	end

	agg('</table>')
	if (allinea == 'destra') then
		if not (dida=='') then agg('<p style="font-size:87%;font-style:normal;border-top:1px solid #c8ccd1;margin:8px 2px 3px">'..dida..'</p>') end
		agg('</div>')
	end

	return table.concat(txt)
end

local function calcolaY(pid, t)
	if (pers[pid].y > t) then t = pers[pid].y end
	for _, v in pairs(pers[pid].figli) do
		t = calcolaY(v,t)
		pers[pid].sp = pers[pid].sp + 1 + pers[v].sp
	end
	return t
end

local function mostraY2(pid, a)
	if (pers[pid].id > 1) then agg('<tr>') end
	if (pers[pid].padre > -1) then
		agg('<td style="width:6px"></td><td style="border-left:1px solid #666;border-bottom:1px solid #666;width:10px;line-height:3px;height:12px">&nbsp;</td>')
		agg(string.format('<td colspan=%d rowspan=2 style="padding: 0px 3px 2px 1px">%s%s</td></tr>',
			2*a-1,
			pers[pid].testo),
			(pers[pid].nota=='') and '' or (' - '..pers[pid].nota)
		)
		agg('<tr><td></td><td style="line-height:8px;line-height:3px;height:12px%s">&nbsp;</td></tr>',
			pers[pid].id < #pers[pers[pid].padre].figli and ';border-left:1px solid #666' or ''
		)
		if (pers[pid].sp > 0) then
			agg(string.format('<tr><td rowspan=%d></td><td rowspan=%d%s>&nbsp;</td>',
				2*pers[pid].sp,
				2*pers[pid].sp,
				pers[pid].id < #pers[pers[pid].padre].figli and ' style="border-left:1px solid #666"' or ''
			))
			agg('')
		end
	else
		agg(string.format('<td colspan=%d style="padding: 0px 0px 2px 2px">%s%s</td></tr><tr>',
			2*a-1,
			pers[pid].testo,
			pers[pid].nota=='' and '' or ' - '..pers[pid].nota
		))
	end
	if (pers[pid].sp > 0) then
		for _, v in pairs(pers[pid].figli) do
			mostraY2(v,a-1)
		end
	end
end

local function mostraY(pid)
	agg('<table cellpadding="0" cellspacing="0" border=0 style="text-align:left;margin:10px 0 10px 16px"><tr>')
	mostraY2(pid,calcolaY(pid,0))
	agg('</tr></table>')
	return table.concat(txt)
end

function p._discendenza(args)
	local capo = -1
	local n1, n2
	local lato = args['allinea'] or 'centro'
	local larg = args['larghezza'] or '300'
	local tipo = args['tipo'] or 'o'
	local dida = args['didascalia'] or ''
	dividi(args)
	n1 = 0
	for i, v in pairs(pers) do
		n1 = n1+1
		if (v.padre == -1) then
			if (capo == -1) then
				capo = i
			else
				error(string.format('Inseriti più elementi come capostipite (id = %d, %d)',capo,i))
			end
		else
			if (v.padre == i) then
				error(string.format('Inserito elemento come figlio di se stesso (id = %d)', i))
			elseif (pers[v.padre]) then
				table.insert(pers[v.padre].figli,i)
			else
				error(string.format('Inserito id genitore = %d non valido (id = %d)',v.padre,i))
			end
		end
	end

	if (capo == -1) then
		error('Capostipite non definito')
	else
		n2 = organizza(capo, 1)
		if (n1 == n2) then
			if (tipo == 'v') then
				return mostraY(capo)
			elseif (tipo == 'o') then
				calcolaX1(capo)
				calcolaX2(capo)
				calcolaX3(capo, 0)
				return mostraX(capo, lato, larg, dida)
			end
		else
			error('Inseriti elementi non collegati al capostipite')
		end
	end
end

function p.main(frame)
	local args = getArgs(frame, {
		valueFunc = function (key, value)
			if type(key) == "number" then
				if value == nil then
					return nil
				else
					value = mw.text.trim(value)
				end
			else
				if value == '' then return nil end
            end
			return value
		end
	})
	return p._discendenza(args)
end

return p