--////////////////////////////// Methods
--//////////////////////////////////////
local function ShallowCopy(table)
	local copy = {}
	for i, v in pairs(table) do
		copy[i] = v
	end
	return copy
end

local methods = {}

local lazyEventNames =
{
    eDestroyed = true,
	eSaidMessage = true,
	eReceivedMessage = true,
	eReceivedUnfilteredMessage = true,
	eMessageDoneFiltering = true,
	eReceivedSystemMessage = true,
	eChannelJoined = true,
	eChannelLeft = true,
	eMuted = true,
	eUnmuted = true,
	eExtraDataUpdated = true,
	eMainChannelSet = true,
	eChannelNameColorUpdated = true,
}
local lazySignalNames =
{
	Destroyed = "eDestroyed",
	SaidMessage = "eSaidMessage",
	ReceivedMessage = "eReceivedMessage",
	ReceivedUnfilteredMessage = "eReceivedUnfilteredMessage",
    RecievedUnfilteredMessage = "eReceivedUnfilteredMessage", -- legacy mispelling
	MessageDoneFiltering = "eMessageDoneFiltering",
	ReceivedSystemMessage = "eReceivedSystemMessage",
	ChannelJoined = "eChannelJoined",
	ChannelLeft = "eChannelLeft",
	Muted = "eMuted",
	Unmuted = "eUnmuted",
	ExtraDataUpdated = "eExtraDataUpdated",
	MainChannelSet = "eMainChannelSet",
	ChannelNameColorUpdated = "eChannelNameColorUpdated"
}

methods.__index = function (self, k)
	local fromMethods = rawget(methods, k)
	if fromMethods then return fromMethods end

    if lazyEventNames[k] and not rawget(self, k) then
        rawset(self, k, Instance.new("BindableEvent"))
    end
    local lazySignalEventName = lazySignalNames[k]
    if lazySignalEventName and not rawget(self, k) then
        if not rawget(self, lazySignalEventName) then
            rawset(self, lazySignalEventName, Instance.new("BindableEvent"))
        end
        rawset(self, k, rawget(self, lazySignalEventName).Event)
    end
    return rawget(self, k)
end

function methods:LazyFire(eventName, ...)
	local createdEvent = rawget(self, eventName)
	if createdEvent then
		createdEvent:Fire(...)
	end
end

function methods:SayMessage(message, channelName, extraData)
	if self.ChatService:InternalDoProcessCommands(self.Name, message, channelName) then
		return
	end
	if not channelName then
		return
	end

	local channel = self.Channels[channelName:lower()]
	if not channel then
		return
	end

	local messageObj = channel:InternalPostMessage(self, message, extraData)
	if messageObj then
		pcall(function()
			self:LazyFire("eSaidMessage", messageObj, channelName)
		end)
	end

	return messageObj
end

function methods:JoinChannel(channelName)
	if (self.Channels[channelName:lower()]) then
		warn("Speaker is already in channel \"" .. channelName .. "\"")
		return
	end

	local channel = self.ChatService:GetChannel(channelName)
	if (not channel) then
		error("Channel \"" .. channelName .. "\" does not exist!")
	end

	self.Channels[channelName:lower()] = channel
	channel:InternalAddSpeaker(self)
	local success, err = pcall(function()
		self.eChannelJoined:Fire(channel.Name, channel:GetWelcomeMessageForSpeaker(self))
	end)
	if not success and err then
		print("Error joining channel: " ..err)
	end
end

function methods:LeaveChannel(channelName)
	if (not self.Channels[channelName:lower()]) then
		warn("Speaker is not in channel \"" .. channelName .. "\"")
		return
	end

	local channel = self.Channels[channelName:lower()]

	self.Channels[channelName:lower()] = nil
	channel:InternalRemoveSpeaker(self)
	local success, err = pcall(function()
		self:LazyFire("eChannelLeft", channel.Name)
		if self.PlayerObj then
			self.EventFolder.OnChannelLeft:FireClient(self.PlayerObj, channel.Name)
		end
	end)
	if not success and err then
		print("Error leaving channel: " ..err)
	end
end

function methods:IsInChannel(channelName)
	return (self.Channels[channelName:lower()] ~= nil)
end

function methods:GetChannelList()
	local list = {}
	for i, channel in pairs(self.Channels) do
		table.insert(list, channel.Name)
	end
	return list
end

function methods:SendMessage(message, channelName, fromSpeaker, extraData)
	local channel = self.Channels[channelName:lower()]
	if (channel) then
		channel:SendMessageToSpeaker(message, self.Name, fromSpeaker, extraData)

	elseif RunService:IsStudio() then
		warn(string.format("Speaker '%s' is not in channel '%s' and cannot receive a message in it.", self.Name, channelName))

	end
end

function methods:SendSystemMessage(message, channelName, extraData)
	local channel = self.Channels[channelName:lower()]
	if (channel) then
		channel:SendSystemMessageToSpeaker(message, self.Name, extraData)

	elseif RunService:IsStudio() then
		warn(string.format("Speaker '%s' is not in channel '%s' and cannot receive a system message in it.", self.Name, channelName))

	end
end

function methods:GetPlayer()
	return self.PlayerObj
end

function methods:GetNameForDisplay()
	if ChatSettings.PlayerDisplayNamesEnabled then
		local player = self:GetPlayer()

		if player then
			return player.DisplayName
		else
			return self.Name
		end
	else
		return self.Name
	end
end

function methods:SetExtraData(key, value)
	self.ExtraData[key] = value
	self:LazyFire("eExtraDataUpdated", key, value)
end

function methods:GetExtraData(key)
	return self.ExtraData[key]
end

function methods:SetMainChannel(channelName)
	local success, err = pcall(function()
		self:LazyFire("eMainChannelSet", channelName)
		if self.PlayerObj then
			self.EventFolder.OnMainChannelSet:FireClient(self.PlayerObj, channelName)
		end
	end)
	if not success and err then
		print("Error setting main channel: " ..err)
	end
end