Module:Mayor

local loader = require('Module:Loader') local getArgs = require('Module:Arguments').getArgs

local string, table, yesno, sbte = loader.lazy.require('String', 'Table', 'Yesno', 'SBTE') local mayorData, eDT, autoEDT = loader.loadData('Mayor/Data', 'Mayor/Elections', 'Mayor/Elections/Automated')

local p = {}

local curTitle = mw.title.getCurrentTitle local crown = 'https://static.wikia.nocookie.net/mcchampionship/images/3/39/WinnerCrown.png/revision/latest/scale-to-width-down/18?cb=20210922151946' local arrowSymbol = '➤' local pointSymbol = '➜' local infosymbol = 'ⓘ' local currYear = 1 + math.floor((os.time(os.date("!*t")) - 1560275700) / 446400)

local function electionsData(key) if autoEDT.elections[key] and autoEDT.elections[key].status == 'in-progress' then return eDT.elections[key] or autoEDT.elections[key] end return autoEDT.elections[key] or eDT.elections[key] end

local evalFuncs = { foxy_events = function (mode, value, default) if (value or ''):match('[Ff]ishing') then return mode == 'tooltip' and '&bFishing/&bFestival' or 'Event << Fishing Festival' elseif (value or ''):match('[Nn]ational') then return mode == 'tooltip' and '&6National/&6Mining Month' or 'Event << National Mining Month' elseif (value or ''):match('[Mm]ining') then return mode == 'tooltip' and '&6Mining/&6Fiesta' or 'Event << Mining Fiesta' elseif (value or ''):match('[Ss]pooky') then return mode == 'tooltip' and '&6Spooky/&6Festival' or 'Event << Spooky Festival' end return value or default end, }

local function getClosestYear for i = currYear, 1, -1 do		local item = electionsData(i) if item then local c = item.status or '' if not (c:match('in%-progress') or c:match('collapsedown')) then return i			end end end end

local function noteParams(params, paramsdata) -- this function returns a table of "note" strings local i, ret, params = 0, {}, params or {} while ((paramsdata or {})[i]) do			local eval, text, note = paramsdata[i].eval, paramsdata[i].note_text, paramsdata[i].note_default local note = params[i] or default if params[i] and (eval and evalFuncs[eval]) then note = evalFuncs[eval]('note', params[i], default) or note elseif params[i] and text then note = text:gsub(('{%s}'):format(i), note) end if note then table.push(ret, note) end i = i + 1 end return ret end

local function tooltipParams(desc, params, paramsdata) -- this function returns a replaced string local i, params = 0, params or {} while ((paramsdata or {})[i]) do		local eval, text, default = paramsdata[i].eval, paramsdata[i].tooltip_text, paramsdata[i].tooltip_default local rpl = params[i] or default or '' if params[i] and (eval and evalFuncs[eval]) then rpl = evalFuncs[eval]('tooltip', params[i], default) or rpl end desc = desc:gsub(('{%s}'):format(i), rpl) i = i + 1 end return desc end

function p.getMayorLink(mayor) return (''):format(mayorData.mayors[mayor] and mayorData.mayors[mayor].link or mayor, mayor) end

-- For Module:Infobox/Mechanics function p._mayorPerksTable(mayor) local dt = table.deepCopy(mayorData.mayors[mayor], true) dt = dt.perks_listed or dt.perks for _, v in ipairs(dt) do		v[1] = mayorData.linkedPerkNames[v[1]] or v[1] end return dt end

-- For Template:Current Mayor function p.getCurrentMayor(frame) local args = getArgs(frame) local year = getClosestYear local ret if args[1] == 'e' then return ' ' .. arrowSymbol .. ' View Current Mayor/Election ' else local stats = p._getElectionStats(year) local m = stats[1].name if args[1] == 'r' then ret = m		elseif args[1] == 'l' then ret = mayorData.mayors[m] and mayorData.mayors[m].link or m		elseif args[1] == 'p' then ret = ('\'\'\'Perks: \'\'\' %s'):format(p.generatePerks(nil, year)) else local d = mayorData.mayors[m] ret = ('\'\'\'Mayor %s\'\'\''):format(d and d.link or m, m)		end end return mw.getCurrentFrame:preprocess(ret) end

function p.specialNextParticipate( frame ) local args = getArgs(frame) return p._specialNextParticipate(args[1]) end

function p._specialNextParticipate(name) local mayordt = mayorData.mayors[name] local nSpecial = table.length(table.filterNamed(mayorData.mayors, function (k, v)		return v.type == 'special' end)) local currYear = 1 + math.floor((os.time(os.date("!*t")) - 1560275700) / 446400) local k = math.floor(currYear / 8) local increment = ((mayordt.special_order + nSpecial) - (k % nSpecial)) % nSpecial local next_ = (k + ((increment == 0 and currYear % 8 > 0) and 3 or increment)) * 8 return ('%s %s %s'):format(next_ - 8 * nSpecial, pointSymbol, next_) end

function p.specialElectionCountdown( frame ) return mw.getCurrentFrame:preprocess(p._specialElectionCountdown) end function p._specialElectionCountdown local currDate = sbte.SkyDate({}) if (currDate.sbstFullYear % 8 == 1 and currDate.sbstMonth > 2) or		(currDate.sbstFullYear % 8 == 2 and currDate.sbstMonth < 2) or		(currDate.sbstFullYear % 8 == 2 and currDate.sbstMonth == 2 and currDate.sbstDate < 27) or		(currDate.sbstFullYear % 8 == 1 and currDate.sbstMonth == 2 and currDate.sbstDate >= 27) then local specialOrder = math.floor(currDate.sbstFullYear / 8) % 3 if specialOrder == 0 then return 'The special mayor is running office!' elseif specialOrder == 1 then return 'The special mayor is running office!' else return ' is Mayor! See Jerry\'s Perkpocalypse Perks.' end end local specialMayors = table.deepCopy(table.filterNamed(mayorData.mayors, function (k, v)		return v.type == 'special' end), true) local nSpecial = table.length(specialMayors) local nextSpecialYear = math.ceil(currDate.sbstFullYear / 8) * 8 local targetOrderNum = nextSpecialYear % 3 local targetMayor for name, mayordt in pairs(specialMayors) do		if mayordt.special_order == targetOrderNum then targetMayor = name break end end local nextDate = sbte.SkyDate({		locale = sbte.helpers.LOCALES.sbst,		year = nextSpecialYear, month = 5, day = 27,		hour = 0, minute = 0, second = 0	}) return (' may participate in Year %s Mayor Election. .'):format(targetMayor, mayorData.mayors[targetMayor].link or targetMayor, nextSpecialYear, nextDate.date.year, nextDate.date.month, nextDate.date.day, nextDate.date.hour, nextDate.date.min) end

-- For Template:MayorPerks function p.mayorPerks(frame) local args = getArgs(frame) local name = args[1] or curTitle.text local mayordt = mayorData.mayors[name] local headtext = args.headText or args.headtext headtext = headtext == 'none' and '' or headtext if mayordt.type == 'special' then headtext = headtext or ('Candidate %s is a special candidate, and will run the election according to the Special Candidates Election Cycle. This candidate is \'\'speculated to\'\' run for mayor next time in the election of with all of the  listed below. '):format(name, p._specialNextParticipate(name), table.length(mayordt.perks)) else headtext = headtext or ('Candidate %s may run for election with any of the listed below. This candidate can offer  up to . When this candidate is elected, the next time they join an election, they will run with . If they lost, the next time they join an election, unless they already offer all possible perks, they will have  to gain . '):format(name, table.length(mayordt.perks)) end local txt = table.concat { headtext, args[2] or '', }	return mw.getCurrentFrame:preprocess(p._mayorPerks(name, txt)) end

function p._mayorPerks(mayor, prependText) local function insertperks(t, perksTb, frontdent) for _, info in ipairs(perksTb) do			table.push(t, ('%s %s : %s'):format( frontdent, mayorData.linkedPerkNames[info[1]] or info[1], type(info[2]) == 'table' and '' or info[2] ))			if type(info[2]) == 'table' then for _, item in ipairs(info[2]) do					table.push(t, ('%s* %s'):format(frontdent, item)) end end end end local dt = mayorData.mayors[mayor] local lines_ = {} local frontdent = dt.perks_listed and '**#' or '*#' if dt.perks_listed then table.push(lines_, '**\'\'\'Listed Perks\'\'\':') insertperks(lines_, dt.perks_listed, frontdent) table.push(lines_, ('**\'\'\'Actual Perks\'\'\': \'\'(hidden until %s is elected)\'\''):format(mayor)) end insertperks(lines_, dt.perks, frontdent) return table.concat({		prependText or '',		('*\'\'\'%s\'\'\' %s'):format( p.getMayorLink(mayor), dt.aka or '' ),		unpack(lines_)	}, '\n') end

-- For Template:MayorList function p.mayorList(frame) local args = getArgs(frame) return mw.getCurrentFrame:preprocess(p._mayorList(args[1])) end

function p._mayorList(_type) local mayors = table.deepCopy(mayorData.mayors, true) if _type then mayors = table.filterNamed(mayors, function(k, v)			return v.type == string.lower(_type)		end) end local paragraphs = {} for k, v in table.sortedPairs(mayors) do		table.push(paragraphs, p._mayorPerks(k)) end return table.concat(paragraphs, '\n') end

-- For Template:ElectionTable function p.electionTable(frame) return mw.getCurrentFrame:preprocess(p._electionTable) end

function p.generatePerks(mayor, year) year = year or getClosestYear local function each(t, i, title, text, params, paramsdata) text = type(text) == 'table' and table.map(table.deepCopy(text, true), function(d)			return '— ' .. d		end) or { text } table.push(t, string.wrapHtml{			i,			' ', {				class = 'minetip gemstone-slot',				['data-minetip-title'] = '&d' .. title,				['data-minetip-text'] = table.concat(table.map(text, function(tx)					tx = tooltipParams(tx, params, paramsdata)					return string.gsubAll(tx, '{', '&#123;', '}', '&#125;', '.*', '')				end), '/'),			}		}) end local stats, perks = p._getElectionStats(year), nil for _, v in ipairs(stats) do		if v.name == mayor then perks = v.perks end end mayor = mayor or stats[1].name perks = perks or stats[1].perks if not perks then error('No perks information for Mayor ' .. mayor .. ' in year ' .. year .. ' election') end local allperks, paramsdata, t = mayorData.mayors[mayor].perks, mayorData.mayors[mayor].paramsdata, {} if perks:match('%*') then for i = 1, table.xlength(allperks, false) do			if not allperks[i] then error(('Perk %s of "%s" not found.'):format(i, mayor)) end if not allperks[i] then break end each(t, i, allperks[i][1], allperks[i][3] ~= '' and allperks[i][3] or allperks[i][2], stats[1].params, paramsdata) end else for _, v in ipairs(string.split(perks, '')) do			v = tonumber(v) each(t, v, allperks[v][1], allperks[v][3] ~= '' and allperks[v][3] or allperks[v][2], stats[1].params, paramsdata) end end return table.concat(t, ' ') end

-- Helper function -- Returns ordered election stats with processed information -- Returns empty table if no relevant information function p._getElectionStats(year) local election = electionsData(tonumber(year)) local ret = {} if election then local c = election.status or '' if election and not c:match('novote') and not c:match('collapsedown') then if election.result or election.data then local tot = 0 local result = election.result or table.mapNamed(election.data, function(k, v)					return v.votes				end) for _, v in pairs(result) do					tot = tot + v				end for k, v in table.sortedPairsByValue(result, function(a, b)					return a > b				end) do					table.push(ret, table.merge({}, (election.data and election.data[k] or {}), { name = k,						votes = v,						percent = v / tot * 100, }))				end ret.total_votes = tot end if election.result or election.mayor then ret[1] = ret[1] or {} ret[1].name = ret[1].name or election.mayor ret[1].votes = ret[1].votes or election.votes ret[1].perks = ret[1].perks or election.perks ret[1].percent = ret[1].percent or election.percent ret[1].params = ret[1].params or election.params end end if c:match('novote') and election.mayor then ret[1] = { name = election.mayor, votes = election.votes, }		end end return ret end

function p._electionTable local rows = {} local _date, _year = {}, {} for i = currYear, 1, -1 do		local item = electionsData(i) if item then local c = item.status or '' if not c:match('in%-progress') then table.push(_date, item.date) table.push(_year, i)				if not c:match('collapsedown') then local y2, y1 = _year[1], _year[#_year] local y = y2 == y1 and y2 or ('%s-%s'):format(y1, y2) local stats = p._getElectionStats(i) local _mayor, _votes, _perks, _other if #stats > 0 then local mayorName = stats[1].name if not mayorName then error(i) end _mayor = p.getMayorLink(mayorName) _votes = stats[1].percent and ('%s%s'):format(							(''):format(stats[1].percent),							stats[1].votes and (' '):format(stats[1].votes)						) or nil _perks = stats[1].perks or item.perks if _perks then if _perks:match('%?') then _perks = '' elseif mayorName then _perks = p.generatePerks(mayorName, i)							else _perks = 'Perk ' .. table.concat(string.split(_perks, ''), ', ') end -- Handle Params local caption = noteParams(stats[1].params, mayorData.mayors[mayorName].paramsdata) -- Handle Extra Notes if item.note then table.push(caption, item.note) end _perks = table.length(caption) > 0 and ('%s %s '):format(_perks, table.concat(caption, ' ')) or _perks end _other = {} for j = 2, #stats do							table.push(_other, (' %s: %s %s '):format( p.getMayorLink(stats[j].name), (''):format(stats[j].percent), (''):format(stats[j].votes) ))						end _other = #_other > 0 and table.concat(_other, ' ') or nil end table.push(rows, {						date = table.concat(_date, ' '),						year = y,						electedY = tonumber(y) and (y + 1),						mayor = _mayor,						votes = _votes,						perks = _perks,						other = _other,						status = c,					}) _date, _year = {}, {} end end end end local wikitable = mw.html.create('table'):addClass('wikitable full-width') wikitable:tag('tr'):tag('th'):wikitext('Election Year'):done :tag('th'):wikitext('Term Began on'):done :tag('th'):wikitext('Mayor'):done :tag('th'):wikitext('Votes'):done :tag('th'):wikitext('Perks'):done :tag('th'):wikitext('Other Candidates'):done local function g(val, status) return val or (status:match('novote') and  or ) end for _, v in ipairs(rows) do		wikitable:tag('tr'):tag('th'):wikitext(v.year):done :tag('td'):wikitext((v.date or '??') .. (v.electedY and ' Year ' .. v.electedY or '')):done :tag('td'):wikitext(g(v.mayor, v.status)):done :tag('td'):wikitext(g(v.votes, v.status)):done :tag('td'):wikitext(g(v.perks, v.status)):done :tag('td'):wikitext(g(v.other, v.status)):done end return tostring(wikitable) end

function p.mayorTooltip(name, mode, opt) opt = opt or {} local nums, col, ago, params = opt.nums, opt.col or 'd', opt.ago, opt.params or {} local m = mayorData.mayors[name] mode = ('%s %s'):format(m.type, mode or '') local info = {} local perksref = table.deepCopy(mode:match('candidate') and m.perks_listed or m.perks, true) for _, perk in ipairs(nums and table.map(string.split(tostring(nums), ''), function(i) return perksref[tonumber(i)] end) or perksref) do		local desc = perk[3] ~= '' and perk[3] or perk[2] desc = type(desc) == 'table' and table.map(desc, function(d)			return '— ' .. d		end) or { desc } desc = table.concat(desc, '/') -- Handle Params desc = tooltipParams(desc, params, m.paramsdata) desc = string.gsubAll(desc, '{', '&#123;', '}', '&#125;', '%|', '&#124;', '.*', '') table.push(info, ('&%s%s/&r%s'):format( col, perk[1], desc ))	end if mode:match('special') and mode:match('candidate') then table.push(info, '&r&' .. col .. 'This is a SPECIAL candidate!/&7It rarely appears!') end if mode:match('candidate') and ago then table.push(info, '&r&7Last elected: &' .. col .. ago .. 'y ago') end return { name, col, m.link or name, table.concat(info, '//'), title = perksref.title, text = perksref.text } end

local function templateTemplate(id, mode) local ret = {} for name, dt in pairs(mayorData.mayors) do		table.push(ret, p.mayorTooltip(name, mode)) end ret.id = id	return ret end

function p.getMayorsTemplate return templateTemplate('Mayor {o}', 'mayor') end

function p.getMayorCandidatesTemplate return templateTemplate(nil, 'candidate') end

-- For Template:MayorWins function p.getWins( frame ) local args = getArgs(frame) local mayor = args[1] if mayor then local stats = p._getWins if not mayorData.mayors[mayor] then return nil end return stats[mayor].wins end end

function p._getWins function newPerkChance(list, maxperks, validElections) local gainedNewPerk, totalChancesToGainPerks, started = 0, 0, true local last for i = 1, currYear do if not (electionsData(i) or list[i] or validElections[i]) then -- we *don't know* the state of that mayor in that election (participated? perks?) last = nil -- break the count so that it does not include the first participation after unknown things have happened elseif list[i] then -- candidate participated local curr = list[i] -- counting if last and last.n < maxperks then local diff = curr.n - last.n					if diff > 0 then -- note: a mayor can gain more than 1 perk, since they can gain perk even if not joined election gainedNewPerk = gainedNewPerk + 1 end if not last.elected then totalChancesToGainPerks = totalChancesToGainPerks + 1 end end if curr.elected then -- mayor is elected last = nil -- break the count so that it does not include the first participation after elected (100% of 1 perk is useless for statistics) else last = curr end else -- candidate not participated, do nothing end end return gainedNewPerk, totalChancesToGainPerks end function nPerks(s, maxperks) return s:match('*') and maxperks or #s end local storage, percentages, ret, validElections = {}, {}, {}, {} for _, v in ipairs(table.keys(mayorData.mayors)) do		storage[v] = { tot_wins = 0, tot_perc = 0, perc_counted = 0, tot_votes = 0, perks_counted = 0, tot_perks = 0, votes_counted = 0, perk_numbers = {}, }	end for i = currYear, 1, -1 do		local item, isValid = electionsData(i), false if item then local stats = p._getElectionStats(i) if stats[1] then local m = stats[1].name if storage[m] then storage[m].tot_wins = storage[m].tot_wins + 1 if stats[1].percent then storage[m].tot_perc = storage[m].tot_perc + (stats[1].percent or 0) storage[m].perc_counted = storage[m].perc_counted + 1 if stats[1].percent > (storage[m].top_perc or 0) then storage[m].top_perc = stats[1].percent storage[m].top_perc_year = i						end end if stats[1].votes then storage[m].tot_votes = storage[m].tot_votes + (stats[1].votes or 0) storage[m].votes_counted = storage[m].votes_counted + 1 if stats[1].votes > (storage[m].top_votes or 0) then storage[m].top_votes = stats[1].votes storage[m].top_votes_year = i						end end isValid = true if stats[1].perks then storage[m].tot_perks = storage[m].tot_perks + #((stats[1].perks):gsub('%D', '')) storage[m].perks_counted = storage[m].perks_counted + 1 storage[m].perk_numbers[i] = { n = nPerks(stats[1].perks), elected = true } else isValid = false end for k = 2, 5 do						if stats[k] and stats[k].name and stats[k].perks then storage[stats[k].name].perk_numbers[i] = { n = nPerks(stats[k].perks), elected = false } else isValid = false end end end end validElections[i] = isValid end end for mayor, stored in pairs(storage) do		local perk_gains, possible_perk_gains = newPerkChance(stored.perk_numbers, table.length(mayorData.mayors[mayor].perks_listed or mayorData.mayors[mayor].perks), validElections) ret[mayor] = { wins = stored.tot_wins, avg_perc = stored.perc_counted > 0 and (stored.tot_perc / stored.perc_counted) or 0, top_perc = stored.top_perc, top_perc_year = stored.top_perc_year, avg_votes = stored.votes_counted > 0 and (stored.tot_votes / stored.votes_counted) or 0, top_votes = stored.top_votes, top_votes_year = stored.top_votes_year, avg_perks = stored.perks_counted > 0 and (stored.tot_perks / stored.perks_counted) or 0, perk_gains = perk_gains, possible_perk_gains = possible_perk_gains, recorded_appearances = table.length(stored.perk_numbers), new_perk_chance = possible_perk_gains > 0 and (perk_gains / possible_perk_gains) or nil, -- perk_numbers = stored.perk_numbers, -- for debugging }	end return ret end

function p.statistics( frame ) return mw.getCurrentFrame:preprocess(table.concat(p._statistics)) end

function p._statistics local function regCheck (v) return mayorData.mayors[v.name].type == 'regular' or mayorData.mayors[v.name].type == 'retired_regular' end local alltables = {} local stats, analysis = p._getWins, p._analyse local tvote, tperc, tdvote, tdperc = analysis.topvotes, analysis.toppercent, analysis.diffvotes, analysis.diffpercent local tvotereg, tpercreg = table.filter(analysis.topvotes, regCheck), table.filter(analysis.toppercent, regCheck) local lvote, lperc, ldiff = table.length(tvote), table.length(tperc), table.length(tdvote) local legendlink =  .. infosymbol ..  -- Table 1 local wikitable = mw.html.create('table'):addClass('article-table') wikitable:tag('tr'):tag('th'):attr('colspan', 10):wikitext(crown .. ' Mayor Election Stats') wikitable:tag('tr'):tag('th'):attr('rowspan', 2):wikitext('Candidate'):done :tag('th'):addClass('lefttxt'):attr('rowspan', 2):wikitext('Info TE: Times Elected NP%: New Perk Chance ' .. legendlink .. ' '):done :tag('th'):attr('colspan', 3):wikitext('Statistics On Elected'):done :tag('tr'):tag('th'):wikitext(string.makeTitle('Avg. Perks', 'Average amount of perks that a mayor has when they are elected.')):done :tag('th'):wikitext(string.makeTitle('Vote Percentage', 'Vote percentage statistics for a mayor when they are elected.')):done :tag('th'):wikitext(string.makeTitle('Vote Count', 'Vote count statistics for a mayor when they are elected.')):done for k, v in table.sortedPairsByValue(stats, function(a, b)		return a.wins > b.wins	end) do		wikitable:tag('tr'):tag('td'):wikitext(p.getMayorLink(k)):done :tag('td'):wikitext(('TE: %s NP%%: %s'):format( v.wins, v.new_perk_chance and ('%.2f%% '):format(v.perk_gains, v.possible_perk_gains, v.new_perk_chance * 100) or 'N/A' )):done :tag('td'):wikitext(v.avg_perks > 0 and ('%.2f'):format(v.avg_perks) or 'N/A'):done :tag('td'):wikitext((' Average: Top \'\'(%s %s)\'\': ')				:format(v.avg_perc, 'Election Year', v.top_perc_year or 'none', v.top_perc or 0)):done :tag('td'):wikitext((' Average: Top \'\'(%s %s)\'\': ')				:format(v.avg_votes, 'Election Year', v.top_votes_year or 'none', v.top_votes or 0)):done end table.push(alltables, tostring(wikitable)) -- Table 2 wikitable = mw.html.create('table'):addClass('article-table') wikitable:tag('tr'):tag('th'):attr('colspan', 6):wikitext('Most Ever Votes ' .. legendlink):done :tag('th'):attr('rowspan', 2):attr('colspan', 3):wikitext('Least Ever Votes ' .. legendlink):done :tag('tr'):tag('th'):attr('colspan', 3):addClass('size-small'):wikitext('(All Candidates)'):done :tag('th'):attr('colspan', 3):addClass('size-small'):wikitext('(No Special Mayor)'):done wikitable:tag('tr') :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext('Election Year'):done:tag('th'):wikitext('Votes'):done :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext('Election Year'):done:tag('th'):wikitext('Votes'):done :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext('Election Year'):done:tag('th'):wikitext('Votes'):done for i = 1, 5 do		local j = lvote - i		wikitable:tag('tr'):tag('td'):wikitext(p.getMayorLink(tvote[i].name)):done :tag('td'):wikitext(tvote[i].year):done :tag('td'):wikitext((''):format(tvote[i].votes)):done :tag('td'):wikitext(p.getMayorLink(tvotereg[i].name)):done :tag('td'):wikitext(tvotereg[i].year):done :tag('td'):wikitext((''):format(tvotereg[i].votes)):done :tag('td'):wikitext(''):done :tag('td'):wikitext(tvote[j].year):done :tag('td'):wikitext((''):format(tvote[j].votes)):done end table.push(alltables, tostring(wikitable)) -- Table 3 wikitable = mw.html.create('table'):addClass('article-table') wikitable:tag('tr'):tag('th'):attr('colspan', 6):wikitext('Most Ever Percentage Support ' .. legendlink):done :tag('th'):attr('rowspan', 2):attr('colspan', 3):wikitext('Least Ever Percentage Support ' .. legendlink):done :tag('tr'):tag('th'):attr('colspan', 3):addClass('size-small'):wikitext('(All Candidates)'):done :tag('th'):attr('colspan', 3):addClass('size-small'):wikitext('(No Special Mayor)'):done wikitable:tag('tr') :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext('Election Year'):done:tag('th'):wikitext('Percent'):done :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext('Election Year'):done:tag('th'):wikitext('Percent'):done :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext('Election Year'):done:tag('th'):wikitext('Percent'):done for i = 1, 5 do		local j = lperc - i		wikitable:tag('tr'):tag('td'):wikitext(p.getMayorLink(tperc[i].name)):done :tag('td'):wikitext(tperc[i].year):done :tag('td'):wikitext((''):format(tperc[i].percent)):done :tag('td'):wikitext(p.getMayorLink(tpercreg[i].name)):done :tag('td'):wikitext(tpercreg[i].year):done :tag('td'):wikitext((''):format(tpercreg[i].percent)):done :tag('td'):wikitext(''):done :tag('td'):wikitext(tperc[j].year):done :tag('td'):wikitext((''):format(tperc[j].percent)):done end table.push(alltables, tostring(wikitable)) -- Table 4 local difflegn = string.makeTitle('Diff', 'Difference between top two.') wikitable = mw.html.create('table'):addClass('article-table') wikitable:tag('tr'):tag('th'):attr('colspan', 3):wikitext('Tightest Race by Votes'):done :tag('th'):attr('colspan', 3):wikitext('Tightest Race by Percentage'):done wikitable:tag('tr') :tag('th'):wikitext('Election Year'):done:tag('th'):wikitext(difflegn):done:tag('th'):wikitext('Top Two'):done :tag('th'):wikitext('Election Year'):done:tag('th'):wikitext(difflegn):done:tag('th'):wikitext('Top Two'):done for i = 1, 5 do		local j = ldiff - i		local resultstr = ('%s %s'):format(			p.getMayorLink(tdvote[j].name[1]),			tdvote[j].votes[1],			p.getMayorLink(tdvote[j].name[2]),			tdvote[j].votes[2]		) local resultstr2 = ('%s %s'):format(			p.getMayorLink(tdperc[j].name[1]),			tdperc[j].percent[1],			p.getMayorLink(tdperc[j].name[2]),			tdperc[j].percent[2]		) wikitable:tag('tr'):tag('td'):wikitext(tdvote[j].year):done :tag('td'):wikitext((''):format(tdvote[j].votes_diff)):done :tag('td'):wikitext(resultstr):done :tag('td'):wikitext(tpercreg[j].year):done :tag('td'):wikitext((''):format(tdperc[j].percent_diff)):done :tag('td'):wikitext(resultstr2):done end table.push(alltables, tostring(wikitable)) return alltables end

function p._analyse -- This function returns all necessary tables/values after analysing all voting data local topvotes, toppercent, diffvotes, diffpercent = {}, {}, {}, {} for i = currYear, 1, -1 do		local item = electionsData(i) if item then local stats = p._getElectionStats(i) if stats[1] and stats[1].votes then table.push(topvotes, {					year = i,					name = stats[1].name,					votes = stats[1].votes,				}) end if stats[1] and stats[1].percent then table.push(toppercent, {					year = i,					name = stats[1].name,					percent = stats[1].percent,				}) end if stats[1] and stats[2] then table.push(diffvotes, {					year = i,					name = { stats[1].name, stats[2].name },					votes = { stats[1].votes, stats[2].votes },					percent = { stats[1].percent, stats[2].percent },					votes_diff = stats[1].votes - stats[2].votes,				}) table.push(diffpercent, {					year = i,					name = { stats[1].name, stats[2].name },					votes = { stats[1].votes, stats[2].votes },					percent = { stats[1].percent, stats[2].percent },					percent_diff = stats[1].percent - stats[2].percent,				}) end end end table.sort(topvotes, function (a, b)		return a.votes > b.votes	end) table.sort(toppercent, function (a, b)		return a.percent > b.percent	end) table.sort(diffvotes, function (a, b)		return a.votes_diff > b.votes_diff	end) table.sort(diffpercent, function (a, b)		return a.percent_diff > b.percent_diff	end) return { topvotes = topvotes, toppercent = toppercent, diffvotes = diffvotes, diffpercent = diffpercent, } end

function p.isValidMayor(mayor) return mayorData.mayors[string.ucfirst((mayor or ""):lower)] and true or false end

return p