function methods.InternalDestroy(u)for v,o in pairs(u.Speakers)do o:LeaveChannel(u.Name)end u.eDestroyed:Fire()u.eDestroyed:Destroy()u.eMessagePosted:Destroy()u.eSpeakerJoined:Destroy()u.eSpeakerLeft:Destroy()u.eSpeakerMuted:Destroy()u.eSpeakerUnmuted:Destroy()end function methods.InternalDoMessageFilter(t,u,v,o)local z=t.FilterMessageFunctions:GetIterator()for t,z,y in z do local s,n=pcall(function()z(u,v,o)end)if not s then warn(string.format("\068\111\077\101\115\115\097\103\101\070\105\108\116\101\114 \070\117\110\099\116\105\111\110 \'\037\115\' \102\097\105\108\101\100 \102\111\114 \114\101\097\115\111\110\058 \037\115",t,n))end end end function methods.InternalDoProcessCommands(t,u,v,o)local z=t.ProcessCommandsFunctions:GetIterator()for t,z,y in z do local s,n=pcall(function()local t=z(u,v,o)if type(t)~="\098\111\111\108\101\097\110"then error("\080\114\111\099\101\115\115 \099\111\109\109\097\110\100 \102\117\110\099\116\105\111\110\115 \109\117\115\116 \114\101\116\117\114\110 \097 \098\111\111\108")end return t end)if not s then warn(string.format("\068\111\080\114\111\099\101\115\115\067\111\109\109\097\110\100\115 \070\117\110\099\116\105\111\110 \'\037\115\' \102\097\105\108\101\100 \102\111\114 \114\101\097\115\111\110\058 \037\115",t,n))elseif n then return true end end return false end function methods.InternalPostMessage(t,u,v,o)if t:InternalDoProcessCommands(u.Name,v,t.Name)then return false end if t.Mutes[u.Name:lower()]~=nil then local v=t.Mutes[u.Name:lower()]if v>0 and os.time()>v then t:UnmuteSpeaker(u.Name)else t:SendSystemMessageToSpeaker(ChatLocalization:Get("\071\097\109\101\067\104\097\116\095\067\104\097\116\067\104\097\110\110\101\108\095\077\117\116\101\100\073\110\067\104\097\110\110\101\108","\089\111\117 \097\114\101 \109\117\116\101\100 \097\110\100 \099\097\110\110\111\116 \116\097\108\107 \105\110 \116\104\105\115 \099\104\097\110\110\101\108"),u.Name)return false end end local z=t:InternalCreateMessageObject(v,u.Name,false,o)v=t:SendMessageObjToFilters(v,z,u)local y={}for o,s in pairs(t.Speakers)do local n=s:IsSpeakerMuted(u.Name)if not n and t:CanCommunicate(u,s)then table.insert(y,s.Name)if s.Name==u.Name then local u=DeepCopy(z)u.Message=v u.IsFiltered=true s:InternalSendMessage(u,t.Name)else s:InternalSendMessage(z,t.Name)end end end local s,n=pcall(function()t.eMessagePosted:Fire(z)end)if not s and n then print("\069\114\114\111\114 \112\111\115\116\105\110\103 \109\101\115\115\097\103\101\058 "..n)end local Y={}for u,o in pairs(y)do local y=t.ChatService:InternalApplyRobloxFilter(z.FromSpeaker,v,o)if y then Y[o]=y else return false end end for u,v in pairs(y)do local o=t.Speakers[v]if o then local u=DeepCopy(z)u.Message=Y[v]u.IsFiltered=true o:InternalSendFilteredMessage(u,t.Name)end end local J=t.ChatService:InternalApplyRobloxFilter(z.FromSpeaker,v)if J then z.Message=J else return false end z.IsFiltered=true t:InternalAddMessageToHistoryLog(z)local K={}for v,o in pairs(t.Speakers)do local z=o:IsSpeakerMuted(u.Name)if not z and t:CanCommunicate(u,o)then local u=false for v,t in pairs(y)do if o.Name==t then u=true break end end if not u then table.insert(K,o.Name)end end end for u,o in pairs(K)do local y=t.Speakers[o]if y then local u=t.ChatService:InternalApplyRobloxFilter(z.FromSpeaker,v,o)if u==nil then return false end local s=DeepCopy(z)s.Message=u s.IsFiltered=true y:InternalSendFilteredMessage(s,t.Name)end end return z end function methods.InternalAddSpeaker(v,u)if v.Speakers[u.Name]then warn("\083\112\101\097\107\101\114 \""..(u.name.."\" \105\115 \097\108\114\101\097\100\121 \105\110 \116\104\101 \099\104\097\110\110\101\108\033"))return end v.Speakers[u.Name]=u local o,t=pcall(function()v.eSpeakerJoined:Fire(u.Name)end)if not o and t then print("\069\114\114\111\114 \114\101\109\111\118\105\110\103 \099\104\097\110\110\101\108\058 "..t)end end function methods.InternalRemoveSpeaker(v,u)if not v.Speakers[u.Name]then warn("\083\112\101\097\107\101\114 \""..(u.name.."\" \105\115 \110\111\116 \105\110 \116\104\101 \099\104\097\110\110\101\108\033"))return end v.Speakers[u.Name]=nil local o,t=pcall(function()v.eSpeakerLeft:Fire(u.Name)end)if not o and t then print("\069\114\114\111\114 \114\101\109\111\118\105\110\103 \115\112\101\097\107\101\114\058 "..t)end end function methods.InternalRemoveExcessMessagesFromLog(u)local v=table.remove while#u.ChatHistory>u.MaxHistory do v(u.ChatHistory,1)end end function methods.InternalAddMessageToHistoryLog(v,u)table.insert(v.ChatHistory,u)v:InternalRemoveExcessMessagesFromLog()end function methods.GetMessageType(o,u,v)if v==nil then return ChatConstants.MessageTypeSystem end return ChatConstants.MessageTypeDefault end function methods.InternalCreateMessageObject(z,u,v,o,t)local y=z:GetMessageType(u,v)local s=-1 local n=nil if v then n=z.Speakers[v]if n then local u=n:GetPlayer()if u then s=u.UserId else s=0 end end end local Y={ID=z.ChatService:InternalGetUniqueMessageId();FromSpeaker=v;SpeakerUserId=s;OriginalChannel=z.Name;MessageLength=string.len(u),MessageType=y,IsFiltered=o,Message=o and u or nil,Time=os.time();ExtraData={}}if n then for u,v in pairs(n.ExtraData)do Y.ExtraData[u]=v end end if t then for u,v in pairs(t)do Y.ExtraData[u]=v end end return Y end function methods.SetChannelNameColor(v,u)v.ChannelNameColor=u for o,t in pairs(v.Speakers)do t:UpdateChannelNameColor(v.Name,u)end end function methods.GetWelcomeMessageForSpeaker(v,u)if v.GetWelcomeMessageFunction then local o=v.GetWelcomeMessageFunction(u)if o then return o end end return v.WelcomeMessage end function methods.RegisterGetWelcomeMessageFunction(v,u)if type(u)~="\102\117\110\099\116\105\111\110"then error("\082\101\103\105\115\116\101\114\071\101\116\087\101\108\099\111\109\101\077\101\115\115\097\103\101\070\117\110\099\116\105\111\110 \109\117\115\116 \098\101 \099\097\108\108\101\100 \119\105\116\104 \097 \102\117\110\099\116\105\111\110\046")end v.GetWelcomeMessageFunction=u end function methods.UnRegisterGetWelcomeMessageFunction(u)u.GetWelcomeMessageFunction=nil end

function methods:InternalDestroy()
	for i, speaker in pairs(self.Speakers) do
		speaker:LeaveChannel(self.Name)
	end

	self.eDestroyed:Fire()

	self.eDestroyed:Destroy()
	self.eMessagePosted:Destroy()
	self.eSpeakerJoined:Destroy()
	self.eSpeakerLeft:Destroy()
	self.eSpeakerMuted:Destroy()
	self.eSpeakerUnmuted:Destroy()
end

function methods:InternalDoMessageFilter(speakerName, messageObj, channel)
	local filtersIterator = self.FilterMessageFunctions:GetIterator()
	for funcId, func, priority in filtersIterator do
		local success, errorMessage = pcall(function()
			func(speakerName, messageObj, channel)
		end)

		if not success then
			warn(string.format("DoMessageFilter Function '%s' failed for reason: %s", funcId, errorMessage))
		end
	end
end

function methods:InternalDoProcessCommands(speakerName, message, channel)
	local commandsIterator = self.ProcessCommandsFunctions:GetIterator()
	for funcId, func, priority in commandsIterator do
		local success, returnValue = pcall(function()
			local ret = func(speakerName, message, channel)
			if type(ret) ~= "boolean" then
				error("Process command functions must return a bool")
			end
			return ret
		end)

		if not success then
			warn(string.format("DoProcessCommands Function '%s' failed for reason: %s", funcId, returnValue))
		elseif returnValue then
			return true
		end
	end

	return false
end

function methods:InternalPostMessage(fromSpeaker, message, extraData)
	if (self:InternalDoProcessCommands(fromSpeaker.Name, message, self.Name)) then return false end

	if (self.Mutes[fromSpeaker.Name:lower()] ~= nil) then
		local t = self.Mutes[fromSpeaker.Name:lower()]
		if (t > 0 and os.time() > t) then
			self:UnmuteSpeaker(fromSpeaker.Name)
		else
			self:SendSystemMessageToSpeaker(ChatLocalization:Get("GameChat_ChatChannel_MutedInChannel","You are muted and cannot talk in this channel"), fromSpeaker.Name)
			return false
		end
	end

	local messageObj = self:InternalCreateMessageObject(message, fromSpeaker.Name, false, extraData)
	message = self:SendMessageObjToFilters(message, messageObj, fromSpeaker)

	local sentToList = {}
	for i, speaker in pairs(self.Speakers) do
		local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
		if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
			table.insert(sentToList, speaker.Name)
			if speaker.Name == fromSpeaker.Name then
				-- Send unfiltered message to speaker who sent the message.
				local cMessageObj = DeepCopy(messageObj)
				cMessageObj.Message = message
				cMessageObj.IsFiltered = true
				-- We need to claim the message is filtered even if it not in this case for compatibility with legacy client side code.
				speaker:InternalSendMessage(cMessageObj, self.Name)
			else
				speaker:InternalSendMessage(messageObj, self.Name)
			end
		end
	end

	local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
	if not success and err then
		print("Error posting message: " ..err)
	end

	local filteredMessages = {}
	for i, speakerName in pairs(sentToList) do
		local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
		if filteredMessage then
			filteredMessages[speakerName] = filteredMessage
		else
			return false
		end
	end

	for i, speakerName in pairs(sentToList) do
		local speaker = self.Speakers[speakerName]
		if (speaker) then
			local cMessageObj = DeepCopy(messageObj)
			cMessageObj.Message = filteredMessages[speakerName]
			cMessageObj.IsFiltered = true
			speaker:InternalSendFilteredMessage(cMessageObj, self.Name)
		end
	end

	local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message)
	if filteredMessage then
		messageObj.Message = filteredMessage
	else
		return false
	end
	messageObj.IsFiltered = true
	self:InternalAddMessageToHistoryLog(messageObj)

	-- One more pass is needed to ensure that no speakers do not recieve the message.
	-- Otherwise a user could join while the message is being filtered who had not originally been sent the message.
	local speakersMissingMessage = {}
	for _, speaker in pairs(self.Speakers) do
		local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
		if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
			local wasSentMessage = false
			for _, sentSpeakerName in pairs(sentToList) do
				if speaker.Name == sentSpeakerName then
					wasSentMessage = true
					break
				end
			end
			if not wasSentMessage then
				table.insert(speakersMissingMessage, speaker.Name)
			end
		end
	end

	for _, speakerName in pairs(speakersMissingMessage) do
		local speaker = self.Speakers[speakerName]
		if speaker then
			local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
			if filteredMessage == nil then
				return false
			end
			local cMessageObj = DeepCopy(messageObj)
			cMessageObj.Message = filteredMessage
			cMessageObj.IsFiltered = true
			speaker:InternalSendFilteredMessage(cMessageObj, self.Name)
		end
	end

	return messageObj
end

function methods:InternalAddSpeaker(speaker)
	if (self.Speakers[speaker.Name]) then
		warn("Speaker \"" .. speaker.name .. "\" is already in the channel!")
		return
	end

	self.Speakers[speaker.Name] = speaker
	local success, err = pcall(function() self.eSpeakerJoined:Fire(speaker.Name) end)
	if not success and err then
		print("Error removing channel: " ..err)
	end
end

function methods:InternalRemoveSpeaker(speaker)
	if (not self.Speakers[speaker.Name]) then
		warn("Speaker \"" .. speaker.name .. "\" is not in the channel!")
		return
	end

	self.Speakers[speaker.Name] = nil
	local success, err = pcall(function() self.eSpeakerLeft:Fire(speaker.Name) end)
	if not success and err then
		print("Error removing speaker: " ..err)
	end
end

function methods:InternalRemoveExcessMessagesFromLog()
	local remove = table.remove
	while (#self.ChatHistory > self.MaxHistory) do
		remove(self.ChatHistory, 1)
	end
end

function methods:InternalAddMessageToHistoryLog(messageObj)
	table.insert(self.ChatHistory, messageObj)

	self:InternalRemoveExcessMessagesFromLog()
end

function methods:GetMessageType(message, fromSpeaker)
	if fromSpeaker == nil then
		return ChatConstants.MessageTypeSystem
	end
	return ChatConstants.MessageTypeDefault
end

function methods:InternalCreateMessageObject(message, fromSpeaker, isFiltered, extraData)
	local messageType = self:GetMessageType(message, fromSpeaker)

	local speakerUserId = -1
	local speaker = nil

	if fromSpeaker then
		speaker = self.Speakers[fromSpeaker]
		if speaker then
			local player = speaker:GetPlayer()
			if player then
				speakerUserId = player.UserId
			else
				speakerUserId = 0
			end
		end
	end

	local messageObj =
	{
		ID = self.ChatService:InternalGetUniqueMessageId(),
		FromSpeaker = fromSpeaker,
		SpeakerUserId = speakerUserId,
		OriginalChannel = self.Name,
		MessageLength = string.len(message),
		MessageType = messageType,
		IsFiltered = isFiltered,
		Message = isFiltered and message or nil,
		Time = os.time(),
		ExtraData = {},
	}

	if speaker then
		for k, v in pairs(speaker.ExtraData) do
			messageObj.ExtraData[k] = v
		end
	end

	if (extraData) then
		for k, v in pairs(extraData) do
			messageObj.ExtraData[k] = v
		end
	end

	return messageObj
end

function methods:SetChannelNameColor(color)
	self.ChannelNameColor = color
	for i, speaker in pairs(self.Speakers) do
		speaker:UpdateChannelNameColor(self.Name, color)
	end
end

function methods:GetWelcomeMessageForSpeaker(speaker)
	if self.GetWelcomeMessageFunction then
		local welcomeMessage = self.GetWelcomeMessageFunction(speaker)
		if welcomeMessage then
			return welcomeMessage
		end
	end
	return self.WelcomeMessage
end

function methods:RegisterGetWelcomeMessageFunction(func)
	if type(func) ~= "function" then
		error("RegisterGetWelcomeMessageFunction must be called with a function.")
	end
	self.GetWelcomeMessageFunction = func
end

function methods:UnRegisterGetWelcomeMessageFunction()
	self.GetWelcomeMessageFunction = nil
end
