Module:MatchListAbstract

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_game = require('Module:GameUtil') local util_map = require('Module:MapUtil') local util_html = require('Module:HtmlUtil') local util_matches = require('Module:MatchesUtil') local util_table = require('Module:TableUtil') local util_text = require('Module:TextUtil') local util_time = require('Module:TimeUtil') local util_toggle = require('Module:ToggleUtil') local util_vars = require('Module:VarsUtil') local m_team = require('Module:Team') local ChampionList = require('Module:ChampionList') local i18n = require('Module:i18nUtil') local Phase = require('Module:Phase') local LCS = require('Module:LuaClassSystem').class

local lang = mw.getLanguage('en')

local TZ = { 'CD', 'You', 'PST', 'CET', 'KST' } local NORMAL_TZ = { 'PST', 'CET', 'KST' }

local HIDDENCLASS = 'matches-hiddentab'

local TAB_TOGGLES = { all = { show_attr = '.ml-allw', hide_attr = '.ml-btn', show_class = 'ml-allw', hide_class = 'ml-btn', show_id = 'matchlist-show-all', hiddenclass = HIDDENCLASS, },	week = { show_attr = '.ml-w%s', hide_attr = '.ml-btn%s', hiddenclass = HIDDENCLASS, show_class = 'ml-allw ml-w%s', hide_class = 'ml-btn ml-btn%s', },	row = 'ml-allw ml-w%s %s', daterange = 'ml-btn ml-btn%s %s', }

local TZ_TOGGLES = { init = 'You', order = { 'CD', 'You', 'PST', 'CET', 'KST' }, attrs = { title = { CD = i18n.print('Countdown_title'), You = i18n.print('You_title'), PST = i18n.print('PST_title'), CET = i18n.print('CET_title'), KST = i18n.print('KST_title') },	} }

local MATCHLIST_PATCH_TOGGLES = { init = 'patch_number', order = { 'patch_all', 'patch_number', 'patch_none' }, showall = 'patch_all', }

local HASFLEX = false local HAS_GROUPS = false local FORCE_ALLOW_PREDICTIONS = false

local h = {} local p = LCS

-- will not be overridden in subclasses, just needs to be accessible p.RS_TOGGLES = { hiddenclass = 'matchlist-rs-hidden', order = { 'res', 'sch' }, attrs = { id = { sch = 'matchlist-show-schedule' }	} }

p.THIS = nil p.COLSPAN = 4 p.WIDTHS = { 110, 25, 25, 110 } p.MATCH_LIST_HAS_PATCH = false function p:init(args) i18n.init('MatchList') local matchData = self:getMatchData(util_esports.getOverviewPage(args.page), args) return self:makeOutput(matchData, args) end

-- match cargo function p:getMatchData(page, args) local matchData = self:getAndRunMatchQuery(page, args) for i, tab in ipairs(matchData) do		tab.index = i		tab.groupList = {} for j, row in ipairs(tab) do			row.index = j			self:processMatchRow(row, i, j, args, tab) end end matchData.groupList = h.getListOfGroupsFromCargo(page) self.THIS = util_matches.determineThis(matchData, args.This) return matchData end

function p:getAndRunMatchQuery(page, args) local outergroup = args.outergroup or 'Tab' local innergroup = args.innergroup or 'Date' local matchResult = util_cargo.queryAndCast(self:makeMatchQuery(page, args)) return util_cargo.groupResultOrdered(matchResult, outergroup) end

function p:makeMatchQuery(page, args) local query = { tables = { 'MatchSchedule=MS', 'TournamentGroups=TG1', 'TournamentGroups=TG2' }, join = { 'MS.PageAndTeam1=TG1.PageAndTeam', 'MS.PageAndTeam2=TG2.PageAndTeam', },		fields = self:makeMatchFields(args), where = h.makeMatchWhere(page, args), orderBy = 'MS.N_Page ASC, MS.N_TabInPage ASC, MS.N_MatchInTab ASC', groupBy = 'MS.UniqueMatch', }	return query end

function p:makeMatchFields(args) local tbl = { 'MS.Team1', 'MS.Team2', 'MS.Team1Final', 'MS.Team2Final', 'MS.Player1', 'MS.Player2', 'MS.Winner [number]', 'MS.Team1Score [number]', 'MS.Team2Score [number]', 'MS.Patch', 'MS.Disabled', 'MS.Hotfix', 'MS.PatchFootnote', 'MS.FF [number]', 'MS.Tab', 'MS.N_TabInPage [number]', 'MS.UniqueMatch', 'MS.DateTime_UTC=UTC', 'MS.DST', 'MS.HasTime [boolean]', 'MS.OverviewPage', 'MS.MatchDay', 'MS.Stream', 'MS.IsFlexibleStart [boolean]', 'MS.BestOf [number]', 'MS.InitialN_MatchInTab', 'MS.InitialPageAndTab', 'MS.Phase', 'TG1.GroupName=GroupName1', 'TG2.GroupName=GroupName2', 'CONCAT(N_Page,"_",N_TabInPage)=PageAndTab', 'MS.OverrideAllowPredictions [boolean]', 'MS.UniqueMatch', }	if not util_args.castAsBool(args.nofootnotes) then util_table.mergeArrays(tbl, { 'MS.Team1Footnote', 'MS.Team2Footnote', 'MS.Footnote' }) end return tbl end

function h.makeMatchWhere(page, args) local tbl = { ('MS.OverviewPage="%s"'):format(page), util_cargo.whereFromArg('MS.Tab="%s"', args.onlytab), }	if args.team then local teamlink = m_team.teamlinkname(args.team) tbl[#tbl+1] = ('(MS.Team1Final="%s" OR MS.Team2Final="%s")'):format(teamlink, teamlink) end return util_cargo.concatWhere(tbl) end

function p:processMatchRow(row, i, j, args, tab) h.processDateTime(row, i, j, h.lastRow(tab, j)) self:processFF(row) row.innergroup = args.innergroup and row[args.innergroup] row.NewDay = h.nextRow(tab, j).MatchDay and h.nextRow(tab, j).MatchDay ~= row.MatchDay row.TimestampAttr = h.getTimestampAttr(row, tab, j)	row.GroupName = h.getRowGroupName(row) HAS_GROUPS = HAS_GROUPS or row.Phase row.Phase = Phase(row.Phase) HASFLEX = HASFLEX or row.IsFlexibleStart FORCE_ALLOW_PREDICTIONS = FORCE_ALLOW_PREDICTIONS or row.OverrideAllowPredictions self:getAndAddPatchData(row, tab) end

function h.lastRow(tab, j)	return tab[j-1] or { NewDay = true } end

function h.nextRow(tab, j)	return tab[j+1] or {} end

function h.processDateTime(row, i, j, lastrow) if row.UTC then h.processDateTimeKnown(row, i, j, lastrow) else h.setDatesTBD(row) end end

function h.processDateTimeKnown(row, i, j, lastrow) util_time.addTimezonesToRowFromUTC(row) for _, tz in ipairs(NORMAL_TZ) do row[tz .. '_Time'] = row[tz]:match('(%d%d:%d%d)') local date = lang:formatDate('D Y-m-d',row[tz]) row[tz .. '_Date'] = date row[tz .. '_isNewDate'] = date ~= lastrow[tz .. '_Date'] end if row.HasTime then h.setTimesYou(row, row.UTC, i, j)	else h.setTimesTBD(row) end end

function h.setTimesYou(row, UTC, i, j)	row.You_Time = util_time.timeInLocal(UTC) row.CD_Time = util_time.countdown(UTC, h.getCountdownSettings(row, i, j)) row.You_Date = util_time.dateInLocal(UTC) row.You_Date_Range = util_time.dateInLocalMatches(UTC) end

function h.getCountdownSettings(row, i, j)	if row.Stream then return { options = { 'matches-format', 'no-leading-zeros' }, data_end = 'toggle', default = ' ', i = ('%s_%s'):format(i, j)		} else return { options = { 'matches-format', 'no-leading-zeros' } }	end end

function h.setTimesTBD(row) for _, tz in ipairs(TZ) do row[tz .. '_Time'] = 'TBD' end row.You_Date = 'TBD' row.You_Date_Range = 'TBD' return end

function h.setDatesTBD(row) for _, tz in ipairs(TZ) do row[tz .. '_Date'] = 'TBD' end row.You_Date = 'TBD' row.You_Date_Range = 'TBD' h.setTimesTBD(row) end

function p:processFF(row) if not row.FF then return end row.Team1Score = row.FF == 2 and 'W' or 'FF' row.Team2Score = row.FF == 1 and 'W' or 'FF' end

function h.getTimestampAttr(row, tab, j)	if h.lastRow(tab, j).NewDay then return util_time.unixNumber(row.UTC) else return h.lastRow(tab, j).TimestampAttr end end

function h.getRowGroupName(row) if row.Team1 == 'TBD' then return row.GroupName2 end if row.Team2 == 'TBD' then return row.GroupName1 end if row.GroupName1 ~= row.GroupName2 then return nil end return row.GroupName1 end

function h.getListOfGroupsFromCargo(page) return util_cargo.getOrderedList({		tables = 'TournamentGroups',		fields = 'GroupName',		where = ('OverviewPage="%s"'):format(page),		orderBy = 'GroupN',		groupBy = 'GroupName',	}, 'GroupName') end

function p:getAndAddPatchData(row, tab) -- add patch as a table caption util_table.initTable(tab, 'patchinfo', { patches = {}, hotfixes = {}, disabled = {}, footnotes = {} }) if not row.Patch then return true end self.MATCH_LIST_HAS_PATCH = true util_table.initDict(tab.patchinfo.patches, row.Patch) if row.Hotfix then util_table.initDict(tab.patchinfo.hotfixes, row.Hotfix) end if row.PatchFootnote then util_table.initDict(tab.patchinfo.footnotes, row.PatchFootnote) end util_table.mergeArrays(tab.patchinfo.disabled, util_text.split(row.Disabled)) end

-- print function p:makeOutput(data, args) util_footnote.init local output = mw.html.create('div'):attr('id','matchlist') if HASFLEX then h.printFlexNote(output) end self:printTogglersAndButtons(output, data.groupList) self:printAllTabs(output, data, args) util_footnote.printTexts(output) return output end

function p:printTogglersAndButtons(tbl, groupList) util_toggle.printSectionToggler(tbl, TAB_TOGGLES.all) h.printResultsToggle(tbl) h.printTZToggle(tbl) self:printPatchToggle(tbl) self:printPredictionsButton(tbl) h.printGroups(tbl, groupList) tbl:tag('div') :css('clear','left') :attr('id', 'matchlist-section-start') end

function h.printResultsToggle(tbl) local div = tbl:tag('div') :addClass('toggle-button') p.RS_TOGGLES.displays = { res = i18n.print('results_init'), sch = i18n.print('results') }	util_toggle.printOptionFromListTogglers(div, p.RS_TOGGLES) end

function h.printTZToggle(tbl) local div = tbl:tag('div'):addClass('toggle-button') util_toggle.printOptionFromListTogglers(div, TZ_TOGGLES) end

function p:printPatchToggle(tbl) if not self.MATCH_LIST_HAS_PATCH then return end div = tbl:tag('div'):addClass('toggle-button') util_toggle.printOptionFromListTogglers(div, MATCHLIST_PATCH_TOGGLES) end

function p:printPredictionsButton(tbl) if not self.THIS and not FORCE_ALLOW_PREDICTIONS then return end local div = tbl:tag('div') :addClass('toggle-button') :addClass('prediction-action') :attr('id', 'matches-prediction-begin') :wikitext(i18n.print('launch_predictions')) end

function h.printGroups(tbl, groupList) if not next(groupList) then return end if not HAS_GROUPS then return end tbl:tag('div') :css('clear','left') local div = tbl:tag('div') :addClass('toggle-button') div:tag('span'):wikitext(i18n.print('highlightGroup')) util_map.arraySafe(groupList, h.printOneGroup, div) end

function h.printOneGroup(group, div) div:wikitext('|') div:tag('div') :addClass('group-highlighter') :wikitext(group) :attr('data-group-highlighter', group) end

function h.printFlexNote(tbl) tbl:wikitext(mw.getCurrentFrame:expandTemplate{title='FlexNotice',args={''}}) end

function p:printAllTabs(output, data, args) local div = output:tag('div') :attr('id', 'matchlist-content-wrapper') :addClass('ml-normal-pred-and-results') :addClass(args.tabwrapperclass) for i, tab in ipairs(data) do		self:printTab(div, tab, self.THIS == i)	end end

function p:printTab(div, tab, isfocused) local innerDiv = div:tag('div') :addClass('matchlist-tab-wrapper') local tbl = innerDiv:tag('table') :addClass('wikitable') :addClass('matchlist') self:printPatchCaption(tbl, tab) self:printTabHeader(tbl, tab.index, tab.name, isfocused) self:printDateRange(		tbl,		tab,		h.getDateRangeToggleClass(tab.index, isfocused)	) util_html.printEmptyWidthRowPX(tbl, self.WIDTHS) local toggle_class = h.getRowToggleClass(tab.index, isfocused) for k, row in ipairs(tab) do		self:printFullRow(tbl, row, toggle_class, k == 1) end h.printPredictionTotals(innerDiv, tab.predictionTotals) return end

function p:printPatchCaption(tbl, tab) if not self.MATCH_LIST_HAS_PATCH then return true end local caption = tbl:tag('caption') -- the toggle here is needed because footnotes are attached only to the parent element, not the spans -- since it's impossible to show disabled champions but not patch number, -- we don't need to add patch_disabled since that would be showall, and showall is auto added util_toggle.oflCellClasses(caption, MATCHLIST_PATCH_TOGGLES, 'patch_number') h.printPatchNumber(caption, tab.patchinfo) h.printDisabledText(caption, tab.patchinfo) util_footnote.tag(caption, tab.patchinfo.footnotes) end

function h.printPatchNumber(caption, patchinfo) local span = caption:tag('span') :addClass('matchlist-patch-number') :wikitext(i18n.print('patchCaption', h.getPatchText(patchinfo.patches))) util_toggle.oflCellClasses(span, MATCHLIST_PATCH_TOGGLES, 'patch_number') end

function h.getPatchText(patches) return util_table.concatNonempty(patches, ', ', util_game.linkPatch) or i18n.print('tbd') end

function h.printDisabledText(caption, patchinfo) if not patchinfo.disabled or not next(patchinfo.disabled) then return end local span = caption:tag('span') :addClass('matchlist-patch-disabled') :wikitext(i18n.print('patchDisabled', h.getDisabledText(patchinfo.disabled))) util_toggle.oflCellClasses(span, MATCHLIST_PATCH_TOGGLES, 'patch_disabled') end

function h.getDisabledText(disabled) return ChampionList(util_table.removeDuplicates(disabled)):images end

function p:printTabHeader(tbl, i, name, isfocused) local data = mw.clone(TAB_TOGGLES.week) util_toggle.prepDataByWeek(data, i)	data.initshown = isfocused util_toggle.printToggleHeader(tbl, self.COLSPAN, name, data) return end

function p:printDateRange(tbl, tab_data, daterowclass) local tr = tbl:tag('tr') :addClass(daterowclass) local td = tr:tag('td') :attr('colspan', self.COLSPAN) local n = #tab_data for _, tz in ipairs(NORMAL_TZ) do		h.printNormalTZDatesToRangeCell(			td:tag('span'),			tz,			tab_data[1][tz .. '_Date'],			tab_data[n][tz .. '_Date']		) end self:printYouTZDatesToRangeCell(		td:tag('span'),		tab_data[1].You_Date_Range,		tab_data[n].You_Date_Range	) end

function h.printNormalTZDatesToRangeCell(span, tz, text1, text2) text1 = text1 ~= 'TBD' and lang:formatDate('D j M', text1) or text1 text2 = text2 ~= 'TBD' and lang:formatDate('D j M', text2) or text2 span:addClass('matchlist-daterange') -- for css, toggle displays handled through util below :wikitext(text1) if text2 ~= text1 then span:wikitext(' - ') span:wikitext(text2) end util_toggle.oflCellClasses(span, TZ_TOGGLES, tz) return end

function p:printYouTZDatesToRangeCell(span, text1, text2) -- this is a special case because we need extra classes for JS to do its thing -- because we don't know if the dates are the same or not -- given that this is already special, we may as well do You and CD as one instead of splitting util_toggle.oflCellClasses(span, TZ_TOGGLES, 'You') span:addClass(TZ_TOGGLES.classes.CD) -- manually add this one because it's the same as YOU self:printYouTZDatesToRangeData(span, text1, text2) end

function p:printYouTZDatesToRangeData(span, text1, text2) span:addClass('matchlist-daterange') :addClass('matchlist-daterange-you') span:tag('span') :addClass('matchlist-daterange-you-1') :wikitext(text1) span:tag('span') :addClass('matchlist-daterange-you-hyphen') :wikitext(' - ') span:tag('span') :addClass('matchlist-daterange-you-hyphen') :wikitext(text2) end

function h.getDateRangeToggleClass(i, isfocused) return TAB_TOGGLES.daterange:format(i, not isfocused and '' or HIDDENCLASS) end

function h.getRowToggleClass(i, isfocused) return TAB_TOGGLES.row:format(i, isfocused and '' or HIDDENCLASS) end

function p:printFullRow(tbl_tab, row, toggle_class, isfirst) self:printDateRow(tbl_tab, row, toggle_class, isfirst) local tr = tbl_tab:tag('tr') :addClass(toggle_class) :addClass('ml-row') :attr('data-initial-order', row.InitialN_MatchInTab) :attr('data-initial-pageandtab', row.InitialPageAndTab) h.addGroupAttr(tr, row) h.tagRowIfOver(tr, row) h.addDateAttr(tr, row) h.addNewDay(tr, row) h.addFlexStart(tr, row) util_matches.printCustomClass(tr, row) self:printTeam1(tr, row) self:printMiddle(tr, row) self:printTeam2(tr, row) end

function h.addGroupAttr(tr, row) if not row.Phase:isgroups then return end tr:attr('data-group', row.GroupName) end

function p:printDateRow(tbl, row, toggle_class, isfirst) for _, tz in ipairs(NORMAL_TZ) do if row[tz .. '_isNewDate'] then local tr = tbl:tag('tr') :addClass(toggle_class) self:printNormalTZDateToRow(tr, tz, row[tz .. '_Date']) end end local tr = tbl:tag('tr') :addClass(toggle_class) self:printYouTZDateToRow(tr, row.You_Date) tr:attr('data-isfirst',util_args.boolToStringYN(isfirst)) return end

function p:printNormalTZDateToRow(tr, tz, text) tr:addClass('matchlist-date') util_toggle.oflCellClasses(tr, TZ_TOGGLES, tz) local td = tr:tag('td') :attr('colspan', self.COLSPAN) td:wikitext(text) return end

function p:printYouTZDateToRow(tr, text) tr:addClass('matchlist-date') :addClass('matchlist-you-date') -- to select and remove the 'You' rows :addClass(TZ_TOGGLES.classes.CD) util_toggle.oflCellClasses(tr, TZ_TOGGLES, 'You') local td = tr:tag('td') :attr('colspan', self.COLSPAN) td:wikitext(text) return td end

function h.tagRowIfOver(tr, row) if not h.isOver(row) then tr:addClass('ml-row-tbd') :attr('data-prediction-expire', row.TimestampAttr) end if row.OverrideAllowPredictions then tr:addClass('ml-row-tbd') :attr('data-prediction-expire', '9999999999') tr:addClass('ml-verify-prediction-expire') tr:attr('data-game-id', row.UniqueMatch) end end

function h.isOver(row) return ((row.Team1Score and row.Team2Score) or row.Winner) and not row.OverrideAllowPredictions end

function h.addDateAttr(tr, row) if not row.UTC then return end tr:attr('data-date',util_time.strToDateStr(row.UTC)) end

function h.addNewDay(tr, row) if not row.NewDay then return end tr:addClass('matchlist-newday') end

function h.addFlexStart(tr, row) if not util_args.castAsBool(row.IsFlexibleStart) then return end tr:addClass('matchlist-flex') end

function p:printTeam1(tr, row) if (row.Team1 == 'TBD' or not row.Team1) and not row.Player1 then tr:addClass('ml-row-unknown-team') end local td = tr:tag('td') :addClass('matchlist-team1') :addClass('ml-team') util_esports.addTeamHighlighter(td, row.Team1Final) h.printWinnerClasses(td, row, 1) h.printTeam1Content(td, row) end

function h.printWinnerClasses(td, row, this) if row.Winner == this then td:addClass('matchlist-winner-team') elseif row.Winner == 0 then td:addClass('matchlist-tied-team') end end

function h.printTeam1Content(td, row) util_footnote.tag(td, row.Team1Footnote) if not row.Player1 then td:wikitext(m_team.leftshort(row.Team1, {link=''})) return end td :wikitext(util_esports.playerLinked(row.Player1)) :wikitext(m_team.onlyimageshort(row.Team1)) end

function p:printTeam2(tr, row) if (row.Team2 == 'TBD' or not row.Team2) and not row.Player2 then tr:addClass('ml-row-unknown-team') end local td = tr:tag('td') :addClass('matchlist-team2') :addClass('ml-team') util_esports.addTeamHighlighter(td, row.Team2Final) h.printWinnerClasses(td, row, 2) h.printTeam2Content(td, row) end

function h.printTeam2Content(td, row) if not row.Player2 then td:wikitext(m_team.rightshort(row.Team2, {link=''})) else td:wikitext(m_team.onlyimageshort(row.Team2)) :wikitext(util_esports.playerLinked(row.Player2)) end util_footnote.tag(td, h.team2Footnotes(row)) end

function h.team2Footnotes(row) local tbl = { row.Team2Footnote, row.Footnote } util_table.removeFalseEntries(tbl, 2) return tbl end

function p:printMiddle(tr, row) if h.isOver(row) then self:printScore(tr, row) self:printTime(tr, row, true) else self:printTime(tr, row) end end

function p:printScore(tr, row, suppresstoggle) local td1 = self:printOneScore(tr, row, 1) local td2 = self:printOneScore(tr, row, 2) if row.Winner == 1 then td1:addClass('matchlist-winner-score') elseif row.Winner == 2 then td2:addClass('matchlist-winner-score') elseif row.Winner == 0 then td1:addClass('matchlist-tied-score') td2:addClass('matchlist-tied-score') end if not suppresstoggle then util_toggle.oflCellClasses(td1, p.RS_TOGGLES, 'res') util_toggle.oflCellClasses(td2, p.RS_TOGGLES, 'res') end return end

function p:printOneScore(tr, row, n)	local td = tr:tag('td'):wikitext(self:getOneScore(row, n)) :addClass('matchlist-score') util_esports.addTeamHighlighter(td, row[('Team%sFinal'):format(n)]) return td end

function p:getOneScore(row, n)	return row[('Team%sScore'):format(n)] end

function p:printTime(tr, row, usetoggle) local td = tr:tag('td') :attr('colspan', self.COLSPAN - 2) :addClass('matchlist-time-cell') :addClass('plainlinks') if usetoggle then util_toggle.oflCellClasses(td, p.RS_TOGGLES, 'sch') end for _, tz in ipairs(TZ) do		local span = td:tag('span') if row.Stream then span:wikitext('[', row.Stream, ' ', row[tz .. '_Time'], ']') else span:wikitext(row[tz .. '_Time']) end util_toggle.oflCellClasses(span, TZ_TOGGLES, tz) end return end

function h.printPredictionTotals(div, totals) if not totals then return end local inner = div:tag('div') :addClass('ml-user-prediction-totals') inner:wikitext(('Correct: %s/%s (%s Guessed)'):format(totals.right, totals.over, totals.made)) end

return p