Quantcast
--[[
Jamba - Jafula's Awesome Multi-Boxer Assistant
Copyright 2008 - 2015 Michael "Jafula" Miller
License: The MIT License
]]--

local AJM = LibStub( "AceAddon-3.0" ):NewAddon(
	"JambaCommunications",
	"AceComm-3.0",
	"AceEvent-3.0",
	"AceConsole-3.0",
	"AceTimer-3.0",
	"AceHook-3.0"
)

-- Get the locale for JambaCommunications.
local L = LibStub( "AceLocale-3.0" ):GetLocale( "Jamba-Core" )

-- Get libraries.
local AceSerializer = LibStub:GetLibrary( "AceSerializer-3.0" )
local JambaHelperSettings = LibStub:GetLibrary( "JambaHelperSettings-1.0" )

-- JambaCommunications is not a module, but the same naming convention for these values is convenient.
AJM.moduleName = "Jamba-Communications"
AJM.moduleDisplayName = L["Core: Communications"]
AJM.settingsDatabaseName = "JambaCommunicationsProfileDB"
AJM.parentDisplayName = L["Advanced"]
AJM.chatCommand = "jamba-comm"

-------------------------------------------------------------------------------------------------------------
-- Constants
-------------------------------------------------------------------------------------------------------------

-- Communication methods.
AJM.COMMUNICATION_WHISPER = "WHISPER"
AJM.COMMUNICATION_GROUP = "RAID"

-- Communication message prefix.
AJM.MESSAGE_PREFIX = "JmbCmMsg"

-- Communication over channel for online status.
AJM.COMMUNICATION_TEAM_ONLINE_PREFIX = "JmbCmTmOn"
AJM.COMMUNICATION_MESSAGE_ONLINE = "JmbCmTmOnTe"
AJM.COMMUNICATION_MESSAGE_OFFLINE = "JmbCmTmOnFe"

-- Communication priorities.
AJM.COMMUNICATION_PRIORITY_BULK = "BULK"
AJM.COMMUNICATION_PRIORITY_NORMAL = "NORMAL"
AJM.COMMUNICATION_PRIORITY_ALERT = "ALERT"

-- Communication command.
AJM.COMMAND_PREFIX = "JmbCmCmd"
AJM.COMMAND_SEPERATOR = "\004"
AJM.COMMAND_ARGUMENT_SEPERATOR = "\005"

-- Internal commands sent by Jamba Communications.
AJM.COMMAND_INTERNAL_SEND_SETTINGS = "JmbCmSdSet"

-------------------------------------------------------------------------------------------------------------
-- Messages module sends.
-------------------------------------------------------------------------------------------------------------

AJM.MESSAGE_CHARACTER_ONLINE = "JmbTmChrOn"
AJM.MESSAGE_CHARACTER_OFFLINE = "JmbTmChrOf"

-- Get a settings value.
function AJM:ConfigurationGetSetting( key )
	return AJM.db[key[#key]]
end

-- Set a settings value.
function AJM:ConfigurationSetSetting( key, value )
	AJM.db[key[#key]] = value
end

-- Settings - the values to store and their defaults for the settings database.
AJM.settings = {
	profile = {
		teamOnlineChannelName = "JambaTeamIsOnline",
		teamOnlineChannelPassword = "JambaTeamPassword",
		showOnlineChannel = false,
		assumeTeamAlwaysOnline = true,
		boostCommunication = true,
	},
}

-- Configuration.
local function GetConfiguration()
	local configuration = {
		name = AJM.moduleDisplayName,
		handler = AJM,
		type = 'group',
		get = "ConfigurationGetSetting",
		set = "ConfigurationSetSetting",
		args = {
			push = {
				type = "input",
				name = L["Push Settings"],
				desc = L["Push settings to all characters in the team list."],
				usage = "/jamba-comm push",
				get = false,
				set = "JambaSendSettings",
			},
			channel = {
				type = "input",
				name = L["Change Channel"],
				desc = L["Change the communications channel."],
				usage = "/jamba-comm channel <channel name> <channel password>",
				get = false,
				set = "ChangeChannelCommand",
			},
		},
	}
	return configuration
end


-- Debug message.
function AJM:DebugMessage( ... )
    --AJM:Print( ... )
end

-------------------------------------------------------------------------------------------------------------
-- Character online management.
-------------------------------------------------------------------------------------------------------------

local function AssumeTeamAlwaysOnline()
	return AJM.db.assumeTeamAlwaysOnline
end

-- Join the team online status channel.
local function JoinTeamOnlineStatusChannel()
	local channelType, channelName = JoinTemporaryChannel( AJM.db.teamOnlineChannelName, AJM.db.teamOnlineChannelPassword )
	AJM.lastChannel = AJM.db.teamOnlineChannelName
end

-- Leave the team online status channel.
local function LeaveTeamOnlineStatusChannel()
	LeaveChannelByName( AJM.channelJustMovedFrom )
	AJM.channelJustMovedFrom = AJM.lastChannel
end

function AJM:SettingsChangeChannelClick( event )
	AJM:StopChannelPollTimer()
	AJM:Print( "Changing Channel from [", AJM.lastChannel, "] to [", AJM.db.teamOnlineChannelName, "]." )
	AJM.channelJustMovedFrom = AJM.lastChannel
	JoinTeamOnlineStatusChannel()
	LeaveTeamOnlineStatusChannel()
	AJM:StartChannelPollTimer()
end

-- Is a character online?
local function IsCharacterOnline( characterName )
	if AJM.db.assumeTeamAlwaysOnline == true then
		return true
	end
	return JambaPrivate.Team.GetCharacterOnlineStatus( characterName )
end

-- Test to see if the channel name provided is the name of the team online channel.
local function IsChannelTeamOnlineChannel( channelName )
	local isTeamOnlineChannel = false
	if channelName ~= nil then
		if AJM.db.showOnlineChannel == true then
			AJM:Print( "Team Channel: match? this:", string.utf8lower( channelName ), "team:", string.utf8lower( AJM.lastChannel ), "or:", string.utf8lower( AJM.channelJustMovedFrom ) )
		end
		if string.utf8lower( channelName ) == string.utf8lower( AJM.lastChannel ) then
			isTeamOnlineChannel = true
		end
		if string.utf8lower( channelName) == string.utf8lower( AJM.channelJustMovedFrom ) then
			isTeamOnlineChannel = true
		end
	end
	return isTeamOnlineChannel
end

function AJM:CHAT_MSG_CHANNEL_LIST( event, ... )
	if AJM.db.showOnlineChannel == true then
		AJM:Print( "CHAT_MSG_CHANNEL_LIST" )
	end
	local message, arg2, arg3, arg4, arg5, arg6, arg7, arg8, channelName, arg10, arg11, arg12 = ...
	-- Is this the team online channel?
	if IsChannelTeamOnlineChannel( channelName ) == true then
		-- Yes, set all characters to be offline.
		JambaPrivate.Team.SetTeamStatusToOffline()
		-- Parse the message for characters.
		local characters = {}
		for character in message:gmatch( "[^,]+" ) do
			table.insert( characters, character )
		end
		for index, character in pairs( characters ) do
			local characterName = character:gsub( "%s*%@*%**([^%s]+)", "%1" )
			-- Is this character in our team?
			if JambaPrivate.Team.IsCharacterInTeam( characterName ) == true then
				-- Set the character status to online.
				JambaPrivate.Team.SetCharacterOnlineStatus( characterName, true )
			end
		end
	end
end

function AJM:CHAT_MSG_CHANNEL_JOIN( event, ... )
	if AJM.db.showOnlineChannel == true then
		AJM:Print( "CHAT_MSG_CHANNEL_JOIN" )
	end
	local arg1, sender, arg3, arg4, arg5, arg6, arg7, arg8, channelName, arg10, arg11, arg12 = ...
	-- Is this the team online channel?
	if IsChannelTeamOnlineChannel( channelName ) == true then
		-- Is this character in our team?
		if JambaPrivate.Team.IsCharacterInTeam( sender ) == true then
			-- Set the character status to online.
			JambaPrivate.Team.SetCharacterOnlineStatus( sender, true )
			AJM:SendMessage( AJM.MESSAGE_CHARACTER_ONLINE )
		end
	end
end

function AJM:CHAT_MSG_CHANNEL_LEAVE( event, ... )
	if AJM.db.showOnlineChannel == true then
		AJM:Print( "CHAT_MSG_CHANNEL_LEAVE" )
	end
	local arg1, sender, arg3, arg4, arg5, arg6, arg7, arg8, channelName, arg10, arg11, arg12 = ...
	-- Is this the team online channel?
	if IsChannelTeamOnlineChannel( channelName ) == true then
		-- Is this character in our team?
		if JambaPrivate.Team.IsCharacterInTeam( sender ) == true then
			-- Set the character status to offline.
			JambaPrivate.Team.SetCharacterOnlineStatus( sender, false )
			AJM:SendMessage( AJM.MESSAGE_CHARACTER_OFFLINE )
		end
	end
end

-- TODO: Investigate ChatFrame_AddMessageEventFilter (hooks may be the wrong thing to do here)
-- TODO: See http://www.wowwiki.com/API_ChatFrame_AddMessageEventFilter
-- The ChatFrame_MessageEventHandler hook.
function AJM:ChatFrame_MessageEventHandler( self, event, ... )
	if AJM.db.showOnlineChannel == true then
		AJM:Print( "ChatFrame_MessageEventHandler" )
	end
	local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, channelName, arg10, arg11, arg12 = ...
	-- Is this the team online channel?
	if IsChannelTeamOnlineChannel( channelName ) == true then
		-- Yes, the chat frames don't need to know about this channel.
		if AJM.db.showOnlineChannel == false then
			return true
		end
	end
	-- Call the orginal function.
	return AJM.hooks["ChatFrame_MessageEventHandler"]( self, event, ... )
end

-------------------------------------------------------------------------------------------------------------
-- Command management.
-------------------------------------------------------------------------------------------------------------

-- Creates a command to send.
local function CreateCommandToSend( moduleName, commandName, ... )
	-- Start the message with the module name and a seperator.
	local message = moduleName..AJM.COMMAND_SEPERATOR
	-- Add the command  name and a seperator.
	message = message..commandName..AJM.COMMAND_SEPERATOR
	-- Add any arguments to the message (serialized and seperated).
	local numberArguments = select( "#", ... )
	for iterateArguments = 1, numberArguments do
		local argument = select( iterateArguments, ... )
		message = message..AceSerializer:Serialize( argument )
		if iterateArguments < numberArguments then
			message = message..AJM.COMMAND_ARGUMENT_SEPERATOR
		end
	end
	-- Return the command to send.
	return message
end

-- Send a command to all members of the current team.
local function CommandAll( moduleName, commandName, ... )
    AJM:DebugMessage( "Command All: ", moduleName, commandName, ... )
	-- Get the message to send.
	local message = CreateCommandToSend( moduleName, commandName, ... )
	-- Send command to all in group/raid.
	--if not UnitInBattleground( "player" ) then
	if UnitInParty( "player" ) == true then --or UnitInRaid( "player" ) then
		if not UnitInBattleground( "player" ) then
		AJM:DebugMessage( "Sending command to group." )
			AJM:SendCommMessage(
			AJM.COMMAND_PREFIX,
			message,
			AJM.COMMUNICATION_GROUP,
			nil,
			AJM.COMMUNICATION_PRIORITY_ALERT
			)
		end
	end
	-- If player not in a party or raid, then send to player. Pointless as does it under anyway, x2 everything!
--	if GetNumSubgroupMembers() == 0 and GetNumGroupMembers() == 0 then
--	AJM:DebugMessage( "Sending command just to single player." )
--		AJM:SendCommMessage(
--			AJM.COMMAND_PREFIX,
--			message,
--			AJM.COMMUNICATION_WHISPER,
--			UnitName( "player" ),
--			AJM.COMMUNICATION_PRIORITY_ALERT
--		)
--	end
	-- Send the message to all members of the current team that are not in a party / raid.
	for characterName, characterOrder in JambaPrivate.Team.TeamList() do
		if IsCharacterOnline( characterName ) == true then
			local canSend = false
			-- Team member not in party then send command.
			if not UnitInParty( characterName ) then
				canSend = true
			end
			--If in a battleground then send a whisper as the party/raid would have not been sent.
			if UnitInBattleground( characterName ) then
				canSend = true
			end
			if canSend == true then
                AJM:DebugMessage( "Sending command to others not in party/raid." )
				AJM:SendCommMessage(
					AJM.COMMAND_PREFIX,
					message,
					AJM.COMMUNICATION_WHISPER,
					characterName,
					AJM.COMMUNICATION_PRIORITY_ALERT
				)
			end
		end
	end
end

-- Send a command to the master.
local function CommandMaster( moduleName, commandName, ... )
--	AJM:DebugMessage( "Command Master: ", moduleName, commandName, ... )
	-- Get the message to send.
	local message = CreateCommandToSend( moduleName, commandName, ... )
	-- Send the message to the master.
	local characterName = JambaPrivate.Team.GetMasterName()
	if IsCharacterOnline( characterName ) == true then
		AJM:SendCommMessage(
			AJM.COMMAND_PREFIX,
			message,
			AJM.COMMUNICATION_WHISPER,
			characterName,
			AJM.COMMUNICATION_PRIORITY_ALERT
		)
	end
end

-- Send a command to the master.
local function CommandToon( moduleName, characterName, commandName, ... )
	-- Get the message to send.
	local message = CreateCommandToSend( moduleName, commandName, ... )
	if IsCharacterOnline( characterName ) == true then
		AJM:SendCommMessage(
			AJM.COMMAND_PREFIX,
			message,
			AJM.COMMUNICATION_WHISPER,
			characterName,
			AJM.COMMUNICATION_PRIORITY_ALERT
		)
	end
end

-- Send a command to all slave characters of the current team.
local function CommandSlaves( moduleName, commandName, ... )
	-- Get the message to send.
	local message = CreateCommandToSend( moduleName, commandName, ... )
	-- Send the message to all members of the current team.
	for characterName, characterOrder in JambaPrivate.Team.TeamList() do
		if IsCharacterOnline( characterName ) == true then
			if JambaPrivate.Team.IsCharacterTheMaster( characterName ) == false then
				AJM:SendCommMessage(
					AJM.COMMAND_PREFIX,
					message,
					AJM.COMMUNICATION_WHISPER,
					characterName,
					AJM.COMMUNICATION_PRIORITY_ALERT
				)
			end
		end
	end
end

-- Receive a command from another character.
function AJM:CommandReceived( prefix, message, distribution, sender )
    AJM:DebugMessage( "Command received: ", prefix, message, distribution, sender )
	-- Check if the command is for Jamba Communications.
	if prefix == AJM.COMMAND_PREFIX then
		-- Check if the sender is trusted.
        sender = Ambiguate(sender, "none")
        AJM:DebugMessage( "Sender after ambiguate: ", sender )
		if JambaPrivate.Team.IsCharacterInTeam( sender ) == true then
            AJM:DebugMessage( "Sender is in team list." )
			-- Split the command into its components.
			local moduleName, commandName, argumentsStringSerialized = strsplit( AJM.COMMAND_SEPERATOR, message )
			local argumentsTable  = {}
			-- Are there any arguments?
			if (argumentsStringSerialized ~= nil) and (argumentsStringSerialized:trim() == "") then
				-- No.
			else
				-- Deserialize the arguments.
				local argumentsTableSerialized = { strsplit( AJM.COMMAND_ARGUMENT_SEPERATOR, argumentsStringSerialized ) }
				for index, argumentSerialized in ipairs( argumentsTableSerialized ) do
					local success, argument = AceSerializer:Deserialize( argumentSerialized )
					if success == true then
						table.insert( argumentsTable, argument )
					else
						error( L["A: Failed to deserialize command arguments for B from C."]( "AJM", moduleName, sender ) )
					end
				end
			end
			-- Look for internal Jamba Communication commands.
			if commandName == AJM.COMMAND_INTERNAL_SEND_SETTINGS then
				-- Tell JambaCore to handle the settings received.
				JambaPrivate.Core.OnSettingsReceived( sender, moduleName, unpack( argumentsTable ) )
			else
				-- Any other command can go directly to the module that sent it.
                AJM:DebugMessage( "Sending command on to module: ", sender, moduleName, commandName, unpack( argumentsTable ) )
				JambaPrivate.Core.OnCommandReceived( sender, moduleName, commandName, unpack( argumentsTable ) )
			end
		end
	end
end

-------------------------------------------------------------------------------------------------------------
-- Jamba Communications API.  These methods should only be called by Jamba Core.
-------------------------------------------------------------------------------------------------------------

-- Send settings to all members of the current team.
local function SendSettings( moduleName, settings )
	-- Send a push settings command to all.
	CommandAll( moduleName, AJM.COMMAND_INTERNAL_SEND_SETTINGS, settings )
end

-- Command all members of the current team.
local function SendCommandAll( moduleName, commandName, ... )
	-- Send the command to all.
	CommandAll( moduleName, commandName, ... )
end

-- 	Command the master.
local function SendCommandMaster( moduleName, commandName, ... )
	-- Send the command to the master character.
	CommandMaster( moduleName, commandName, ... )
end

-- Command the master.
local function SendCommandToon( moduleName, characterName, commandName, ... )
	-- Send the command to the master character.
	CommandToon( moduleName, characterName, commandName, ... )
end

-------------------------------------------------------------------------------------------------------------
-- Jamba Communications Initialization.
-------------------------------------------------------------------------------------------------------------

-- Initialize the addon.
function AJM:OnInitialize()
	AJM.channelPollTimer = nil
	-- Register commands with AceComms - tell AceComms to call the CommandReceived function when a command is received.
	AJM:RegisterComm( AJM.COMMAND_PREFIX, "CommandReceived" )
	-- Create the settings database supplying the settings values along with defaults.
    AJM.completeDatabase = LibStub( "AceDB-3.0" ):New( AJM.settingsDatabaseName, AJM.settings )
	AJM.db = AJM.completeDatabase.profile
	-- Create the settings.
	LibStub( "AceConfig-3.0" ):RegisterOptionsTable(
		AJM.moduleName,
		GetConfiguration()
	)
	AJM:SettingsCreate()
	AJM.settingsFrame = AJM.settingsControl.widgetSettings.frame
	AJM:SettingsRefresh()
	if AJM.db.assumeTeamAlwaysOnline == false then
		-- Hook the ChatFrame_MessageEventHandler to hide any messages that are for the team online channel.
		AJM:RawHook( "ChatFrame_MessageEventHandler", true )
	end
	AJM.characterName = UnitName( "player" )
	AJM.characterGUID = UnitGUID( "player" )
	AJM:RegisterChatCommand( AJM.chatCommand, "JambaChatCommand" )
	-- Register communications as a module.
	JambaPrivate.Core.RegisterModule( AJM, AJM.moduleName )
	-- The last channel joined.
	AJM.lastChannel = AJM.db.teamOnlineChannelName
	AJM.channelJustMovedFrom = AJM.lastChannel
end

function AJM:OnEnable()
	if AJM.db.assumeTeamAlwaysOnline == false then
		-- Wait for some seconds before initialising the team online channel.
		-- This lets the defaults channels get the usual channel numbers (i.e. Trade is /2).
		AJM:ScheduleTimer( "InitialiseTeamOnlineChannel", 10 )
	end
	if AJM.db.boostCommunication == true then
		AJM:BoostCommunication()
		-- Repeat every 5 minutes.
		AJM:ScheduleRepeatingTimer( "BoostCommunication", 300 )
	end
end

function AJM:BoostCommunication()
	if AJM.db.boostCommunication == true then
		-- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800.
		ChatThrottleLib.MAX_CPS = 1200 --800
		-- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff
		ChatThrottleLib.MSG_OVERHEAD = 40
		-- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now.
		ChatThrottleLib.BURST = 6000 --4000
		-- Reduce output CPS to half (and don't burst) if FPS drops below this value
		ChatThrottleLib.MIN_FPS = 10 --20
	end
end

-- Handle the chat command.
function AJM:JambaChatCommand( input )
    if not input or input:trim() == "" then
        InterfaceOptionsFrame_OpenToCategory( AJM.moduleDisplayName )
    else
        LibStub( "AceConfigCmd-3.0" ):HandleCommand( AJM.chatCommand, AJM.moduleName, input )
    end
end

function AJM:InitialiseTeamOnlineChannel()
	-- Register for the list of members in a channel event.
	AJM:RegisterEvent( "CHAT_MSG_CHANNEL_LIST" )
	-- And for joiners and leavers.
	AJM:RegisterEvent( "CHAT_MSG_CHANNEL_JOIN" )
	AJM:RegisterEvent( "CHAT_MSG_CHANNEL_LEAVE" )
	-- Join the team online channel.
	JoinTeamOnlineStatusChannel()
	-- Ask for a list of characters in the team online channel.
	ListChannelByName( AJM.lastChannel )
	AJM:StartChannelPollTimer()
end

function AJM:StopChannelPollTimer()
	if AJM.channelPollTimer ~= nil then
		AJM:CancelTimer( AJM.channelPollTimer )
	end
end

function AJM:StartChannelPollTimer()
	-- Poll for characters every 5 seconds.
	AJM.channelPollTimer = AJM:ScheduleRepeatingTimer( ListChannelByName, 5, AJM.lastChannel )
end

function AJM:OnDisable()
	AJM:CancelAllTimers()
end

-------------------------------------------------------------------------------------------------------------
-- Settings Dialogs.
-------------------------------------------------------------------------------------------------------------

function AJM:SettingsCreate()
	AJM.settingsControl = {}
	JambaHelperSettings:CreateSettings(
		AJM.settingsControl,
		AJM.moduleDisplayName,
		AJM.parentDisplayName,
		AJM.JambaSendSettings
	)
	local bottomOfOptions = AJM:SettingsCreateOptions( JambaHelperSettings:TopOfSettings() )
	AJM.settingsControl.widgetSettings.content:SetHeight( -bottomOfOptions )
	-- Help
	local helpTable = {}
	JambaHelperSettings:CreateHelp( AJM.settingsControl, helpTable, GetConfiguration() )
end

function AJM:SettingsCreateOptions( top )
	-- Get positions and dimensions.
	local checkBoxHeight = JambaHelperSettings:GetCheckBoxHeight()
	local labelContinueHeight = JambaHelperSettings:GetContinueLabelHeight()
	local editBoxHeight = JambaHelperSettings:GetEditBoxHeight()
	local buttonHeight = JambaHelperSettings:GetButtonHeight()
	local left = JambaHelperSettings:LeftOfSettings()
	local headingHeight = JambaHelperSettings:HeadingHeight()
	local headingWidth = JambaHelperSettings:HeadingWidth( false )
	local horizontalSpacing = JambaHelperSettings:GetHorizontalSpacing()
	local verticalSpacing = JambaHelperSettings:GetVerticalSpacing()
	local halfWidth = (headingWidth - horizontalSpacing) / 2
	local column1Left = left
	local movingTop = top
	-- Create a heading for information.
	JambaHelperSettings:CreateHeading( AJM.settingsControl, L["Team Online Check"], movingTop, false )
	movingTop = movingTop - headingHeight
	AJM.settingsControl.checkBoxAssumeAlwaysOnline = JambaHelperSettings:CreateCheckBox(
		AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["Assume All Team Members Always Online*"],
		AJM.CheckBoxAssumeAlwaysOnline
	)
	movingTop = movingTop - checkBoxHeight
	AJM.settingsControl.labelInformationAlwaysOnline = JambaHelperSettings:CreateContinueLabel(
		AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["*reload UI to take effect"]
	)
	movingTop = movingTop - labelContinueHeight
	AJM.settingsControl.checkBoxBoostCommunication = JambaHelperSettings:CreateCheckBox(
		AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["Boost Jamba to Jamba Communications**"],
		AJM.CheckBoxBoostCommunication
	)
	movingTop = movingTop - checkBoxHeight
	AJM.settingsControl.labelInformationBoost = JambaHelperSettings:CreateContinueLabel(
		AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["**reload UI to take effect, may cause disconnections"]
	)
	movingTop = movingTop - labelContinueHeight
	-- Create a heading for information.
	JambaHelperSettings:CreateHeading( AJM.settingsControl, L["Team Online Channel"], movingTop, false )
	movingTop = movingTop - headingHeight
	-- Information line 1.
	AJM.settingsControl.labelInformation1 = JambaHelperSettings:CreateContinueLabel(
		AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["After you change the channel name or password, push the"]
	)
	movingTop = movingTop - labelContinueHeight
	-- Information line 2.
	AJM.settingsControl.labelInformation2 = JambaHelperSettings:CreateContinueLabel(
		AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["new settings to all your other characters and then log off"]
	)
	movingTop = movingTop - labelContinueHeight
	-- Information line 3.
	AJM.settingsControl.labelInformation3 = JambaHelperSettings:CreateContinueLabel(
		AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["all your characters and log them on again."]
	)
	movingTop = movingTop - labelContinueHeight
	-- Channel name.
	AJM.settingsControl.editBoxChannelName = JambaHelperSettings:CreateEditBox( AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["Channel Name"]
	)
	AJM.settingsControl.editBoxChannelName:SetCallback( "OnEnterPressed", AJM.EditBoxChannelNameChanged )
	movingTop = movingTop - editBoxHeight
	-- Channel password.
	AJM.settingsControl.editBoxChannelPassword = JambaHelperSettings:CreateEditBox( AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["Channel Password"]
	)
	AJM.settingsControl.editBoxChannelPassword:SetCallback( "OnEnterPressed", AJM.EditBoxChannelPasswordChanged )
	movingTop = movingTop - editBoxHeight
	-- Change channel button.
	AJM.settingsControl.buttonChangeChannel = JambaHelperSettings:CreateButton(
		AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["Change Channel (Debug)"],
		AJM.SettingsChangeChannelClick
	)
	movingTop = movingTop - buttonHeight
	AJM.settingsControl.checkBoxShowChannel = JambaHelperSettings:CreateCheckBox(
		AJM.settingsControl,
		headingWidth,
		column1Left,
		movingTop,
		L["Show Online Channel Traffic (For Debugging Purposes)"],
		AJM.CheckBoxShowChannelClick
	)
	movingTop = movingTop - checkBoxHeight
	return movingTop
end

function AJM:EditBoxChannelNameChanged( event, text )
	AJM.db.teamOnlineChannelName = text
end

function AJM:EditBoxChannelPasswordChanged( event, text )
	AJM.db.teamOnlineChannelPassword = text
end

function AJM:CheckBoxShowChannelClick( event, value )
	AJM.db.showOnlineChannel = value
	AJM:SettingsRefresh()
end

function AJM:CheckBoxAssumeAlwaysOnline( event, value )
	AJM.db.assumeTeamAlwaysOnline = value
	AJM:SettingsRefresh()
end

function AJM:CheckBoxBoostCommunication( event, value )
	AJM.db.boostCommunication = value
	AJM:SettingsRefresh()
end

function AJM:BeforeJambaProfileChanged()
end

function AJM:OnJambaProfileChanged()
	AJM:SettingsRefresh()
end

function AJM:SettingsRefresh()
	AJM.settingsControl.editBoxChannelName:SetText( AJM.db.teamOnlineChannelName )
	AJM.settingsControl.editBoxChannelPassword:SetText( AJM.db.teamOnlineChannelPassword )
	AJM.settingsControl.checkBoxShowChannel:SetValue( AJM.db.showOnlineChannel )
	AJM.settingsControl.checkBoxAssumeAlwaysOnline:SetValue( AJM.db.assumeTeamAlwaysOnline )
	AJM.settingsControl.checkBoxBoostCommunication:SetValue( AJM.db.boostCommunication )
end

-- Settings received.
function AJM:JambaSendSettings()
	SendSettings( AJM.moduleName, AJM.db )
end

-- Settings received.
function AJM:JambaOnSettingsReceived( characterName, settings )
	if characterName ~= AJM.characterName then
		-- Update the settings.
		AJM.db.teamOnlineChannelName = settings.teamOnlineChannelName
		AJM.db.teamOnlineChannelPassword = settings.teamOnlineChannelPassword
		AJM.db.showOnlineChannel = settings.showOnlineChannel
		AJM.db.assumeTeamAlwaysOnline = settings.assumeTeamAlwaysOnline
		AJM.db.boostCommunication = settings.boostCommunication
		-- Refresh the settings.
		AJM:SettingsRefresh()
		-- Tell the player.
		AJM:Print( L["Settings received from A."]( characterName ) )
	end
end

function AJM:ChangeChannelCommand( info, parameters )
	local name, password = strsplit( " ", parameters )
	AJM:ChangeChannelCommandAction( name, password )
end

function AJM:ChangeChannelCommandAction( name, password )
	AJM.db.teamOnlineChannelName = name
	AJM.db.teamOnlineChannelPassword = password
	AJM:SettingsRefresh()
	AJM:SettingsChangeChannelClick( nil )
end

-- text = message to send
-- chatDestination = "PARTY, WHISPER, RAID, CHANNEL, etc"
-- characterOrChannelName = character name if WHISPER or channel name if CHANNEL or nil otherwise
-- priority = one of
--   AJM.COMMUNICATION_PRIORITY_BULK,
--   AJM.COMMUNICATION_PRIORITY_NORMAL
--   AJM.COMMUNICATION_PRIORITY_ALERT
local function SendChatMessage( text, chatDestination, characterOrChannelName, priority )
	-- Message small enough to send?
	if text:len() <= 255 then
		ChatThrottleLib:SendChatMessage( priority, AJM.MESSAGE_PREFIX, text, chatDestination, nil, characterOrChannelName, nil )
	else
		-- No, message is too big, split into smaller messages, taking UTF8 characters into account.
		local bytesAvailable = string.utf8len(text1)
		local currentPosition = 1
		local countBytes = 1
		local startPosition = currentPosition
		local splitText = ""
		-- Iterate all the utf8 characters, character by character until we reach 255 characters, then send
		-- those off and start counting over.
		while currentPosition <= bytesAvailable do
			-- Count the number of bytes the character at this position takes up.
			countBytes = countBytes + jambautf8charbytes( text, currentPosition )
			-- More than 255 bytes yet?
			if countBytes <= 255 then
				-- No, increment the position and keep counting.
				currentPosition = currentPosition + jambautf8charbytes( text, currentPosition )
			else
				-- Yes, more than 255.  Send this amount off.
				splitText = text:sub( startPosition, currentPosition )
				ChatThrottleLib:SendChatMessage( priority, AJM.MESSAGE_PREFIX, splitText, chatDestination, nil, characterOrChannelName, nil )
				-- New start position and count.
				startPosition = currentPosition + 1
				countBytes = 1
			end
		end
		-- Any more bytes left to send?
		if startPosition < currentPosition then
			-- Yes, send them.
			splitText = text:sub( startPosition, currentPosition )
			ChatThrottleLib:SendChatMessage( priority, AJM.MESSAGE_PREFIX, splitText, chatDestination, nil, characterOrChannelName, nil )
		end
	end
end

-- Functions available from Jamba Communications for other Jamba internal objects.
JambaPrivate.Communications.COMMUNICATION_PRIORITY_BULK = AJM.COMMUNICATION_PRIORITY_BULK
JambaPrivate.Communications.COMMUNICATION_PRIORITY_NORMAL = AJM.COMMUNICATION_PRIORITY_NORMAL
JambaPrivate.Communications.COMMUNICATION_PRIORITY_ALERT = AJM.COMMUNICATION_PRIORITY_ALERT
JambaPrivate.Communications.SendChatMessage = SendChatMessage
JambaPrivate.Communications.SendSettings = SendSettings
JambaPrivate.Communications.SendCommandAll = SendCommandAll
JambaPrivate.Communications.SendCommandMaster = SendCommandMaster
JambaPrivate.Communications.SendCommandToon = SendCommandToon
JambaPrivate.Communications.AssumeTeamAlwaysOnline = AssumeTeamAlwaysOnline
JambaPrivate.Communications.MESSAGE_CHARACTER_ONLINE = AJM.MESSAGE_CHARACTER_ONLINE
JambaPrivate.Communications.MESSAGE_CHARACTER_OFFLINE = AJM.MESSAGE_CHARACTER_OFFLINE

JambaApi.SendChatMessage = SendChatMessage
JambaApi.COMMUNICATION_PRIORITY_BULK = AJM.COMMUNICATION_PRIORITY_BULK
JambaApi.COMMUNICATION_PRIORITY_NORMAL = AJM.COMMUNICATION_PRIORITY_NORMAL
JambaApi.COMMUNICATION_PRIORITY_ALERT = AJM.COMMUNICATION_PRIORITY_ALERT
JambaApi.MESSAGE_CHARACTER_ONLINE = AJM.MESSAGE_CHARACTER_ONLINE
JambaApi.MESSAGE_CHARACTER_OFFLINE = AJM.MESSAGE_CHARACTER_OFFLINE