GroupCalendar_cTitle = string.format(GroupCalendar_cTitle, gGroupCalendar_VersionString);

gGroupCalendar_Settings =
{
	Debug = false,
	ShowEventsInLocalTime = false,
};

gGroupCalendar_PlayerSettings = nil;
gGroupCalendar_RealmSettings = nil;

gGroupCalendar_PlayerName = nil;
gGroupCalendar_PlayerGuild = nil;
gGroupCalendar_PlayerLevel = nil;
gGroupCalendar_PlayerFactionGroup = nil;
gGroupCalendar_PlayerGuildRank = nil;
gGroupCalendar_RealmName = GetRealmName();
gGroupCalendar_Initialized = false;

gGroupCalendar_ServerTimeZoneOffset = 0; -- Offset that, when added to the server time yields the local time

gGroupCalendar_ActiveDialog = nil;

gGroupCalendar_CurrentPanel = 1;

gGroupCalendar_PanelFrames =
{
	"GroupCalendarCalendarFrame",
	"GroupCalendarChannelFrame",
	"GroupCalendarTrustFrame",
	"GroupCalendarAboutFrame",
};

function GroupCalendar_OnLoad()
	SlashCmdList["CALENDAR"] = GroupCalendar_ExecuteCommand;
	
	SLASH_CALENDAR1 = "/calendar";
	
	tinsert(UISpecialFrames, "GroupCalendarFrame");
	
	UIPanelWindows["GroupCalendarFrame"] = {area = "left", pushable = 5, whileDead = 1};
	
	gGroupCalendar_PlayerName = UnitName("player");
	gGroupCalendar_PlayerLevel = UnitLevel("player");
	
	-- Register events
	
	GroupCalendar_RegisterEvent(this, "VARIABLES_LOADED", GroupCalendar_VariablesLoaded);
	GroupCalendar_RegisterEvent(this, "PLAYER_ENTERING_WORLD", GroupCalendar_PlayerEnteringWorld);
	
	-- For updating auto-config settings and guild trust
	-- values
	
	GroupCalendar_RegisterEvent(this, "GUILD_ROSTER_UPDATE", GroupCalendar_GuildRosterUpdate);
	GroupCalendar_RegisterEvent(this, "PLAYER_GUILD_UPDATE", GroupCalendar_PlayerGuildUpdate);
	
	-- For updating the enabled events when the players
	-- level changes
	
	GroupCalendar_RegisterEvent(this, "PLAYER_LEVEL_UP", GroupCalendar_PlayerLevelUp);
	
	-- For monitoring the status of the chat channel
	
	GroupCalendar_RegisterEvent(this, "CHAT_MSG_SYSTEM", GroupCalendar_ChatMsgSystem);
	GroupCalendar_RegisterEvent(this, "CHAT_MSG_CHANNEL_NOTICE", GroupCalendar_ChatMsgChannelNotice);
	GroupCalendar_RegisterEvent(this, "CHAT_MSG_WHISPER", GroupCalendar_ChatMsgWhisper);
	
	-- For suspending/resuming the chat channel during logout
	
	GroupCalendar_RegisterEvent(this, "PLAYER_CAMPING", CalendarNetwork_SuspendChannel);
	GroupCalendar_RegisterEvent(this, "PLAYER_QUITING", CalendarNetwork_SuspendChannel);
	GroupCalendar_RegisterEvent(this, "LOGOUT_CANCEL", CalendarNetwork_ResumeChannel);
	
	-- For monitoring tradeskill cooldowns
	
	GroupCalendar_RegisterEvent(this, "TRADE_SKILL_UPDATE", EventDatabase_UpdateCurrentTradeskillCooldown);
	GroupCalendar_RegisterEvent(this, "TRADE_SKILL_SHOW", EventDatabase_UpdateCurrentTradeskillCooldown);
	GroupCalendar_RegisterEvent(this, "BAG_UPDATE_COOLDOWN", GroupCalendar_CheckItemCooldowns);
	GroupCalendar_RegisterEvent(this, "BAG_UPDATE", GroupCalendar_CheckItemCooldowns);
	
	-- For managing group invites
	
	GroupCalendar_RegisterEvent(this, "PARTY_MEMBERS_CHANGED", CalendarGroupInvites_PartyMembersChanged);
	GroupCalendar_RegisterEvent(this, "RAID_ROSTER_UPDATE", CalendarGroupInvites_PartyMembersChanged);
	GroupCalendar_RegisterEvent(this, "PARTY_LOOT_METHOD_CHANGED", CalendarGroupInvites_PartyLootMethodChanged);
	
	-- For dragging the window
	
	this:RegisterForDrag("LeftButton");
	
	-- Tabs
	
	PanelTemplates_SetNumTabs(this, table.getn(gGroupCalendar_PanelFrames));
	GroupCalendarFrame.selectedTab = gGroupCalendar_CurrentPanel;
	PanelTemplates_UpdateTabs(this);
	
	-- Done initializing
	
	if DEFAULT_CHAT_FRAME then
		DEFAULT_CHAT_FRAME:AddMessage(GroupCalendar_cLoadMessage, 0.8, 0.8, 0.2);
	end
end

function GroupCalendar_RegisterEvent(pFrame, pEvent, pHandler)
	if not pHandler then
		Calendar_ErrorMessage("GroupCalendar: Attemping to install a nil handler for event "..pEvent);
		return;
	end
	
	if not pFrame.EventHandlers then
		pFrame.EventHandlers = {};
	end
	
	pFrame.EventHandlers[pEvent] = pHandler;
	pFrame:RegisterEvent(pEvent);
end

function GroupCalendar_UnregisterEvent(pFrame, pEvent)
	if pFrame.EventHandlers then
		pFrame.EventHandlers[pEvent] = nil;
	end
	
	pFrame:UnregisterEvent(pEvent);
end

function GroupCalendar_DispatchEvent(pFrame, pEvent)
	if not pFrame.EventHandlers then	
		return false;
	end
	
	local	vEventHandler = pFrame.EventHandlers[pEvent];
	
	if not vEventHandler then
		Calendar_ErrorMessage("GroupCalendar: No event handler for "..pEvent);
		return false;
	end
	
	vEventHandler(pEvent);
	return true;
end

function GroupCalendar_VariablesLoaded()
	gGroupCalendar_MinimumEventDate = Calendar_GetCurrentLocalDate() - gGroupCalendar_MaximumEventAge;
	
	EventDatabase_SetUserName(gGroupCalendar_PlayerName);
	EventDatabase_PlayerLevelChanged(gGroupCalendar_PlayerLevel);
	CalendarNetwork_CheckPlayerGuild();
	
	EventDatabase_Initialize();
	
	CalendarNetwork_CalendarLoaded();
end

function GroupCalendar_OnShow()
	PlaySound("igCharacterInfoOpen");
	
	local	vYear, vMonth, vDay, vHour, vMinute = Calendar_GetCurrentYearMonthDayHourMinute();
	local	vMonthStartDate = Calendar_ConvertMDYToDate(vMonth, 1, vYear);
	
	-- Update the guild roster
	
	if IsInGuild() and GetNumGuildMembers() == 0 then
		GuildRoster();
	end
	
	Calendar_SetDisplayDate(vMonthStartDate);
	Calendar_SetActualDate(vMonthStartDate + vDay - 1);
	GroupCalendar_ShowPanel(1); -- Always switch  back to the Calendar view when showing the window
	
	GroupCalendarUseServerTime:SetChecked(not gGroupCalendar_Settings.ShowEventsInLocalTime);
end

function GroupCalendar_OnHide()
	PlaySound("igCharacterInfoClose");
	CalendarEventEditor_DoneEditing();
	CalendarEventViewer_DoneViewing();
	CalendarEditor_Close();
end

function GroupCalendar_OnEvent(pEvent)
	local	vLatencyStartTime;
	
	if gGroupCalendar_Settings.DebugLatency then
		vLatencyStartTime = GetTime();
	end
	
	if GroupCalendar_DispatchEvent(this, pEvent) then
		
		-- Handled
		
	elseif pEvent == "CHAT_MSG_SYSTEM" then
		CalendarNetwork_SystemMessage(arg1);
		CalendarGroupInvites_SystemMessage(arg1);
		
	elseif pEvent == "GUILD_ROSTER_UPDATE" then
		-- Ignore the update if we're not initialized yet
		
		if not gGroupCalendar_Initialized then
			return;
		end
		
		CalendarNetwork_GuildRosterChanged();
		CalendarGroupInvites_GuildRosterChanged();
		
	elseif pEvent == "PLAYER_GUILD_UPDATE" then
		CalendarNetwork_CheckPlayerGuild();

		-- Ignore the update if we're not initialized yet
		
		if not gGroupCalendar_Initialized then
			return;
		end
		
		GroupCalendar_UpdateEnabledControls();
		
	elseif pEvent == "PLAYER_LEVEL_UP" then
		gGroupCalendar_PlayerLevel = tonumber(arg1);
		EventDatabase_PlayerLevelChanged(gGroupCalendar_PlayerLevel);
		GroupCalendar_MajorDatabaseChange(nil);
	end
	
	--
	
	if gGroupCalendar_Settings.DebugLatency then
		local	vElapsed = GetTime() - vLatencyStartTime;
		
		if vElapsed > 0.1 then
			Calendar_DebugMessage("Event "..pEvent.." took "..vElapsed.."s to execute");
		end
	end
end

function GroupCalendar_PlayerEnteringWorld(pEvent)
	Calendar_InitDebugging();
	
	gGroupCalendar_PlayerSettings = GroupCalendar_GetPlayerSettings(gGroupCalendar_PlayerName, GetRealmName());
	gGroupCalendar_RealmSettings = GroupCalendar_GetRealmSettings(GetRealmName());
	
	gGroupCalendar_PlayerLevel = UnitLevel("player");
	gGroupCalendar_PlayerFactionGroup = UnitFactionGroup("player");
	gGroupCalendar_PlayerSettings = GroupCalendar_GetPlayerSettings(gGroupCalendar_PlayerName, GetRealmName());
	gGroupCalendar_RealmSettings = GroupCalendar_GetRealmSettings(GetRealmName());
	EventDatabase_PlayerLevelChanged(gGroupCalendar_PlayerLevel);
	GroupCalendar_CalculateTimeZoneOffset();
	GroupCalendar_MajorDatabaseChange(nil);
end

function GroupCalendar_ChatMsgChannel(pEvent)
	if arg9
	and strlower(arg9) == gGroupCalendar_Channel.NameLower
	and arg1
	and type(arg1) == "string"
	then
		if arg2 == gGroupCalendar_PlayerName then
			-- Ignore messages from ourselves
			
			if not gGroupCalendar_Settings.Debug then
				return;
			end
			
			-- Special debugging case: allow self-send of messages starting with '!'
			
			if strsub(arg1, 1, 1) ~= "!" then
				return;
			end
			
			arg1 = strsub(arg1, 2);
		end
		
		-- Clean up drunkeness
		
		if strsub(arg1, -8) == " ...hic!" then
			arg1 = strsub(arg1, 1, -9);
		end
		
		arg1 = Calendar_UnescapeChatString(arg1);
		
		--
		
		CalendarNetwork_ChannelMessageReceived(arg2, arg1);
	end
end

function GroupCalendar_ChatMsgChannelNotice(pEvent)
	local	vChannelMessage = arg1;
	local	vChannelName = arg4;
	local	vChannelID = arg8;
	local	vActualChannelName = arg9;

	CalendarNetwork_ChannelNotice(vChannelMessage, vChannelName, vChannelID, vActualChannelName);
end

function GroupCalendar_ChatMsgWhisper(pEvent)
	CalendarWhisperLog_AddWhisper(arg2, arg1);
end

function GroupCalendar_ChatMsgSystem(pEvent)
	CalendarNetwork_SystemMessage(arg1);
	CalendarGroupInvites_SystemMessage(arg1);
end

function GroupCalendar_GuildRosterUpdate(pEvent)
	-- Ignore the update if we're not initialized yet

	if not gGroupCalendar_Initialized then
		return;
	end

	CalendarNetwork_GuildRosterChanged();
	CalendarGroupInvites_GuildRosterChanged();
end

function GroupCalendar_PlayerGuildUpdate(pEvent)
	CalendarNetwork_CheckPlayerGuild();

	-- Ignore the update if we're not initialized yet
	
	if not gGroupCalendar_Initialized then
		return;
	end
	
	GroupCalendar_UpdateEnabledControls();
end

function GroupCalendar_PlayerLevelUp(pEvent)
	gGroupCalendar_PlayerLevel = tonumber(arg1);
	EventDatabase_PlayerLevelChanged(gGroupCalendar_PlayerLevel);
	GroupCalendar_MajorDatabaseChange(nil);
end

GroupCalendar_cCooldownItemInfo =
{
	[15846] = {EventID = "Leatherworking"}, -- Salt Shaker
	[17716] = {EventID = "Snowmaster"}, -- Snowmaster 9000
};

function GroupCalendar_CheckItemCooldowns()
	for vBagIndex = 0, NUM_BAG_SLOTS do
		local	vNumBagSlots = GetContainerNumSlots(vBagIndex);
		
		for vBagSlotIndex = 1, vNumBagSlots do
			local	vItemLink = GetContainerItemLink(vBagIndex, vBagSlotIndex);
			
			if vItemLink then
				local	vStartIndex, vEndIndex, vLinkColor, vItemCode, vItemEnchantCode, vItemSubCode, vUnknownCode, vItemName = strfind(vItemLink, "|(%x+)|Hitem:(%d+):(%d+):(%d+):(%d+)|h%[([^%]]+)%]|h|r");
				
				if vStartIndex then
					vItemCode = tonumber(vItemCode);
					
					local	vCooldownItemInfo = GroupCalendar_cCooldownItemInfo[vItemCode];
					
					if vCooldownItemInfo then
						local vStart, vDuration, vEnable = GetContainerItemCooldown(vBagIndex, vBagSlotIndex);
						
						-- local	texture, itemCount, locked, quality, readable = GetContainerItemInfo(vBagIndex, vBagSlotIndex);
						-- Calendar_TestMessage(vItemName..": "..texture);
						
						if vEnable > 0 then
							vRemainingTime = vDuration - (GetTime() - vStart);
							
							if vRemainingTime > 0 then
								EventDatabase_ScheduleTradeskillCooldownEvent(gGroupCalendar_UserDatabase, vCooldownItemInfo.EventID, vRemainingTime);
							end
						end
					end
				end
			end
		end
	end
end

function GroupCalendar_UpdateEnabledControls()
	if GroupCalendarFrame.selectedTab == 1 then
		-- Update the calendar display
		
	elseif GroupCalendarFrame.selectedTab == 2 then
		-- Update the channel frame
		
		Calendar_SetCheckButtonEnable(GroupCalendarAutoChannelConfig, IsInGuild());
		
		Calendar_SetEditBoxEnable(GroupCalendarChannelName, GroupCalendarManualChannelConfig:GetChecked());
		Calendar_SetEditBoxEnable(GroupCalendarChannelPassword, GroupCalendarManualChannelConfig:GetChecked());
		
		if IsInGuild()
		and CanEditPublicNote()
		and GroupCalendarManualChannelConfig:GetChecked() then
			Calendar_SetCheckButtonEnable(GroupCalendarStoreAutoConfig, true);
			
			if GroupCalendarStoreAutoConfig:GetChecked() then
				Calendar_SetEditBoxEnable(GroupCalendarAutoConfigPlayer, true);
			else
				Calendar_SetEditBoxEnable(GroupCalendarAutoConfigPlayer, false);
			end
		else
			Calendar_SetCheckButtonEnable(GroupCalendarStoreAutoConfig, false);
			Calendar_SetEditBoxEnable(GroupCalendarAutoConfigPlayer, false);
		end
		
		Calendar_SetButtonEnable(GroupCalendarApplyChannelButton, GroupCalendar_ChannelPanelHasChanges());
		
	elseif GroupCalendarFrame.selectedTab == 3 then
		-- Update the trust frame
		
		if gGroupCalendar_PlayerSettings.Channel.AutoConfig then
			Calendar_SetDropDownEnable(GroupCalendarTrustGroup, false);
			Calendar_SetDropDownEnable(GroupCalendarTrustMinRank, false);
		else
			GroupCalendar_SaveTrustGroup();
			
			Calendar_SetDropDownEnable(GroupCalendarTrustGroup, true);
			Calendar_SetDropDownEnable(GroupCalendarTrustMinRank, UIDropDownMenu_GetSelectedValue(GroupCalendarTrustGroup) == 2);
		end
		
		if UIDropDownMenu_GetSelectedValue(GroupCalendarTrustGroup) == 2 then
			GroupCalendarTrustMinRank:Show();
		else
			GroupCalendarTrustMinRank:Hide();
		end
		
	elseif GroupCalendarFrame.selectedTab == 4 then
		-- Update the ignore frame
	
	end
end

function GroupCalendar_SetAutoChannelConfig(pEnableAutoConfig)
	GroupCalendarAutoChannelConfig:SetChecked(pEnableAutoConfig);
	GroupCalendarManualChannelConfig:SetChecked(not pEnableAutoConfig);
	
	GroupCalendar_UpdateEnabledControls();
end

function GroupCalendar_EnableAutoConfigPlayer(pEnableStoreAutoConfig)
	GroupCalendarStoreAutoConfig:SetChecked(pEnableStoreAutoConfig);
	GroupCalendar_UpdateEnabledControls();
end

function GroupCalendar_SavePanel(pIndex)
	if pIndex == 2 then
		-- Channel panel
		
		gGroupCalendar_PlayerSettings.Channel.AutoConfig = GroupCalendarAutoChannelConfig:GetChecked() == 1;
		
		if gGroupCalendar_PlayerSettings.Channel.AutoConfig then
			gGroupCalendar_PlayerSettings.Channel.Name = nil;
			gGroupCalendar_PlayerSettings.Channel.Password = nil;
			gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer = nil;
			
			CalendarNetwork_ScheduleAutoConfig(0.5);
			
			GroupCalendar_ShowPanel(pIndex); -- Refresh the controls
		else
			gGroupCalendar_PlayerSettings.Channel.Name = GroupCalendarChannelName:GetText();

			if gGroupCalendar_PlayerSettings.Channel.Name == "" then
				gGroupCalendar_PlayerSettings.Channel.Name = nil;
			end
			
			gGroupCalendar_PlayerSettings.Channel.Password = GroupCalendarChannelPassword:GetText();
			
			if gGroupCalendar_PlayerSettings.Channel.Password == "" then
				gGroupCalendar_PlayerSettings.Channel.Password = nil;
			end
			
			CalendarNetwork_SetChannel(gGroupCalendar_PlayerSettings.Channel.Name, gGroupCalendar_PlayerSettings.Channel.Password);
			
			if GroupCalendarStoreAutoConfig:GetChecked() then
				gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer = GroupCalendarAutoConfigPlayer:GetText();
				CalendarNetwork_SetAutoConfigData(gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer);
			elseif gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer then
				gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer = nil;

				if CanEditPublicNote() then
					CalendarNetwork_RemoveAllAutoConfigData();
				end
			end
		end
	elseif pIndex == 3 then
		-- Trust panel
		
		if not gGroupCalendar_PlayerSettings.Channel.AutoConfig then
			GroupCalendar_SaveTrustGroup();
		end
	end
	
	GroupCalendar_UpdateEnabledControls();
end

function GroupCalendar_ChannelPanelHasChanges()
	if (GroupCalendarAutoChannelConfig:GetChecked() == 1)
	~= gGroupCalendar_PlayerSettings.Channel.AutoConfig then
		return true;
	end
	
	if GroupCalendarManualChannelConfig:GetChecked() then
		local	vChannelName = gGroupCalendar_PlayerSettings.Channel.Name;
		
		if not vChannelName then
			vChannelName = "";
		end
		
		local	vChannelPassword = gGroupCalendar_PlayerSettings.Channel.Password;
		
		if not vChannelPassword then
			vChannelPassword = "";
		end
		
		if GroupCalendarChannelName:GetText() ~= vChannelName
		or GroupCalendarChannelPassword:GetText() ~= vChannelPassword then
			return true;
		end
	end
	
	local	vStoreAutoConfigIsEnabled = (not gGroupCalendar_PlayerSettings.Channel.AutoConfig)
	                               and (gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer ~= nil);

	if (GroupCalendarStoreAutoConfig:GetChecked() == 1)
	~= vStoreAutoConfigIsEnabled then
		return true;
	end
	
	if GroupCalendarStoreAutoConfig:GetChecked()
	and GroupCalendarAutoConfigPlayer:GetText() ~= gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer then
		return true;
	end
	
	return false;
end

function GroupCalendar_SaveTrustGroup()
	local	vTrustGroup = UIDropDownMenu_GetSelectedValue(GroupCalendarTrustGroup);
	local	vChanged = false;
	
	if vTrustGroup == 1
	and not gGroupCalendar_PlayerSettings.Security.TrustAnyone then
		gGroupCalendar_PlayerSettings.Security.TrustAnyone = true;
		gGroupCalendar_PlayerSettings.Security.TrustGuildies = false;
		vChanged = true;
	elseif vTrustGroup == 2 then
		if not gGroupCalendar_PlayerSettings.Security.TrustGuildies then
			gGroupCalendar_PlayerSettings.Security.TrustAnyone = false;
			gGroupCalendar_PlayerSettings.Security.TrustGuildies = true;
			vChanged = true;
		end
		
		local	vMinTrustedRank = UIDropDownMenu_GetSelectedValue(GroupCalendarTrustMinRank);
		
		if not vMinTrustedRank then
			vMinTrustedRank = 1;
			CalendarDropDown_SetSelectedValue(GroupCalendarTrustMinRank, vMinTrustedRank);
		end
		
		if vMinTrustedRank ~= gGroupCalendar_PlayerSettings.Security.MinTrustedRank then
			gGroupCalendar_PlayerSettings.Security.MinTrustedRank = vMinTrustedRank;
			vChanged = true;
		end
	elseif vTrustGroup == 3
	and (gGroupCalendar_PlayerSettings.Security.TrustAnyone
	or gGroupCalendar_PlayerSettings.Security.TrustGuildies) then
		gGroupCalendar_PlayerSettings.Security.TrustAnyone = false;
		gGroupCalendar_PlayerSettings.Security.TrustGuildies = false;
		vChanged = true;
	end
	
	if vChanged then
		-- Update auto-config string with the trust config
		
		if CanEditPublicNote()
		and gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer then
			CalendarNetwork_SetAutoConfigData(gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer);
		end
		
		CalendarTrust_TrustSettingsChanged();
	end
end

function GroupCalendar_ShowPanel(pPanelIndex)
	if gGroupCalendar_CurrentPanel > 0
	and gGroupCalendar_CurrentPanel ~= pPanelIndex then
		GroupCalendar_HidePanel(gGroupCalendar_CurrentPanel);
	end
	
	-- NOTE: Don't check for redundant calls since this function
	-- will be called to reset the field values as well as to 
	-- actuall show the panel when it's hidden
	
	gGroupCalendar_CurrentPanel = pPanelIndex;
	
	-- Hide the event editor/viewer if the calendar panel is being hidden
	
	if pPanelIndex ~= 1 then
		CalendarEventEditor_DoneEditing();
		CalendarEventViewer_DoneViewing();
		CalendarEditor_Close();
	end
	
	-- Update the control values
	
	if pPanelIndex == 1 then
	elseif pPanelIndex == 2 then
		-- Channel panel
		
		if gGroupCalendar_PlayerSettings.Channel.AutoConfig
		and not IsInGuild() then
			gGroupCalendar_PlayerSettings.Channel.AutoConfig = false;
		end
		
		GroupCalendarAutoChannelConfig:SetChecked(gGroupCalendar_PlayerSettings.Channel.AutoConfig);
		GroupCalendarManualChannelConfig:SetChecked(not gGroupCalendar_PlayerSettings.Channel.AutoConfig);
		
		local	vChannelName;
		local	vAutoConfigPlayer;
		
		if gGroupCalendar_PlayerSettings.Channel.AutoConfig then
			vChannelName = gGroupCalendar_Channel.Name;
			if gGroupCalendar_Channel.Password then
				GroupCalendarChannelPassword:SetText("******");
			else
				GroupCalendarChannelPassword:SetText("");
			end
			
			vAutoConfigPlayer = gGroupCalendar_Channel.AutoPlayer;
		else
			vChannelName = gGroupCalendar_PlayerSettings.Channel.Name;
			vAutoConfigPlayer = gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer;
			
			if gGroupCalendar_PlayerSettings.Channel.Password then
				GroupCalendarChannelPassword:SetText(gGroupCalendar_PlayerSettings.Channel.Password);
			else
				GroupCalendarChannelPassword:SetText("");
			end
		end
		
		if vChannelName ~= nil then
			GroupCalendarChannelName:SetText(vChannelName);
		else
			GroupCalendarChannelName:SetText("");
		end
		
		if vAutoConfigPlayer then
			GroupCalendarAutoConfigPlayer:SetText(vAutoConfigPlayer);
		else
			GroupCalendarAutoConfigPlayer:SetText("");
		end
		
		if not gGroupCalendar_PlayerSettings.Channel.AutoConfig
		and gGroupCalendar_PlayerSettings.Channel.AutoConfigPlayer then
			GroupCalendarStoreAutoConfig:SetChecked(true);
		else
			GroupCalendarStoreAutoConfig:SetChecked(false);
		end
		
		GroupCalendar_UpdateChannelStatus();
		
	elseif pPanelIndex == 3 then
		-- Trust panel
		
		GroupCalendarTrustGroup.ChangedValueFunc = GroupCalendar_UpdateEnabledControls;
		
		if gGroupCalendar_PlayerSettings.Security.TrustGuildies
		and not IsInGuild() then
			gGroupCalendar_PlayerSettings.Security.TrustGuildies = false;
		end
		
		if gGroupCalendar_PlayerSettings.Security.TrustAnyone then
			CalendarDropDown_SetSelectedValue(GroupCalendarTrustGroup, 1);
		elseif gGroupCalendar_PlayerSettings.Security.TrustGuildies then
			CalendarDropDown_SetSelectedValue(GroupCalendarTrustGroup, 2);
		else
			CalendarDropDown_SetSelectedValue(GroupCalendarTrustGroup, 3);
		end
		
		if gGroupCalendar_PlayerSettings.Security.TrustGuildies then
			GroupCalendarTrustMinRank:Show();
			
			if gGroupCalendar_PlayerSettings.Security.MinTrustedRank ~= nil then
				CalendarDropDown_SetSelectedValue(GroupCalendarTrustMinRank, gGroupCalendar_PlayerSettings.Security.MinTrustedRank);
			else
				UIDropDownMenu_SetText("", GroupCalendarTrustMinRank); -- In case the value doesn't exist
			end
		else
			GroupCalendarTrustMinRank:Hide();
		end
		
		CalendarPlayerList_SetItemFunction(CalendarTrustedPlayersList, GroupCalendar_GetIndexedTrustedPlayer);
		CalendarPlayerList_SetSelectionChangedFunction(CalendarTrustedPlayersList, GroupCalendar_TrustedPlayerSelected);
		
		CalendarPlayerList_SetItemFunction(CalendarExcludedPlayersList, GroupCalendar_GetIndexedExcludedPlayer);
		CalendarPlayerList_SetSelectionChangedFunction(CalendarExcludedPlayersList, GroupCalendar_ExcludedPlayerSelected);
	end
	
	GroupCalendar_UpdateEnabledControls();
	
	getglobal(gGroupCalendar_PanelFrames[pPanelIndex]):Show();
	
	PanelTemplates_SetTab(GroupCalendarFrame, pPanelIndex);
end

function GroupCalendar_HidePanel(pFrameIndex)
	if gGroupCalendar_CurrentPanel ~= pFrameIndex then
		return;
	end
	
	GroupCalendar_SavePanel(pFrameIndex);
	
	getglobal(gGroupCalendar_PanelFrames[pFrameIndex]):Hide();
	gGroupCalendar_CurrentPanel = 0;
end

function GroupCalendar_UpdateChannelStatus()
	local	vChannelStatus = CalendarNetwork_GetChannelStatus();
	local	vStatusText = GroupCalendar_cChannelStatus[vChannelStatus];
	
	GroupCalendarChannelStatus:SetText(string.format(vStatusText.mText, gGroupCalendar_Channel.StatusMessage));
	GroupCalendarChannelStatus:SetTextColor(vStatusText.mColor.r, vStatusText.mColor.g, vStatusText.mColor.b);
	
	if vChannelStatus == "Connected" then
		Calendar_SetButtonEnable(GroupCalendarConnectChannelButton, true);
		GroupCalendarConnectChannelButton:SetText(GroupCalendar_cDisconnectChannel);
	elseif vChannelStatus == "Disconnected" or vChannelStatus == "Error" then
		Calendar_SetButtonEnable(GroupCalendarConnectChannelButton, true);
		GroupCalendarConnectChannelButton:SetText(GroupCalendar_cConnectChannel);
	else
		Calendar_SetButtonEnable(GroupCalendarConnectChannelButton, false);
	end
end

function GroupCalendar_FixPlayerName(pName)
	if pName == nil
	or pName == "" then
		return nil;
	end
	
	local	vFirstChar = string.sub(pName, 1, 1);
	
	if (vFirstChar >= "a" and vFirstChar <= "z")
	or (vFirstChar >= "A" and vFirstChar <= "Z") then
		return string.upper(vFirstChar)..string.lower(string.sub(pName, 2));
	else
		return pName;
	end
end

function GroupCalendar_AddTrustedPlayer(pPlayerName)
	local	vPlayerName = GroupCalendar_FixPlayerName(pPlayerName);
	
	if vPlayerName == nil then
		return;
	end
	
	gGroupCalendar_PlayerSettings.Security.Player[vPlayerName] = 1;
	GroupCalendar_UpdateTrustedPlayerList();
	CalendarTrust_TrustSettingsChanged();
end

function GroupCalendar_AddExcludedPlayer(pPlayerName)
	local	vPlayerName = GroupCalendar_FixPlayerName(pPlayerName);

	if vPlayerName == nil then
		return;
	end
	
	gGroupCalendar_PlayerSettings.Security.Player[vPlayerName] = 2;
	GroupCalendar_UpdateTrustedPlayerList();
	CalendarTrust_TrustSettingsChanged();
end

function GroupCalendar_RemoveTrustedPlayer(pPlayerName)
	local	vPlayerName = GroupCalendar_FixPlayerName(pPlayerName);

	if vPlayerName == nil then
		return;
	end
	
	gGroupCalendar_PlayerSettings.Security.Player[vPlayerName] = nil;
	
	GroupCalendar_UpdateTrustedPlayerList();
	
	CalendarPlayerList_SelectIndexedPlayer(CalendarTrustedPlayersList, 0);
	CalendarPlayerList_SelectIndexedPlayer(CalendarExcludedPlayersList, 0);
	
	CalendarTrust_TrustSettingsChanged();
end

function GroupCalendar_UpdateTrustedPlayerList()
	CalendarPlayerList_Update(CalendarTrustedPlayersList);
	CalendarPlayerList_Update(CalendarExcludedPlayersList);
end

function GroupCalendar_GetIndexedTrustedPlayer(pIndex)
	if pIndex == 0 then
		return CalendarTrust_GetNumTrustedPlayers(1);
	end
	
	return
	{
		Text = CalendarTrust_GetIndexedTrustedPlayers(1, pIndex);
	};
end

function GroupCalendar_GetIndexedExcludedPlayer(pIndex)
	if pIndex == 0 then
		return CalendarTrust_GetNumTrustedPlayers(2);
	end
	
	return
	{
		Text = CalendarTrust_GetIndexedTrustedPlayers(2, pIndex);
	};
end

function GroupCalendar_TrustedPlayerSelected(pIndex)
	if pIndex == 0 then
		return;
	end
	
	CalendarPlayerList_SelectIndexedPlayer(CalendarExcludedPlayersList, 0);
	
	local	vName = CalendarTrust_GetIndexedTrustedPlayers(1, pIndex);
	
	if vName then
		CalendarTrustedPlayerName:SetText(vName);
		CalendarTrustedPlayerName:HighlightText();
		CalendarTrustedPlayerName:SetFocus();
	end
end

function GroupCalendar_ExcludedPlayerSelected(pIndex)
	if pIndex == 0 then
		return;
	end
	
	CalendarPlayerList_SelectIndexedPlayer(CalendarTrustedPlayersList, 0);
	
	local	vName = CalendarTrust_GetIndexedTrustedPlayers(2, pIndex);
	
	if vName then
		CalendarTrustedPlayerName:SetText(vName);
		CalendarTrustedPlayerName:HighlightText();
		CalendarTrustedPlayerName:SetFocus();
	end
end

function GroupCalendar_SelectDateWithToggle(pDate)
	if CalendarEditor_IsOpen()
	and gCalendarEditor_SelectedDate == pDate then
		CalendarEditor_Close();
	else
		GroupCalendar_SelectDate(pDate);
	end
end

function GroupCalendar_SelectDate(pDate)
	Calendar_SetSelectedDate(pDate);
	
	local	vCompiledSchedule = EventDatabase_GetCompiledSchedule(pDate);
	
	CalendarEditor_ShowCompiledSchedule(pDate, vCompiledSchedule);
end

function GroupCalendar_EditorClosed()
	Calendar_ClearSelectedDate();
end

function GroupCalendar_EventChanged(pDatabase, pEvent, pChangedFields)
	GroupCalendar_ScheduleChanged(pDatabase, pEvent.mDate);
	CalendarEventEditor_EventChanged(pEvent);
end

function GroupCalendar_ScheduleChanged(pDatabase, pDate)
	GroupCalendar_ScheduleChanged2(pDatabase, pDate);
	
	if gGroupCalendar_Settings.ShowEventsInLocalTime then
		if gGroupCalendar_ServerTimeZoneOffset < 0 then
			GroupCalendar_ScheduleChanged2(pDatabase, pDate - 1);
		elseif gGroupCalendar_ServerTimeZoneOffset > 0 then
			GroupCalendar_ScheduleChanged2(pDatabase, pDate + 1);
		end
	end
end

function GroupCalendar_ScheduleChanged2(pDatabase, pDate)
	local	vSchedule = pDatabase.Events[pDate];
	
	CalendarEditor_ScheduleChanged(pDate, pSchedule);
	Calendar_ScheduleChanged(pDate, pSchedule);
	CalendarEventViewer_ScheduleChanged(pDate);
end

function GroupCalendar_MajorDatabaseChange(pDatabase)
	CalendarEditor_MajorDatabaseChange();
	CalendarEventViewer_MajorDatabaseChange();
	CalendarEventEditor_MajorDatabaseChange();
	Calendar_MajorDatabaseChange();
end

gGroupCalendar_QueueElapsedTime = 0;

function GroupCalendar_Update(pElapsed)
	local	vLatencyStartTime;
	
	-- Process invites
	
	if gGroupCalendar_Settings.DebugLatency then
		vLatencyStartTime = GetTime();
	end
	
	CalendarGroupInvites_Update(pElapsed);
	
	if gGroupCalendar_Settings.DebugLatency then
		local	vElapsed = GetTime() - vLatencyStartTime;
		
		if vElapsed > 0.1 then
			Calendar_DebugMessage("CalendarGroupInvites_Update took "..vElapsed.."s to execute");
		end
		
		vLatencyStartTime = GetTime();
	end

	-- Process the event queues if it's time
	
	gGroupCalendar_QueueElapsedTime = gGroupCalendar_QueueElapsedTime + pElapsed;
	
	if gGroupCalendar_QueueElapsedTime > 0.1 then
		CalendarNetwork_ProcessQueues(gGroupCalendar_QueueElapsedTime);
		
		gGroupCalendar_QueueElapsedTime = 0;
	end
	
	if gGroupCalendar_Settings.DebugLatency then
		local	vElapsed = GetTime() - vLatencyStartTime;
		
		if vElapsed > 0.1 then
			Calendar_DebugMessage("CalendarNetwork_ProcessQueues took "..vElapsed.."s to execute");
		end
		
		vLatencyStartTime = GetTime();
	end

	-- Stop the timer if it isn't needed
	
	if not CalendarNetwork_NeedsUpdateTimer()
	and not CalendarGroupInvites_NeedsUpdateTimer() then
		if gGroupCalendar_Settings.DebugTimer then
			Calendar_DebugMessage("GroupCalendar_Update: Stopping timer");
		end
		
		GroupCalendarUpdateFrame:Hide();
	end

	if gGroupCalendar_Settings.DebugLatency then
		local	vElapsed = GetTime() - vLatencyStartTime;
		
		if vElapsed > 0.1 then
			Calendar_DebugMessage("NeedsUpdateTimer took "..vElapsed.."s to execute");
		end
	end
end

function GroupCalendar_StartUpdateTimer()
	if GroupCalendarUpdateFrame:IsVisible() then
		return;
	end
	
	if gGroupCalendar_Settings.DebugTimer then
		Calendar_DebugMessage("GroupCalendar_StartUpdateTimer()");
	end
	
	GroupCalendarUpdateFrame:Show();
end

function GroupCalendar_StartMoving()
	if not gGroupCalendar_PlayerSettings.UI.LockWindow then
		GroupCalendarFrame:StartMoving();
	end
end

function GroupCalendar_GetPlayerSettings(pPlayerName, pRealmName)
	-- Wipe the settings if they're the alpha version
	
	if not gGroupCalendar_Settings.Format then
		gGroupCalendar_Settings =
		{
			Debug = false,
			Format = 1,
		};
	end
	
	local	vSettings = gGroupCalendar_Settings[pRealmName.."_"..pPlayerName];
	
	if vSettings == nil then
		vSettings =
		{
			Security =
			{
				TrustAnyone = false,
				TrustGuildies = IsInGuild(),
				MinTrustedRank = 1,
				Player = {},
			},
			
			Channel =
			{
				AutoConfig = IsInGuild(),
				AutoConfigPlayer = nil,
				Name = nil,
				Password = nil,
			},
			
			UI =
			{
				LockWindow = false,
			},
		};
		
		gGroupCalendar_Settings[pRealmName.."_"..pPlayerName] = vSettings;
	end
	
	return vSettings;
end

function GroupCalendar_GetRealmSettings(pRealmName)
	local	vSettings = gGroupCalendar_Settings[pRealmName];
	
	if vSettings == nil then
		vSettings = {};
		gGroupCalendar_Settings[pRealmName] = vSettings;
	end
	
	return vSettings;
end

function GroupCalendar_RoundTimeOffsetToNearest30(pOffset)
	local	vNegativeOffset;
	local	vOffset;
	
	if pOffset < 0 then
		vNegativeOffset = true;
		vOffset = -pOffset;
	else
		vNegativeOffset = false;
		vOffset = pOffset;
	end
	
	vOffset = vOffset - (math.mod(vOffset + 15, 30) - 15);
	
	if vNegativeOffset then
		return -vOffset;
	else
		return vOffset;
	end
end

function GroupCalendar_CalculateTimeZoneOffset()
	local	vServerTime = Calendar_ConvertHMToTime(GetGameTime());
	local	vLocalDate, vLocalTime = Calendar_GetCurrentLocalDateTime();
	local	vUTCDate, vUTCTime = Calendar_GetCurrentUTCDateTime();
	
	local	vLocalDateTime = vLocalDate * 1440 + vLocalTime;
	local	vUTCDateTime = vUTCDate * 1440 + vUTCTime;
	
	local	vLocalUTCDelta = GroupCalendar_RoundTimeOffsetToNearest30(vLocalDateTime - vUTCDateTime);
	local	vLocalServerDelta = GroupCalendar_RoundTimeOffsetToNearest30(vLocalTime - vServerTime);
	local	vServerUTCDelta = vLocalUTCDelta - vLocalServerDelta;
	
	if vServerUTCDelta < (-12 * 60) then
		vServerUTCDelta = vServerUTCDelta + (24 * 60);
	elseif vServerUTCDelta > (12 * 60) then
		vServerUTCDelta = vServerUTCDelta - (24 * 60);
	end
	
	vLocalServerDelta = vLocalUTCDelta - vServerUTCDelta;
	
	if vLocalServerDelta ~= gGroupCalendar_ServerTimeZoneOffset then
		gGroupCalendar_ServerTimeZoneOffset = vLocalServerDelta;
		
		-- Time zone changed
		
		if gGroupCalendar_ServerTimeZoneOffset == 0 then
			GroupCalendarUseServerTime:Hide();
		else
			GroupCalendarUseServerTime:Show();
		end
	end
end

function GroupCalendar_UpdateTimeTooltip()
	local	vServerTime = Calendar_ConvertHMToTime(GetGameTime());
	local	vLocalDate = Calendar_GetCurrentLocalDate();
	
	local	vServerTimeString = Calendar_GetShortTimeString(vServerTime);
	local	vLocalDateString = Calendar_GetLongDateString(vLocalDate, true);

	GameTooltip:AddLine(vLocalDateString);
	GameTooltip:AddLine(vServerTimeString);
	
	if gGroupCalendar_ServerTimeZoneOffset ~= 0 then
		local	vLocalTime = Calendar_GetLocalTimeFromServerTime(vServerTime);
		local	vLocalTimeString = Calendar_GetShortTimeString(vLocalTime);
		
		GameTooltip:AddLine(string.format(GroupCalendar_cLocalTimeNote, vLocalTimeString));
	end
	
	GameTooltip:Show();
end

function GroupCalendar_ChannelChanged()
	if CalendarNetwork_GetChannelStatus() == "Connected" then
		GroupCalendar_RegisterEvent(GroupCalendarFrame, "CHAT_MSG_CHANNEL", GroupCalendar_ChatMsgChannel);
	else
		GroupCalendar_UnregisterEvent(GroupCalendarFrame, "CHAT_MSG_CHANNEL");
	end

	if GroupCalendarFrame:IsVisible()
	and (gGroupCalendar_CurrentPanel == 2
	or gGroupCalendar_CurrentPanel == 3) then
		if GroupCalendarAutoChannelConfig:GetChecked() then
			GroupCalendar_ShowPanel(gGroupCalendar_CurrentPanel); -- Refresh the channel and auto-player names
		end
		
		if gGroupCalendar_CurrentPanel == 2 then
			GroupCalendar_UpdateChannelStatus();
		end
	end
end

function GroupCalendar_GetLocalizedStrings(pLocale)
	local	vStrings = {};
	
	-- Initialize the strings with copies form the enUS set
	
	for vIndex, vString in gCalendarLocalizedStrings.enUS do
		vStrings[vIndex] = vString;
	end
	
	-- Select a set to use for overwriting
	
	local	vLocalizedStrings = gCalendarLocalizedStrings[pLocale];
	
	if not vLocalizedStrings then
		-- There's not a fit for the exact language/country specified, so just match the language
		
		local	vLanguageCode = string.sub(pLocale, 1, 2);
		
		vLocalizedStrings = GroupCalendar_SelectLanguage(vLanguageCode);
		
		if not vLocalizedStrings then
			return vStrings;
		end
	end
	
	-- Overwrise the english strings with translated strings
	
	for vIndex, vString in vLocalizedStrings do
		vStrings[vIndex] = vString;
	end
	
	return vStrings;
end

function GroupCalendar_SelectLanguage(pLanguageCode)
	-- There's not a fit for the exact language/country specified, so just match the language

	local	vLanguageCode = string.sub(pLocale, 1, 2);

	for vLocale, vLocalizedStrings in gCalendarLocalizedStrings do
		if pLanguageCode == string.sub(vLocale, 1, 2) then
			return vLocalizedStrings;
		end
	end
	
	-- No luck, hope they know english!
	
	return nil;
end

function GroupCalendar_ToggleChannelConnection()
	local	vChannelStatus = CalendarNetwork_GetChannelStatus();
	
	if vChannelStatus == "Initializing" then
		return;
	end
	
	if vChannelStatus == "Connected" then
		gGroupCalendar_Channel.Disconnected = true;
		
		CalendarNetwork_LeaveChannel();
	else
		gGroupCalendar_Channel.Disconnected = false;
		
		CalendarNetwork_SetChannelStatus("Initializing");
		
		if gGroupCalendar_PlayerSettings.Channel.AutoConfig then
			CalendarNetwork_ScheduleAutoConfig(0.5);
		elseif gGroupCalendar_PlayerSettings.Channel.Name then
			CalendarNetwork_SetChannel(
					gGroupCalendar_PlayerSettings.Channel.Name,
					gGroupCalendar_PlayerSettings.Channel.Password);
		else
			CalendarNetwork_SetChannelStatus("Disconnected");
		end
	end
end

function GroupCalendar_ToggleCalendarDisplay()
	if GroupCalendarFrame:IsVisible() then
		HideUIPanel(GroupCalendarFrame);
	else
		ShowUIPanel(GroupCalendarFrame);
	end
end

function GroupCalendar_SetUseServerDateTime(pUseServerDateTime)
	gGroupCalendar_Settings.ShowEventsInLocalTime = not pUseServerDateTime;
	
	GroupCalendarUseServerTime:SetChecked(pUseServerDateTime);
	
	GroupCalendar_MajorDatabaseChange(nil); -- Force the display to update
end

function GroupCalendar_BeginModalDialog(pDialogFrame)
	if gGroupCalendar_ActiveDialog then
		GroupCalendar_EndModalDialog(gGroupCalendar_ActiveDialog);
	end
	
	gGroupCalendar_ActiveDialog = pDialogFrame;
end

function GroupCalendar_EndModalDialog(pDialogFrame)
	if pDialogFrame ~= gGroupCalendar_ActiveDialog then
		return;
	end
	
	gGroupCalendar_ActiveDialog = nil;
	
	pDialogFrame:Hide();
end

function GroupCalendar_ExecuteCommand(pCommandString)
	local	vStartIndex, vEndIndex, vCommand, vParameter = string.find(pCommandString, "(%w+) ?(.*)");
	
	if not vCommand then
		ShowUIPanel(GroupCalendarFrame);
		return;
	end
	
	local	vCommandTable =
	{
		["help"] = {func = GroupCalendar_ShowCommandHelp},
		["versions"] = {func = GroupCalendar_DumpUserVersions},
	};
	
	local	vCommandInfo = vCommandTable[strlower(vCommand)];
	
	if not vCommandInfo then
		GroupCalendar_ShowCommandHelp();
		return;
	end
	
	vCommandInfo.func(vParameter);
end

function GroupCalendar_ShowCommandHelp()
	Calendar_NoteMessage("GroupCalendar commands:");
	Calendar_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE.."/calendar help"..NORMAL_FONT_COLOR_CODE..": Shows this list of commands");
	Calendar_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE.."/calendar versions"..NORMAL_FONT_COLOR_CODE..": Displays the last known versions of GroupCalendar each user was running");
end

function GroupCalendar_DumpUserVersions()
	for vRealmUser, vDatabase in gGroupCalendar_Database.Databases do
		if EventDatabase_DatabaseIsVisible(vDatabase) then
			if vDatabase.IsPlayerOwned then
				Calendar_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE..vDatabase.UserName..NORMAL_FONT_COLOR_CODE..": "..gGroupCalendar_VersionString);
			elseif vDatabase.AddonVersion then
				Calendar_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE..vDatabase.UserName..NORMAL_FONT_COLOR_CODE..": "..vDatabase.AddonVersion);
			else
				Calendar_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE..vDatabase.UserName..NORMAL_FONT_COLOR_CODE..": Unknown version");
			end
		end
	end
end
