gGroupCalendar_MessagePrefix0 = "GC2";
gGroupCalendar_MessagePrefix = gGroupCalendar_MessagePrefix0.."/";

gGroupCalendar_MessagePrefixLength = string.len(gGroupCalendar_MessagePrefix);

gGroupCalendar_Channel =
{
	Name          = nil,
	NameLower     = nil,
	ID            = nil,
	Password      = nil,
	AutoPlayer    = nil,
	Status        = "Disconnected",
	StatusMessage = nil,
	GotTooManyChannelsMessage = false,
};

gGroupCalendar_Queue =
{
	TasksDelay       = 0,
	TasksElapsed     = 0,
	Tasks            = {},
	
	RequestsDelay    = 0,
	RequestsElapsed  = 0,
	Requests         = {[1] = {}, [2] = {}},
	
	InboundDelay     = 0,
	InboundMessages  = {},
	InboundLastSender = nil,
	InboundTimeSinceLastMessage = 0,
	
	OutboundDelay    = 0,
	OutboundMessages = {},
	OutboundTimeSinceLastMessage = 0,
	
	DatabaseUpdates  = {},
	RSVPUpdates      = {},
};

gCalendarNetwork_UserTrustCache =
{
};

gCalendarNetwork_RequestDelay =
{
	Init = 120,
	ShortInit = 5,
	SelfUpdateRequest = 1,
	ExternalUpdateRequest = 30,
	OwnedNotices = 300, -- Allow time for updates to arrive before we'll start advertising databases
	JoinChannel2 = 2,
	OwnedUpdate = 3,
	ProxyUpdateMin = 6,
	ProxyUpdateRange = 4,
	
	RFUMin = 1,
	RFURange = 5,
	
	OwnedNOURange = 2,
	ProxyNOUMin = 6,
	ProxyNOURange = 4,
	
	InboundQueue = 0.2,
	OutboundQueue = 0.2,
	RequestQueue = 0.2,
	
	-- Outbound queue delay 1 - 5 seconds
	
	OutboundQueueGapMin = 1, -- Delay for after last inbound message was processed
	OutboundQueueGapWidth = 4, -- Random delay after the min time
	
	-- Request queue delay 1 - 5 seconds
	
	RequestQueueGapMin = 1,  -- Delay for after last inbound or outbound message was processed
	RequestQueueGapWidth = 4, -- Random delay after the min time
	
	GuildUpdateAutoConfig = 2,
	
	CheckDatabaseTrust = 10,
	GuildRosterUpdate = 1,
	
	-- Maximum age for an update is two minutes
	
	MaximumUpdateAge = 120,
};

gGroupCalendar_EnableUpdates = false;  -- Don't allow the user to send updates to any databases
                                       -- until we're sure that his databases are up-to-date themselves
gGroupCalendar_EnableSelfUpdates = false; -- Don't allow the user to send updates for their own databases
                                          -- until well after they're sure nobody has updates for them

function CalendarNetwork_GetChannelStatus()
	return gGroupCalendar_Channel.Status;
end

function CalendarNetwork_SetChannel(pChannel, pPassword)
	-- Leave the channel and return if the new channel is nil
	
	if not pChannel then
		CalendarNetwork_LeaveChannel();
		return true;
	end
	
	-- Just return if nothing is actually changing
	
	if gGroupCalendar_Channel.Name
	and strupper(gGroupCalendar_Channel.Name) == strupper(pChannel)
	and gGroupCalendar_Channel.Password == pPassword
	and gGroupCalendar_Channel.Status == "Connected" then
		return false; -- return false to indicate that no change was made
	end
	
	-- Leave the old channel and join the new one
	
	CalendarNetwork_LeaveChannel();
	
	return CalendarNetwork_JoinChannel(pChannel, pPassword);
end

function CalendarNetwork_JoinChannel(pChannelName, pPassword)
	if not pChannelName
	or pChannelName == ""
	or gGroupCalendar_Channel.Disconnected then
		return false;
	end
	
	-- There seems to be some sort of bug with passworded channels getting
	-- screwed up if the UI is reloaded and the solution appears to be to
	-- leave and re-join the channel.  Therefore, the code below...
	
	LeaveChannelByName(pChannelName);
	CalendarNetwork_LeftChannel();
	
	CalendarNetwork_SetChannelStatus("Initializing");
	
	CalendarNetwork_QueueTask(
			CalendarNetwork_JoinChannel2,
			{mChannelName = pChannelName, mPassword = pPassword},
			gCalendarNetwork_RequestDelay.JoinChannel2,
			"JOINCHANNEL2");
	
	return true;
end

function CalendarNetwork_JoinChannelFailed()
	if gGroupCalendar_Channel.GotTooManyChannelsMessage then
		CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cTooManyChannels);
	else
		CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cJoinChannelFailed);
	end
end

function CalendarNetwork_LeftChannel()
	gGroupCalendar_Channel.Name = nil;
	gGroupCalendar_Channel.Password = nil;
	gGroupCalendar_Channel.NameLower = nil;
	gGroupCalendar_Channel.ID = nil;
	
	CalendarNetwork_SetChannelStatus("Disconnected");
end

function CalendarNetwork_SetChannelStatus(pStatus, pStatusMessage)
	gGroupCalendar_Channel.Status = pStatus;
	gGroupCalendar_Channel.StatusMessage = pStatusMessage;
	
	GroupCalendar_ChannelChanged();
end

function CalendarNetwork_SystemMessage(pMessage)
	if pMessage == ERR_TOO_MANY_CHAT_CHANNELS then
		gGroupCalendar_Channel.GotTooManyChannelsMessage = true;
		
		if gGroupCalendar_Channel.Status == "Error" then
			CalendarNetwork_SetChannelStatus("Error", pMessage);
		end
	elseif pMessage == INSTANCE_SAVED then
		-- Fetch the raid info so the reset events can be logged on
		-- the calendar
		
		RequestRaidInfo();
	elseif pMessage == NO_RAID_INSTANCES_SAVED then
		EventDatabase_RemoveSavedInstanceEvents(gGroupCalendar_UserDatabase);
	elseif pMessage == RAID_INSTANCE_INFO_HDR then
		-- Remove existing events since they will be replaced by new incoming info
		
		EventDatabase_RemoveSavedInstanceEvents(gGroupCalendar_UserDatabase);
	else
		local	vStartIndex, vEndIndex, vName, vID, vDays, vHours, vMinutes, vSeconds = string.find(pMessage, "(.+) %(ID=(%x+)%): (%d+)d (%d+)h (%d+)m (%d+)s");
		
		if vStartIndex then
			-- Calculate the number of seconds until the instance resets
			
			local	vRemainingDateTime60 = tonumber(vDays) * gCalendarSecondsPerDay
	                                     + Calendar_ConvertHMSToTime60(tonumber(vHours), tonumber(vMinutes), tonumber(vSeconds));
			
			local	vServerResetDate, vServerResetTime = Calendar_GetServerDateTimeFromSecondsOffset(vRemainingDateTime60);
			
			EventDatabase_ScheduleSavedInstanceEvent(gGroupCalendar_UserDatabase, vName, vServerResetDate, vServerResetTime);
		end
	end
end

function CalendarNetwork_JoinChannel2(pParams)
	if gGroupCalendar_Settings.DebugInit then
		Calendar_DebugMessage("CalendarNetwork_JoinChannel: "..pParams.mChannelName);
	end
	
	gGroupCalendar_Channel.GotTooManyChannelsMessage = false;
	gGroupCalendar_Channel.Name = pParams.mChannelName;
	
	local	vChannelName = GetChannelName(pParams.mChannelName);
	local	vZoneChannel;
	local	vChannelAlreadyExists = false;
	
	if vChannelName and vChannelName ~= 0 then
		if gGroupCalendar_Settings.DebugInit then
			Calendar_DebugMessage("Found existing channel "..pParams.mChannelName.." ("..vChannelName..")");
		end
		
		vChannelName = pParams.mChannelName;
		vChannelAlreadyExists = true;
	else
		if gGroupCalendar_Settings.DebugInit then
			Calendar_DebugMessage("Channel "..pParams.mChannelName.." not found ("..vChannelName..") joining channel...");
		end
		
		vZoneChannel, vChannelName = JoinChannelByName(pParams.mChannelName, pParams.mPassword, DEFAULT_CHAT_FRAME:GetID());
		
		if not vZoneChannel then
			CalendarNetwork_JoinChannelFailed();
			return false;
		end
		
		if not vChannelName then
			vChannelName = pParams.mChannelName;
		end
	end
	
	-- Remove the channel from the chat frame so the user doesn't
	-- have to watch all that data comm. (if we're running debug
	-- code then leave the setting alone)
	
	ChatFrame_RemoveChannel(DEFAULT_CHAT_FRAME, pParams.mChannelName);
	
	if gCalendar_DebugFrame then
		ChatFrame_AddChannel(gCalendar_DebugFrame, pParams.mChannelName);
	end
	
	gGroupCalendar_Channel.Password = pParams.mPassword;
	gGroupCalendar_Channel.NameLower = strlower(pParams.mChannelName);
	gGroupCalendar_Channel.ID = GetChannelName(gGroupCalendar_Channel.Name);
	
	if not gGroupCalendar_Channel.ID
	or gGroupCalendar_Channel.ID == 0 then
		gGroupCalendar_Channel.ID = nil;
		
		CalendarNetwork_JoinChannelFailed();
		
		return false;
	end
	
	if vChannelAlreadyExists then
		CalendarNetwork_JoinedChannel();
	end
	
	return true;
end

function CalendarNetwork_LeaveChannel()
	if gGroupCalendar_Channel.Name
	and gGroupCalendar_Channel.ID
	and gGroupCalendar_Channel.Status ~= "Suspended" then
		LeaveChannelByName(gGroupCalendar_Channel.Name);
		CalendarNetwork_LeftChannel();
	end
end

function CalendarNetwork_JoinedChannel()
	CalendarNetwork_SetChannelStatus("Connected");
	
	if gGroupCalendar_Settings.DebugInit then
		Calendar_DebugMessage("CalendarNetwork_JoinChannel succeeded channel "..gGroupCalendar_Channel.Name.." ID "..gGroupCalendar_Channel.ID);
	end

	-- Send update requests/notices

	CalendarNetwork_QueueTask(CalendarNetwork_SendNotices, nil, gCalendarNetwork_RequestDelay.SelfUpdateRequest, "SELFUPDATE");
end

function CalendarNetwork_ChannelNotice(pChannelMessage, pChannelName, pChannelID, pActualChannelName)
	-- Decode the channel name if it's in the nn. <channel name> format
	
	local	vChannelName = pChannelName;
	
	for vFoundNumber, vFoundName in string.gfind(pChannelName, "(%d)%. (.+)") do
		vChannelName = vFoundName;
	end
	
	-- Just leave if it's not a channel we're interested in
	
	local	vIsDataChannel = strlower(vChannelName) == gGroupCalendar_Channel.NameLower;
	
	--
	
	if pChannelMessage == "YOU_JOINED" then
		-- If it's one of the system channels, then shorten the initialization
		-- timer if one is present
		
		if pChannelID == 1 then
			CalendarNetwork_SystemChannelJoined();
		end
		
		if not vIsDataChannel then
			return;
		end
		
		if pChannelID > 0 then
			CalendarNetwork_JoinedChannel();
		else
			-- Joining failed
			
			CalendarNetwork_JoinChannelFailed();
		end
		
	elseif pChannelMessage == "YOU_LEFT" then
		if not vIsDataChannel then
			return;
		end
		
		if not gGroupCalendar_Channel.ID then
			return;
		end
		
		CalendarNetwork_LeftChannel();
		
	elseif pChannelMessage == "WRONG_PASSWORD" then
		if not vIsDataChannel then
			return;
		end
		
		CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cWrongPassword);
	end
end

function CalendarNetwork_SuspendChannel()
	if gGroupCalendar_Channel.Status == "Suspended"
	or not gGroupCalendar_Channel.Name
	or not gGroupCalendar_Channel.ID then
		return;
	end
	
	LeaveChannelByName(gGroupCalendar_Channel.Name);
	
	gGroupCalendar_Channel.ID = nil;
	CalendarNetwork_SetChannelStatus("Suspended");
end

function CalendarNetwork_ResumeChannel()
	if not gGroupCalendar_Channel.Status ~= "Suspended" then
		return;
	end
	
	JoinChannelByName(gGroupCalendar_Channel.Name, gGroupCalendar_Channel.Password, DEFAULT_CHAT_FRAME:GetID());
	gGroupCalendar_Channel.ID = GetChannelName(gGroupCalendar_Channel.Name);
	
	ChatFrame_RemoveChannel(DEFAULT_CHAT_FRAME, gGroupCalendar_Channel.Name);
	
	CalendarNetwork_SetChannelStatus("Connected");
end

function CalendarNetwork_UnpackIndexedList(...)
	local	vList = {};
	
	for vIndex = 1, arg.n, 2 do
		vList[tonumber(arg[vIndex])] = arg[vIndex + 1];
	end
	
	return vList;
end

function CalendarNetwork_GetChannelList()
	local	vChannelList = CalendarNetwork_UnpackIndexedList(GetChannelList());
	
	return vChannelList;
end

function CalendarNetwork_CalendarLoaded()
	-- Set up a deferred initialization
	
	CalendarNetwork_SetChannelStatus("Starting");
	
	-- See if there are system channels yet and use
	-- the shorter delay if there are
	
	local	vDelay = gCalendarNetwork_RequestDelay.Init;
	
	local	vChannelList = CalendarNetwork_GetChannelList();
	
	if vChannelList[1] then
		vDelay = gCalendarNetwork_RequestDelay.ShortInit;
	end
	
	--
	
	CalendarNetwork_QueueTask(CalendarNetwork_Initialize, nil, vDelay, "INIT");
end

function CalendarNetwork_SystemChannelJoined()
	CalendarNetwork_SetTaskDelay("INIT", gCalendarNetwork_RequestDelay.ShortInit);
end

function CalendarNetwork_ProcessCommandString(pSender, pTrustLevel, pCommandString, pCurrentTimeStamp)
	local	vCommand = CalendarNetwork_ParseCommandString(pCommandString);
	
	if not vCommand then
		if gGroupCalendar_Settings.Debug then
			Calendar_DebugMessage("ProcessCommandString: Couldn't parse ["..pSender.."]:"..pCommandString);
		end
		
		return false;
	end
	
	return CalendarNetwork_ProcessCommand(pSender, pTrustLevel, vCommand, pCurrentTimeStamp);
end

function CalendarNetwork_ProcessCommand(pSender, pTrustLevel, pCommand, pCurrentTimeStamp)
	-- Age out old updates which haven't been seen in a while
	
	local	vMinimumUpdateTime = pCurrentTimeStamp - gCalendarNetwork_RequestDelay.MaximumUpdateAge;
	
	CalendarNetwork_KillOldUpdates("DB", gGroupCalendar_Queue.DatabaseUpdates, vMinimumUpdateTime);
	CalendarNetwork_KillOldUpdates("RAT", gGroupCalendar_Queue.RSVPUpdates, vMinimumUpdateTime);
	
	--
	
	local	vOpcode = pCommand[1].opcode;
	local	vOperands = pCommand[1].operands;
	
	table.remove(pCommand, 1);
	
	if vOpcode == "DB" then
		local	vUserName = vOperands[1];
		local	vDatabaseID = tonumber(vOperands[2]);
		local	vRevision = tonumber(vOperands[3]);
		local	vAuthRevision = tonumber(vOperands[4]);
		
		-- If the sender is using wildcards, fetch the path from
		-- the update records
		
		if vUserName == "*" then
			local	vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender);
			
			if not vDatabaseUpdate then
				return;
			end
			
			vUserName = vDatabaseUpdate.mUserName;
			vDatabaseID = vDatabaseUpdate.mDatabaseID;
			vRevision = vDatabaseUpdate.mRevision;
			vAuthRevision = nil;
		end
		
		--
		
		if not vRevision then
			vRevision = 0;
		end
		
		if CalendarTrust_UserIsTrusted(vUserName) then -- only accept databases for trusted users (or our own)
			CalendarNetwork_ProcessDatabaseCommand(pSender, pTrustLevel, vUserName, vDatabaseID, vRevision, vAuthRevision, pCommand);
		else
			if gGroupCalendar_Settings.DebugTrust then
				Calendar_DebugMessage("ChannelMessageReceived: User "..vUserName.." is not trusted for DB command");
			end
		end
	elseif vOpcode == "RAT" then
		local	vUserName = vOperands[1];
		local	vDatabaseID = tonumber(vOperands[2]);
		local	vRevision = tonumber(vOperands[3]);
		local	vAuthRevision = tonumber(vOperands[4]);
		
		-- If the sender is using wildcards, fetch the path from
		-- the update records
		
		if vUserName == "*" then
			local	vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender);
			
			if not vRSVPUpdate then
				return;
			end
			
			vUserName = vRSVPUpdate.mUserName;
			vDatabaseID = vRSVPUpdate.mDatabaseID;
			vRevision = vRSVPUpdate.mRevision;
			vAuthRevision = nil;
		end
		
		--
		
		if not vRevision then
			vRevision = 0;
		end
		
		if CalendarTrust_UserIsTrustedForRSVPs(vUserName) then -- only accept RSVP for trusted users (or our own)
			CalendarNetwork_ProcessRSVPCommand(pSender, vUserName, vDatabaseID, vRevision, vAuthRevision, pCommand);
		else
			if gGroupCalendar_Settings.DebugTrust then
				Calendar_DebugMessage("ChannelMessageReceived: User "..vUserName.." is not trusted for RAT command");
			end
		end
	elseif vOpcode == "GLD" then
		local	vGuildName = vOperands[1];
		local	vMinRank = tonumber(vOperands[2]);
		
		CalendarNetwork_ProcessGuildCommand(pSender, pTrustLevel, vGuildName, vMinRank, pCommand);
	elseif vOpcode == "ALL" then
		CalendarNetwork_ProcessAllCommand(pSender, pTrustLevel, pCommand);
	elseif vOpcode == "VER" then
		local	vDatabase = EventDatabase_GetDatabase(pSender, true);
		
		if vDatabase then
			vDatabase.AddonVersion = vOperands[1];
		end
	else
		if gGroupCalendar_Settings.Debug then
			Calendar_DebugMessage("ProcessCommand: Unknown opcode "..vOpcode);
		end
	end
end

function CalendarNetwork_ProcessChangesRFU(pChanges, pDatabaseTag, pPlayerOwned, pPriority, pUserName, pDatabaseID, pRevision, pAuthRevision)
	if gGroupCalendar_Settings.DebugUpdates then
		Calendar_DebugMessage(pDatabaseTag.." changes update requested for: "..pUserName..","..pRevision);
	end
	
	-- Cancel a queued RFU for the same database if it's redundant
	
	CalendarNetwork_CancelRedundantRFURequest(pDatabaseTag, pUserName, pDatabaseID, pRevision)
	
	-- Just bail out if we don't have the requested database
	
	if not pChanges then
		return;
	end
	
	-- Create the request
	
	local		vRequest;
	local		vFromRevision;
	local		vForceUpdate = false;
	
	if pChanges.ID ~= pDatabaseID then
		if pPlayerOwned
		or pChanges.ID > pDatabaseID then
			vFromRevision = 0;
			vForceUpdate = true;
		else
			if gGroupCalendar_Settings.DebugUpdates then
				Calendar_DebugMessage("Not sending "..pDatabaseTag.." update: Requested database isn't available for "..pUserName..","..pDatabaseID);
			end
			
			return;
		end
	else
		if pPlayerOwned
		and pAuthRevision
		and pAuthRevision < pRevision then
			vFromRevision = pAuthRevision;
		else
			vFromRevision = pRevision;
		end
	end
	
	vRequest = CalendarNetwork_FindUPDRequest(pDatabaseTag, pUserName);
	
	if vRequest then
		if vFromRevision < vRequest.mRevision then
			if gGroupCalendar_Settings.DebugUpdates then
				Calendar_DebugMessage("Changing existing "..pDatabaseTag.." request to revision "..vFromRevision.." for "..pUserName);
			end
			
			vRequest.mRevision = vFromRevision;
		else
			if gGroupCalendar_Settings.DebugUpdates then
				Calendar_DebugMessage("Existing request for "..pDatabaseTag.." "..pUserName..","..pRevision);
			end
		end
		
		CalendarNetwork_SetRequestPriority(vRequest, pPriority);
	elseif pChanges.Revision > vFromRevision
	or vForceUpdate then
		vRequest = {};
		
		vRequest.mOpcode = pDatabaseTag.."_UPD";
		vRequest.mUserName = pUserName;
		vRequest.mDatabaseID = pChanges.ID;
		vRequest.mRevision = vFromRevision;
		
		-- Determine a delay
		
		local	vDelay;
		
		if pPlayerOwned then
			vDelay = gCalendarNetwork_RequestDelay.OwnedUpdate;
		else
			vDelay = gCalendarNetwork_RequestDelay.ProxyUpdateMin + math.random() * gCalendarNetwork_RequestDelay.ProxyUpdateRange;
		end
		
		CalendarNetwork_QueueRequest(vRequest, vDelay, pPriority);
	end
end

function CalendarNetwork_GetDatabaseChanges(pUserName, pCreate)
	local	vDatabase = EventDatabase_GetDatabase(pUserName, pCreate);
	
	if vDatabase then
		return vDatabase, vDatabase.Changes, vDatabase.IsPlayerOwned;
	else
		return nil, nil, nil;
	end
end

function CalendarNetwork_GetDatabaseRSVPChanges(pUserName, pCreate)
	local	vDatabase = EventDatabase_GetDatabase(pUserName, pCreate);
	
	if vDatabase then
		return vDatabase, vDatabase.RSVPs, vDatabase.IsPlayerOwned;
	else
		return nil, nil, nil;
	end
end

function CalendarNetwork_ProcessDatabaseCommand(pSender, pTrustLevel, pUserName, pDatabaseID, pRevision, pAuthRevision, pCommand)
	local	vOpcode = pCommand[1].opcode;
	local	vOperands = pCommand[1].operands;
	
	table.remove(pCommand, 1);
	
	local	vDatabase, vChanges, vIsPlayerOwned = CalendarNetwork_GetDatabaseChanges(pUserName, false);
	local	vIsThisPlayerOwned = vIsPlayerOwned and pUserName == gGroupCalendar_PlayerName;
	
	if vOpcode ~= "RFU"
	and vOpcode ~= "RFV"
	and pTrustLevel < 2 then
		-- Players with moderate trust levels (ie, fellow guild members) are
		-- only allowed to requeust updates, not provide updates
		
		if gGroupCalendar_Settings.DebugTrust then
			Calendar_DebugMessage("CalendarNetwork_ProcessDatabaseCommand: Ignoring "..vOpcode.." request from "..pSender);
		end
		
		return;
	end
	
	if vOpcode == "RFU" then
		-- If the sender is seen transmitting an RFU while he has a database update
		-- pending then it probably means he d/c'd.  Kill the database update in that
		-- case
		
		CalendarNetwork_KillSendersDatabaseUpdates(pSender);
		
		--
		
		if vChanges then
			-- Give the owner high-priority to help ensure that he can roam successfully
			
			local	vPriority;
			
			if pSender == pUserName then
				vPriority = 1;
			else
				vPriority = 2;
			end
			
			CalendarNetwork_ProcessChangesRFU(vChanges, "DB", vIsThisPlayerOwned, vPriority, pUserName, pDatabaseID, pRevision, pAuthRevision);
		end
	elseif vOpcode == "RFV" then
		-- If the sender is seen transmitting an RFV while he has a database update
		-- pending then it probably means he d/c'd.  Kill the database update in that
		-- case
		
		CalendarNetwork_KillSendersDatabaseUpdates(pSender);
		
		--
		
		if vIsThisPlayerOwned then
			CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix.."VER:"..gGroupCalendar_VersionString);
		end
	elseif vOpcode == "NOU" then
		-- If the sender is seen transmitting an NOU while he has a database update
		-- pending then it probably means he d/c'd.  Kill the database update in that
		-- case
		
		CalendarNetwork_KillSendersDatabaseUpdates(pSender);
		
		--
		
		local		vCurrentRevision = 0;
		local		vDatabaseIDChanged = false;
		
		if not vIsThisPlayerOwned then
			CalendarNetwork_CancelRedundantNOURequests(vChanges, "DB", pSender, pUserName, pDatabaseID, pRevision);
		end
		
		if not vDatabase then
			vDatabase = EventDatabase_AssumeDatabase(pUserName);
		end
		
		if vDatabase then
			-- Ignore external updates to our own databases
			
			if vDatabase.IsPlayerOwned then
				-- If someone sent out a NOU for one of our databases and it's older than
				-- ours and ours is now empty, send out a DEL to let them know that it's newer
				-- and empty now
				
				if CalendarChanges_IsEmpty(vChanges) then
					CalendarNetwork_SendEmptyChanges(vChanges, "DB", pUserName);
				end
				
				return;
			end
			
			-- Ignore the update if the ID is older than the one we have
			-- unless it's from the owner
			
			if vChanges
			and pDatabaseID < vChanges.ID
			and pSender ~= vDatabase.UserName then
				return;
			end
			
			-- Force a RFU if the ID is changing
			
			if not vChanges
			or vChanges.ID ~= pDatabaseID then
				vDatabaseIDChanged = true;
			else
				vCurrentRevision = vChanges.Revision;
				
				if not vCurrentRevision then
					Calendar_ErrorMessage("Changes for "..pUserName.." has nil revision");
					return;
				end
			end
		end
		
		if vDatabaseIDChanged
		or vCurrentRevision < pRevision then
			CalendarNetwork_QueueRFURequest(pUserName, "DB", pDatabaseID, pRevision);
		end
	elseif vOpcode == "UPD" then
		-- If the sender is seen transmitting an UPD while he has another database update
		-- pending then it probably means he d/c'd.  Kill the database update in that
		-- case
		
		CalendarNetwork_KillSendersDatabaseUpdates(pSender, pUserName);
		
		-- Begin a database update
		
		local	vSinceRevision = tonumber(vOperands[1]);
		
		if not vSinceRevision then
			if gGroupCalendar_Settings.DebugErrors then
				Calendar_DebugMessage("GroupCalendar: DB UPD received from "..pSender.." for "..pUserName.." with no SinceRevision");
			end
			
			return;
		end
		
		if not vIsThisPlayerOwned then
			-- If we're waiting to request this same update, cancel the request
			
			CalendarNetwork_CancelRedundantRFURequest("DB", pUserName, pDatabaseID, vSinceRevision);

			-- If we're waiting to send this same update, cancel the request
			
			CalendarNetwork_CancelRedundantUPDRequests(vChanges, "DB", pUserName, pDatabaseID, pRevision, vSinceRevision);
		end
		
		CalendarNetwork_BeginDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision);

	elseif vOpcode == "DEL" then
		-- Delete the database since it's empty
		
		if not vIsThisPlayerOwned then
			CalendarNetwork_CancelRedundantUPDRequests(vChanges, "DB", pUserName, pDatabaseID, pRevision, 0);
		end
	
		CalendarNetwork_DeleteDatabase(pSender, pUserName, pDatabaseID, pRevision);
		
	elseif vOpcode == "EVT" then
		-- Event data
		
		local	vEventID = tonumber(vOperands[1]);
		
		CalendarNetwork_InsertEventUpdate(pSender, pUserName, pDatabaseID, pRevision, vEventID, pCommand);
		
	elseif vOpcode == "END" then
		-- End a database update
		
		local	vSinceRevision = tonumber(vOperands[1]);
		
		CalendarNetwork_EndDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision);
	end
end

function CalendarNetwork_ProcessRSVPCommand(pSender, pUserName, pDatabaseID, pRevision, pAuthRevision, pCommand)
	local	vOpcode = pCommand[1].opcode;
	local	vOperands = pCommand[1].operands;
	local	vOperandString = pCommand[1].operandString;
	
	table.remove(pCommand, 1);
	
	local	vDatabase, vChanges, vIsPlayerOwned = CalendarNetwork_GetDatabaseRSVPChanges(pUserName, false);
	local	vIsThisPlayerOwned = vIsPlayerOwned and pUserName == gGroupCalendar_PlayerName;
	
	if vOpcode == "RFU" then
		-- If the sender is seen transmitting an RFU while he has a database update
		-- pending then it probably means he d/c'd.  Kill the database update in that
		-- case
		
		CalendarNetwork_KillSendersRSVPUpdates(pSender);
		
		--
		
		if vChanges then
			-- Give the owner high-priority to help ensure that he can roam successfully
			
			local	vPriority;
			
			if pSender == pUserName then
				vPriority = 1;
			else
				vPriority = 2;
			end
			
			CalendarNetwork_ProcessChangesRFU(vChanges, "RAT", vIsThisPlayerOwned, vPriority, pUserName, pDatabaseID, pRevision, pAuthRevision);
		end
	elseif vOpcode == "NOU" then
		-- If the sender is seen transmitting a NOU while he has a database update
		-- pending then it probably means he d/c'd.  Kill the database update in that
		-- case
		
		CalendarNetwork_KillSendersRSVPUpdates(pSender);
		
		--
		
		local		vCurrentRevision = 0;
		local		vDatabaseIDChanged = false;
		
		if not vIsThisPlayerOwned then
			CalendarNetwork_CancelRedundantNOURequests(vChanges, "RAT", pSender, pUserName, pDatabaseID, pRevision);
		end
		
		if vDatabase then
			-- Ignore external updates to our own databases
			
			if vDatabase.IsPlayerOwned then
				-- If someone sent out a NOU for one of our databases and it's older than
				-- ours and ours is now empty, send out a DEL to let them know that it's newer
				-- and empty now
				
				if CalendarChanges_IsEmpty(vChanges) then
					CalendarNetwork_SendEmptyChanges(vChanges, "RAT", pUserName);
				end
				
				return;
			end

			-- Ignore the update if the ID is older than the one we have
			-- unless it's from the owner
			
			if vChanges
			and pDatabaseID < vChanges.ID
			and pSender ~= vDatabase.UserName then
				return;
			end
			
			-- Purge the existing database if the ID is changing
			
			if not vChanges
			or vChanges.ID ~= pDatabaseID then
				vDatabaseIDChanged = true;
			else
				vCurrentRevision = vChanges.Revision;
			end
		end
		
		if vDatabaseIDChanged
		or vCurrentRevision < pRevision then
			CalendarNetwork_QueueRFURequest(pUserName, "RAT", pDatabaseID, pRevision);
		end
	elseif vOpcode == "UPD" then
		-- If the sender is seen transmitting an UPD while he has a database update
		-- pending then it probably means he d/c'd.  Kill the database update in that
		-- case
		
		CalendarNetwork_KillSendersRSVPUpdates(pSender, pUserName);
		
		--
		
		-- Begin a RSVP database update
		
		local	vSinceRevision = tonumber(vOperands[1]);
		
		if not vSinceRevision then
			if gGroupCalendar_Settings.DebugErrors then
				Calendar_DebugMessage("GroupCalendar: RAT UPD received from "..pSender.." for "..pUserName.." with no SinceRevision");
			end
			
			return;
		end
		
		if not vIsThisPlayerOwned then
			-- If we're waiting to ask for an update to this database, cancel the request
			
			CalendarNetwork_CancelRedundantRFURequest("RAT", pUserName, pDatabaseID, vSinceRevision);
			
			-- If we're waiting to send this same update, cancel the request
			
			CalendarNetwork_CancelRedundantUPDRequests(vChanges, "RAT", pUserName, pDatabaseID, pRevision, vSinceRevision);
		end
		
		CalendarNetwork_BeginRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision);
		
	elseif vOpcode == "DEL" then
		-- Delete the database since it's empty
		
		if not vIsThisPlayerOwned then
			CalendarNetwork_CancelRedundantUPDRequests(vChanges, "RAT", pUserName, pDatabaseID, pRevision, 0);
		end
		
		CalendarNetwork_DeleteRSVPs(pSender, pUserName, pDatabaseID, pRevision);
		
	elseif vOpcode == "EVT" then
		-- Event data
		
		CalendarNetwork_InsertRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, vOperandString);
		
	elseif vOpcode == "END" then
		-- End a RSVP update
		
		local	vSinceRevision = tonumber(vOperands[1]);
		
		CalendarNetwork_EndRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision);
	end
end

function CalendarNetwork_ProcessGuildCommand(pSender, pTrustLevel, pGuildName, pMinRank, pCommand)
	-- Ignore guild commands if they're not directed at the player's guild
	-- or not at the player's rank
	
	if pGuildName ~= gGroupCalendar_PlayerGuild
	or gGroupCalendar_PlayerGuildRank > pMinRank then
		return;
	end
	
	-- Pass it on to the ALL handler for further processing
	
	CalendarNetwork_ProcessAllCommand(pSender, pTrustLevel, pCommand);
end

function CalendarNetwork_ProcessAllCommand(pSender, pTrustLevel, pCommand)
	local	vOpcode = pCommand[1].opcode;
	local	vOperands = pCommand[1].operands;

	table.remove(pCommand, 1);
	
	if vOpcode == "RFU" then
		CalendarNetwork_SendAllRevisionNotices();
	elseif vOpcode == "RFV" then
		CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix.."VER:"..gGroupCalendar_VersionString);
	end
end

function CalendarNetwork_DatabaseIsNewer(pDatabaseID1, pRevision1, pDatabaseID2, pRevision2)
	if pDatabaseID2 > pDatabaseID1 then
		return true;
	elseif pDatabaseID2 < pDatabaseID1 then
		return false;
	else
		return pRevision2 > pRevision1;
	end
end

function CalendarNetwork_UpdateIsBetterThanOurs(pSender, pUserName, pDatabaseID, pRevision, pDatabase, pChanges)
	-- Updates for our own databases are always worse if
	-- we're on the toon
	
	if pDatabase.IsPlayerOwned and pUserName == gGroupCalendar_PlayerName then
		return false;
	end
	
	-- Updates from the owner are always better than ours
	-- as are updates when our list is empty
	
	if string.lower(pSender) == string.lower(pUserName)
	or not pChanges then
		return true;
	end
	
	-- If the revision is higher than what we have it's better
	
	return CalendarNetwork_DatabaseIsNewer(pChanges.ID, pChanges.Revision, pDatabaseID, pRevision);
end

function CalendarNetwork_DeleteDatabase(pSender, pUserName, pDatabaseID, pRevision)
	-- Get the database
	
	local		vDatabase = EventDatabase_GetDatabase(pUserName, false);
	
	-- Nothing to do if we don't even have the database
	
	if not vDatabase then
		return;
	end
	
	-- Bail out if our stuff is better
	
	if not CalendarNetwork_UpdateIsBetterThanOurs(pSender, pUserName, pDatabaseID, pRevision, vDatabase, vDatabase.Changes) then
		return;
	end
	
	-- Empty the specified changelist
	
	EventDatabase_PurgeDatabase(vDatabase, pDatabaseID);
	vDatabase.Changes.Revision = pRevision;
end

function CalendarNetwork_BeginDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision)
	-- If the same sender has a pending update already in progress, kill it
	
	CalendarNetwork_KillSendersDatabaseUpdates(pSender, pUserName);
	
	-- Get the database
	
	local		vDatabase = EventDatabase_GetDatabase(pUserName, false);
	
	-- Ignore updates for our own databases

	if vDatabase
	and vDatabase.IsPlayerOwned
	and not gGroupCalendar_Settings.AllowSelfUpdate then
		return;
	end
	
	local	vChanges;
	
	if vDatabase then
		vChanges = vDatabase.Changes;
	else
		vChanges = nil;
	end
	
	-- Ignore the update if it isn't from the owner and
	-- our database ID is newer
	
	if string.lower(pSender) ~= string.lower(pUserName)
	and vChanges then
		if (vChanges.ID > pDatabaseID)
		or (vChanges.ID == pDatabaseID
		and vChanges.Revision >= pRevision) then
			return;
		end
	end
	
	-- If it's from the owner and isn't the same revision as ours,
	-- then delete the database.  If the update is from revision
	-- zero go ahead with the update otherwise ignore it and request
	-- and update from zero
	
	if string.lower(pSender) == string.lower(pUserName)
	and vChanges
	and vChanges.ID ~= pDatabaseID then
		EventDatabase_PurgeDatabase(vDatabase, pDatabaseID);
		
		if pSinceRevision ~= 0 then
			-- Request an update and exit
			
			CalendarNetwork_QueueRFURequest(pUserName, "DB", pDatabaseID, 0)
			return;
		end
	end
	
	-- See if there's another update for the same database already in progress
	
	local	vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(nil, pUserName, nil);
	
	if vDatabaseUpdate then
		-- If the new update is from the owner or
		-- the old one isn't from the owner and is a higher revision,
		-- then cancel the old update
		
		if string.lower(pSender) == string.lower(pUserName)
		or (vDatabaseUpdate.mSender ~= vDatabaseUpdate.mUserName
		and CalendarNetwork_DatabaseIsNewer(vDatabaseUpdate.mDatabaseID, vDatabaseUpdate.mRevision, pDatabaseID, pRevision))  then
			CalendarNetwork_CancelDatabaseUpdate(nil, pUserName, nil);
		
		-- Otherwise cancel this one
		
		else
			return;
		end
	end
	
	if gGroupCalendar_Settings.DebugUpdates then
		Calendar_DebugMessage("CalendarNetwork_BeginDatabaseUpdate: "..pUserName..","..pRevision.." since revision "..pSinceRevision.." from "..pSender);
	end

	-- Can't accept updates which don't cover the last revision we received
	
	if vChanges
	and pSinceRevision > vChanges.Revision then
		-- Ask for another copy of the update starting with the revision we need
		
		CalendarNetwork_QueueRFURequest(pUserName, "DB", pDatabaseID, vChanges.Revision)
		return;
	end
	
	-- Create the database update record
	
	vDatabaseUpdate = {};
	
	vDatabaseUpdate.mSender = pSender;
	vDatabaseUpdate.mUserName = pUserName;
	vDatabaseUpdate.mDatabaseID = pDatabaseID;
	vDatabaseUpdate.mRevision = pRevision;
	vDatabaseUpdate.mSinceRevision = pSinceRevision;
	vDatabaseUpdate.mChanges = {};
	vDatabaseUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp();
	
	table.insert(gGroupCalendar_Queue.DatabaseUpdates, vDatabaseUpdate);
end

function CalendarNetwork_InsertEventUpdate(pSender, pUserName, pDatabaseID, pRevision, pEventID, pCommand)
	local	vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID);
	
	if not vDatabaseUpdate then
		return;
	end
	
	-- Bump the update time
	
	vDatabaseUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp();
	
	--
	
	local	vChanges = vDatabaseUpdate.mChanges[pRevision];
	
	if not vChanges then
		vChanges = {};
		vDatabaseUpdate.mChanges[pRevision] = vChanges;
	end
	
	-- Reconstruct the change string
	
	local	vChangeString = "EVT:"..pEventID;
	
	for vIndex, vCommand in pCommand do
		vChangeString = vChangeString.."/"..vCommand.opcode;
		
		if vCommand.operandString and vCommand.operandString ~= "" then
			vChangeString = vChangeString..":"..vCommand.operandString;
		end
	end
	
	-- Save the string
	
	table.insert(vChanges, vChangeString);
end

function CalendarNetwork_EndDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision)
	local	vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID, true);
	
	if not vDatabaseUpdate then
		return;
	end
	
	-- Sanity check: make sure the sinceRevision field matches the original UPD message
	
	if vDatabaseUpdate.mSinceRevision ~= pSinceRevision then
		return;
	end
	
	-- The update was received successfully.
	-- Copy the changes to the change list
	
	if gGroupCalendar_Settings.DebugUpdates then
		Calendar_DebugMessage("CalendarNetwork_EndDatabaseUpdate: Process update for "..pUserName..","..pDatabaseID..","..pRevision.." since revision "..pSinceRevision.." from "..pSender);
	end
	
	local	vDatabase = EventDatabase_GetDatabase(pUserName, true);
	local	vDatabaseChanges = vDatabase.Changes;
	local	vReconstructDatabase = false;
	
	-- If the update is for one of our own databases process it
	-- separately
	
	if vDatabase.IsPlayerOwned then
		CalendarNetwork_AskProcessSelfUpdate(vDatabaseUpdate);
		return;
	end
	
	-- Process the update
	
	CalendarNetwork_ProcessDatabaseUpdate(vDatabaseUpdate, false);
end

function CalendarNetwork_ProcessDatabaseUpdate(pDatabaseUpdate, pForceReconstruct)
	local	vIsOwnerUpdate = pDatabaseUpdate.mSender == pDatabaseUpdate.mUserName;
	local	vDatabase = EventDatabase_GetDatabase(pDatabaseUpdate.mUserName, true);
	local	vDatabaseChanges = vDatabase.Changes;
	local	vReconstructDatabase = pForceReconstruct;
	
	if not vDatabaseChanges
	or vDatabaseChanges.ID ~= pDatabaseUpdate.mDatabaseID then
		vDatabase.Changes = CalendarChanges_New(pDatabaseUpdate.mDatabaseID);
		vDatabaseChanges = vDatabase.Changes;
	end
	
	for vRevision = pDatabaseUpdate.mSinceRevision + 1, pDatabaseUpdate.mRevision do
		local	vChanges = pDatabaseUpdate.mChanges[vRevision];
		
		-- If the revision is newer than what we have, insert the new data
		
		if vRevision > vDatabaseChanges.Revision then
			CalendarChanges_SetChangeList(vDatabaseChanges, vRevision, vChanges);
			
			if vChanges then
				EventDatabase_ExecuteChangeList(vDatabase, vChanges, true);
			end
		
		-- If the revision overlaps what we have and the update is from
		-- the owner, then compare the data to make sure it's intact.  If
		-- it doesn't match then update the changes and flag the database
		-- for reconstruction
		
		elseif vIsOwnerUpdate then
			-- If we're not reconstructing then compare the owner's changes to what
			-- we've gotten before and see if they match.  Switch to reconstruction
			-- mode if there's a discrepancy
			
			if not vReconstructDatabase then
				local	vChangeList = CalendarChanges_GetChangeList(vDatabaseChanges, vRevision);
				
				if ((vChanges ~= nil) ~= (vChangeList ~= nil))
				or (vChangeList ~= nil and table.getn(vChangeList) ~= table.getn(vChanges)) then
					vReconstructDatabase = true;
					
					if gGroupCalendar_Settings.DebugReconstruct then
						Calendar_DebugMessage("Reconstructing "..vDatabase.UserName.." because changes for revision "..vRevision.." are different lengths");
					end
				elseif vChanges ~= nil then
					for vChangeIndex, vChange in vChanges do
						local	vOldChange = vChangeList[vChangeIndex];
						
						if vOldChange ~= vChange then
							vReconstructDatabase = true;
							
							if gGroupCalendar_Settings.DebugReconstruct then
								Calendar_DebugMessage("Reconstructing "..vDatabase.UserName.." because change "..vChangeIndex.." for revision "..vRevision.." doesn't match");
								Calendar_DebugMessage("Previously: "..vOldChange);
								Calendar_DebugMessage("Now: "..vChange);
							end
							break;
						end
					end
				end
			end
			
			-- Just copy the changes over if we're in re-construction mode
			
			if vReconstructDatabase then
				CalendarChanges_SetChangeList(vDatabaseChanges, vRevision, vChanges);
			end
		end
	end
	
	-- Make sure the current revision stamp matches the update
	
	vDatabaseChanges.Revision = pDatabaseUpdate.mRevision;
	
	-- Update AuthRevision if the update came from the owner
	
	if vIsOwnerUpdate
	and pDatabaseUpdate.mSinceRevision <= vDatabaseChanges.AuthRevision then
		vDatabaseChanges.AuthRevision = pDatabaseUpdate.mRevision;
	end
	
	if vReconstructDatabase then
		EventDatabase_ReconstructDatabase(vDatabase);
	else
		GroupCalendar_MajorDatabaseChange(vDatabase);
	end
end

StaticPopupDialogs["CONFIRM_CALENDAR_SELF_UPDATE"] = {
	text = TEXT(GroupCalendar_cConfirmSelfUpdateMsg),
	button1 = TEXT(GroupCalendar_cUpdate),
	button2 = TEXT(CANCEL),
	OnAccept = function() CalendarNetwork_ProcessSelfUpdate(); end,
	OnCancel = function() CalendarNetwork_RejectSelfUpdate(); end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 1,
	showAlert = 1,
};

gGroupCalendar_DidAskSelfUpdate = false;
gGroupCalendar_SelfDatabaseUpdate = nil;

function CalendarNetwork_AskProcessSelfUpdate(pDatabaseUpdate)
	-- Only ask once
	
	if gGroupCalendar_DidAskSelfUpdate then
		return;
	end
	
	gGroupCalendar_DidAskSelfUpdate = true;
	
	gGroupCalendar_SelfDatabaseUpdate = pDatabaseUpdate;
	
	-- Format the message
	
	local	vMessage = Calendar_FormatNamed(GroupCalendar_cConfirmSelfUpdateParamFormat, pDatabaseUpdate);
	
	-- Show the dialog
	
	StaticPopup_Show("CONFIRM_CALENDAR_SELF_UPDATE", vMessage);
end

StaticPopupDialogs["CONFIRM_CALENDAR_SELF_RSVP_UPDATE"] = {
	text = TEXT(GroupCalendar_cConfirmSelfUpdateMsg),
	button1 = TEXT(GroupCalendar_cUpdate),
	button2 = TEXT(CANCEL),
	OnAccept = function() CalendarNetwork_ProcessSelfRSVPUpdate(); end,
	OnCancel = function() CalendarNetwork_RejectSelfRSVPUpdate(); end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 1,
	showAlert = 1,
};

gGroupCalendar_DidAskSelfRSVPUpdate = false;
gGroupCalendar_SelfRSVPUpdate = nil;

function CalendarNetwork_AskProcessSelfRSVPUpdate(pRSVPUpdate)
	-- Only ask once
	
	if gGroupCalendar_DidAskSelfRSVPUpdate then
		return;
	end
	
	gGroupCalendar_DidAskSelfRSVPUpdate = true;
	gGroupCalendar_SelfRSVPUpdate = pRSVPUpdate;
	
	-- Format the message
	
	local	vMessage = Calendar_FormatNamed(GroupCalendar_cConfirmSelfRSVPUpdateParamFormat, pRSVPUpdate);
	
	-- Show the dialog
	
	StaticPopup_Show("CONFIRM_CALENDAR_SELF_RSVP_UPDATE", vMessage);
end

function CalendarNetwork_ProcessSelfUpdate()
	CalendarNetwork_ProcessDatabaseUpdate(gGroupCalendar_SelfDatabaseUpdate, true);
end

function CalendarNetwork_RejectSelfUpdate()
	local	vDatabase = EventDatabase_GetDatabase(gGroupCalendar_SelfDatabaseUpdate.mUserName, true);
	
	if not vDatabase then
		return;
	end
	
	EventDatabase_RebuildDatabase(vDatabase);
end

function CalendarNetwork_CancelDatabaseUpdate(pSender, pUserName, pDatabaseID)
	CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID, true);
end

function CalendarNetwork_FindOpenEventChangeRecord(pChanges, pEventID)
	for vIndex, vChange in pChanges do
		if vChange.mEventID == pEventID
		and vChange.mOpen then
			return vChange;
		end
	end
	
	return nil;
end

function CalendarNetwork_KillSendersDatabaseUpdates(pSender)
	local	vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender, nil, nil, true);
	
	if not vDatabaseUpdate
	or vDatabaseUpdate.mUserName == pDontRequestForUserName then
		return;
	end
	
	CalendarNetwork_RequestUpdateForUser(vDatabaseUpdate.mUserName, "DB");
end

function CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID, pDelete)
	for vIndex, vDatabaseUpdate in gGroupCalendar_Queue.DatabaseUpdates do
		if (pSender == nil or vDatabaseUpdate.mSender == pSender)
		and (pUserName == nil or vDatabaseUpdate.mUserName == pUserName)
		and (pDatabaseID == nil or vDatabaseUpdate.mDatabaseID == pDatabaseID) then
			-- Delete the update if requested
			
			if pDelete then
				table.remove(gGroupCalendar_Queue.DatabaseUpdates, vIndex);
			end
			
			return vDatabaseUpdate;
		end
	end
	
	return nil;
end

function CalendarNetwork_DeleteRSVPs(pSender, pUserName, pDatabaseID, pRevision)
	-- Get the database
	
	local		vDatabase = EventDatabase_GetDatabase(pUserName, false);
	
	-- Nothing to do if we don't even have the database
	
	if not vDatabase then
		return;
	end
	
	-- Bail out if our stuff is better
	
	if not CalendarNetwork_UpdateIsBetterThanOurs(pSender, pUserName, pDatabaseID, pRevision, vDatabase, vDatabase.RSVPs) then
		return;
	end
	
	-- Empty the specified changelist
	
	EventDatabase_PurgeRSVPs(vDatabase, pDatabaseID);
	vDatabase.RSVPs.Revision = pRevision;
end

function CalendarNetwork_BeginRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision)
	-- If the same sender has a pending update already in progress, kill it
	
	CalendarNetwork_KillSendersRSVPUpdates(pSender, pUserName);
	
	-- Get the database
	
	local		vDatabase = EventDatabase_GetDatabase(pUserName, false);
	
	-- Don't listen to updates for our own databases
	
	if vDatabase
	and vDatabase.IsPlayerOwned
	and not gGroupCalendar_Settings.AllowSelfUpdate then
		return;
	end
	
	local		vChanges;
	
	if vDatabase then
		vChanges = vDatabase.RSVPs;
	else
		vChanges = nil;
	end
	
	-- Ignore the update if it isn't from the owner and
	-- our ID is newer
	
	if string.lower(pSender) ~= string.lower(pUserName)
	and vChanges then
		if (vChanges.ID > pDatabaseID)
		or (vChanges.ID == pDatabaseID
		and vChanges.Revision >= pRevision) then
			return;
		end
	end

	-- If it's from the owner and isn't the same revision as ours,
	-- then delete the database.  If the update is from revision
	-- zero go ahead with the update otherwise ignore it and request
	-- and update from zero
	
	if string.lower(pSender) == string.lower(pUserName)
	and vChanges
	and vChanges.ID ~= pDatabaseID then
		EventDatabase_PurgeRSVPs(vDatabase, pDatabaseID);
		
		if pSinceRevision ~= 0 then
			-- Request an update and exit
			
			CalendarNetwork_QueueRFURequest(pUserName, "RAT", pDatabaseID, 0)
			return;
		end
	end
	
	-- See if there's another update for the same database already in progress
	
	local	vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(nil, pUserName, nil);
	
	if vRSVPUpdate then
		-- If the new update is from the owner or
		-- the old one isn't from the owner and is a higher revision,
		-- then cancel the old update
		
		if string.lower(pSender) == string.lower(pUserName)
		or (vRSVPUpdate.mSender ~= vRSVPUpdate.mUserName
		and CalendarNetwork_DatabaseIsNewer(vRSVPUpdate.mDatabaseID, vRSVPUpdate.mRevision, pDatabaseID, pRevision))  then
			CalendarNetwork_CancelRSVPUpdate(nil, pUserName, nil);
		
		-- Otherwise cancel this one
		
		else
			return;
		end
	end
	
	if gGroupCalendar_Settings.DebugUpdates then
		Calendar_DebugMessage("CalendarNetwork_BeginRSVPUpdate: "..pUserName..","..pRevision.." since revision "..pSinceRevision.." from "..pSender);
	end

	-- Can't accept updates which don't cover the last revision we received
	
	if vChanges
	and pSinceRevision > vChanges.Revision then
		-- Ask for another copy of the update starting with the revision we need
		
		CalendarNetwork_QueueRFURequest(pUserName, "RAT", pDatabaseID, vChanges.Revision);
		return;
	end
	
	-- Create the update record
	
	vRSVPUpdate = {};
	
	vRSVPUpdate.mSender = pSender;
	vRSVPUpdate.mUserName = pUserName;
	vRSVPUpdate.mDatabaseID = pDatabaseID;
	vRSVPUpdate.mRevision = pRevision;
	vRSVPUpdate.mSinceRevision = pSinceRevision;
	vRSVPUpdate.mChanges = {};
	vRSVPUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp();
	
	table.insert(gGroupCalendar_Queue.RSVPUpdates, vRSVPUpdate);
end

function CalendarNetwork_InsertRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, pEventFields)
	local	vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID);
	
	if not vRSVPUpdate then
		if gGroupCalendar_Settings.DebugUpdates then
			Calendar_DebugMessage("CalendarNetwork_InsertRSVPUpdate: Ignoring update for "..pUserName..","..pDatabaseID..": Update not found");
		end
		return;
	end
	
	-- Bump the update time
	
	vRSVPUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp();
	
	--
	
	local	vChanges = vRSVPUpdate.mChanges[pRevision];
	
	if not vChanges then
		vChanges = {};
		vRSVPUpdate.mChanges[pRevision] = vChanges;
	end
	
	-- Process the event command
	
	local	vChangeString = "EVT:"..pEventFields;
	
	table.insert(vChanges, vChangeString);
end

function CalendarNetwork_EndRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision)
	local	vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID, true);
	
	if not vRSVPUpdate then
		return;
	end
	
	-- Sanity check: make sure the sinceRevision field matches the original UPD message
	
	if vRSVPUpdate.mSinceRevision ~= pSinceRevision then
		return;
	end
	
	-- The update was received successfully.
	-- Process the commands in the update
	
	if gGroupCalendar_Settings.DebugUpdates then
		Calendar_DebugMessage("CalendarNetwork_EndRSVPUpdate: Process update for "..pUserName..","..pRevision.." since revision "..pSinceRevision.." from "..pSender);
	end
	
	local	vDatabase = EventDatabase_GetDatabase(pUserName, true);
	local	vRSVPChanges = vDatabase.RSVPs;
	local	vReconstructRSVPs = false;
	
	-- If the update is for one of our own databases process it
	-- separately
	
	if vDatabase.IsPlayerOwned then
		CalendarNetwork_AskProcessSelfRSVPUpdate(vRSVPUpdate);
		return;
	end
	
	-- Process the update
	
	CalendarNetwork_ProcessRSVPUpdate(vRSVPUpdate, false);
end

function CalendarNetwork_ProcessRSVPUpdate(pRSVPUpdate, pForceReconstruct)
	local	vIsOwnerUpdate = pRSVPUpdate.mSender == pRSVPUpdate.mUserName;
	local	vDatabase = EventDatabase_GetDatabase(pRSVPUpdate.mUserName, true);
	local	vRSVPChanges = vDatabase.RSVPs;
	
	if not vRSVPChanges
	or vRSVPChanges.ID ~= pRSVPUpdate.mDatabaseID then
		vDatabase.RSVPs = CalendarChanges_New(pRSVPUpdate.mDatabaseID);
		vRSVPChanges = vDatabase.RSVPs;
	end
	
	for vRevision = pRSVPUpdate.mSinceRevision + 1, pRSVPUpdate.mRevision do
		local	vChanges = pRSVPUpdate.mChanges[vRevision];
		
		-- If the revision is newer than what we have, insert the new data
		
		if vRevision > vRSVPChanges.Revision then
			CalendarChanges_SetChangeList(vRSVPChanges, vRevision, vChanges);
			CalendarChanges_Close(vRSVPChanges, vRevision);
			
			if vChanges then
				EventDatabase_ExecuteRSVPChangeList(vDatabase, vChanges, true);
			end
		elseif vIsOwnerUpdate and vRevision > vRSVPChanges.AuthRevision then
			if gGroupCalendar_Settings.DebugUpdates then
				Calendar_DebugMessage("CalendarNetwork_EndRSVPUpdate: Ignoring owner update for "..vRevision..": Not implemented");
			end
		else
			if gGroupCalendar_Settings.DebugUpdates then
				Calendar_DebugMessage("CalendarNetwork_EndRSVPUpdate: Ignoring revision "..vRevision..": Already exists");
			end
		end
	end
	
	vRSVPChanges.Revision = pRSVPUpdate.mRevision;
	
	-- Update AuthRevision if the update came from the owner
	
	if vIsOwnerUpdate
	and pRSVPUpdate.mSinceRevision <= vRSVPChanges.AuthRevision then
		vRSVPChanges.AuthRevision = pRSVPUpdate.mRevision;
	end
end

function CalendarNetwork_ProcessSelfRSVPUpdate()
	CalendarNetwork_ProcessRSVPUpdate(gGroupCalendar_SelfRSVPUpdate, true);
end

function CalendarNetwork_RejectSelfRSVPUpdate()
	local	vDatabase = EventDatabase_GetDatabase(gGroupCalendar_SelfRSVPUpdate.mUserName, true);
	
	if not vDatabase then
		return;
	end
	
	EventDatabase_RebuildRSVPs(vDatabase);
end

function CalendarNetwork_CancelRSVPUpdate(pSender, pUserName, pDatabaseID)
	CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID, true);
end

function CalendarNetwork_KillSendersRSVPUpdates(pSender, pDontRequestForUserName)
	local	vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender, nil, nil, pDelete);
	
	if not vRSVPUpdate
	or vRSVPUpdate.mUserName == pDontRequestForUserName then
		return;
	end
	
	CalendarNetwork_RequestUpdateForUser(vRSVPUpdate.mUserName, "RAT");
end

function CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID, pDelete)
	for vIndex, vRSVPUpdate in gGroupCalendar_Queue.RSVPUpdates do
		if (pSender == nil or vRSVPUpdate.mSender == pSender)
		and (pUserName == nil or vRSVPUpdate.mUserName == pUserName)
		and (pDatabaseID == nil or vRSVPUpdate.mDatabaseID == pDatabaseID) then
			-- Delete the update if requested
			
			if pDelete then
				table.remove(gGroupCalendar_Queue.RSVPUpdates,vIndex);
			end
			
			return vRSVPUpdate;
		end
	end
	
	return nil;
end

function CalendarNetwork_KillOldUpdates(pDatabaseTag, pUpdates, pMinimumTime)
	local	vIndex = 1;
	local	vNumUpdates = table.getn(pUpdates);
	
	while vIndex <= vNumUpdates do
		local	vUpdate = pUpdates[vIndex];
		
		if vUpdate.mLastMessageTime < pMinimumTime then
			table.remove(pUpdates, vIndex);
			vNumUpdates = vNumUpdates - 1;
			
			-- Re-request the update
			
			CalendarNetwork_RequestUpdateForUser(vUpdate.mUserName, pDatabaseTag, false);
		else
			vIndex = vIndex + 1;
		end
	end
end

function CalendarNetwork_ParseCommandString(pCommandString)
	
	-- Verify the command begins with the message prefix
	
	if strsub(pCommandString, 1, gGroupCalendar_MessagePrefixLength) ~= gGroupCalendar_MessagePrefix then
		return nil;
	end
	
	local	vCommandString = strsub(pCommandString, gGroupCalendar_MessagePrefixLength);
	
	return CalendarNetwork_ParseCommandSubString(vCommandString)
end

function CalendarNetwork_ParseCommandSubString(pCommandString)
	-- Break the command into parts
	
	local	vCommand = {};
	
	for vOpcode, vOperands in string.gfind(pCommandString, "/(%w+):*([^/]*)") do
		local	vOperation = {};
		
		vOperation.opcode = vOpcode;
		vOperation.operandString = vOperands;
		vOperation.operands = CalendarNetwork_ParseParameterString(vOperands);
		
		table.insert(vCommand, vOperation);
	end
	
	return vCommand;
end

function CalendarNetwork_ParseParameterString(pParameterString)
	local	vParameters = {};
	local	vIndex = 0;
	local	vFound = true;
	local	vStartIndex = 1;
	
	while vFound do
		local	vEndIndex;
		
		vFound, vEndIndex, vParameter = string.find(pParameterString, "([^,]*),", vStartIndex);
		
		vIndex = vIndex + 1;
		
		if not vFound then
			vParameters[vIndex] = string.sub(pParameterString, vStartIndex);
			break;
		end
		
		vParameters[vIndex] = vParameter;
		vStartIndex = vEndIndex + 1;
	end
	
	return vParameters;
end

function CalendarNetwork_NewEvent(pDatabase, pEvent)
	-- Don't record private events in the change history
	
	if pEvent.mPrivate then
		return;
	end
	
	-- Append a change record for the event
	
	local	vChangeList = EventDatabase_GetCurrentChangeList(pDatabase);
	
	EventDatabase_AppendNewEvent(vChangeList, pEvent, EventDatabase_GetEventPath(pEvent));
end

function CalendarNetwork_EventChanged(pDatabase, pEvent, pChangedFields)
	-- Don't record private events in the change history
	
	if pEvent.mPrivate then
		return;
	end
	
	-- Append a change record for the event
	
	local	vChangeList = EventDatabase_GetCurrentChangeList(pDatabase);
	
	EventDatabase_AppendEventUpdate(
			vChangeList,
			pEvent,
			EventDatabase_GetEventPath(pEvent),
			pChangedFields);
end

function CalendarNetwork_RemovingEvent(pDatabase, pEvent)
	-- Don't record private events in the change history
	
	if pEvent.mPrivate then
		return;
	end
	
	-- Remove any references to the event from the change history

	EventDatabase_RemoveEventChanges(pDatabase, pEvent);
	
	-- Insert a delete event
	
	local	vChangeList = EventDatabase_GetCurrentChangeList(pDatabase);
	
	table.insert(vChangeList, EventDatabase_GetEventPath(pEvent).."DEL");
end

function CalendarNetwork_SendRevisionChanged(pChanges, pLabel, pUserName)
	-- Just leave if there's no channel to communicate on
	-- or no changes to announce
	
	if not gGroupCalendar_Channel.ID
	or not pChanges then
		return;
	end
	
	CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, pChanges.Revision).."NOU");
end

function CalendarNetwork_DBRevisionChanged(pDatabase)
	CalendarNetwork_SendRevisionChanged(pDatabase.Changes, "DB", pDatabase.UserName);
end

function CalendarNetwork_RSVPRevisionChanged(pDatabase)
	CalendarNetwork_SendRevisionChanged(pDatabase.RSVPs, "RAT", pDatabase.UserName);
end

function CalendarNetwork_RequestAllUpdate()
	-- Just leave if there's no channel to communicate on
	
	if not gGroupCalendar_Channel.ID then
		return;
	end
	
	-- Send the request immmediately since delaying it for channel silence will only
	-- increase the number of times that everyone has to transmit their NOU responses
	
	CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."ALL/RFU");
end

function CalendarNetwork_RequestGuildUpdate(pGuildName, pMinRank)
	-- Just leave if there's no channel to communicate on
	
	if not gGroupCalendar_Channel.ID then
		return;
	end
	
	-- Send the request immmediately since delaying it for channel silence will only
	-- increase the number of times that everyone has to transmit their NOU responses
	
	if pMinRank then
		CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."GLD:"..pGuildName..","..pMinRank.."/RFU");
	else
		CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."GLD:"..pGuildName.."/RFU");
	end
end

function CalendarNetwork_RequestUpdateForUser(pUserName, pDatabaseTag, pRequestImmediately)
	local	vDatabase = EventDatabase_GetDatabase(pUserName);

	if not vDatabase then
		return;
	end
	
	local	vChanges;
	
	if pDatabaseTag == "DB" then
		vChanges = vDatabase.Changes;
	elseif pDatabaseTag == "RAT" then
		vChanges = vDatabase.RSVPs;
	else
		Calendar_ErrorMessage("CalendarNetwork_RequestUpdateForUser: Unknown database tag "..pDatabaseTag);
		return;
	end
	
	if not vChanges then
		return;
	end
	
	CalendarNetwork_RequestUpdate(vDatabase, vChanges, pDatabaseTag, pRequestImmediately);
end

function CalendarNetwork_RequestUpdate(pDatabase, pChanges, pDatabaseTag, pRequestImmediately)
	-- Just leave if there's no channel to communicate on
	
	if not gGroupCalendar_Channel.ID then
		return;
	end
	
	local	vID, vRevision;
	
	if pChanges then
		vID = pChanges.ID;
		vRevision = pChanges.Revision;
	else
		vID = 0;
		vRevision = 0;
	end
	
	if pRequestImmediately then
		CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pDatabaseTag, pDatabase.UserName, vID, vRevision, 0).."RFU");
	else
		CalendarNetwork_QueueRFURequest(pDatabase.UserName, pDatabaseTag, vID, vRevision);
	end
end

function CalendarNetwork_SendEmptyChanges(pChanges, pLabel, pUserName)
	local	vID, vRevision;
	
	if pChanges then
		vID = pChanges.ID;
		vRevision = pChanges.Revision;
	else
		vID = Calendar_GetCurrentDateTimeUT60();
		vRevision = 0;
	end
	
	CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, vID, vRevision).."DEL");
end

function CalendarNetwork_SendChanges(pChanges, pLabel, pUserName, pLockdown, pSinceRevision)
	if pLockdown then
		CalendarChanges_LockdownCurrentChangeList(pChanges);
	end
	
	if CalendarChanges_IsEmpty(pChanges) then
		CalendarNetwork_SendEmptyChanges(pChanges, pLabel, pUserName);
		return;
	end
	
	CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, pChanges.Revision).."UPD:"..pSinceRevision);
	
	for vRevision = pSinceRevision + 1, pChanges.Revision do
		local	vRevisionPath = CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, vRevision);
		local	vChangeList = pChanges.ChangeList[vRevision];
		
		if vChangeList then
			vChangeList.IsOpen = nil; -- Make sure IsOpen is cleared, a bug may have caused it to remain open
			
			for vIndex, vChange in vChangeList do
				CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..vRevisionPath..vChange);
			end
		end
	end
	
	CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, pChanges.Revision).."END:"..pSinceRevision);
end

function CalendarNetwork_QueueOutboundMessage(pMessage)
	table.insert(gGroupCalendar_Queue.OutboundMessages, pMessage);
	
	GroupCalendar_StartUpdateTimer();
end

function CalendarNetwork_QueueInboundMessage(pSender, pTrustLevel, pMessage)
	table.insert(gGroupCalendar_Queue.InboundMessages, {mSender = pSender, mTrustLevel = pTrustLevel, mMessage = pMessage});
	
	if table.getn(gGroupCalendar_Queue.InboundMessages) == 1 then
		gGroupCalendar_Queue.InboundDelay = 0;
	end
	
	GroupCalendar_StartUpdateTimer();
end

function CalendarNetwork_QueueTask(pTaskFunc, pTaskParam, pDelay, pTaskID)
	local	vTask = {mTaskFunc = pTaskFunc, mTaskParam = pTaskParam, mDelay = pDelay, mID = pTaskID};
	
	-- Ignore tasks with duplicate IDs
	
	if pTaskID then
		for vIndex, vTask in gGroupCalendar_Queue.Tasks do
			if vTask.mID == pTaskID then
				return;
			end
		end
	end
	
	-- Insert the task
	
	table.insert(gGroupCalendar_Queue.Tasks, vTask);
	
	CalendarNetwork_UpdateTaskQueueDelay();
	
	return true;
end

function CalendarNetwork_UpdateTaskQueueDelay()
	local	vDelay = nil;
	
	for vIndex, vTask in gGroupCalendar_Queue.Tasks do
		if not vDelay
		or vTask.mDelay < vDelay then
			vDelay = vTask.mDelay;
		end
	end
	
	gGroupCalendar_Queue.TasksDelay = vDelay;
	
	if vDelay then
		GroupCalendar_StartUpdateTimer();
	end
end

function CalendarNetwork_SetTaskDelay(pTaskID, pDelay)
	-- Ignore tasks with duplicate IDs
	
	for vIndex, vTask in gGroupCalendar_Queue.Tasks do
		if vTask.mID == pTaskID then
			vTask.mDelay = pDelay;
			CalendarNetwork_UpdateTaskQueueDelay();
			return;
		end
	end
end

function CalendarNetwork_QueueRequest(pRequest, pDelay, pPriority)
	pRequest.mDelay = pDelay;
	
	if gGroupCalendar_Settings.DebugQueues then
		Calendar_DebugMessage("CalendarNetwork_QueueRequest: "..pRequest.mOpcode.." in "..pDelay.." seconds");
	end
	
	if not pPriority then
		pPriority = 2;
	end
	
	table.insert(gGroupCalendar_Queue.Requests[pPriority], pRequest);
	
	local	vTotalRequests = 0;
	
	for vPriority, vRequestQueue in gGroupCalendar_Queue.Requests do
		vTotalRequests = vTotalRequests + table.getn(gGroupCalendar_Queue.Requests[vPriority]);
	end
	
	if vTotalRequests == 1
	or (pRequest.mDelay < gGroupCalendar_Queue.RequestsDelay
	and pRequest.mDelay < gCalendarNetwork_RequestDelay.RequestQueue) then
		gGroupCalendar_Queue.RequestsDelay = pRequest.mDelay;
	end
	
	GroupCalendar_StartUpdateTimer();
	
	return true;
end

function CalendarNetwork_SetRequestPriority(pRequest, pPriority)
	for vPriority, vRequests in gGroupCalendar_Queue.Requests do
		for vIndex, vRequest in vRequests do
			if vRequest == pRequest then
				if vPriority ~= pPriority then
					table.remove(vRequests, vIndex);
					table.insert(gGroupCalendar_Queue.Requests[pPriority], vRequest);
				end
				return;
			end
		end
	end
end

function CalendarNetwork_QueueUniqueOpcodeRequest(pRequest, pDelay)
	-- Remove an existing request with the same opcode
	
	for vPriority, vRequests in gGroupCalendar_Queue.Requests do
		for vIndex, vRequest in vRequests do
			if vRequest.mOpcode == pRequest.mOpcode then
				table.remove(vRequests, vIndex);
				break;
			end
		end
	end
	
	return CalendarNetwork_QueueRequest(pRequest, pDelay);
end

function CalendarNetwork_QueueUniqueUserRequest(pRequest, pDelay)
	-- Remove an existing request with the same opcode and user name
	
	for vPriority, vRequests in gGroupCalendar_Queue.Requests do
		for vIndex, vRequest in vRequests do
			if vRequest.mOpcode == pRequest.mOpcode
			and vRequest.mUserName == pRequest.mUserName then
				table.remove(vRequests, vIndex);
				break;
			end
		end
	end
	
	return CalendarNetwork_QueueRequest(pRequest, pDelay);
end

function CalendarNetwork_ArrayContainsArray(pArray, pSubArray)
	for vFieldName, vFieldValue in pSubArray do
		if pArray[vFieldName] ~= vFieldValue then
			return false;
		end
	end
	
	return true;
end

function CalendarNetwork_FindRequest(pRequest)
	for vPriority, vRequests in gGroupCalendar_Queue.Requests do
		for vIndex, vRequest in vRequests do
			if CalendarNetwork_ArrayContainsArray(vRequest, pRequest) then
				return vRequest;
			end
		end
	end
	
	return nil;
end

function CalendarNetwork_FindUPDRequest(pDatabaseTag, pUserName)
	return CalendarNetwork_FindRequest({mOpcode = pDatabaseTag.."_UPD", mUserName = pUserName});
end

function CalendarNetwork_CancelRedundantRFURequest(pDatabaseTag, pUserName, pDatabaseID, pFromRevision)
	local	vOpcode = pDatabaseTag.."_RFU";
	
	for vPriority, vRequests in gGroupCalendar_Queue.Requests do
		for vIndex, vRequest in vRequests do
			if vRequest.mOpcode == vOpcode
			and vRequest.mUserName == pUserName then
				if vRequest.mDatabaseID == pDatabaseID
				and pFromRevision <= vRequest.mRevision then
					if gGroupCalendar_Settings.DebugUpdates then
						Calendar_DebugMessage("Removing redundant RFU for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pFromRevision);
					end
					
					table.remove(vRequests, vIndex);
					return false; -- Return false to indicate there is no longer an RFU for this user
				else
					if gGroupCalendar_Settings.DebugUpdates then
						Calendar_DebugMessage("Keeping RFU request for "..pDatabaseTag.." "..pUserName..","..vRequest.mDatabaseID..","..vRequest.mRevision);
						Calendar_DebugMessage("Better thean "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pFromRevision);
					end
				end
				
				if gGroupCalendar_Settings.DebugUpdates then
					Calendar_DebugMessage("RFU for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pFromRevision.." already exists");
				end
				
				return true; -- Return true to indicate there is an RFU for this user
			end
		end
	end
	
	return false; -- Return false to indicate no RFU for this user was found
end

function CalendarNetwork_NeedsUpdateTimer()
	if gGroupCalendar_Queue.TasksDelay then
		return true;
	end
	
	for vPriority, vRequests in gGroupCalendar_Queue.Requests do
		if table.getn(vRequests) > 0 then
			return true;
		end
	end
	
	if table.getn(gGroupCalendar_Queue.InboundMessages) > 0 then
		return true;
	end

	if table.getn(gGroupCalendar_Queue.OutboundMessages) > 0 then
		return true;
	end
	
	return false;
end

function CalendarNetwork_ProcessTaskQueue(pElapsed)
	if not gGroupCalendar_Queue.TasksDelay then
		return;
	end
	
	gGroupCalendar_Queue.TasksDelay = gGroupCalendar_Queue.TasksDelay - pElapsed;
	gGroupCalendar_Queue.TasksElapsed = gGroupCalendar_Queue.TasksElapsed + pElapsed;
	
	if gGroupCalendar_Queue.TasksDelay <= 0 then
		local	vNumTasks = table.getn(gGroupCalendar_Queue.Tasks);
		local	vIndex = 1;
		
		gGroupCalendar_Queue.TasksDelay = nil;
		
		local	vLatencyStartTime;
		
		if gGroupCalendar_Settings.DebugLatency then
			vLatencyStartTime = GetTime();
		end
		
		while vIndex <= vNumTasks do
			local	vTask = gGroupCalendar_Queue.Tasks[vIndex];
			
			vTask.mDelay = vTask.mDelay - gGroupCalendar_Queue.TasksElapsed;
			
			if vTask.mDelay <= 0 then
				table.remove(gGroupCalendar_Queue.Tasks, vIndex);
				vNumTasks = vNumTasks - 1;
				
				-- Perform the task
				
				vTask.mTaskFunc(vTask.mTaskParam);

			else
				if not gGroupCalendar_Queue.TasksDelay
				or vTask.mDelay < gGroupCalendar_Queue.TasksDelay then
					gGroupCalendar_Queue.TasksDelay = vTask.mDelay;
				end
				
				vIndex = vIndex + 1;
			end
		end
		
		if gGroupCalendar_Settings.DebugLatency then
			local	vElapsed = GetTime() - vLatencyStartTime;
			
			if vElapsed > 0.1 then
				Calendar_DebugMessage("Tasks took "..vElapsed.."s to execute ("..vNumTasks.." tasks)");
			end
		end
		
		gGroupCalendar_Queue.TasksElapsed = 0;
	end
end

function CalendarNetwork_ProcessInboundQueue(pElapsed, pCurrentTimeStamp)
	local	vNumInboundMessages = table.getn(gGroupCalendar_Queue.InboundMessages);
	
	if vNumInboundMessages > 0 then
		local	vCollisionDetected = false;
		
		gGroupCalendar_Queue.InboundDelay = gGroupCalendar_Queue.InboundDelay - pElapsed;
		
		if gGroupCalendar_Queue.InboundDelay <= 0 then
			-- Process one message
			
			local	vMessage = gGroupCalendar_Queue.InboundMessages[1];
			
			table.remove(gGroupCalendar_Queue.InboundMessages, 1);
			
			local	vLatencyStartTime;
			
			if gGroupCalendar_Settings.DebugLatency then
				vLatencyStartTime = GetTime();
			end
			
			CalendarNetwork_ProcessCommandString(vMessage.mSender, vMessage.mTrustLevel, vMessage.mMessage, pCurrentTimeStamp);
			
			if gGroupCalendar_Settings.DebugLatency then
				local	vElapsed = GetTime() - vLatencyStartTime;
				
				if vElapsed > 0.1 then
					Calendar_DebugMessage("Inbound message took "..vElapsed.."s to process");
					Calendar_DumpArray("Message", vMessage);
				end
			end

			gGroupCalendar_Queue.InboundDelay = gCalendarNetwork_RequestDelay.InboundQueue;
			
			if gGroupCalendar_Queue.InboundLastSender ~= vMessage.mSender then
				if gGroupCalendar_Queue.InboundTimeSinceLastMessage < gCalendarNetwork_RequestDelay.OutboundQueueGapMin then
					-- Collision between other senders detected
					
					if gGroupCalendar_Settings.DebugQueues then
						Calendar_DebugMessage("Collision detected between "..gGroupCalendar_Queue.InboundLastSender.." and "..vMessage.mSender);
					end
					
					vCollisionDetected = true;
				end
				
				gGroupCalendar_Queue.InboundLastSender = vMessage.mSender;
			end
			
			gGroupCalendar_Queue.InboundTimeSinceLastMessage = 0;
		end
		
		-- Terminate processing while there are any pending inbound messages and delay further
		-- processing of the outbound and request queues
		
		-- If there was a collision and we *weren't* part of it then increase the range to
		-- avoid allowing us to become a part of the next collision.  This should gradually eliminate
		-- transmittors from the collisions until someone successfully takes over the wire.
		-- This probably seems really counterintuitive, but the idea is to eliminate players who
		-- didn't participate in a collision from attempting to try again.  By doing this, each collision
		-- should eliminate a significant number of players from the accident, leaving only the actual
		-- victims to try again.  The next collision will then eliminate several of those players
		-- until within a few seconds only one player remains and that player will then have control
		-- and finish his transmission.
		
		local	vOutboundQueueGapMin = gCalendarNetwork_RequestDelay.OutboundQueueGapMin;
		local	vRequestQueueGapMin = gCalendarNetwork_RequestDelay.RequestQueueGapMin;
		
		if vCollisionDetected
		and gGroupCalendar_Queue.OutboundTimeSinceLastMessage > gCalendarNetwork_RequestDelay.OutboundQueueGapMin then
			-- Collision detected between other players so use a longer gap to make sure
			-- we're not part of the next collision

			vOutboundQueueGapMin = vOutboundQueueGapMin + gCalendarNetwork_RequestDelay.OutboundQueueGapWidth;
			vRequestQueueGapMin = vRequestQueueGapMin  + gCalendarNetwork_RequestDelay.RequestQueueGapWidth;
		end
		
		vRandom = math.random();
		
		if gGroupCalendar_Queue.OutboundDelay < vOutboundQueueGapMin then
			gGroupCalendar_Queue.OutboundDelay = vOutboundQueueGapMin + vRandom * gCalendarNetwork_RequestDelay.OutboundQueueGapWidth;
		end
		
		if gGroupCalendar_Queue.RequestsDelay < vRequestQueueGapMin then
			gGroupCalendar_Queue.RequestsDelay = vRequestQueueGapMin + vRandom * gCalendarNetwork_RequestDelay.RequestQueueGapWidth;
		end
		
		return true;
	end
	
	return false;
end

function CalendarNetwork_ProcessOutboundQueue(pElapsed)
	local	vNumOutboundMessages = table.getn(gGroupCalendar_Queue.OutboundMessages);
	
	if vNumOutboundMessages > 0 then
		gGroupCalendar_Queue.OutboundDelay = gGroupCalendar_Queue.OutboundDelay - pElapsed;
		
		if gGroupCalendar_Queue.OutboundDelay <= 0 then
			local	vLatencyStartTime;
			
			if gGroupCalendar_Settings.DebugLatency then
				vLatencyStartTime = GetTime();
			end
			
			-- Send one message

			local	vMessage = gGroupCalendar_Queue.OutboundMessages[1];
			
			table.remove(gGroupCalendar_Queue.OutboundMessages, 1);
			
			CalendarNetwork_SendMessage(vMessage);
			
			gGroupCalendar_Queue.OutboundDelay = gCalendarNetwork_RequestDelay.OutboundQueue;
			
			-- Reset the time since last message to figure out if we're part of a collision
			
			gGroupCalendar_Queue.OutboundTimeSinceLastMessage = 0;
			
			--
			
			if gGroupCalendar_Settings.DebugLatency then
				local	vElapsed = GetTime() - vLatencyStartTime;
				
				if vElapsed > 0.1 then
					Calendar_DebugMessage("Outbound message took "..vElapsed.."s to process");
					Calendar_DebugMessage(vMessage);
				end
			end
			
			-- Stop processing if this isn't the last outbound message, otherwise
			-- go ahead and allow the request queue to be processed
			
			if vNumOutboundMessages == 1 then
				gGroupCalendar_Queue.RequestsDelay = 0;
			else
				return true;
			end
		else
			-- Terminate processing while there are any pending outbound messages and delay further
			-- processing of the request queues
			
			gGroupCalendar_Queue.RequestsDelay = gCalendarNetwork_RequestDelay.RequestQueueGapMin + math.random() * gCalendarNetwork_RequestDelay.RequestQueueGapWidth;
			return true;
		end
	end
	
	return false;
end

GroupCalendar_cUpdateRequestOpcodes =
{
	["DB_UPD"] = true,
	["RAT_UPD"] = true,
	["DB_NOU"] = true,
	["RAT_NOU"] = true
};

function CalendarNetwork_CanProcessRequest(pRequest)
	if not GroupCalendar_cUpdateRequestOpcodes[pRequest.mOpCode] then
		return true;
	end
	
	if not gGroupCalendar_EnableUpdates then
		return false;
	end
	
	if pRequest.mUserName then
		local	vDatabase = EventDatabase_GetDatabase(pRequest.mUserName, false);
		
		if vDatabase
		and vDatabase.IsPlayerOwned
		and not gGroupCalendar_EnableSelfUpdates then
			return false;
		end
	end
	
	return true;	
end

function CalendarNetwork_ProcessRequestQueue(pElapsed, pSuppressProcessing)
	local	vNumRequests = 0;
	
	for vPriority, vRequests in gGroupCalendar_Queue.Requests do
		vNumRequests = vNumRequests + table.getn(vRequests);
	end
	
	if vNumRequests > 0 then
		gGroupCalendar_Queue.RequestsDelay = gGroupCalendar_Queue.RequestsDelay - pElapsed;
		gGroupCalendar_Queue.RequestsElapsed = gGroupCalendar_Queue.RequestsElapsed + pElapsed;
		
		if gGroupCalendar_Queue.RequestsDelay <= 0 then
			-- Process one request
			
			local	vDidProcessRequest = pSuppressProcessing;
			local	vMinDelayForNextRequest = nil;
			
			local	vLatencyStartTime;
			local	vTotalRequests = 0;
			
			if gGroupCalendar_Settings.DebugLatency then
				vLatencyStartTime = GetTime();
			end
			
			for vPriority, vRequests in gGroupCalendar_Queue.Requests do
				local	vIndex = 1;
				local	vNumRequestsThisQueue = table.getn(vRequests);
				
				vTotalRequests = vTotalRequests + vNumRequestsThisQueue;
				
				while vIndex <= vNumRequestsThisQueue do
					local	vRequest = vRequests[vIndex];
					
					if vRequest.mDelay > 0 then
						vRequest.mDelay = vRequest.mDelay - gGroupCalendar_Queue.RequestsElapsed;
						
						if vRequest.mDelay < 0 then
							vRequest.mDelay = 0;
						end
					end
					
					if vRequest.mDelay == 0
					and not vDidProcessRequest then
						table.remove(vRequests, vIndex);
						vNumRequestsThisQueue = vNumRequestsThisQueue - 1;
						
						CalendarNetwork_ProcessRequest(vRequest);
						
						vDidProcessRequest = true;
					else
						if not vMinDelayForNextRequest
						or vRequest.mDelay < vMinDelayForNextRequest then
							vMinDelayForNextRequest = vRequest.mDelay;
						end
						
						vIndex = vIndex + 1;
					end
				end -- while vIndex
			end -- for vPriority
			
			if gGroupCalendar_Settings.DebugLatency then
				local	vElapsed = GetTime() - vLatencyStartTime;
				
				if vElapsed > 0.1 then
					Calendar_DebugMessage("Requests took "..vElapsed.."s to process ("..vTotalRequests.." total requests)");
				end
			end
			
			if not vMinDelayForNextRequest
			or vMinDelayForNextRequest < gCalendarNetwork_RequestDelay.RequestQueue then
				vMinDelayForNextRequest = gCalendarNetwork_RequestDelay.RequestQueue;
			end
			
			gGroupCalendar_Queue.RequestsDelay = vMinDelayForNextRequest;
			gGroupCalendar_Queue.RequestsElapsed = 0;
		end
	end
end

function CalendarNetwork_ProcessQueues(pElapsed)
	-- Get the current time stamp
	
	local	vCurrentTimeStamp = Calendar_GetCurrentLocalDateTimeStamp();

	-- Process tasks
	
	CalendarNetwork_ProcessTaskQueue(pElapsed);
	
	-- Update the collision detection counters
	
	gGroupCalendar_Queue.InboundTimeSinceLastMessage = gGroupCalendar_Queue.InboundTimeSinceLastMessage + pElapsed;
	gGroupCalendar_Queue.OutboundTimeSinceLastMessage = gGroupCalendar_Queue.OutboundTimeSinceLastMessage + pElapsed;
	
	-- Process inbound messages
	
	local	vSuppressRequests = false;
	
	if CalendarNetwork_ProcessInboundQueue(pElapsed, vCurrentTimeStamp) then
		vSuppressRequests = true;
	
	elseif CalendarNetwork_ProcessOutboundQueue(pElapsed) then
		vSuppressRequests = true;
	end
	
	-- Process pending requests if there are no outbound messages pending
	
	CalendarNetwork_ProcessRequestQueue(pElapsed, vSuppressRequests);
end

function CalendarNetwork_ProcessRequest(pRequest)
	local	vDatabase;
	
	if pRequest.mUserName then
		vDatabase = EventDatabase_GetDatabase(pRequest.mUserName, false);
	else
		vDatabase = nil;
	end
	
	if pRequest.mOpcode == "DB_UPD" then
		if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue
			CalendarNetwork_SendChanges(vDatabase.Changes, "DB", vDatabase.UserName, vDatabase.IsPlayerOwned, pRequest.mRevision);
		end

	elseif pRequest.mOpcode == "RAT_UPD" then
		if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue
			CalendarNetwork_SendChanges(vDatabase.RSVPs, "RAT", vDatabase.UserName, vDatabase.IsPlayerOwned, pRequest.mRevision);
		end

	elseif pRequest.mOpcode == "DB_NOU" then
		if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue
			CalendarNetwork_DBRevisionChanged(vDatabase);
		end

	elseif pRequest.mOpcode == "RAT_NOU" then
		if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue
			CalendarNetwork_RSVPRevisionChanged(vDatabase);
		end

	elseif pRequest.mOpcode == "DB_RFU" then
		local	vCurrentRevision;
		local	vAuthRevision;
		
		if vDatabase and vDatabase.Changes then
			vCurrentRevision = vDatabase.Changes.Revision;
			vAuthRevision = vDatabase.Changes.AuthRevision;
		else
			vCurrentRevision = 0;
			vAuthRevision = nil;
		end
		
		if vCurrentRevision < pRequest.mRevision
		or vCurrentRevision == 0 then
			CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..EventDatabase_GetDBRevisionPath(pRequest.mUserName, pRequest.mDatabaseID, vCurrentRevision, vAuthRevision).."RFU");
		end

	elseif pRequest.mOpcode == "RAT_RFU" then
		local	vCurrentRevision;
		local	vAuthRevision;
		
		if vDatabase and vDatabase.RSVPs then
			vCurrentRevision = vDatabase.RSVPs.Revision;
			vAuthRevision = vDatabase.RSVPs.AuthRevision;
		else
			vCurrentRevision = 0;
			vAuthRevision = nil;
		end
		
		if vCurrentRevision < pRequest.mRevision
		or vCurrentRevision == 0 then
			CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..EventDatabase_GetRSVPRevisionPath(pRequest.mUserName, pRequest.mDatabaseID, vCurrentRevision, vAuthRevision).."RFU");
		end
	
	elseif pRequest.mOpcode == "AUTOCONFIG" then
		CalendarNetwork_DoAutoConfig(pRequest.mCheckDatabaseTrust);
	
	elseif pRequest.mOpcode == "DBTRUST" then
		EventDatabase_CheckDatabaseTrust();
	
	elseif pRequest.mOpcode == "OWNEDNOTICES" then
		gGroupCalendar_EnableUpdates = true;
		gGroupCalendar_EnableSelfUpdates = true;
		CalendarNetwork_SendOwnedDatabaseUpdateNotices();
	end
end

function CalendarNetwork_Initialize()
	gGroupCalendar_Initialized = true;
	
	if gGroupCalendar_Settings.DebugInit then
		Calendar_DebugMessage("GroupCalendar Initializing: Starting initialization");
	end

	CalendarNetwork_PlayerGuildChanged();

	-- Go ahead and do manual channel configuration now, automatic
	-- config will be handled once the player guild gets set and
	-- the roster gets loaded

	if not gGroupCalendar_PlayerSettings.Channel.AutoConfig then
		if gGroupCalendar_Settings.DebugInit then
			Calendar_DebugMessage("GroupCalendar INIT: Starting up data channel (manual configuration)");
		end
		
		if gGroupCalendar_PlayerSettings.Channel.Name then
			CalendarNetwork_SetChannel(
					gGroupCalendar_PlayerSettings.Channel.Name,
					gGroupCalendar_PlayerSettings.Channel.Password);
		else
			CalendarNetwork_SetChannelStatus("Disconnected");
			Calendar_DebugMessage("GroupCalendar channel is not set");
		end
	end

	Calendar_GetPrimaryTradeskills();
end

function CalendarNetwork_SendNotices()
	-- If trust isn't available yet just defer the notices
	
	if not CalendarTrust_TrustCheckingAvailable() then
		gGroupCalendar_SendNoticesOnRosterUpdate = true;
		return;
	end
	
	--
	
	if gGroupCalendar_Settings.DebugInit then
		Calendar_DebugMessage("Sending notices");
	end

	CalendarNetwork_RequestAllDatabaseUpdates();

	-- Request databases for trusted players who haven't sent one yet

	if not gGroupCalendar_PlayerSettings.Security.TrustAnyone then
		CalendarNetwork_RequestMissingDatabases();
	end

	-- Schedule a request to send out owned database notices in two minutes

	CalendarNetwork_QueueUniqueOpcodeRequest({mOpcode = "OWNEDNOTICES"}, gCalendarNetwork_RequestDelay.OwnedNotices);
end

function CalendarNetwork_RequestAllDatabaseUpdates()
	-- Immediately send a notice of our version
	
	CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."VER:"..gGroupCalendar_VersionString);
	
	-- Immediately request updates to our own databases
	
	for vRealmName, vDatabase in gGroupCalendar_Database.Databases do
		if EventDatabase_DatabaseIsVisible(vDatabase)
		and vDatabase.IsPlayerOwned then
			CalendarNetwork_RequestUpdate(vDatabase, vDatabase.Changes, "DB", true);
			CalendarNetwork_RequestUpdate(vDatabase, vDatabase.RSVPs, "RAT", true);
		end
	end
	
	-- Request updates to all other databases after a delay
	
	CalendarNetwork_QueueTask(CalendarNetwork_RequestExternalDatabaseUpdates, nil, gCalendarNetwork_RequestDelay.ExternalUpdateRequest, "EXTERNALUPDATE");
end

function CalendarNetwork_RequestExternalDatabaseUpdates()
	if gGroupCalendar_PlayerSettings.Security.TrustAnyone then
		CalendarNetwork_RequestAllUpdate();

	elseif gGroupCalendar_PlayerSettings.Security.TrustGuildies
	and gGroupCalendar_PlayerGuild then
		CalendarNetwork_RequestGuildUpdate(gGroupCalendar_PlayerGuild, gGroupCalendar_PlayerSettings.Security.MinTrustedRank);
	end
	
	if not gGroupCalendar_PlayerSettings.Security.TrustAnyone then
		for vRealmName, vDatabase in gGroupCalendar_Database.Databases do
			if EventDatabase_DatabaseIsVisible(vDatabase) then
				if not vDatabase.IsPlayerOwned
				and (not gGroupCalendar_PlayerSettings.Security.TrustGuildies
				or vDatabase.Guild ~= gGroupCalendar_PlayerGuild) then
					CalendarNetwork_RequestUpdate(vDatabase, vDatabase.Changes, "DB", false);
					CalendarNetwork_RequestUpdate(vDatabase, vDatabase.RSVPs, "RAT", false);
				end
			end
		end
	end
end

function CalendarNetwork_SendOwnedDatabaseUpdateNotices()
	for vRealmName, vDatabase in gGroupCalendar_Database.Databases do
		if EventDatabase_DatabaseIsVisible(vDatabase)
		and vDatabase.IsPlayerOwned then
			if not CalendarChanges_IsEmpty(vDatabase.Changes) then
				CalendarNetwork_DBRevisionChanged(vDatabase);
			end
			
			if not CalendarChanges_IsEmpty(vDatabase.RSVPs) then
				CalendarNetwork_RSVPRevisionChanged(vDatabase);
			end
		end
	end
end

gCalendarNetwork_GuildMemberRankCache = nil;

function CalendarNetwork_FlushCaches()
	gCalendarNetwork_GuildMemberRankCache = nil;
	gCalendarNetwork_UserTrustCache = {};
end

function CalendarNetwork_GetGuildRosterCache()
	if gCalendarNetwork_GuildMemberRankCache then
		return gCalendarNetwork_GuildMemberRankCache;
	end
	
	-- Clear the cache
	
	gCalendarNetwork_GuildMemberRankCache = {};
	
	-- Scan the roster and collect the info
	
	local		vNumGuildMembers = GetNumGuildMembers(true);
	
	for vIndex = 1, vNumGuildMembers do
		local	vName, vRank, vRankIndex, vLevel, vClass, vZone, vNote, vOfficerNote, vOnline = GetGuildRosterInfo(vIndex);
		
		if vName then -- Have to check for name in case a guild member gets booted while querying the roster
			local	vMemberInfo =
			{
				Name = vName,
				RankIndex = vRankIndex,
				Level = vLevel,
				Class = vClass,
				Zone = vZone,
				OfficerNote = vOfficerNote,
				Online = vOnline
			};
			
			gCalendarNetwork_GuildMemberRankCache[strupper(vName)] = vMemberInfo;
		end
	end
	
	-- Dump any cached trust info
	
	gCalendarNetwork_UserTrustCache = {};
	
	return gCalendarNetwork_GuildMemberRankCache;
end

local	gGroupCalendar_SentLoadGuildRoster = false;

function CalendarNetwork_LoadGuildRosterTask()
	if IsInGuild() then
		GuildRoster();
	end
	
	-- Schedule another task to load the roster again
	-- in four minutes
	
	CalendarNetwork_QueueTask(
			CalendarNetwork_LoadGuildRosterTask, nil,
			240, "GUILDROSTER");
end

function CalendarNetwork_LoadGuildRoster()
	if not IsInGuild()
	or GetNumGuildMembers() > 0
	or gGroupCalendar_SentLoadGuildRoster then
		return;
	end
	
	gGroupCalendar_SentLoadGuildRoster = true;
	
	if gGroupCalendar_Settings.DebugInit then
		Calendar_DebugMessage("CalendarNetwork_LoadGuildRoster: Loading");
	end
	
	CalendarNetwork_LoadGuildRosterTask();
end

function CalendarNetwork_UserIsInSameGuild(pUserName)
	if not IsInGuild() then
		return false, nil;
	end
	
	-- Build the roster
	
	if GetNumGuildMembers() == 0 then
		CalendarNetwork_LoadGuildRoster();
		return false, nil; -- have to return false for now since we don't really know
	end
	
	-- Search for the member
	
	local		vUpperUserName = strupper(pUserName);
	local		vRosterCache = CalendarNetwork_GetGuildRosterCache();
	local		vMemberInfo = vRosterCache[vUpperUserName];
	
	if not vMemberInfo then
		return false, nil;
	end
	
	return true, vMemberInfo.RankIndex;
end

function CalendarNetwork_SendAllRevisionNotices()
	-- Use the same delay for all the requests so that the requests will all go out together
	-- once they start getting processed
	
	local	vDelay = gCalendarNetwork_RequestDelay.ProxyNOUMin + math.random() * gCalendarNetwork_RequestDelay.ProxyNOURange;
	local	vOwnedDelay = math.random() * gCalendarNetwork_RequestDelay.OwnedNOURange;
	
	for vRealmName, vDatabase in gGroupCalendar_Database.Databases do
		if EventDatabase_DatabaseIsVisible(vDatabase) then
			local		vRequest = {mOpcode = "DB_NOU", mUserName = vDatabase.UserName};
			
			if vDatabase.IsPlayerOwned then
				CalendarNetwork_QueueUniqueUserRequest(vRequest, vOwnedDelay);
			else
				CalendarNetwork_QueueUniqueUserRequest(vRequest, vDelay);
			end
			
			local		vRequest = {mOpcode = "RAT_NOU", mUserName = vDatabase.UserName};
			
			if vDatabase.IsPlayerOwned then
				CalendarNetwork_QueueUniqueUserRequest(vRequest, vOwnedDelay);
			else
				CalendarNetwork_QueueUniqueUserRequest(vRequest, vDelay);
			end
		end
	end
end

function CalendarNetwork_CancelRedundantUPDRequests(pChanges, pDatabaseTag, pUserName, pDatabaseID, pRevision, pSinceRevision)
	local	vOpcode = pDatabaseTag.."_UPD";
	
	for vPriority, vRequests in gGroupCalendar_Queue.Requests do
		for vIndex, vRequest in vRequests do
			if vRequest.mOpcode == vOpcode
			and vRequest.mUserName == pUserName then
				-- Theirs is better if:
				--   - Their database ID is higher
				--   - Their database ID is the same and
				--         - Their fromRevision is lower or the same
				--         - Their toRevision is higher or the same
				
				if pDatabaseID > vRequest.mDatabaseID
				or (pDatabaseID == vRequest.mDatabaseID
				and pSinceRevision <= vRequest.mRevision
				and (not pChanges or pRevision  >= pChanges.Revision)) then
					if gGroupCalendar_Settings.DebugQueues then
						Calendar_DebugMessage("Removing UPD request for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pSinceRevision.." to "..pRevision);
					end
					
					table.remove(vRequests, vIndex);
				else
					if gGroupCalendar_Settings.DebugQueues then
						Calendar_DebugMessage("Keeping UPD request for "..pDatabaseTag.." "..pUserName..","..vRequest.mDatabaseID..","..vRequest.mRevision.." to "..pChanges.Revision);
						Calendar_DebugMessage("Better thean "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pSinceRevision.." to "..pRevision);
					end
				end
				
				return;
			end
		end -- for vIndex
	end -- for vPriority
end

function CalendarNetwork_CancelRedundantNOURequests(pChanges, pDatabaseTag, pSender, pUserName, pDatabaseID, pRevision)
	local	vOpcode = pDatabaseTag.."_NOU";
	
	for vPriority, vRequests in gGroupCalendar_Queue.Requests do
		for vIndex, vRequest in vRequests do
			if vRequest.mOpcode == vOpcode
			and vRequest.mUserName == pUserName then
				-- Cancel our update if the revision they're advertising is equal
				-- to or better than ours or if they're the owner
				
				local		vTheirsIsBetter;
				
				if pSender == pUserName then
					vTheirsIsBetter = true;
				else
					vTheirsIsBetter = not pChanges
										or pDatabaseID > pChanges.ID
										or (pDatabaseID == pChanges.ID and pRevision >= pChanges.Revision);
				end
				
				if vTheirsIsBetter then
					if gGroupCalendar_Settings.DebugQueues then
						Calendar_DebugMessage("Removing NOU request for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pRevision);
					end
					
					table.remove(vRequests, vIndex);
				else
					if gGroupCalendar_Settings.DebugQueues then
						Calendar_DebugMessage("Keeping NOU request for "..pDatabaseTag.." "..pUserName..","..pChanges.ID..","..pChanges.Revision);
						Calendar_DebugMessage("Better than "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pRevision);
					end
				end
				
				return;
			end
		end -- for vIndex
	end -- for vPriority
end

function CalendarNetwork_SendMessage(pMessage)
	-- Just leave if there's no channel to communicate on
	
	if not gGroupCalendar_Channel.ID then
		return;
	end
	
	local	vSavedAutoClearAFK = GetCVar("autoClearAFK");
	SetCVar("autoClearAFK", 0);
	
	SendChatMessage(Calendar_EscapeChatString(pMessage), "CHANNEL", nil, gGroupCalendar_Channel.ID);
	
	SetCVar("autoClearAFK", vSavedAutoClearAFK);
end

function CalendarNetwork_ChannelMessageReceived(pSender, pMessage)
	local	vTrustLevel = CalendarTrust_GetUserTrustLevel(pSender);
	
	if vTrustLevel  > 0 then
		CalendarNetwork_QueueInboundMessage(pSender, vTrustLevel, pMessage);
	else
		if gGroupCalendar_Settings.DebugTrust then
			Calendar_DebugMessage("ChannelMessageReceived: "..pSender.." is not trusted");
		end
	end
end

function CalendarNetwork_GetGuildMemberIndex(pPlayerName)
	local		vUpperUserName = strupper(pPlayerName);
	local		vNumGuildMembers = GetNumGuildMembers(true);
	
	for vIndex = 1, vNumGuildMembers do
		local	vName = GetGuildRosterInfo(vIndex);
		
		if strupper(vName) == vUpperUserName then
			return vIndex;
		end
	end
	
	return nil;
end

function CalendarNetwork_SetAutoConfigData(pPlayerName)
	if not CanEditPublicNote()
	or not gGroupCalendar_PlayerSettings.Channel.Name then
		return false;
	end
	
	local		vMemberIndex = CalendarNetwork_GetGuildMemberIndex(pPlayerName);
	
	if not vMemberIndex then
		return false;
	end
	
	CalendarNetwork_RemoveAllAutoConfigData(pPlayerName);
	
	local		vNote = "["..gGroupCalendar_MessagePrefix.."C:"..gGroupCalendar_PlayerSettings.Channel.Name;
	
	if gGroupCalendar_PlayerSettings.Channel.Password then
		vNote = vNote..","..gGroupCalendar_PlayerSettings.Channel.Password
	end
	
	-- Add the trust group
	
	local		vTrustGroup = CalendarTrust_GetCurrentTrustGroup();
	
	vNote = vNote.."/T:"..vTrustGroup;
	
	-- Add the trust rank
	
	if gGroupCalendar_PlayerSettings.Security.TrustGuildies
	and gGroupCalendar_PlayerSettings.Security.MinTrustedRank then
		vNote = vNote..","..gGroupCalendar_PlayerSettings.Security.MinTrustedRank;
	end
	
	vNote = vNote.."]";
	
	if gGroupCalendar_Settings.DebugConfig then
		Calendar_DebugMessage("Setting auto-config data on player "..pPlayerName);
	end
	
	GuildRosterSetPublicNote(vMemberIndex, vNote);
	
	return true;
end

function CalendarNetwork_RemoveAllAutoConfigData(pExcludePlayerName)
	-- Remove it from the GuildInfoText
	
	local	vGuildInfoText = GetGuildInfoText();
	local	vStartIndex, vEndIndex = string.find(vGuildInfoText, "%["..gGroupCalendar_MessagePrefix.."[^%]]+%]");
	
	if vStartIndex then
		vGuildInfoText = string.sub(vGuildInfoText, 1, vStartIndex - 1)..string.sub(vGuildInfoText, vEndIndex + 1);
		SetGuildInfoText(vGuildInfoText);
	end
	
	-- Remove it from player notes
	
	local	vIndex = CalendarNetwork_FindAutoConfigData(nil, pExcludePlayerName);
	
	while vIndex do
		CalendarNetwork_RemoveAutoConfigData(vIndex);
		vIndex = CalendarNetwork_FindAutoConfigData(vIndex + 1, pExcludePlayerName);
	end
end

function CalendarNetwork_RemoveAutoConfigData(pMemberIndex)
	if not pMemberIndex then
		Calendar_DebugMessage("CalendarNetwork_RemoveAutoConfigData: nil index");
		return;
	end
	
	local	vName, vNote = CalendarNetwork_GetGuildPublicNote(pMemberIndex);
	
	if not vNote then
		return false;
	end
	
	if gGroupCalendar_Settings.DebugConfig then
		Calendar_DebugMessage("Removing auto-config data from player "..vName);
	end
	
	local	vPatternString = "%["..gGroupCalendar_MessagePrefix0.."[^%]]+%]";
	
	vNote = string.gsub(vNote, vPatternString, "");
	
	GuildRosterSetPublicNote(pMemberIndex, vNote);
	
	return true;
end

function CalendarNetwork_GetGuildPublicNote(pMemberIndex)
	local	vName, vRank, vRankIndex, vLevel, vClass, vZone, vNote, vOfficerNote, vOnline = GetGuildRosterInfo(pMemberIndex);
	
	return vName, vNote;
end

function CalendarNetwork_GetAutoConfigData()
	local	vGuildInfoText = GetGuildInfoText();
	local	vConfigSource = GroupCalendar_cGuildInfoConfigSource;
	
	local	vStartIndex, vEndIndex, vConfigString = string.find(vGuildInfoText, "%[("..gGroupCalendar_MessagePrefix.."[^%]]+)%]");
	
	if not vStartIndex then
		local	vIndex = CalendarNetwork_FindAutoConfigData();
		
		if not vIndex then
			return nil;
		end
		
		local	vName, vNote = CalendarNetwork_GetGuildPublicNote(vIndex);
		
		if not vNote then
			return nil;
		end
		
		vStartIndex, vEndIndex, vConfigString = string.find(vNote, "%[("..gGroupCalendar_MessagePrefix.."[^%]]+)%]");
		
		if not vStartIndex then
			return nil;
		end
		
		vConfigSource = vName;
	end
	
	return CalendarNetwork_ParseCommandString(vConfigString), vConfigSource;
end

function CalendarNetwork_FindAutoConfigData(pStartingIndex, pExcludePlayerName)
	-- Build the roster
	
	if GetNumGuildMembers() == 0 then
		CalendarNetwork_LoadGuildRoster();
		return nil;
	end
	
	-- Search for the member
	
	local		vNumGuildMembers = GetNumGuildMembers(true);
	local		vStartingIndex;
	
	if pStartingIndex then
		vStartingIndex = pStartingIndex;
	else
		vStartingIndex = 1;
	end
	
	for vIndex = vStartingIndex, vNumGuildMembers do
		local	vName, vNote = CalendarNetwork_GetGuildPublicNote(vIndex);
		
		if vNote
		and (not pExcludePlayerName or string.lower(vName) ~= string.lower(pExcludePlayerName))
		and strfind(vNote, "%["..gGroupCalendar_MessagePrefix0) then
			if gGroupCalendar_Settings.DebugConfig then
				Calendar_DebugMessage("Found auto-config data on player "..vName);
			end
			
			return vIndex, vName;
		end
	end
	
	return nil;
end

function CalendarNetwork_ScheduleCheckDatabaseTrust(pDelay)
	CalendarNetwork_QueueUniqueOpcodeRequest({mOpcode = "DBTRUST"}, pDelay);
end

function CalendarNetwork_ScheduleAutoConfig(pDelay, pCheckDatabaseTrust)
	if gGroupCalendar_Channel.Disconnected then
		return;
	end
	
	local	vRequest = CalendarNetwork_FindRequest({mOpcode = "AUTOCONFIG"});
	
	if vRequest then
		if gGroupCalendar_Settings.DebugInit then
			Calendar_DebugMessage("CalendarNetwork_ScheduleAutoConfig: Rescheduling existing request");
		end
		
		if pDelay < vRequest.mDelay then
			vRequest.mDelay = pDelay;
		end
		
		if pCheckDatabaseTrust then
			vRequest.mCheckDatabaseTrust = true;
		end
	else
		if gGroupCalendar_Settings.DebugInit then
			Calendar_DebugMessage("CalendarNetwork_ScheduleAutoConfig: Scheduling request");
		end
		
		CalendarNetwork_QueueRequest({mOpcode = "AUTOCONFIG", mCheckDatabaseTrust = pCheckDatabaseTrust}, pDelay);
	end
end

function CalendarNetwork_DoAutoConfig(pCheckDatabaseTrust)
	if gGroupCalendar_Settings.DebugInit then
		Calendar_DebugMessage("CalendarNetwork_DoAutoConfig: Performing auto-config");
	end
	
	local	vCommand, vConfigSource = CalendarNetwork_GetAutoConfigData();
	
	if not vCommand then
		CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cAutoConfigNotFound);
		return false;
	end
	
	local		vChannel = nil;
	local		vPassword = nil;
	local		vMinTrustedRank = nil;
	local		vTrustGroup = nil;
	
	while vCommand[1] ~= nil do
		local	vOpcode = vCommand[1].opcode;
		local	vOperands = vCommand[1].operands;
		
		table.remove(vCommand, 1);
		
		if vOpcode == "CHN"
		or vOpcode == "C" then
			vChannel = vOperands[1];
			vPassword = vOperands[2];
			
		elseif vOpcode == "RNK" then
			vMinTrustedRank = tonumber(vOperands[1]);
		elseif vOpcode == "T" then
			vTrustGroup = tonumber(vOperands[1]);
			vMinTrustedRank = tonumber(vOperands[2]);
		end
	end
	
	local	vAutoPlayerChanged = gGroupCalendar_Channel.AutoPlayer ~= vConfigSource;
	
	gGroupCalendar_Channel.AutoPlayer = vConfigSource;
	
	if vAutoPlayerChanged then
		GroupCalendar_ChannelChanged(); -- Send out a change notice just so the calendar knows the player changed
	end
	
	CalendarNetwork_SetChannel(vChannel, vPassword);
	
	-- Update the trust settings
	
	local	vTrustChanged = false;
	local	vCurrentTrustGroup = CalendarTrust_GetCurrentTrustGroup();
	
	if vTrustGroup ~= nil then
		if vCurrentTrustGroup ~= vTrustGroup then
			vTrustChanged = true;
		end
	else
		vTrustGroup = vCurrentTrustGroup;
	end
	
	if vMinTrustedRank ~= nil then
		if gGroupCalendar_PlayerSettings.Security.MinTrustedRank ~= vMinTrustedRank then
			vTrustChanged = true;
		end
	else
		vMinTrustedRank = gGroupCalendar_PlayerSettings.Security.MinTrustedRank;
	end
	
	if vTrustChanged then
		CalendarTrust_SetCurrentTrustGroup(vTrustGroup, vMinTrustedRank);
	elseif pCheckDatabaseTrust then
		EventDatabase_CheckDatabaseTrust();
	end
	
	return true;
end

function CalendarTrust_GetCurrentTrustGroup()
	if gGroupCalendar_PlayerSettings.Security.TrustAnyone then
		return 2;
	elseif gGroupCalendar_PlayerSettings.Security.TrustGuildies then
		return 1;
	else
		return 0;
	end
end

function CalendarTrust_SetCurrentTrustGroup(pTrustGroup, pMinRank)
	if pTrustGroup == 2 then
		gGroupCalendar_PlayerSettings.Security.TrustAnyone = true;
		gGroupCalendar_PlayerSettings.Security.TrustGuildies = false;
	elseif pTrustGroup == 1 then
		gGroupCalendar_PlayerSettings.Security.TrustAnyone = false;
		gGroupCalendar_PlayerSettings.Security.TrustGuildies = true;
	else
		gGroupCalendar_PlayerSettings.Security.TrustAnyone = false;
		gGroupCalendar_PlayerSettings.Security.TrustGuildies = false;
	end
	
	gGroupCalendar_PlayerSettings.Security.MinTrustedRank =  pMinRank;
	
	CalendarTrust_TrustSettingsChanged();
end

function CalendarTrust_TrustSettingsChanged()
	CalendarNetwork_FlushCaches();
	
	EventDatabase_CheckDatabaseTrust(); -- Delete databases owned by players we no longer trust
	CalendarNetwork_RequestMissingDatabases(); -- Request databases for newly trusted players
	
	if gGroupCalendar_PlayerSettings.Security.TrustAnyone then
		CalendarNetwork_RequestAllUpdate();
	else
		if gGroupCalendar_PlayerSettings.Security.TrustGuildies
		and gGroupCalendar_PlayerGuild then
			CalendarNetwork_RequestGuildUpdate(gGroupCalendar_PlayerGuild, gGroupCalendar_PlayerSettings.Security.MinTrustedRank);
		end
	end
	
	CalendarNetwork_SendAllRevisionNotices(); -- Send out revision notices since trusted players may want to know now
end

function CalendarTrust_TrustCheckingAvailable()
	if not gGroupCalendar_PlayerSettings.Security.TrustGuildies then
		return true;
	end
	
	-- Doesn't matter if they're not in a guild
	
	if not IsInGuild() then
		return true;
	end

	-- If trust is guild members only then verify that the roster has been loaded
	
	return GetNumGuildMembers() > 0;
end

function CalendarTrust_GetUserTrustLevel(pUserName)
	local	vUserTrustInfo = gCalendarNetwork_UserTrustCache[pUserName];
	
	if not vUserTrustInfo then
		vUserTrustInfo = {};
		vUserTrustInfo.mTrustLevel = CalendarTrust_CalcUserTrust(pUserName);
		gCalendarNetwork_UserTrustCache[pUserName] = vUserTrustInfo;
	end
	
	return vUserTrustInfo.mTrustLevel;
end

function CalendarTrust_UserIsTrusted(pUserName)
	return CalendarTrust_GetUserTrustLevel(pUserName) == 2;
end

function CalendarTrust_UserIsTrustedForRSVPs(pUserName)
	return CalendarTrust_GetUserTrustLevel(pUserName) >= 1;
end

function CalendarTrust_CalcUserTrust(pUserName)
	-- If the user is one of our own characters, then trust them completely
	
	local	vDatabase = EventDatabase_GetDatabase(pUserName, false);
	
	if vDatabase
	and vDatabase.IsPlayerOwned then
		if gGroupCalendar_Settings.DebugTrust then
			Calendar_DebugMessage("CalendarTrust_CalcUserTrust: Implicit trust for "..pUserName);
		end
		
		return 2;
	end

	local	vPlayerSecurity = gGroupCalendar_PlayerSettings.Security.Player[pUserName];
	
	-- See if they're explicity allowed/forbidden
	
	if vPlayerSecurity ~= nil then
		if vPlayerSecurity == 1 then
			-- Trusted
			
			if gGroupCalendar_Settings.DebugTrust then
				Calendar_DebugMessage("CalendarTrust_CalcUserTrust: Explicit trust for "..pUserName);
			end
			
			return 2;
		elseif vPlayerSecurity == 2 then
			-- Excluded
			
			if gGroupCalendar_Settings.DebugTrust then
				Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." explicity excluded");
			end
			
			return 0;
		else
			Calendar_DebugMessage("GroupCalendar: Unknown player security setting of "..vPlayerSecurity.." for "..pUserName);
		end
	end
	
	-- Return true if we'll allow anyone in the channel
	
	if gGroupCalendar_PlayerSettings.Security.TrustAnyone then
		if gGroupCalendar_Settings.DebugTrust then
			Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." trusted (all trusted)");
		end
		
		return 2;
	end
	
	-- Return true if they're in the same guild and of sufficient rank
	
	if gGroupCalendar_PlayerSettings.Security.TrustGuildies then
		local	vIsInGuild, vGuildRank = CalendarNetwork_UserIsInSameGuild(pUserName);
		
		if vIsInGuild then
			if not gGroupCalendar_PlayerSettings.Security.MinTrustedRank
			or vGuildRank <= gGroupCalendar_PlayerSettings.Security.MinTrustedRank then
				if gGroupCalendar_Settings.DebugTrust then
					Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." trusted (guild member)");
				end
				
				return 2;
			else
				if gGroupCalendar_Settings.DebugTrust then
					Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." partially trusted (guild member)");
				end
				
				return 1;
			end
		end
	end
	
	-- Failed all tests
		
	if gGroupCalendar_Settings.DebugTrust then
		Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." not trusted (all tests failed)");
	end
	
	return 0;
end

function CalendarTrust_GetNumTrustedPlayers(pTrustSetting)
	local	vNumPlayers = 0;
	
	for vPlayerName, vPlayerSecurity in gGroupCalendar_PlayerSettings.Security.Player do
		if vPlayerSecurity == pTrustSetting then
			vNumPlayers = vNumPlayers + 1;
		end
	end
	
	return vNumPlayers;
end

function CalendarTrust_GetIndexedTrustedPlayers(pTrustSetting, pIndex)
	local	vPlayerIndex = 1;
	
	for vPlayerName, vPlayerSecurity in gGroupCalendar_PlayerSettings.Security.Player do
		if vPlayerSecurity == pTrustSetting then
			if vPlayerIndex == pIndex then
				return vPlayerName;
			end
			
			vPlayerIndex = vPlayerIndex + 1;
		end
	end
	
	return nil;
end

function CalendarNetwork_RequestMissingDatabases()
	-- For each player we explicitly trust, see if there's a database for
	-- them yet.  If not, request one
	
	for vPlayerName, vPlayerSecurity in gGroupCalendar_PlayerSettings.Security.Player do
		if vPlayerSecurity == 1 then
			-- Found a trusted player, see if they have a database
			
			local	vDatabase = EventDatabase_GetDatabase(vPlayerName, false);
			
			if not vDatabase then
				CalendarNetwork_QueueRFURequest(vPlayerName, "DB", 0, 0);
				CalendarNetwork_QueueRFURequest(vPlayerName, "RAT", 0, 0);
			end
		end
	end
	
	return nil;
end

function CalendarNetwork_QueueRFURequest(pUserName, pDatabaseTag, pDatabaseID, pRevision)
	if CalendarNetwork_CancelRedundantRFURequest(pDatabaseTag, pUserName, pDatabaseID, pRevision) then
		return;
	end
	
	local	vRequest =
	{
		mOpcode = pDatabaseTag.."_RFU",
		mUserName = pUserName,
		mDatabaseID = pDatabaseID,
		mRevision = pRevision,
	}
	
	CalendarNetwork_QueueRequest(vRequest, gCalendarNetwork_RequestDelay.RFUMin + math.random() * gCalendarNetwork_RequestDelay.RFURange);
end

function CalendarNetwork_PlayerGuildChanged()
	-- Update the guild in the database
	
	if gGroupCalendar_UserDatabase then
		gGroupCalendar_UserDatabase.Guild = gGroupCalendar_PlayerGuild;
	end
	
	-- Just return if we're not initialized yet
	
	if not gGroupCalendar_Initialized then
		EventDatabase_UpdateGuildRankCache();
		return;
	end
	
	-- Clear the roster load flag
	
	gGroupCalendar_SentLoadGuildRoster = false;
	
	CalendarNetwork_FlushCaches();
	
	-- If the player is unguilded then simply leave the data
	-- channel if it was auto-configured, flush any databases
	-- which are no longer trusted and exit
	
	if not IsInGuild() then
		if gGroupCalendar_Settings.DebugInit then
			Calendar_DebugMessage("PlayerGuildChanged: Player is now unguilded");
		end
		
		if gGroupCalendar_PlayerSettings.Channel.AutoConfig then
			CalendarNetwork_LeaveChannel();
		end
		
		EventDatabase_CheckDatabaseTrust();
		
		return;
	end
	
	-- The player is in a new guild or has changed guilds, so
	-- schedule a roster update if necessary
	
	if GetNumGuildMembers() > 0 then
		if gGroupCalendar_Settings.DebugInit then
			Calendar_DebugMessage("PlayerGuildChanged: Roster is already loaded, calling GuildRosterChanged()");
		end
		
		CalendarNetwork_GuildRosterChanged();
	end
	
	-- Force the roster to reload or to start loading
	
	CalendarNetwork_LoadGuildRosterTask();
end

function CalendarNetwork_GuildRosterChanged()
	if gGroupCalendar_Settings.DebugInit then
		Calendar_DebugMessage("CalendarNetwork_GuildRosterChanged: Checking changes");
	end
	
	EventDatabase_UpdateGuildRankCache();
	
	CalendarNetwork_FlushCaches();

	if gGroupCalendar_PlayerSettings.Channel.AutoConfig then
		CalendarNetwork_ScheduleAutoConfig(gCalendarNetwork_RequestDelay.GuildUpdateAutoConfig, true);
	else
		if gGroupCalendar_Settings.DebugInit then
			Calendar_DebugMessage("PlayerGuildChanged: Channel is set for manual config, verifying database trust");
		end
		
		CalendarNetwork_ScheduleCheckDatabaseTrust(5);
	end
	
	-- Start sending notices now if we were waiting for a roster update
	
	if gGroupCalendar_SendNoticesOnRosterUpdate then
		gGroupCalendar_SendNoticesOnRosterUpdate = nil;
		CalendarNetwork_SendNotices();
	end
end

function CalendarNetwork_CheckPlayerGuild()
	local	vPlayerGuild;

	if IsInGuild() then
		vPlayerGuild, _, gGroupCalendar_PlayerGuildRank = GetGuildInfo("player");
		
		-- Just return if the server is lagging and the guild info
		-- isn't available yet
		
		if not vPlayerGuild then
			return;
		end
	else
		vPlayerGuild = nil;
		gGroupCalendar_PlayerGuildRank = nil;
	end

	if gGroupCalendar_PlayerGuild ~= vPlayerGuild then
		gGroupCalendar_PlayerGuild = vPlayerGuild;
		
		CalendarNetwork_PlayerGuildChanged();
	end
end
