Module:Timeline

local util_args = require('Module:ArgsUtil') local util_cargo = require('Module:CargoUtil') local util_esports = require('Module:EsportsUtil') local util_footnote = require('Module:FootnoteUtil') local util_html = require('Module:HtmlUtil') local util_map = require('Module:MapUtil') local util_math = require('Module:MathUtil') local util_table = require('Module:TableUtil') local util_text = require('Module:TextUtil') local util_title = require('Module:TitleUtil') local util_toggle = require('Module:ToggleUtil') local util_tournament = require('Module:TournamentUtil') local util_vars = require('Module:VarsUtil')

local m_team = require('Module:Team')

local lang = mw.getLanguage('en')

local TOGGLES = { order = { 'cu', 'wk' }, displays = { wk = 'Weekly', cu = 'Cumulative' }, }

local DISPLAY_TYPE = { record = 'record', recordwithgames = 'record', points = 'points', recordwithpoints = 'recordwithpoints', bo2nopoints = 'recordbo2', } --	Table structure:	{		{			name = Tab 1,			team1, team2, team3,			team1 = { this = { w =, l = , p = }, total = {}, place = 1, diff = 0 }		},		{			name = Tab 2,			team1, team2, team3,			team1 = { this = {}, total = {}, place = , diff = }		}	}	first construct all tabs, except for standings & diffs	then determine standings & diffs

local h = {} local p = {} function p.fromCargo(frame) local args = util_args.merge h.verifyParams(args) local tltype = lang:lc(args.orderby) local overviewPage = util_esports.getOverviewPage(args.page) local data = h.getData(overviewPage, args, tltype) if #data == 0 then return '' end return p.main(data, args, tltype) end

function p.fromArgs(frame) local args = util_args.merge local tltype = lang:lc(args.orderby) local data = h.getDataFromArgs(args, tltype) return p.main(data, args, tltype) end

function p.main(data, args, tltype) local nTabs, nTeams = h.countParts(data) local colors = h.getColors(args, nTabs, nTeams) return h.makeOutput(data, tltype, colors) end

function h.verifyParams(args) if not args.orderby then error('Missing |orderby=') end end

function h.getData(page, args, tltype) local teamlist = h.getTeamlist(page, args.teamlist, args.onlygroup) local query = h.getQuery(page, args) local result = util_cargo.queryAndCast(query) local data = h.parseResult(result, tltype, args, teamlist) return data end

function h.getTeamlist(page, teamlist, group) if teamlist then return util_map.split(teamlist,nil,m_team.teamlinkname) elseif group then return util_tournament.getGroupTeamList(page, group) end return nil end

-- cargo function h.getQuery(page, args) local query = { tables = 'MatchSchedule', fields = h.getFields(args), where = h.getWhere(page, args), orderBy = 'N_Page ASC, N_TabInPage ASC, N_MatchInTab ASC', limit = 999, groupBy = 'UniqueMatch', }	return query end

function h.getFields(args) local tbl = { 'Team1', 'Team2', 'Team1Final', 'Team2Final', 'Winner [number]', 'Team1Score [number]', 'Team2Score [number]', 'Team1Points [number]', 'Team2Points [number]', 'Team1PointsTB [number]', 'Team2PointsTB [number]', 'Tab', 'N_MatchInTab [number]', 'N_TabInPage [number]', 'CONCAT(N_Page,"_",N_TabInPage)=Index' }	if not util_args.castAsBool(args.nofootnotes) then util_table.mergeArrays(tbl, { 'Team1Footnote', 'Team2Footnote' }) end return tbl end

function h.getWhere(page, args) local tbl = { ('OverviewPage="%s"'):format(page), -- we will actually prune the data later as if we had no where condition here -- because if data is split across pages then we might get too many things here that we don't want -- but certainly this is an upper bound on the amount of data we need -- so i'll leave this condition here because it isn't hurting anything -- and technically it is potentially allowing us to do less computation overall util_cargo.whereFromArg('N_TabInPage <="%s"', args.weeks), 'IsTiebreaker="0"', }	return util_table.concat(tbl, ' AND ') end

-- data processing function h.parseResult(result, tltype, args, teamlist) if not teamlist then teamlist = h.getTeamList(result) end local gamesByTab = h.splitResultByTab(result) local weeks = args.weeks or h.countWeeks(gamesByTab) local starting_week = args.starting_week and tonumber(args.starting_week) or 1 local dataByTab = util_map.arraySafe(gamesByTab, h.parseTab) h.normalizeTeams(dataByTab, teamlist) h.addAndAdjustFirstTabTotals(dataByTab[1], tltype, teamlist, args) h.addRestTabTotals(dataByTab, tltype) for _, tab in ipairs(dataByTab) do		h.sortTab(tab) end h.pruneExtraWeeks(dataByTab, tonumber(weeks), starting_week) h.sortFinalTab(dataByTab[#dataByTab], args.finalorder, args.finalplaces) h.addPlaceAndDiffs(dataByTab) util_map.rowsInPlace(dataByTab, h.addClassesToTab) return dataByTab end

function h.getTeamList(result) local tbl = {} for _, row in ipairs(result) do		if not tbl[row.Team1Final] then tbl[row.Team1Final] = true end if not tbl[row.Team2Final] then tbl[row.Team2Final] = true end end tbl.TBD = nil -- undefine teams that dont actually exist local teamlist = {} for team, _ in pairs(tbl) do		teamlist[#teamlist+1] = team end return teamlist end

function h.splitResultByTab(result) local gamesByTab = {} local lastindex = '' local thistab = 0 for _, row in ipairs(result) do		if row.Index ~= lastindex then thistab = thistab + 1 lastindex = row.Index gamesByTab[thistab] = { name = row.Tab } end gamesByTab[thistab][#gamesByTab[thistab]+1] = row end return gamesByTab end

function h.countWeeks(gamesByTab) for k, v in ipairs(gamesByTab) do		if not v[1].Winner then return math.max(1, k-1) end end return 9999 end

function h.parseTab(tab) local ret = { name = tab.name } for _, row in ipairs(tab) do		h.parseRow(ret, row) end return ret end

function h.parseRow(tab, row) local Team1 = row.Team1Final local Team2 = row.Team2Final h.initializeTeam(tab, Team1) h.initializeTeam(tab, Team2) if row.Winner then h.addRowTotals(row, tab[Team1].this, tab[Team2].this) end tab[Team1].team = row.Team1 tab[Team2].team = row.Team2 tab[Team1].footnotes[#tab[Team1].footnotes+1] = row.Team1Footnote tab[Team2].footnotes[#tab[Team2].footnotes+1] = row.Team2Footnote return end

function h.initializeTeam(tab, team) if tab[team] then return end tab[team] = { teamfinal = team, this = { w = 0, l = 0, t = 0, wg = 0, lg = 0, p = 0, tb = 0 }, total = { w = 0, l = 0, t = 0, wg = 0, lg = 0, p = 0, tb = 0 }, footnotes = {} }	return end

function h.addRowTotals(row, this1, this2) this1.w = this1.w + (row.Winner == 1 and 1 or 0) this1.l = this1.l + (row.Winner == 2 and 1 or 0) this1.t = this1.t + (row.Winner == 0 and 1 or 0) this1.p = this1.p + (row.Team1Points or 0) this1.wg = this1.wg + (row.Team1Score or 0) this1.lg = this1.lg + (row.Team2Score or 0) this1.tb = this1.tb + (row.Team1PointsTB or 0) this2.w = this2.w + (row.Winner == 2 and 1 or 0) this2.l = this2.l + (row.Winner == 1 and 1 or 0) this2.t = this2.t + (row.Winner == 0 and 1 or 0) this2.p = this2.p + (row.Team2Points or 0) this2.wg = this2.wg + (row.Team2Score or 0) this2.lg = this2.lg + (row.Team1Score or 0) this2.tb = this2.tb + (row.Team2PointsTB or 0) end

function h.normalizeTeams(dataByTab, teamlist) for _, tab in ipairs(dataByTab) do		for i, team in ipairs(teamlist) do			tab[i] = team if not tab[team] then h.initializeTeam(tab, team) end end end h.fixRenamedTeams(dataByTab) return end

function h.fixRenamedTeams(dataByTab) -- we only populate teamfinal when we initialize a team -- so now we need to populate actually "team" local week = #dataByTab while week >= 1 do		local tab = dataByTab[week] local nw = week + 1 if dataByTab[nw] then for _, team in ipairs(tab) do				if not tab[team].team then tab[team].team = dataByTab[nw][team].team end end else for _, team in ipairs(tab) do				if not tab[team].team then tab[team].team = tab[team].teamfinal end end end week = week - 1 end end

function h.addAndAdjustFirstTabTotals(tab, tltype, teamlist, args) if not tab then return end local f_sort = util_tournament.getSortMethod(tltype) for _, team in ipairs(tab) do		h.copyThisToTotal(tab[team].total, tab[team].this) f_sort(tab[team].total) end h.addArgsToWeekOneTotals(tab, teamlist, args) end

function h.addArgsToWeekOneTotals(weekOne, teamlist, args) -- for GLL format, they had points count from results in the previous split. -- these have to be manually added by an editor via the "adjustment" parameters, same as Module:Standings -- here we add them ONLY to the total for week 1, and not to the individual week. -- this must be done after the total for week 1 is copied from 'this' but before the rest of the weeks are added if not weekOne then return end local arg_adjust = { w = h.getAdjustmentArgData(args.wadjust), l = h.getAdjustmentArgData(args.ladjust), t = h.getAdjustmentArgData(args.tadjust), wg = h.getAdjustmentArgData(args.wgadjust), lg = h.getAdjustmentArgData(args.lgadjust), p = h.getAdjustmentArgData(args.pointadjust), }	for _, teamstr in ipairs(weekOne) do	   h.addAdjustmentsToWeekOne(weekOne[teamstr].total, teamstr, arg_adjust) end end

function h.getAdjustmentArgData(arg) if not arg then return {} end local tbl = {} for val in arg:gmatch('%(%(%((.-)%)%)%)') do		k, v = val:match('(.*)===(.*)') tbl[m_team.teamlinkname(k)] = v	end return tbl end

function h.addAdjustmentsToWeekOne(team, teamstr, arg_adjust) team.w = team.w + (tonumber(arg_adjust.w[teamstr] or 0)) team.l = team.l + (tonumber(arg_adjust.l[teamstr] or 0)) team.t = team.t + (tonumber(arg_adjust.t[teamstr] or 0)) team.wg = team.wg + (tonumber(arg_adjust.wg[teamstr] or 0)) team.lg = team.lg + (tonumber(arg_adjust.lg[teamstr] or 0)) end

function h.addRestTabTotals(dataByTab, tltype) local f_sort = util_tournament.getSortMethod(tltype) for i, tab in ipairs(dataByTab) do		if i == 1 then -- pass else h.addLaterTabTotals(tab, dataByTab[i-1], f_sort) end end end

function h.copyThisToTotal(total, this) total.w = this.w	total.l = this.l	total.t = this.t	total.wg = this.wg	total.lg = this.lg	total.p = this.p	total.tb = this.tb end

function h.addLaterTabTotals(tab, last, f_sort) for _, team in ipairs(tab) do		h.addThisAndLastAsTotal(tab[team].total, tab[team].this, last[team].total) f_sort(tab[team].total) end end

function h.addThisAndLastAsTotal(total, this, last) total.w = this.w + last.w	total.l = this.l + last.l	total.t = this.t + last.t	total.wg = this.wg + last.wg	total.lg = this.lg + last.lg	total.p = this.p + last.p	total.tb = this.tb + last.tb end

function h.sortTab(tab) table.sort(tab,		function(a,b)			if tab[a].total.sort == tab[b].total.sort then				return lang:lc(a) < lang:lc(b)			else				return tab[a].total.sort > tab[b].total.sort			end		end	) return end

function h.pruneExtraWeeks(data, weeks, startingWeek) if not weeks then return end for k, _ in ipairs(data) do		if k > weeks then data[k] = nil end -- in the event that we have a startingWeek > 1, this pushes the earlier weeks up		-- and then sets the later weeks to nil once it gets to the end -- if startingWeek == 1 then this does nothing data[k] = data[k + startingWeek - 1] end end

function h.sortFinalTab(tab, finalorder, places) if not finalorder then return end local order_tbl = util_text.split(finalorder) local places_tbl = places and util_text.split(places) or {} for k, v in ipairs(order_tbl) do		local team = m_team.teamlinkname(v) tab[k] = team tab[team].total.sort = places_tbl[k] or k	end return end

function h.addPlaceAndDiffs(dataByTab) for i, tab in ipairs(dataByTab) do		if i == 1 then h.addFirstTabPlaceAndDiffs(tab) else h.addLaterTabPlaceAndDiffs(tab, dataByTab[i-1]) end end return end

function h.addFirstTabPlaceAndDiffs(tab) local place = 0 local lastsort for k, team in ipairs(tab) do		if tab[team].total.sort ~= lastsort then place = k			lastsort = tab[team].total.sort end tab[team].place = place tab[team].diff = 0 end end

function h.addLaterTabPlaceAndDiffs(tab, lasttab) local place = 0 local lastsort for k, team in ipairs(tab) do		if tab[team].total.sort ~= lastsort then place = k			lastsort = tab[team].total.sort end tab[team].place = place -- it's better to have a lower place number tab[team].diff = lasttab[team].place - place end end

function h.addClassesToTab(tab) for _, team in ipairs(tab) do		h.addClassesToTeam(tab[team]) end end

function h.addClassesToTeam(team) if not team.classes then team.classes = {} end team.classes[#team.classes+1] = h.getTeamPercentageClass(team.total) end

function h.getTeamPercentageClass(total) if total.w == total.l then return 'tl-row-equal' elseif total.w > total.l then return 'tl-row-above' end return 'tl-row-below' end

-- from args function h.getDataFromArgs(args, tltype) local dataByTab = {} local pointskey = tltype == 'points' and 'p' or 'tb' local w = 1 local t = 1 while args[('w%steam%s'):format(w,t)] do		dataByTab[w] = { name = ('Week %s'):format(w) } while args[('w%steam%s'):format(w,t)] do			local team = m_team.teamlinkname(args[('w%steam%s'):format(w,t)]) dataByTab[w][t] = team dataByTab[w][team] = { team = team, this = { w = tonumber(args[('w%sw%s'):format(w,t)] or '') or 0, l = tonumber(args[('w%sl%s'):format(w,t)] or '') or 0, t = tonumber(args[('w%st%s'):format(w,t)] or '') or 0, wg = 0, lg = 0, p = 0, tb = 0, },				total = { w = 0, l = 0, t = 0, p = 0, tb = 0 }, }			dataByTab[w][team].this[pointskey] = tonumber(args[('w%spt%s'):format(w,t)] or '') or 0 t = t + 1 end t = 1 w = w + 1 end h.addAndAdjustFirstTabTotals(dataByTab[1], tltype, teamlist, args) h.addRestTabTotals(dataByTab, tltype) if util_args.castAsBool(args.isover) then h.sortFinalTabFromArgs(dataByTab[#dataByTab]) end h.addPlaceAndDiffs(dataByTab) return dataByTab end

function h.sortFinalTabFromArgs(tab) for k, v in ipairs(tab) do		tab[v].total.sort = k	end return end

-- data has been gotten !

function h.countParts(data) return #data, #data[1] end

function h.getColors(args, nTabs, nTeams) local tbl = { init = args.places and util_map.split(args.places,',',h.getClassName) or {} }	for i = 1, nTabs do		tbl[i] = {} for j = 1, nTeams do			local arg = args[('w%sbg%s'):format(i,j)] if arg then tbl[i][j] = h.getClassName(arg) end end end return tbl end

function h.getClassName(class) -- prepend every individual "word" in the class with 'standings-' return class:gsub('([^ ]+)','standings-%1') end

function h.makeOutput(data, tltype, colors) util_footnote.init if not DISPLAY_TYPE[tltype] then error('Invalid orderby') end tltype = DISPLAY_TYPE[tltype] local output = mw.html.create('div'):addClass('timeline-section') h.makeToggler(output) local div = output:tag('div') for i, tab in ipairs(data) do		h.makeTable(div, tab, tltype, colors.init, colors[i]) end util_footnote.printTexts(output) return output end

function h.makeTable(div, tab, tltype, colorsPlace, colorsBg) local tbl = div:tag('table') :addClass('wikitable') :addClass('timeline') h.printHeading(tbl, tab.name) for i, team in ipairs(tab) do		h.printRow(tbl, tab[team], tltype, colorsPlace[i], colorsBg[i]) end end

function h.printHeading(tbl, name) tbl:tag('tr'):tag('th'):attr('colspan','4'):wikitext(name) return end

function h.printRow(tbl, teamdata, tltype, colorPlace, colorBg) local tr = tbl :tag('tr') :addClass(colorBg) h.printRowClasses(tr, teamdata.classes) util_esports.addTeamHighlighter(tr, teamdata.teamfinal) tr:tag('td') :addClass('timeline-place') :addClass(colorPlace) :wikitext(teamdata.place) h.printDiff(tr, teamdata.diff) tr:tag('td') :addClass('timeline-team') :wikitext(m_team.onlyimage(teamdata.team,{size=45})) h.makeCell(tr, tltype, teamdata) return end

function h.printRowClasses(tr, classes) if not classes then return end for _, c in ipairs(classes) do		tr:addClass(c) end end

function h.printDiff(tr, diff) local td = tr:tag('td') util_esports.printDiff(td, diff) return end

function h.makeCell(tr, tltype, data) local wk, cu, class if tltype == 'points' then wk = data.this.p		cu = data.total.p		class = 'timeline-points timeline-score' elseif tltype == 'record' then wk = ('%s-%s'):format(data.this.w, data.this.l)		cu = ('%s-%s'):format(data.total.w, data.total.l)		class = 'timeline-record timeline-score' elseif tltype == 'recordbo2' then wk = ('%s-%s-%s'):format(data.this.w, data.this.t, data.this.l)		cu = ('%s-%s-%s'):format(data.total.w, data.total.t, data.total.l)		class = 'timeline-record timeline-score' elseif tltype == 'recordwithpoints' then wk = ("%s-%s (%s)"):format(data.this.w, data.this.l, util_math.printWithSign(data.this.tb)) cu = ("%s-%s (%s)"):format(data.total.w, data.total.l, util_math.printWithSign(data.total.tb)) class = 'timeline-recordwithpoints timeline-score' end h.printCell(tr, wk, cu, class, data.footnotes) return end

function h.printCell(tr, wk, cu, class, footnotes) local td_wk = tr:tag('td') :addClass(class) :wikitext(wk) util_toggle.oflCellClasses(td_wk, TOGGLES, 'wk') util_footnote.tag(td_wk, footnotes) local td_cu = tr:tag('td') :addClass(class) :wikitext(cu) util_toggle.oflCellClasses(td_cu, TOGGLES, 'cu') util_footnote.tag(td_cu, footnotes) return end

function h.makeToggler(tbl) local div = tbl:tag('div') :addClass('toggle-button') util_toggle.printOptionFromListTogglers(div, TOGGLES) util_html.clear(tbl) return end

return p