local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Teams = game:GetService("Teams")

spawn(function ()
	local StarterGui = game:GetService("StarterGui")
	StarterGui:SetCoreGuiEnabled("PlayerList", false)
	
	local player = Players.LocalPlayer
	
	local playerGui = player:WaitForChild("PlayerGui")
	playerGui:SetTopbarTransparency(1)
end)

--------------------------------------------------------------------------------------------------------------------------------------
-- Setup
--------------------------------------------------------------------------------------------------------------------------------------

local playerStates = {}
local teamGroups = {}

local statLookup = {}
local statNames = {}

local inTeamMode = false

local playerList = script.Parent
local templates = playerList:WaitForChild("Templates")

local basePlayerLbl = templates:WaitForChild("BasePlayerLbl")
local baseGroup = templates:WaitForChild("BaseGroup")
local baseStat = templates:WaitForChild("BaseStat")

local backdrop = playerList:WaitForChild("Backdrop")
local container = playerList:WaitForChild("Container")

local coreGroup = baseGroup:Clone()
coreGroup.Name = "Default"
coreGroup.Parent = container

local coreFooter = coreGroup.Footer
local coreHeader = coreGroup.Header

local eUpdateStatLayout = Instance.new("BindableEvent")
local updateStatLayout = eUpdateStatLayout.Event

local eUpdateTeamTotal = Instance.new("BindableEvent")
local updateTeamTotal = eUpdateTeamTotal.Event

local ePlayerTeamChanged = Instance.new("BindableEvent")
local playerTeamChanged = ePlayerTeamChanged.Event

--------------------------------------------------------------------------------------------------------------------------------------
-- Player Colors
--------------------------------------------------------------------------------------------------------------------------------------

local PLAYER_COLORS = 
{
	[0] = Color3.fromRGB(173,  35,  35); -- red
	[1] = Color3.fromRGB( 42,  75, 215); -- blue
	[2] = Color3.fromRGB( 29, 105,  20); -- green
	[3] = Color3.fromRGB(129,  38, 192); -- purple
	[4] = Color3.fromRGB(255, 146,  51); -- orange
	[5] = Color3.fromRGB(255, 238,  51); -- yellow
	[6] = Color3.fromRGB(255, 205, 243); -- pink
	[7] = Color3.fromRGB(233, 222, 187); -- tan
}

local function computePlayerColor(player)
	local pName = player.Name
	local length = #pName

	local oddShift = (1 - (length % 2))
	local value = 0
	
	for i = 1,length do
		local char = pName:sub(i,i):byte()
		local rev = (length - i) + oddShift

		if (rev % 4) >= 2 then
			value = value - char
		else
			value = value + char	
		end 
	end 
	
	return PLAYER_COLORS[value % 8]
end

--------------------------------------------------------------------------------------------------------------------------------------
-- Backdrop Handler
--------------------------------------------------------------------------------------------------------------------------------------

local isTeamMode = false
local hasStats = false

local size1x1 = Vector2.new(1,1)
local rawGroups = {}

local function onContainerChildAdded(child)
	if child:IsA("Frame") then
		local listLayout = child:WaitForChild("ListLayout",2)
		if listLayout then
			rawGroups[child] = listLayout
		end
	end
end

local function onContainerChildRemoved(child)
	if rawGroups[child] then
		rawGroups[child] = nil
	end
end

local function sortGroups(a,b)
	if a == coreGroup then
		return true
	elseif b == coreGroup then
		return false
	else
		local orderA,orderB = a.LayoutOrder,b.LayoutOrder
		if orderA == orderB then
			return a.Name < b.Name
		else
			return orderA < orderB
		end
	end
end

local function updateBackdrop()
	local groups = {}
	local at = 1
	
	for group in pairs(rawGroups) do
		if group.Visible then
			groups[at] = group
			at = at + 1
		end
	end
	
	local height = 0
	table.sort(groups,sortGroups)
	
	for i = 1,#groups do
		local group = groups[i]
		local layout = rawGroups[group]
		group.Position = UDim2.new(0,0,0,height)
		height = height + layout.AbsoluteContentSize.Y
	end
	
	if #statNames > 0 and not hasStats then
		hasStats = true
		container.AnchorPoint = Vector2.new(1,0)
		for _,group in pairs(groups) do
			group.Header.TeamUnderline.Size = UDim2.new(2,-4,0,1)
		end
		eUpdateStatLayout:Fire()
	elseif #statNames == 0 and hasStats then
		hasStats = false
		container.AnchorPoint = Vector2.new(0,0)
		for _,group in pairs(groups) do
			group.Header.TeamUnderline.Size = UDim2.new(1,-4,0,1)
		end
		eUpdateStatLayout:Fire()
	end
	
	if isTeamMode then
		height = height + coreHeader.AbsoluteSize.Y
	end
	
	local width = container.AbsoluteSize.X * (container.AnchorPoint.X+1)
	backdrop.Size = UDim2.new(0,width,0,height)
end

for _,child in pairs(container:GetChildren()) do
	onContainerChildAdded(child)
end

container.ChildAdded:Connect(onContainerChildAdded)
container.ChildRemoved:Connect(onContainerChildRemoved)
RunService.Heartbeat:Connect(updateBackdrop)

--------------------------------------------------------------------------------------------------------------------------------------
-- Header Size Stuff
--------------------------------------------------------------------------------------------------------------------------------------

local function switchHeaderMode(isTeamMode)
	if isTeamMode then
		coreHeader.Size = UDim2.new(1,0,1/3,0)
		coreHeader.Stats.Size = UDim2.new(0.75,0,1,0)
		coreHeader.Title.Size = UDim2.new(1,0,1,0)
	else
		coreHeader.Size = UDim2.new(1,0,0.4,0)
		coreHeader.Stats.Size = UDim2.new(0.75,0,0.85,0)
		coreHeader.Title.Size = UDim2.new(1,0,0.85,0)
	end
end

switchHeaderMode(false)

--------------------------------------------------------------------------------------------------------------------------------------
-- Player Stats
--------------------------------------------------------------------------------------------------------------------------------------

local function incrementStat(statName)
	if not statLookup[statName] then
		statLookup[statName] = 1
		table.insert(statNames,statName)
	else
		statLookup[statName] = statLookup[statName] + 1
	end
	eUpdateStatLayout:Fire()
end

local function decrementStat(statName)
	if statLookup[statName] then
		statLookup[statName] = statLookup[statName] - 1
		
		if statLookup[statName] == 0 then
			statLookup[statName] = nil
			for i,name in ipairs(statNames) do
				if name == statName then
					table.remove(statNames,i)
					break
				end
			end
		end
		
		eUpdateStatLayout:Fire()
	end	
end

local function getPlayerStateFromStat(stat)
	local leaderstats = stat.Parent
	if leaderstats then
		local player = leaderstats.Parent
		if player then
			return playerStates[player]
		end
	end
end

local function refreshTeamStats()
	for _,team in pairs(Teams:GetTeams()) do
		eUpdateTeamTotal:Fire(team)
	end
end

local function onStatRemoved(stat,statName)
	if stat.ClassName == "IntValue" then
		local statName = statName or stat.Name
		local playerState = getPlayerStateFromStat(stat)

		if playerState and playerState.Stats[statName] then
			playerState.Stats[statName]:Destroy()
			playerState.Stats[statName] = nil
		end

		decrementStat(statName)
		refreshTeamStats()
	end
end

local function onStatAdded(stat)
	if stat.ClassName == "IntValue" then
		local statName = stat.Name
		local playerState = getPlayerStateFromStat(stat)

		if playerState then
			local changeSignal
			
			if not playerState.Stats[statName] then
				local statLbl = baseStat:Clone()
				statLbl.Name = statName
				
				local function updateStat()
					statLbl.Text = stat.Value
					if isTeamMode then
						local team = playerState.Player.Team
						if team then
							eUpdateTeamTotal:Fire(team)
						end
					end
				end
				
				updateStat()
				changeSignal = stat.Changed:Connect(updateStat)
				
				statLbl.Parent = playerState.Label.Stats
				playerState.Stats[statName] = statLbl
			end
			
			local nameSignal do
				local function onNameChanged()
					if changeSignal then
						changeSignal:Disconnect()
						changeSignal = nil
					end

					nameSignal:Disconnect()
					nameSignal = nil
					
					-- Rebuild the stat
					onStatRemoved(stat,statName)
					onStatAdded(stat)
				end
				
				nameSignal = stat:GetPropertyChangedSignal("Name"):Connect(onNameChanged)
			end
		end
		
		incrementStat(statName)
		refreshTeamStats()
	end
end

local function onPlayerChildAdded(leaderstats)
	if leaderstats.Name == "leaderstats" then
		local player = leaderstats.Parent
		local playerState = playerStates[player]

		if playerState and not playerState.leaderstats then
			playerState.leaderstats = leaderstats

			for _,stat in pairs(leaderstats:GetChildren()) do
				onStatAdded(stat)
			end

			leaderstats.ChildAdded:Connect(onStatAdded)
			leaderstats.ChildRemoved:Connect(onStatRemoved)
		end
	end
end

local function onPlayerChildRemoved(child)
	if child.Name == "leaderstats" then
		for _,stat in pairs(child:GetChildren()) do
			onStatRemoved(stat)
		end

		for player,playerState in pairs(playerStates) do
			if playerState.leaderstats == child then
				playerState.leaderstats = nil
				break
			end
		end
	end
end

local function updateStatLbl(statLbl,index)
	statLbl.Size = UDim2.new(1/#statNames,0,1,0)
	statLbl.Position = UDim2.new((index-1)/#statNames)	
end

local function onUpdateStatLayout()
	local statBin = coreHeader.Stats
	
	for _,statLbl in pairs(statBin:GetChildren()) do
		if statLbl:IsA("TextLabel") and not statLookup[statLbl.Name] then
			statLbl:Destroy()
		end
	end

	for i,statName in pairs(statNames) do
		local statLbl = statBin:FindFirstChild(statName)

		if not statLbl then
			statLbl = baseStat:Clone()
			statLbl.Name = statName
			statLbl.Text = statName
			statLbl.Parent = statBin
		end

		updateStatLbl(statLbl,i)
	end
	
	for player,playerState in pairs(playerStates) do
		for statName,statLbl in pairs(playerState.Stats) do
			if not statLookup[statName] then
				statLbl:Destroy()
				playerState.Stats[statName] = nil
			end
		end

		for i,statName in pairs(statNames) do
			local statLbl = playerState.Stats[statName]

			if statLbl then
				if player.Team then
					statLbl.TextColor = player.Team.TeamColor
				else
					statLbl.TextColor3 = Color3.new(1,1,1)
				end
				updateStatLbl(statLbl,i)
			end
		end
	end
	
	isTeamMode = (#Teams:GetTeams() > 0)
	
	if isTeamMode then
		coreHeader.Visible = hasStats
		coreHeader.Title.Text = "Team"
	else
		coreHeader.Visible = true
		if hasStats then
			coreHeader.Title.Text = "Players"
		else
			coreHeader.Title.Text = "Player List"
		end
	end
	
	switchHeaderMode(isTeamMode)
end

updateStatLayout:Connect(onUpdateStatLayout)

--------------------------------------------------------------------------------------------------------------------------------------
-- Player States
--------------------------------------------------------------------------------------------------------------------------------------

local function onPlayerAdded(player)
	local playerState = {}
	local name = player.Name
	
	local lbl = basePlayerLbl:Clone()
	lbl.Name = name
	lbl.PlayerName.Text = name
	lbl.PlayerName.TextColor3 = computePlayerColor(player)
	lbl.Parent = coreGroup
	
	playerState.Player = player
	playerState.Label = lbl
	playerState.Stats = {}
	
	playerStates[player] = playerState
	
	for _,child in pairs(player:GetChildren()) do
		onPlayerChildAdded(child)
	end
	
	player.ChildAdded:Connect(onPlayerChildAdded)
	player.ChildRemoved:Connect(onPlayerChildRemoved)
	
	player.Changed:Connect(function (property)
		if property == "Team" then
			ePlayerTeamChanged:Fire(player)
		end
	end)

	ePlayerTeamChanged:Fire(player)
end

local function onPlayerRemoved(player)
	local state = playerStates[player]
	playerStates[player] = nil
	
	if state and state.Label then
		state.Label:Destroy()
	end
end

for _,player in pairs(Players:GetPlayers()) do
	onPlayerAdded(player)
end

Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoved)

--------------------------------------------------------------------------------------------------------------------------------------
-- Teams
--------------------------------------------------------------------------------------------------------------------------------------

local function neutralizePlayer(player)
	local playerState = playerStates[player]

	if playerState then
		local playerLbl = playerState.Label
		playerLbl.PlayerName.Text = player.Name
		playerLbl.PlayerName.TextColor3 = computePlayerColor(player)
		playerLbl.PlayerName.Position = UDim2.new(0, 0, 0, 0)

		for stat,statLbl in pairs(playerState.Stats) do
			statLbl.TextColor3 = Color3.new(1,1,1)
		end

		playerLbl.Visible = (not isTeamMode)
		playerLbl.Parent = coreGroup
	end
end

local function onPlayerAddedToTeam(player)
	local team = player.Team
	local group = teamGroups[team]

	if group then
		local playerState = playerStates[player]
		if playerState then
			local playerLbl = playerState.Label
			playerLbl.PlayerName.TextColor = team.TeamColor
			playerLbl.PlayerName.Position = UDim2.new(0, 4, 0, 0)

			for stat, statLbl in pairs(playerState.Stats) do
				statLbl.TextColor = team.TeamColor
			end

			playerLbl.Parent = group
			playerLbl.Visible = true

			eUpdateStatLayout:Fire()
			refreshTeamStats()
		end
	end
end

local function onPlayerRemovedFromTeam(player)
	if not player.Team then
		neutralizePlayer(player)
		refreshTeamStats()
	end
end

local function onUpdateTeamTotal(team)
	local teamGroup = teamGroups[team]

	if teamGroup then
		local teamStats = teamGroup.Header.Stats
		local totals = {}
		
		for i,statName in ipairs(statNames) do
			local total = totals[i]

			if not total then
				total = { Name = statName, Value = 0 }
				totals[i] = total
			end

			for _,player in pairs(team:GetPlayers()) do
				local playerState = playerStates[player]
				
				if playerState then
					local leaderstats = playerState.leaderstats
					if leaderstats then
						local stat = leaderstats:FindFirstChild(statName)
						if stat then
							total.Value = total.Value + stat.Value
						end
					end
				end
			end
		end
		
		local numStats = #statNames
		
		for i,statRecord in ipairs(totals) do
			local statName = statRecord.Name
			local statLbl = teamStats:FindFirstChild(statName)

			if not statLbl then
				statLbl = baseStat:Clone()
				statLbl.Name = statName
				statLbl.TextColor = team.TeamColor
				statLbl.TextStrokeTransparency = 0.5
				statLbl.Parent = teamStats
			end

			statLbl.Text = statRecord.Value
			updateStatLbl(statLbl,i)
		end
		
		for _,statLbl in pairs(teamStats:GetChildren()) do
			if not statLookup[statLbl.Name] then
				statLbl:Destroy()
			end
		end
	end
end

local function onTeamAdded(team)
	if team.ClassName == "Team" then
		local teamGroup = baseGroup:Clone()
		teamGroup.Name = team.Name
		teamGroup.Footer.Visible = true
		
		local teamHeader = teamGroup.Header
		
		local teamUnderline = teamHeader.TeamUnderline
		teamUnderline.Visible = true
		teamUnderline.BackgroundColor = team.TeamColor
		
		if hasStats then
			teamUnderline.Size = teamUnderline.Size + UDim2.new(1,0,0,0)
		end
		
		local teamTitle = teamHeader.Title
		teamTitle.Text = team.Name
		teamTitle.TextColor = team.TeamColor
		teamTitle.TextStrokeTransparency = 0.5
		
		teamGroup.Parent = container
		teamGroups[team] = teamGroup
		
		for _,player in pairs(team:GetPlayers()) do
			onPlayerAddedToTeam(player)
		end
		
		eUpdateTeamTotal:Fire(team)
		eUpdateStatLayout:Fire()
	end
	if #Teams:GetTeams() > 0 and not isTeamMode then
		isTeamMode = true
		for _,player in pairs(Players:GetPlayers()) do
			if not player.Team then
				neutralizePlayer(player)
			end
		end
	end
end

local function onTeamRemoved(team)
	if teamGroups[team] then
		for _,player in pairs(Players:GetPlayers()) do
			if player.TeamColor == team.TeamColor then
				neutralizePlayer(player)
			end
		end

		teamGroups[team]:Destroy()
		teamGroups[team] = nil

		eUpdateStatLayout:Fire()
	end

	if #Teams:GetTeams() == 0 then
		isTeamMode = false
		for _,player in pairs(Players:GetPlayers()) do
			neutralizePlayer(player)
		end
	end
end

local function onPlayerTeamChange(player)
	local team = player.Team

	if team then
		onPlayerAddedToTeam(player)
	else
		onPlayerRemovedFromTeam(player)
	end
end

for _,team in pairs(Teams:GetTeams()) do
	onTeamAdded(team)
end

for _,player in pairs(Players:GetPlayers()) do
	onPlayerTeamChange(player)
end

Teams.ChildAdded:Connect(onTeamAdded)
Teams.ChildRemoved:Connect(onTeamRemoved)

updateTeamTotal:Connect(onUpdateTeamTotal)
playerTeamChanged:Connect(onPlayerTeamChange)

--------------------------------------------------------------------------------------------------------------------------------------