Module:MatchList

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_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 i18n = require('Module:i18nUtil')

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 TIME_UNTIL_AUTO_NEXT_TAB = 2 * 24 * 60 * 60 local PERCENT_UNTIL_AUTO_NEXT_TAB = 0.5

local WIDTHS = { 110, 25, 25, 110 }

local RS_TOGGLES = { hiddenclass = 'matchlist-rs-hidden', order = { 'res', 'sch' }, attrs = { id = { sch = 'matchlist-show-schedule' }	} }

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 HASFLEX = false local THIS = nil

local h = {} local p = {} function p.main(frame) i18n.initGlobalFromFile('MatchList') local args = util_args.merge(true) local overviewPage = util_esports.getOverviewPage(args.page) local matchData = p._getMatchData(overviewPage, args) return p._makeOutput(matchData, args) end

function p.printSingleTab(args) local args = util_args.merge(true) local overviewPage = util_esports.getOverviewPage(args.page) local data_tab = p._getMatchData(overviewPage, args) local tbl_tab = mw.html.create('table') :addClass('wikitable') :addClass('matchlist') util_html.makeColspanHeader(tbl_tab, args.display, 4) util_html.makeEmptyWidthRowPX(tbl_tab, WIDTHS) local lastgroup for k, row in ipairs(data_tab[1] or {}) do		if row.innergroup ~= lastgroup then tbl_tab:tag('tr'):addClass('matchlist-date') :tag('td'):attr('colspan',4):wikitext(row.innergroup) lastgroup = row.innergroup end local tr = tbl_tab:tag('tr') h.printTeam1(tr, row) h.printScore(tr, row, true) h.printTeam2(tr, row) end return tbl_tab end

-- match cargo function p._getMatchData(page, args) local outergroup = args.outergroup or 'Tab' local innergroup = args.innergroup or 'Date' local matchResult = util_cargo.queryAndCast(h.makeMatchQuery(page, args)) local matchData = util_cargo.groupResultOrdered(matchResult, outergroup) for i, tab in ipairs(matchData) do		for j, row in ipairs(tab) do			h.processMatchRow(row, i, j, args, tab) end end h.determineThis(matchData) return matchData end

function h.makeMatchQuery(page, args) local query = { tables = 'MatchSchedule', fields = h.makeMatchFields(args), where = h.makeMatchWhere(page, args), orderBy = 'N_Page ASC, N_TabInPage ASC, N_MatchInTab ASC', types = { Winner = 'number', Team1Score = 'number', Team2Score = 'number', FF = 'number', N_TabInPage = 'number', HasTime = 'boolean' },		limit = 9999, groupBy = 'UniqueMatch', }	return query end

function h.makeMatchFields(args) local tbl = { 'Team1', 'Team2', 'Team1Final', 'Team2Final', 'Winner', 'Team1Score', 'Team2Score', 'FF', 'Tab', 'N_TabInPage', 'UniqueMatch', 'DateTime_UTC=UTC', 'DateTime_PST=PST', 'DateTime_KST=KST', 'DateTime_CET=CET', 'HasTime', 'OverviewPage', 'MatchDay', 'Stream', 'IsFlexibleStart', 'BestOf', 'InitialN_MatchInTab', }	if not util_args.castAsBool(args.nofootnotes) then util_table.mergeArrays(tbl, { 'Team1Footnote', 'Team2Footnote', 'Footnote' }) end return tbl end

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

function h.processMatchRow(row, i, j, args, tab) if row.UTC then h.processDateTime(row, i, j, h.lastRow(tab, j)) else h.setDatesTBD(row) end h.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)	HASFLEX = HASFLEX or util_args.castAsBool(row.IsFlexibleStart) 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) for _, tz in ipairs(NORMAL_TZ) do row[tz .. '_Time'] = row[tz]:match('(%d%d:%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 h.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 or true then -- temp fix to do it 30min before each individual game instead of entire day return util_time.unixNumber(row.UTC) else return h.lastRow(tab, j).TimestampAttr end end

function h.determineThis(matchData) if not next(matchData) then return end if not h.firstWinnerInTab(matchData[1]) then THIS = 1 end if h.lastWinnerInTab(matchData[#matchData]) then return end for i = 1, #matchData do		if not h.lastWinnerInTab(matchData[i]) then THIS = i			return elseif h.setThisInBetweenWeeks(matchData[i], matchData[i+1], i) then return end end end

function h.firstWinnerInTab(tab) if not tab[1] then return nil end return tab[1].Winner end

function h.lastWinnerInTab(tab) if not tab[1] then return end return tab[#tab].Winner end

function h.setThisInBetweenWeeks(lastTab, nextTab, i)	if h.firstWinnerInTab(nextTab) then return false end local lastTime = h.lastTimeInTab(lastTab) local nextTime = h.firstTimeInTab(nextTab) local now = os.time if now > lastTime * PERCENT_UNTIL_AUTO_NEXT_TAB + nextTime * (1 - PERCENT_UNTIL_AUTO_NEXT_TAB) then THIS = i + 1 elseif now > lastTime + TIME_UNTIL_AUTO_NEXT_TAB then THIS = i + 1 else THIS = i	end return true end

function h.firstTimeInTab(tab) return util_time.unixNumber(tab[1].UTC) end

function h.lastTimeInTab(tab) return util_time.unixNumber(tab[#tab].UTC) end

-- print function p._makeOutput(data, args) util_footnote.initializeAllFootnotes local output = mw.html.create('div'):attr('id','matchlist') if HASFLEX then h.printFlexNote(output) end h.printToggles(output) h.printAllTabs(output, data, args) util_footnote.printFootnotes(output) return output end

function h.printToggles(tbl) util_toggle.printSectionToggler(tbl, TAB_TOGGLES.all) h.printResultsToggle(tbl) h.printTZToggle(tbl) h.printPredictionsButton(tbl) tbl:tag('div') :css('clear','left') :attr('id', 'matchlist-section-start') return end

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

function h.printTZToggle(tbl) local div = tbl:tag('div'):addClass('toggle-button') util_toggle.printOptionFromListTogglers(div, TZ_TOGGLES) div:wikitext('|') util_html.printHelpText(div, i18n.print('helptext')) end

function h.printPredictionsButton(tbl) if not THIS 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.printFlexNote(tbl) tbl:wikitext(mw.getCurrentFrame:expandTemplate{title='FlexNotice',args={''}}) end

function h.printAllTabs(output, data, args) local thisweek = tonumber(args.This or '') or THIS 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		h.printTab(div, tab, thisweek == i)	end end

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

function h.printTabHeader(tbl, i, name, isfocused) local data = mw.clone(TAB_TOGGLES.week) util_toggle.prepDataByWeek(data, i)	data.initshown = isfocused util_toggle.printToggleHeader(tbl, 4, name, data) return end

function h.printDateRange(tbl, tab_data, daterowclass) local tr = tbl:tag('tr') :addClass(daterowclass) local td = tr:tag('td') :attr('colspan',4) 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 h.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 h.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('matchlist-daterange') :addClass('matchlist-daterange-you') :addClass(TZ_TOGGLES.classes.CD) -- manually add this one because it's the same as 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) return 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 h.printFullRow(tbl_tab, row, toggle_class, isfirst) h.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) h.tagRowIfOver(tr, row) h.addDateAttr(tr, row) h.addNewDay(tr, row) h.addFlexStart(tr, row) util_matches.printCustomClass(tr, row) h.printTeam1(tr, row) h.printMiddle(tr, row) h.printTeam2(tr, row) end

function h.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) h.printNormalTZDateToRow(tr, tz, row[tz .. '_Date']) end end local tr = tbl:tag('tr') :addClass(toggle_class) h.printYouTZDateToRow(tr, row.You_Date) if isfirst then tr:attr('data-isfirst','yes') end return end

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

function h.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','4') 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 end

function h.isOver(row) return (row.Team1Score and row.Team2Score) or row.Winner 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 h.printTeam1(tr, row) if not row.Team1 or row.Team1 == 'TBD' 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) util_footnote.tagFootnote(td, row.Team1Footnote) td:wikitext(m_team.leftshort(row.Team1, {link=''})) if row.Winner == 1 then td:addClass('matchlist-winner-team') elseif row.Winner == 0 then td:addClass('matchlist-tied-team') end return end

function h.printTeam2(tr, row) if not row.Team2 or row.Team2 == 'TBD' 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) td:wikitext(m_team.rightshort(row.Team2, {link=''})) util_footnote.tagFootnotes(td, h.team2Footnotes(row)) if row.Winner == 2 then td:addClass('matchlist-winner-team') elseif row.Winner == 0 then td:addClass('matchlist-tied-team') end return end

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

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

function h.printScore(tr, row, suppresstoggle) local td1 = h.printOneScore(tr, row.Team1Score, row.Team1Final) local td2 = h.printOneScore(tr, row.Team2Score, row.Team2Final) 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, RS_TOGGLES, 'res') util_toggle.oflCellClasses(td2, RS_TOGGLES, 'res') end return end

function h.printOneScore(tr, score, team) local td = tr:tag('td'):wikitext(score) :addClass('matchlist-score') util_esports.addTeamHighlighter(td, team) return td end

function h.printTime(tr, row, usetoggle) local td = tr:tag('td') :attr('colspan','2') :addClass('matchlist-time-cell') :addClass('plainlinks') if usetoggle then util_toggle.oflCellClasses(td, 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