Module:PlayerTeamHistoryAbstract

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_math = require("Module:MathUtil") 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_time = require("Module:TimeUtil") local util_timedelta = require("Module:TimedeltaUtil") local util_vars = require("Module:VarsUtil") local i18n = require('Module:i18nUtil')

local p = require('Module:GroupedRosterChangesAbstract'):extends local h = {}

function p:init(name) self:super('init', name) self.STATUSES_TO_IGNORE = { set_to_leave = true, opportunities = true, }	self.PRELOADS_TO_IGNORE = { 'extended', 'gcd_extended', 'remain', 'expire_notleave', 'set_to_leave', 'opportunities', 'set_to_leave_already_joined', 'gcd_expire_notleave', 're_sign', 'gcd_remove_notleave', leave = { 'retirement_no_team' }, join = { 'gcd_expire_notleave_yet', }, }	self.INCLUDE_STATUS = false self.DEBUG = false self.CONTRACT_DATES = {} end

function p:run(args) util_cargo.setStoreNamespace('') h.castArgs(args) self:setConstants(args) self:setDebugIfEnabled return self:super('run', args) end

function h.castArgs(args) args.player = util_args.strOrTitle(args.player) end

function p:setConstants(args) if util_args.castAsBool(args.debug) then self.DEBUG = true end end

function p:setDebugIfEnabled if self.DEBUG then self.COLUMNS[#self.COLUMNS+1] = 'KeyJoin' self.COLUMNS[#self.COLUMNS+1] = 'KeyLeave' self.COLUMNS[#self.COLUMNS+1] = 'PreloadJoin' self.COLUMNS[#self.COLUMNS+1] = 'PreloadLeave' self.COLUMNS[#self.COLUMNS+1] = 'SisterTeamPage' self.COLUMNS[#self.COLUMNS+1] = 'Status' self.COLUMNS[#self.COLUMNS+1] = 'ContractEnd' -- self.COLUMNS[#self.COLUMNS+1] = 'NextTeam' self.COLUMNS[#self.COLUMNS+1] = 'changeIndex' end end

function p:getTables local ret = { 'NewsItems__Players', 'NewsItems=News', 'PlayerRedirects=PR', 'RosterChanges=RC', 'PlayerRenames=RN', 'ResidencyChanges=ResChange', 'Contracts', 'TeamRedirects=TRed1', -- join from contracts to ST1 'TeamRedirects=TRed2', -- join from news to ST2 'SisterTeams=ST1', -- the sister team page (if one exists) of the contract team 'SisterTeams=ST2', -- the sister team page (if one exists) of the news team }	return ret end

function p:getJoin -- remember this join is JUST for retrieving fields -- so we dont have to worry about one-to-many conditions really, because nothing is happening -- in any where conditions -- so all of the redirects and stuff just discovering the target -- not then also joining to another field making a many:many local ret = { -- joins to associate all of the relevant news in order 'NewsItems__Players._rowID=News._ID', 'NewsItems__Players._value=PR.AllName', 'News.NewsId=RC.NewsId', 'News.NewsId=RN.NewsId', 'News.NewsId=ResChange.NewsId', 'News.NewsId=Contracts.NewsId', -- contracts need to respect contracts from the same org 'Contracts.Team=TRed1.AllName', 'TRed1._pageName=ST1.Team', -- roster changes need to discover the org so we can pull contracts 'RC.Team=TRed2.AllName', 'TRed2._pageName=ST2.Team', }	return ret end

function p:getWhere(args) local tbl = { ('PR._pageName="%s"'):format(args.player), util_news.getExcludedPreloadsWhereCondition(self.PRELOADS_TO_IGNORE), }	util_table.mergeArrays(tbl, h.getCorrectPlayerWhereConditions) return util_cargo.concatWhere(tbl) end

function h.getCorrectPlayerWhereConditions local tablesNeedingConditions = { 'RC', 'Contracts' } return util_map.inPlace(tablesNeedingConditions, h.getOneCorrectPlayerWhereCondition) end

function h.getOneCorrectPlayerWhereCondition(tbl) return ('%s.Player=NewsItems__Players._value OR %s.Player IS NULL'):format(tbl, tbl) end

function p:getFields local fields = self:super('getFields') local newFields = { 'RN.OriginalName=NameOld', 'ResChange.ResidencyOld=ResidencyOld', 'News._pageName=ResPageName', 'Contracts.ContractEnd', 'Contracts.IsRemoval=IsContractRemoval [boolean]', 'TRed2._pageName=TeamPage', -- Here's a bunch of info about the SisterTeamPage field, which is ridiculous -- priority: --	first, we want the Contracts SisterTeams entry if it exists --	if not, then just try and follow the redirect of the team --	if not, then the Contracts Team entry if it exists --	if not, we're not in a Contracts news entry anymore --	so next, try and grab sister teams if it exists --	if not, then at least try and follow the redirect of the team --	finally, otherwise just use the news team -- the TRed2._pageName was added because of Froggy who moved from -- Dashing Buffalo to Saigon Buffalo with team_rename_same_page -- TRed1._pageName was added anticipating a similar issue 'COALESCE(ST1._pageName, TRed1._pageName, Contracts.Team, ST2._pageName, TRed2._pageName, RC.Team)=SisterTeamPage', }	return util_table.mergeArrays(fields, newFields) end

function p:getGroupBy return 'COALESCE(RC.RosterChangeId, News.NewsId)' end

-- format raw data rows function p:formatOneNonRCRow(row) if row.IsContractRemoval then self.CONTRACT_DATES[row.SisterTeamPage] = nil end if not row.ContractEnd then return end self.CONTRACT_DATES[row.SisterTeamPage] = row.ContractEnd end

function p:formatOneRawDataRow(row) self:super('formatOneRawDataRow', row) -- dealing with contracts here will bring information *forward* when a player is on same sister team -- and we want to preserve the fact that they had a contract already -- this construction is done as we sort through the changes and apply formatting -- later on we'll go back through the entire thing a 2nd time to do "after-the-fact" information -- which will apply "normal" contract information, when contract info is stored *after* the -- joining-the-team roster change in question -- because maps happen forward, we can just do this in order -- CONTRACT_DATES is the repository of info we maintain moving forward local potentialContractDate = self.CONTRACT_DATES[row.SisterTeamPage] if row.Date_SortJoin and potentialContractDate and potentialContractDate > row.Date_SortJoin then row.ContractEnd = potentialContractDate end end

function p:getKey(row) local key = ('%s%s%s%s'):format(		row.Team or '',		row.RoleModifier or 'Normal',		row.Role or 'Unknown',		self:getAndSetMeaningfulStatus(row)	) return key end

function p:getAndSetMeaningfulStatus(row) if self.STATUSES_TO_IGNORE[row.Status] then row.Status = nil end return row.Status or '' end

-- group stuff function p:updateAncillaryInformation(changesByLine, queryRow) -- this is now propagating stuff backwards -- for each new line we add, we go back through our entire output and check if we want to update anything -- this has to be done in order, not all at once in the end, because information changes over time -- so for the most part we'll go back and say, "what teams is the player CURRENTLY on at time of news?" -- and then update this piece of information (e.g. residency change) on all of them -- but we'll leave any rows alone that already have departures for _, outputRow in ipairs(changesByLine) do		h.updateRowAncillaryInformationIfNeeded(outputRow, queryRow) end end

function h.updateRowAncillaryInformationIfNeeded(outputRow, queryRow) if not outputRow.TeamLeave then -- information that needs to get updated when the player is CURRENTLY on -- the team. This includes contract information. h.updateAncillaryInformationOnCurrentTeamsIfNeeded(outputRow, queryRow) -- do not continue to do the only-after-they-left stuff return end -- these are PRE-CHANGE values, so when they occur it means only apply them -- when you already left the team -- if you're still on the team, either the NEXT old one will apply, or the one in infobox will apply for _, v in ipairs({ 'NameOld', 'ResidencyOld', }) do		h.updateAncillaryValueInRow(outputRow, queryRow, v)	end end

function h.updateAncillaryInformationOnCurrentTeamsIfNeeded(outputRow, queryRow) -- this is part of the propagating information backward process if queryRow.ContractEnd and queryRow.SisterTeamPage == outputRow.SisterTeamPage then outputRow.ContractEnd = queryRow.ContractEnd end end

function h.updateAncillaryValueInRow(outputRow, queryRow, value) -- don't overwrite. also it's possible value is nil but whatever. outputRow[value] = outputRow[value] or queryRow[value] end

function p:isOriginalNews(changesByLine, row, index) if not index then return true end if not index then return true end for _, oldChangeLineKey in ipairs(index) do		local oldChange = self:getOldChangeFromIndex(changesByLine, oldChangeLineKey) if h.isThisLineARepeatOfAnOldOne(row, oldChange) then return false end end return true end

function h.isThisLineARepeatOfAnOldOne(row, oldRow) if row.TeamJoin and not oldRow.TeamLeave then return h.teamsWhenAreIdentical(row, oldRow, 'Join') end return false end

function h.teamsWhenAreIdentical(row, oldRow, when) for _, v in ipairs({ 'Team', 'RoleModifier', 'Role', 'Status' }) do if row[v .. when] ~= oldRow[v .. when] then return false end end return true end

function p:doWeNeedANewLine(changesByLine, row, index) if not row.TeamLeave then return true end return not index end

function p:getOldChangeFromIndex(changesByLine, oldChangeLineKey) return changesByLine[oldChangeLineKey.changeNumber] end

function p:getMostRecentLineNumberFromIndex(index) return index[#index].changeNumber end

function p:updateIndex(changesByLine, row, index) index[#index+1] = { changeNumber = #changesByLine } end

function p:addNewTeamToExistingLine(line, row) -- we earlier split stuff into separate start/end so we should be fine to just merge now if line.TeamLeave then return end self:super('addNewTeamToExistingLine', line, row) end

-- formatting function p:formatRowForOutput(row) row.PlayerLinked = util_esports.playerLinked(row.Player) row.RoleDisplay = row.Roles:images{ sep="", size=15 } row.TeamDisplay = self:getTeamDisplay(row) row.RegionDisplay = row.Region:image row.DateLeaveDisplay = self:getDateDisplay(row, 'Leave') row.DateJoinDisplay = self:getDateDisplay(row, 'Join') row.DurationDisplay = util_timedelta.approxDurationDisplay(self:getDurationArgs(row)) self:getStatusAndUpdateFlag(row) self:addSortKeys(row) h.correctMissingDates(row) end

function p:getTeamDisplay(row) end

function p:getDateDisplay(row, when) end

function p:getDurationArgs(row) local tbl = { row.DateJoin or 'xxxx-xx-xx', row.DateLeave or os.date('%Y-%m-%d'), row.IsApproxDateJoin or row.IsApproxDateLeave, }	return unpack(tbl) end

function p:getStatusAndUpdateFlag(row) end

function h.correctMissingDates(row) if not row.DateJoinDisplay then row.DateJoinDisplay = '??? ????'		h.addSortKey(row, 'DateJoinDisplay', -1) end if not row.DateLeaveDisplay then row.DateLeaveDisplay = ("%s"):format(i18n.print('present')) h.addSortKey(row, 'DateLeaveDisplay', 9999999999999) end end

function p:addSortKeys(row) h.addSortKey(row, 'DateJoinDisplay', row.index) h.addSortKey(row, 'TeamDisplay', row.Team) h.addSortKey(		row,		'DateLeaveDisplay',		util_time.unix(row.Date_SortLeave)	) h.addSortKey(		row,		'DurationDisplay',		util_timedelta.approxDurationSeconds(self:getDurationArgs(row))	) end

function h.addSortKey(row, col, sortkey) if not row[col] then return end row[col .. '_Sort'] = sortkey end

function p:setEarlierRowNextTeamValues(changesByLine, queryRow) if queryRow.Direction == 'Leave' then return end for _, outputRow in ipairs(changesByLine) do		if not outputRow.TeamLeave then -- we can't add a NextTeam if they're still on the team else outputRow.NextTeam = outputRow.NextTeam or queryRow.Team outputRow.changeIndexNext = 'queryRow.index' end end end

-- cargo STORE function p:storeCargo(changesByLine) end

-- output function p:makeOutput(changesByLine) self:setColumnsBasedOnIncludedInformation local output = mw.html.create local tbl = output:tag('table') :addClass('player-team-history') :addClass('hoverable-rows') :addClass('sortable') self:printHeader(tbl) util_map.selfRowsInPlace(self, changesByLine, p.printOneRow, tbl) return output end

function p:printHeader(tbl) local tr = tbl:tag('tr') for _, col in ipairs(self.COLUMNS) do		tr:tag('th') :wikitext(i18n.print(col)) :attr('data-sort-type', self.COLUMNS.sorttypes[col]) :addClass(self.COLUMNS.colclasses[col]) end end

function p:setColumnsBasedOnIncludedInformation if self.INCLUDE_STATUS then self.COLUMNS[#self.COLUMNS+1] = 'StatusDisplay' end self.COLUMNS.classes[self.COLUMNS[#self.COLUMNS]] = 'roster-portal-lastcell' end

function p:printOneRow(row, tbl) local tr = tbl:tag('tr') for _, col in ipairs(self.COLUMNS) do		self:printOneCell(tr, row, col, self.COLUMNS) end self:printEditButtons(util_html.lastChild(tr), row) end

function p:printOneCell(tr, row, col) tr:tag('td') :wikitext(row[col]) :addClass(self.COLUMNS.classes[col]) :attr('data-sort-value', row[col .. '_Sort']) end

function p:printEditButtons(td, row) end

return p