Module:CurrentRostersPortal

local util_args = require('Module:ArgsUtil') local util_cargo = require("Module:CargoUtil") local util_esports = require("Module:EsportsUtil") local util_html = require("Module:HtmlUtil") local util_map = require("Module:MapUtil") local util_news = require("Module:NewsUtil") local util_source = require("Module:SourceUtil") local util_table = require("Module:TableUtil") local util_text = require("Module:TextUtil") local util_title = require("Module:TitleUtil") local util_vars = require("Module:VarsUtil") local i18n = require('Module:i18nUtil')

local ListOfRoles = require('Module:ListOfRoles') local OD = require('Module:OrderedDict')

local m_team = require('Module:Team') local Role = require('Module:Role')

local PARITY local OVER = false

local PRELOADS_TO_IGNORE = { 'opportunities', 'set_to_leave', 'to_main_also_stay', 'to_academy_also_stay', 'gcd_remove_notleave', join = {}, leave = {}, }

local h = {}

local p = {}

function p.intro(frame) local args = util_args.merge util_vars.setVar('season1', args[1]) util_vars.setVar('season2', args[2]) return '' end

function p.main(frame) local args = util_args.merge i18n.init('CurrentRostersPortal') h.castArgs(args) h.setConstants(args) local playersBefore = util_news.getPlayersFromArg(args.players) h.addTagsToPlayersBefore(playersBefore) local processed = h.getProcessedDataFromPlayers(playersBefore) local listOfChanges = h.queryForChanges(args, playersBefore) h.formatDataFromQuery(listOfChanges) local netStatuses = h.computeNetStatuses(listOfChanges) h.addChangesToProcessedPlayersList(processed, netStatuses) h.moveJoiningPlayersToEmptySlots(processed) return h.makeOutput(processed, args) end

function h.castArgs(args) args.team = m_team.teamlinkname(args.team) args.team2 = args.team2 and m_team.teamlinkname(args.team2) args.over = util_args.castAsBool(args.over) end

function h.setConstants(args) OVER = args.over end

function h.addTagsToPlayersBefore(playersBefore) h.mergeTagsIntoPlayersBefore(playersBefore, h.queryTagsForPlayersBefore(playersBefore)) end

function h.mergeTagsIntoPlayersBefore(playersBefore, newData) for _, player, playerData in ipairs(playersBefore) do		util_table.merge(playerData, newData[mw.ustring.lower(player)]) end end

function h.queryTagsForPlayersBefore(playersBefore) return util_cargo.getRowDict(h.getBeforeTagsQuery(playersBefore), 'PlayerPageOrRedirect') end

function h.getBeforeTagsQuery(playersBefore) local query = { tables = { 'Players=P', 'PlayerRedirects=PR' }, join = { 'P._pageName=PR._pageName' }, where = h.getBeforeTagsWhere(playersBefore), fields = { 'Residency [region]', 'PR.AllName=PlayerPageOrRedirect [unicodelowercase]', },	}	return query end

function h.getBeforeTagsWhere(playersBefore) return playersBefore:formatAndConcatKeys(' OR ', 'PR.AllName="%s"') end

-- args for before function h.getProcessedDataFromPlayers(playersBefore) local processed = { playerLookup = {}, byRole = h.initProcessedByRole }	for _, _, playerData in ipairs(playersBefore) do		h.addBeforePlayerToRole(processed, playerData) end return processed end

function h.initProcessedByRole local ret = OD for _, role in ipairs(ListOfRoles.current_rosters_list) do		ret:set(role, {}) end return ret end

function h.addBeforePlayerToRole(processed, playerData) if not processed.byRole:get(playerData.Role) then h.initProcessedByRoleForUntrackedRole(processed.byRole, playerData.Role) end local roleData = processed.byRole:get(playerData.Role) roleData[#roleData+1] = { before = playerData } processed.playerLookup[playerData.PlayerLink] = { role = playerData.Role, i = #roleData, Residency = playerData.Residency } end

function h.initProcessedByRoleForUntrackedRole(byRole, role) byRole:set(role, { untracked = true }) end

-- query for after function h.queryForChanges(args, playersBefore) return util_cargo.queryAndCast(h.getChangesQuery(args, playersBefore)) end

function h.getChangesQuery(args, playersBefore) local query = { fields = { 'RC.Date_Sort', 'RC.Player', 'PR._pageName=PlayerLink', 'RC.Team', 'RC.Role', 'RC.Source', 'RC.Preload', 'RC.Direction', 'News.Date_Display', 'News.Region', 'News._pageName', 'News.NewsId', 'P.Residency [region]', },		tables = { 'RosterChanges=RC', 'NewsItems=News', 'PlayerRedirects=PR', 'Players=P', 'RosterChangePortalDates=Dates', },		join = { 'RC.NewsId=News.NewsId', 'RC.Player=PR.AllName', 'PR._pageName=P._pageName', 'News.Region=Dates.Region' },		where = h.getChangesWhere(args, playersBefore), orderBy = 'RC.Date_Sort ASC, News.N_LineInDate ASC, RC.N_LineInNews ASC', }	return query end

function h.getChangesWhere(args, playersBefore) local tbl = { h.getTeamWhereCondition(args), 'PR._pageName IS NOT NULL', 'News.ExcludePortal IS NULL OR News.ExcludePortal != "1"', -- disallow trainees here. TODO: put this into PRELOADS_TO_IGNORE 'RC.RoleModifier != "Trainee" OR RC.RoleModifier IS NULL', util_news.getExcludedPreloadsWhereCondition(PRELOADS_TO_IGNORE), }	util_table.mergeArrays(tbl, h.getDateWhereCondition(args)) return util_cargo.concatWhere(tbl) end

function h.getTeamWhereCondition(args) local tbl = { ('RC.Team="%s"'):format(args.team), util_cargo.whereFromArg('RC.Team="%s"', args.team2) }	return util_cargo.concatWhereOr(tbl) end

function h.getDateWhereCondition(args) local ret = { ('Dates.PeriodName="%s"'):format(args.period), 'RC.Date_Sort > Dates.DateStart', 'RC.Date_Sort < Dates.DateEnd', }	return ret end

function h.formatDataFromQuery(listOfChanges) util_map.rowsInPlace(listOfChanges, h.formatOneRowOfDataFromQuery) end

function h.formatOneRowOfDataFromQuery(row) if not row.Direction then error(i18n.print('error_missingDirection', row.NewsId)) end for _, v in ipairs({ 'Role', 'Team' }) do row[v .. row.Direction] = row[v] end end

function h.computeNetStatuses(listOfChanges) local currentPlayerStatuses = {} for _, row in ipairs(listOfChanges) do		currentPlayerStatuses[row.PlayerLink] = row end return currentPlayerStatuses end

-- concat before & after data function h.addChangesToProcessedPlayersList(processed, netStatuses) for player, status in pairs(netStatuses) do		h.updatePlayerWithStatus(processed, player, status) end end

function h.updatePlayerWithStatus(processed, player, status) local oldIndex = processed.playerLookup[player] if not oldIndex then h.addNewAfterPlayer(processed.byRole, status, 'join') return end h.updateOldLocation(		processed.byRole,		processed.byRole:get(oldIndex.role)[oldIndex.i],		status	) end

function h.addNewAfterPlayer(byRole, status, statusAfter) -- only actually add something if the player is on the team at the end if not status.TeamJoin then return end h.addNewAfterPlayerToLocation(byRole:get(status.RoleLeave or status.RoleJoin), status, statusAfter) end

function h.addNewAfterPlayerToLocation(location, status, statusAfter) if not location then return end location[#location+1] = { after = status, statusAfter = statusAfter } end

function h.updateOldLocation(byRole, row, status) if not status.TeamJoin or status.Preload == 'loaned_to' then row.statusBefore = 'leave' return end if status.RoleJoin ~= row.before.Role then row.statusBefore = 'roleswap' h.addNewAfterPlayer(byRole, status, 'roleswap') return end row.after = status row.skip = true row.statusBefore = 'stay' end

function h.moveJoiningPlayersToEmptySlots(processed) for _, role, roleData in ipairs(processed.byRole) do		h.moveJoiningPlayersToEmptySlotsInRole(roleData) end end

function h.moveJoiningPlayersToEmptySlotsInRole(roleData) local emptySlots = h.getListOfEmptySlots(roleData) local rowsToRemove = {} for i, row in ipairs(roleData) do		if not row.before and next(emptySlots) then rowsToRemove[#rowsToRemove+1] = i			local oldSlot = table.remove(emptySlots, 1) util_table.merge(roleData[oldSlot], row) end end h.removeConsolidatedRows(roleData, rowsToRemove) end

function h.getListOfEmptySlots(roleData) local emptySlots = {} for i, row in ipairs(roleData) do		if row.statusBefore == 'leave' or row.statusBefore == 'roleswap' then emptySlots[#emptySlots+1] = i		end end return emptySlots end

function h.removeConsolidatedRows(roleData, rowsToRemove) for i = #rowsToRemove, 1, -1 do		table.remove(roleData, rowsToRemove[i]) end end

-- print function h.makeOutput(processed, args) local output = mw.html.create local tbl = output:tag('table') :addClass('wikitable2') :addClass('rosterswap-current') h.printHeading(tbl, args) h.printRoles(tbl, processed) return output end

function h.printHeading(tbl, args) h.printTopLevelHeading(tbl, args) h.printSeasons(tbl) end

function h.printTopLevelHeading(tbl, args) if args.team2 then h.printHeadingTwoTeams(tbl, args) return end h.printHeadingOneTeam(tbl, args) end

function h.printHeadingTwoTeams(tbl, args) local tr = tbl:tag('tr') tr:tag('th'):attr('rowspan',2) :addClass('rosterswap-current-teamlogo-corner') tr:tag('th') :wikitext(m_team.onlyimagelinked(args.team)) :addClass('rosterswap-current-old') tr:tag('th') :wikitext(m_team.onlyimagelinked(args.team2)) :addClass('rosterswap-current-new') end

function h.printHeadingOneTeam(tbl, args) local tr = tbl:tag('tr') tr:tag('th'):attr('rowspan',2):wikitext(m_team.onlyimagelinked(args.team)) :addClass('rosterswap-current-teamlogo-corner') tr:tag('th'):attr('colspan',2):wikitext(m_team.mediumplainlinked(args.team)) end

function h.printSeasons(tbl) local tr = tbl:tag('tr') tr:tag('th') :addClass('rosterswap-current-season') :wikitext(util_vars.getVar('season1') or i18n.print('before')) tr:tag('th') :addClass('rosterswap-current-season') :addClass('rosterswap-current-new') :wikitext(util_vars.getVar('season2') or i18n.print('after')) end

function h.printRoles(tbl, processed) for i, role, data in ipairs(processed.byRole) do		PARITY = i % 2 h.printOneRole(tbl, role, data) end end

function h.printOneRole(tbl, role, data) if data.untracked then return end local tr = tbl:tag('tr') :addClass('rosterswap-current-firstline') h.printRowParityClass(tr) h.printRowLabel(tr, role, data) h.printRolePlayers(tbl, tr, data) end

function h.printRowParityClass(tr) tr:addClass('rosterswap-current-parity-' .. PARITY) end

function h.printRowLabel(tr, role, data) local td = tr:tag('td') :wikitext(Role(role):get('portal')) if #data == 0 then return end td:attr('rowspan', #data) td:addClass('rosterswap-current-rolename') end

function h.printRolePlayers(tbl, tr, data) if #data == 0 then h.printEmptyRow(tr) return end h.printNonemptyRow(tbl, tr, data) end

function h.printEmptyRow(tr) h.printOneRolePlayer(tr, {}) end

function h.printNonemptyRow(tbl, tr, data) for i, row in ipairs(data) do		if i == 1 then h.printOneRolePlayer(tr, row) else tr = tbl:tag('tr') h.printRowParityClass(tr) h.printOneRolePlayer(tr, row) end end end

function h.printOneRolePlayer(tr, row) h.printBefore(tr, row, row.statusBefore, row.before) h.printAfter(tr, row, row.statusAfter, row.after) end

function h.printBefore(tr, row, status, data) local td = tr:tag('td') :addClass('rosterswap-current-old') if not data then return end td:addClass(h.getStatusClass(status)) h.setCellAsStayIfNeeded(td, status, row) h.printPlayer(td, status, data) end

function h.getStatusClass(status) if not status then return nil end return ('rosterswap-current-%s'):format(status) end

function h.setCellAsStayIfNeeded(td, status, row) if status == 'leave' then return end if status == 'stay' or (OVER and not row.after) then h.setCellAsStay(td) end end

function h.setCellAsStay(td) td:attr('colspan', 2) end

function h.printAfter(tr, row, status, data) if h.doWeSkipAfter(row, data) then return end local td = tr:tag('td') :addClass('rosterswap-current-new') if not data then return end h.printPlayer(td, status, data) end

function h.doWeSkipAfter(row, data) if row.skip then return true end if data then return false end if row.status == 'leave' then return false end return OVER end

function h.printPlayer(td, status, playerData) td:addClass(h.getStatusClass(status)) if playerData.Residency then td:wikitext(playerData.Residency:image) end td:wikitext(util_esports.playerLinked(playerData.Player)) end

return p