--[[
--
--	Party Quests
--		By Marek Gorecki
--
--		Recode by
--			Alexander of Blackrock-US
--
--		Converted to Objects by
--			Miravlix
--
--		Converted to Telepathy by
--			AnduinLothar
--
--	"Secret Quests are no fun. Secret Quests are for everyone!"
--
--	Shares party member quests. 
--
--	$Author: legorol $
--	$Rev: 4365 $
--	$Date: 2006-12-05 09:52:08 -0600 (Tue, 05 Dec 2006) $
--
--      Recent Changelog (fixed)
--	All Quest heading collapsed status isn't saved.
--      Not updating Common QuestLog after requesting objective update.
--	Level display for party/common is missing
--
--      2006-03-02
--      When in a party tooltip contains who else is on quest.
--      Collapsed/Expanded state for party members doesn't stick
--	With QuestLog open clicking from one section (PartyMember, myQuests, common) to another closes questlog.
--      Storing pr. session Expanded/Collapsed state of Party members lists.
--      ShowLevel in PartyMembers quest list.
--
--      2006-05-26
--	Doesn't remove party members after they leave the team.
--
--      Issues
--	Tooltips not showing for common.
--      Refreshing the DetailView after requesting objective details doesn't always work.
--	showLevel on common hides (0/1) status
--	Requesting Objective on quest without it.
--	Doesn't update the timer for self quests. - prolly wont be fixed, to much timing issues in PQ already to add another OnUpdate system to refresh the frame with new data.
--      Reporting Party Completed, when one member isn't and one is.
--	Adding CRCLib to give a more unique identifier for identifying quests.
--]]

local PARTYQUESTS_VERSION = 4.0

-- Debug
PARTYQUESTS_DEBUG_MESSAGES = false;
PARTYQUESTS_DEBUG = "PARTYQUESTS_DEBUG_MESSAGES";

PARTYQUESTS_DEBUG_IO = false;
PARTYQUESTS_DEBUG_I = "PARTYQUESTS_DEBUG_IO";

-- Constants

PartyQuests = {}
PartyQuests.Version = PARTYQUESTS_VERSION;
PartyQuests.Frame = {}
PartyQuests.Startup = {}
PartyQuests.Register = {}

PartyQuests.Telepathy = {}
PartyQuests.Libram = {}

PartyQuests.Self = {}
PartyQuests.myQuests = {}
PartyQuests.Player = {}
PartyQuests.Common = {}
PartyQuests.Ally = {}
PartyQuests.Party = {}

PartyQuests.Team = {}
PartyQuests.Team.num = 0
for i = 1, 4 do
	PartyQuests.Team[i] = ""
end

local PARTYQUESTS_BLOCK_UPDATES_OLDER_THAN = 180;	-- Seconds
local PARTYQUESTS_LOG_SEND_COOLDOWN = 60;		-- Send broadcasts no more than once a minute
local PARTYQUESTS_NEED_LOGS_DELAY = 3;			-- Seconds
local PARTYQUESTS_LOG_EXPIRATION = 300;			-- Seconds
local PARTYQUESTS_WAITING_UPDATE_DELAY = 1;		-- Seconds
local PARTYQUESTS_WAITING_FAILURE_DELAY = 70;		-- Seconds
local PARTYQUESTS_GUI_REFRESH_DELAY = .1;		-- Seconds
local PARTYQUESTS_TOOLTIP_MAX_CHAR_WIDTH = 32;		-- Characters

-- Colors
local PARTYQUESTS_BANNER_UPDATE_COLOR = {r=1,g=1,b=0};	-- Yellow
local PARTYQUESTS_BANNER_COMPLETE_COLOR = {r=0,g=1,b=0};-- Green
local PARTYQUESTS_BANNER_FAILED_COLOR = {r=1,g=0,b=0};	-- Red

-- Command Constants

-- Broadcasts
local PARTYQUESTS_ANNOUNCE = "ANN_";
local PARTYQUESTS_ANNOUNCE_PROGRESS = "ANN_PROG";
local PARTYQUESTS_ANNOUNCE_COMPLETE = "ANN_COMP";
local PARTYQUESTS_ANNOUNCE_FAILED = "ANN_FAIL";

-- Requests
local PARTYQUESTS_REQUEST = "REQ_";
local PARTYQUESTS_REQUEST_NEEDALL = "REQ_NEEDALL";
local PARTYQUESTS_REQUEST_NEED    = "REQ_NEEDLOGS";
local PARTYQUESTS_REQUEST_STATUS  = "REQ_STATUS";
local PARTYQUESTS_REQUEST_DETAILS = "REQ_DETAILS";
local PARTYQUESTS_REQUEST_SHARE = "REQ_SHARE";

-- Recording
local PARTYQUESTS_RECORD = "REC_";
local PARTYQUESTS_RECORD_DETAILS = "REC_DETAILS";
local PARTYQUESTS_RECORD_LOG     = "REC_LOG";
local PARTYQUESTS_RECORD_STATUS  = "REC_STATUS";
local PARTYQUESTS_RECORD_DELETE  = "REC_DELETE";
local PARTYQUESTS_RECORD_ADD	   = "REC_ADD";

-- Negative Acknowledgement
local PARTYQUESTS_NACK = "NAK_";
local PARTYQUESTS_NACK_STATUS   = "NAK_STATUS";
local PARTYQUESTS_NACK_DETAILS  = "NAK_DETAILS";

-- Data Constants
local PARTYQUESTS_NOSUCHQUEST = "NOSUCHQUEST"; 

-- States
local PARTYQUESTS_WAITING = "WAITING";
local PARTYQUESTS_FAILED = "FAILED";

local PARTYQUESTS_TELEPATHY_ID = "PartyQuests";

-- Quests
PartyQuests.PartyQuests = {};
PartyQuests.PartyQuestsCache = {}; -- [PlayerName][QuestID]

-- Global Settings for all chars
-- PartyQuests_Settings = {
--	indentOff		bolean	- Remove quest title indentation
--	showCompletedBanner	bolean	- On screen notice on completing quests
--	showFailedBanner	bolean	- On screen notice on failed quests
--	showDetailedTooltip	bolean	- Detailed tooltip with quest objective
--
--	self			table
--		sortMethod		string	- Sorty by "zone", "level" or "title"
--		showLevel		bolean	- [55] indicator
--		showShared		bolean	- Show if other party members is on quest
--		zonesOn			bolean	- Zone headers in self list
--
--	myQuest			table
--		sortMethod		string	- Sorty by "zone", "level" or "title"
--		showLevel		bolean	- [55] indicator
--		showShared		bolean	- Show if other party members is on quest
--		zonesOn			bolean	- Zone headers in self list
--
--	common			table
--		sortMethod		string	- Sorty by "zone", "level" or "title"
--		showLevel		bolean	- [55] indicator
--		showShared		bolean	- Show if other party members is on quest
--		zonesOn			bolean	- Zone headers in self list
--
--	party			table
--		sortMethod		string	- Sorty by "zone", "level" or "title"
--		showLevel		bolean	- [55] indicator
--		zonesOn			bolean	- Zone headers in self list
--
-- }
function PartyQuests.SetSettingsToDefault()
	PartyQuests_Settings		= {indentOff=true; showCompletedBanner=true; showFailedBanner=true; showDetailedTooltip=true;};
	PartyQuests_Settings.self	= {sortMethod="zone";   showLevel=true;   showShared=true; zonesOn=true;  };
	PartyQuests_Settings.myQuests	= {sortMethod="level";  showLevel=true;   showShared=true; zonesOn=true;  };
	PartyQuests_Settings.common	= {sortMethod="title";  showLevel=false; showShared=true; zonesOn=false; };
	PartyQuests_Settings.party	= {sortMethod="zone";   showLevel=true;   zonesOn=true;  };
end

-- Character tree collapsed/expanded state.
-- PartyQuests_CharSettings = {			- Settings for each char
--      self
--	        collapsed		bolean	- Show self collapsed
--	        [zone]			table	- Zone names
--		        collapsed	bolean	- Show zone collapsed
--
--	myQuest				table	- Self collapsed table
--		collapsed		bolean	- Show self collapsed
--		[zone]			table	- Zone names
--			collapsed	bolean	- Show zone collapsed
--
--	common				table	- Settings for Common view
--		collapsed		bolean	- Show common collapsed
--
--	party				table	- Party View settings
--		collapsed		bolean	- Show common collapsed
--
--
-- }
function PartyQuests.SetCharSettingsToDefault()
	PartyQuests_CharSettings = {}
	PartyQuests_CharSettings.self		= { collapsed = false; }
	PartyQuests_CharSettings.self.AllQuests = { collapsed = false; }
	PartyQuests_CharSettings.myQuests	= { collapsed = true; }
	PartyQuests_CharSettings.common		= { collapsed = false; }
	PartyQuests_CharSettings.party		= { collapsed = true; }
end

-- Party member tree collapsed/expanded state - Not saved between sessions.
-- PartyQuests_TempSettings = {
--	common
--		[zone]			table	- Zone names
--			collapsed	bolean	- Show zone collapsed
--
--	[partymember]
--		collapsed		bolean	- Show self collapsed
--		[zone]			table	- Zone names
--			collapsed	bolean	- Show zone collapsed
--
-- }
PartyQuests_TempSettings = {};
PartyQuests_TempSettings.common = {};
PartyQuests_TempSettings.common.waitCount = 0;
PartyQuests_TempSettings.party = {};

-- Strings
PartyQuests.SettingsString = {
	["self"]	=  PARTYQUESTS_SETTING_SELF;
	["myQuests"]	=  PARTYQUESTS_SETTING_MYQUESTS;
	["common"]	=  PARTYQUESTS_SETTING_COMMON;
	["party"]	=  PARTYQUESTS_SETTING_GROUP;
};

-- Stored Gui
PartyQuests.CurrentQuest = nil;

-- Please don't ask
PartyQuests.Cooldown = 0;

-- Current log log
PartyQuests.MyCurrentLog = {};

--
--[[ OnLoad Handler ]]--
--
function PartyQuests.Frame.OnLoad()
	this:RegisterEvent("VARIABLES_LOADED");

	-- Add ourself to the standard windows
	UIPanelWindows[this:GetName()] = { area = "left", pushable = 5 };

	--
	-- Set the frame values
	-- 
	PartyQuestsFrame.display = "self";
	PartyQuestsFrameTree.tooltipPlacement = "frame";
	--PartyQuestsFrameTree.tooltipAnchor = "ANCHOR_TOPRIGHT";
	PartyQuestsFrameTree.tooltipAnchor = "ANCHOR_RIGHT";

	--
	-- onEvent
	--
	this:SetScript("OnEvent", PartyQuests.Frame.OnEvent);
	this:SetScript("OnShow", PartyQuests.Frame.OnShow);
	this:SetScript("OnHide", PartyQuests.Frame.OnHide);
end

--[[ Initialize ]]--
function PartyQuests.Frame.Initialize()
	PartyQuestsFrameTreeExpand:SetPoint("TOPLEFT", PartyQuestsFrameTreeExpand:GetParent():GetName(), "TOPLEFT", 50, 1);

	PartyQuestsFrameExitButton:Hide();
	PartyQuestsFrameTitleText:SetText(PARTYQUESTS_TITLE_TEXT);
	PartyQuestsFrameMainIcon:SetTexture("Interface\\QuestFrame\\UI-QuestLog-BookIcon");

	QuestLogFramePartyButton:Show();
	QuestLogFrameAbandonButton:SetWidth(84);
	QuestLogFrameAbandonButton:SetText(PARTYQUESTS_BUTTON_ABANDON_TEXT);
	
	QuestFramePushQuestButton:ClearAllPoints();
	QuestFramePushQuestButton:SetPoint("LEFT","QuestLogFrameAbandonButton","RIGHT",-2,0);
	QuestFramePushQuestButton:SetWidth(84);
	QuestFramePushQuestButton:SetText(PARTYQUESTS_BUTTON_SHARE_TEXT);

end

--
--[ Make Sure we have a sane settings set ]--
--
function PartyQuests.Startup.CheckSavedVariables()
	-- Speedup accessing the global
	if (not PartyQuests_Settings) then
		PartyQuests.SetSettingsToDefault();
	end
	local Settings = PartyQuests_Settings
	
	if (not PartyQuests_CharSettings) then
		PartyQuests.SetCharSettingsToDefault();
	end
	local CharSettings = PartyQuests_CharSettings

	-- PartyQuests_Settings
	Settings.indentOff = PartyQuests.Startup.CheckVariable( Settings.indentOff, true );
	Settings.showCompletedBanner = PartyQuests.Startup.CheckVariable( Settings.showCompletedBanner, true );
	Settings.showFailedBanner = PartyQuests.Startup.CheckVariable( Settings.showFailedBanner, true );
	Settings.showDetailedTooltip = PartyQuests.Startup.CheckVariable( Settings.showDetailedTooltip, true );

	-- PartyQuests_Settings.self
	if ( Settings.self == nil ) then
		Settings.self = {};
	end
	if ( Settings.self.sortMethod == nil ) then
		Settings.self.sortMethod = "zone";
	end
	Settings.self.showLevel = PartyQuests.Startup.CheckVariable( Settings.self.showLevel, true );
	Settings.self.showShared = PartyQuests.Startup.CheckVariable( Settings.self.showShared, true );
	Settings.self.zonesOn = PartyQuests.Startup.CheckVariable( Settings.self.zonesOn, true );

	-- PartyQuests_Settings.myQuests
	if ( Settings.myQuests == nil ) then
		Settings.myQuests = {};
	end
	if ( Settings.myQuests.sortMethod == nil ) then
		Settings.myQuests.sortMethod = "level";
	end
	Settings.myQuests.showLevel = PartyQuests.Startup.CheckVariable( Settings.myQuests.showLevel, true );
	Settings.myQuests.showShared = PartyQuests.Startup.CheckVariable( Settings.myQuests.showShared, true );
	Settings.myQuests.zonesOn = PartyQuests.Startup.CheckVariable( Settings.myQuests.zonesOn, true );

	-- PartyQuests_Settings.common
	if ( Settings.common == nil ) then
		Settings.common = {};
	end
	if ( Settings.common.sortMethod == nil ) then
		Settings.common.sortMethod = "title";
	end
	Settings.common.showLevel = PartyQuests.Startup.CheckVariable( Settings.common.showLevel, false );
	Settings.common.showShared = PartyQuests.Startup.CheckVariable( Settings.common.showShared, true );
	Settings.common.zonesOn = PartyQuests.Startup.CheckVariable( Settings.common.zonesOn, false );

	-- PartyQuests_Settings.party
	if ( Settings.party == nil ) then
		Settings.party = {};
	end
	if ( Settings.party.sortMethod == nil ) then
		Settings.party.sortMethod = "zone";
	end
	Settings.party.showLevel = PartyQuests.Startup.CheckVariable( Settings.party.showLevel, true );
	Settings.party.zonesOn = PartyQuests.Startup.CheckVariable( Settings.party.zonesOn, true );

	-- PartyQuests_CharSettings.self
	if ( CharSettings.self == nil ) then
		CharSettings.self = {};
	end
	CharSettings.self.collapsed = PartyQuests.Startup.CheckVariable( CharSettings.self.collapsed, false );

	if ( CharSettings.self.AllQuests == nil ) then
		CharSettings.self.AllQuests = {};
	end
	CharSettings.self.AllQuests.collapsed = PartyQuests.Startup.CheckVariable( CharSettings.self.AllQuests.collapsed, false );

	-- PartyQuests_CharSettings.myQuests
	if ( CharSettings.myQuests == nil ) then
		CharSettings.myQuests = {};
	end
	CharSettings.myQuests.collapsed = PartyQuests.Startup.CheckVariable( CharSettings.myQuests.collapsed, true );

	-- PartyQuests_CharSettings.common
	if ( CharSettings.common == nil ) then
		CharSettings.common = {};
	end
	CharSettings.common.collapsed = PartyQuests.Startup.CheckVariable( CharSettings.common.collapsed, false );

	-- PartyQuests_CharSettings.party
	if ( CharSettings.party == nil ) then
		CharSettings.party = {};
	end
	CharSettings.party.collapsed = PartyQuests.Startup.CheckVariable( CharSettings.party.collapsed, true );
end

function PartyQuests.Startup.CheckVariable( variable, setting )
	if ( variable == nil ) then
		return setting
	else
		return variable
	end
end

--
--[ Main Event Handler ]--
--
function PartyQuests.Frame.OnEvent()
	if event == "PARTY_MEMBERS_CHANGED" then
		if PartyQuestsLogFrame:IsVisible() and
			PartyQuests.CurrentQuest and
			PartyQuests.CurrentQuest.quest then

			if PartyQuests.CurrentQuest.quest.pushable and
				GetNumPartyMembers() > 0 then

				PartyQuestsLogFrameShareButton:Enable();
			else
				PartyQuestsLogFrameShareButton:Disable();
			end
		end

		local num = GetNumPartyMembers()

		-- Are we in a party?
		if num > 0 then
			local removed = false

			for i = num, 1, -1 do
				local name = UnitName( "party" .. i )

				if PartyQuests.Team.num < num then
					-- Removed
					if not removed and i > 1 and PartyQuests.Team[i] ~= UnitName("party" .. ( i - 1 )) or
						i == 1 and not removed then

						Chronos.unscheduleByName( "PartyQuestsTelepathyCheckOn"..PartyQuests.Team[i] )
						PartyQuests.PartyQuests[PartyQuests.Team[i]] = nil
						removed = true
					end
				end
				PartyQuests.Team[i] = name
				PartyQuests.InitializeUser( name )

			end
			PartyQuests.Team.num = num

			-- Remove Extras
			if num < 4 then
				if PartyQuests.Team[num + 1] ~= "" then
					Chronos.unscheduleByName( "PartyQuestsTelepathyCheckOn"..PartyQuests.Team[num + 1] )
					PartyQuests.PartyQuests[PartyQuests.Team[num + 1]] = nil
					PartyQuests.Team[num] = ""
				end
			end
		else
			-- Cleanup after a team
			for i = 1, 4 do
				if PartyQuests.Team[i] ~= "" then
					Chronos.unscheduleByName( "PartyQuestsTelepathyCheckOn"..PartyQuests.Team[i] )
					PartyQuests.PartyQuests[PartyQuests.Team[i]] = nil
					
					PartyQuests.Team[i] = ""
				else
					break
				end
			end
		end

		--if Chronos.isScheduledByName( "PartyQuestsNeedLogsCheck" ) then 
		--	return
		--end
		PartyQuests.Frame.ShowQuestLog()
		--Chronos.scheduleByName("PartyQuestsNeedLogsCheck", PARTYQUESTS_NEED_LOGS_DELAY, PartyQuests.Telepathy.RequestNeededLogs);

	elseif ( event == "UI_INFO_MESSAGE" ) then
		local questUpdateText = gsub(arg1,"(.*): %d+/%d+","%1",1);
		if (questUpdateText ~= arg1) then
			LibramData.updateHistory()
			PartyQuests.Telepathy.SendQuestProgressUpdateToParty(arg1);
		end

	elseif event == "QUEST_LOG_UPDATE" and arg1 == nil and Libram.online then
		Chronos.scheduleByName("Libram.updateHistory", 1, LibramData.updateHistory )

	elseif ( event == "PLAYER_ENTERING_WORLD" ) then
		this:RegisterEvent("QUEST_LOG_UPDATE")

	elseif ( event == "PLAYER_LEAVING_WORLD" ) then
		this:UnregisterEvent("QUEST_LOG_UPDATE")

	elseif ( event == "VARIABLES_LOADED" ) then
		PartyQuests.Startup.CheckSavedVariables()

		-- Queue a Gui Refresh 
		Chronos.afterInit(PartyQuests.Frame.Initialize); 

		-- Register with Earth for the button
		PartyQuests.Register.Earth()

		if ( Khaos ) then 
			PartyQuests.Register.Khaos();
		end

		-- Register with Telepathy for communication
		PartyQuests.Register.Telepathy();

		--PartyQuests.Register.Meteorologist();

		-- Register the scholar
		Libram.registerScholar({id="PartyQuestScholar";callback=PartyQuests.Libram.callback;description="PartyQuest's scholar - informs PQ when the gui is in need of changing. "} );

		--this:RegisterEvent("PARTY_LEADER_CHANGED");
		this:RegisterEvent("PARTY_MEMBERS_CHANGED");

		this:RegisterEvent("UI_INFO_MESSAGE");
		this:RegisterEvent("QUEST_LOG_UPDATE");

		this:RegisterEvent("PLAYER_ENTERING_WORLD");
		this:RegisterEvent("PLAYER_LEAVING_WORLD");
	end
end

--
-- Show PartyQuestFrame
--
function PartyQuests.Frame.OnShow()
	UpdateMicroButtons()
	PlaySound("igQuestLogOpen");
	
	if ( PartyQuestsFrame.display == "party" ) then
		PartyQuests.Telepathy.RequestNeededLogs();
	end

	PartyQuests.Frame.ShowQuestLog()
	-- Show the current quest
	PartyQuests.Frame.ShowCurrentQuest();
end

--
-- Hide PartyQuestFrame
--
function PartyQuests.Frame.OnHide()
	UpdateMicroButtons();

	if ( not PartyQuestsLogFrame:IsVisible() ) then
		PlaySound("igQuestLogClose");
	end
end

--[[ Process Party Changes ]]--
function PartyQuests.Party.ProcessChange()
end

--
--[[ Generates the current player's quest list ]]--
--
function PartyQuests.Player.GetQuestTree()
	PartyQuests.MyCurrentLog = Sea.table.copy(Libram.requestHistory());
	return PartyQuests.MyCurrentLog;
end

--
--[[ Obtains the common quests and party quest tree or filler data ]]--
--
function PartyQuests.Party.GetQuestTree()
	return PartyQuests.PartyQuests;
end

--
--[[ Determines which quests are common with other players ]]--
--
function PartyQuests.Common.GetQuestsTree()
	local myQuests =  PartyQuests.Player.GetQuestTree();
	local partyQuests = PartyQuests.Party.GetQuestTree();
	local commonQuests = {};

	for zone,quests in pairs(myQuests) do 
		if (type (quests) == "table" ) then
			for k,quest in pairs(quests) do 
				if ( type ( quest ) == "table" ) then 
					local added = false;
					for ally,allyData in pairs(partyQuests) do 
						if ( allyData.quests ) then 
							if ( allyData.quests[zone] ) then 
								for k2,allyQuest in pairs(allyData.quests[zone]) do
									if ( type (allyQuest) == "table" ) then 
										if ( quest.title == allyQuest.title and quest.level == allyQuest.level ) then
											if ( not quest.allies ) then 
												-- You minimally should have this quest, or its not common, duh
												quest.allies = {UnitName("player")};
											else
												if ( not Sea.list.isInList(quest.allies, ally) ) then 
													table.insert(quest.allies, ally);
												end
											end

											quest.completeCount = 0;
											if ( quest.complete == 1 ) then
												quest.completeCount = 1;
											end

											quest.failedCount = 0;
											if ( quest.complete == -1 ) then
												quest.failedCount = 1;
											end

											-- Increment the completed/failed counter
											if ( allyQuest.complete == 1 ) then 
												quest.completeCount = quest.completeCount + 1;
											elseif ( allyQuest.complete == -1 ) then
												quest.failedCount = quest.failedCount + 1;
											end

											-- Determine if everyone is done or failed
											local total = #(quest.allies) + 1;

											if ( quest.completeCount == total ) then 
												quest.complete = 1;
											elseif ( quest.failedCount == total ) then
												quest.complete = -1;
											else
												quest.complete = 0;
											end

											-- List the number of complete out of alies so far
											quest.tag = string.format("%d/%d", quest.completeCount, #(quest.allies));

											if ( not quest.objectives ) then
												quest.objectives = {};
											end

											if ( allyQuest.status ) then
												local found = false;
												for k4, allyStatus in pairs(quest.objectives) do 
													if ( allyStatus.name == ally ) then found = true; end
												end
												if ( not found ) then 
													table.insert(quest.objectives, {name=ally,status=allyQuest.status} );
												end
											end

											if ( not added ) then
												added = true;
												if ( not commonQuests[zone] ) then
													commonQuests[zone] = {};
												end
												table.insert(commonQuests[zone], quest);
											end
										end
									end
								end
							end
						end
					end
				end
			end
		end
	end

	return commonQuests;
end

--[[ Converts Quest Trees into Enhanced Tree ]]--
function PartyQuests.ConvertQuestTreeToEnhancedTree(questTree, funcList)
	local enhancedTree = {};

	-- Loop through the zone list
	for zone,questList in pairs(questTree) do
		if ( type(questList) == "table" ) then 
			local zoneQuests = {};
			zoneQuests.title = zone;
			zoneQuests.zone = zone;
			zoneQuests.collapsed = questList.collapsed;
			zoneQuests.children = {};

			-- Loop through the quest list
			for i=1,#(questList) do 
				quest = questList[i];
				if ( type ( quest ) == "table" ) then 
					local entry = {};
					local id = quest.flatid;			

					-- Format it into <#> [12<+/d>] Quest of Danger
					local formatStr = PARTYQUESTS_FORMAT
					if ( questList.showLevel ) then
						if ( quest.tag ) then
							local newFormatStr = PARTYQUESTS_FORMAT_TAGS[quest.tag]
							if ( newFormatStr ) then
								formatStr = newFormatStr
							else
								entry.right = string.format(PARTYQUESTS_QUEST_TITLE_TAG, quest.tag)
							end
						end
						entry.title = string.format(formatStr, quest.level, quest.title)
					else
						entry.title = quest.title
					end

					-- Is other party members on quest?
					if ( questList.showShared ) then
						local numPartyMembers = GetNumPartyMembers();
						local counter = 0;
						if ( numPartyMembers > 0 ) then
							for i=1, numPartyMembers do
								if ( IsUnitOnQuest( id, "party"..i ) ) then
									counter = counter + 1;
								end
							end
							if ( counter > 0 ) then
								entry.prefix = counter; 
								entry.prefixColor = GetDifficultyColor(quest.level);
							else
								entry.prefix = " ";
							end
						end
					end

					entry.level = quest.level;
					entry.questTitle = quest.title;
					entry.zone = zone;

					-- The important value is the id
					entry.value = {id=id, title=quest.title, level=quest.level, zone=zone};

					-- Pretty up the (Elite)
					if ( quest.tag and not questList.showLevel ) then 
						entry.right = string.format(PARTYQUESTS_QUEST_TITLE_TAG, quest.tag)
					end

					if ( quest.complete == 1 ) then 
						entry.right = string.format(PARTYQUESTS_QUEST_TITLE_TAG, TEXT(COMPLETE)); 
					elseif ( quest.complete == -1 ) then
						entry.right = string.format(PARTYQUESTS_QUEST_TITLE_TAG, TEXT(FAILED)); 
					end

					-- Lets add some color
					entry.titleColor = GetDifficultyColor(quest.level);

					-- Add the event handlers
					if ( funcList ) then 
						if ( funcList.onClick ) then 
							entry.onClick = funcList.onClick;
						end

						if ( funcList.onCheck ) then 
							entry.check = true;
							if ( IsQuestWatched(quest.flatid) ) then
								entry.checked = true;
							end
							entry.onCheck = funcList.onCheck;
						end

						if ( funcList.tooltipFunc ) then 
							entry.tooltip = funcList.tooltipFunc
						end
					end
			
					table.insert(zoneQuests.children,entry);
				end
			end

			zoneQuests.right = string.format(PARTYQUESTS_NUMQUESTS,#(zoneQuests.children));
			zoneQuests.rightColor = GRAY_FONT_COLOR;
			zoneQuests.value = {zone=zone};

			-- Add the event handlers
			if ( funcList ) then
				if ( funcList.onZoneClick ) then 
					zoneQuests.onClick = funcList.onZoneClick
				end

				if ( funcList.onCollapseClick ) then 
					zoneQuests.onCollapseClick = funcList.onCollapseClick;
				end

				if ( funcList.tooltipFunc ) then 
					zoneQuests.tooltip = funcList.tooltipFunc
				end
			end
			table.insert(enhancedTree,zoneQuests);
		end
	end

	return enhancedTree;
end

--[[ Converts the player's quest tree to an enhanced tree ]]--
function PartyQuests.Player.ConvertQuestTreeToEnhancedTree(tree)
	local playerTree= {};
	local self = nil;

	-- Set title
	playerTree.title = PARTYQUESTS_MYQUEST;
	if ( PartyQuestsFrame.display == "self" ) then
		self = true;
	else
		self = false;
	end

	if ( self ) then
		playerTree.collapsed = PartyQuests_CharSettings.self.collapsed;
		playerTree.onCollapseClick = PartyQuests.Self.StoreCollapsed;
	else
		playerTree.collapsed = PartyQuests_CharSettings.myQuests.collapsed;
		playerTree.onCollapseClick = PartyQuests.myQuests.StoreCollapsed;
	end

	if ( tree ) then 
	        local collapsefunc
		if ( self ) then
			collapsefunc = PartyQuests.Self.StoreZoneCollapsed
		else
			collapsefunc = PartyQuests.myQuests.StoreZoneCollapsed
		end

		local funcList = {
			onClick=PartyQuests.Player.OnQuestClick;
			onCheck=PartyQuests.Player.OnQuestCheck;
			onZoneClick=PartyQuests.Player.OnQuestClick;
			onCollapseClick=collapsefunc;
			tooltipFunc=PartyQuests.Player.CreateTooltip;
		};

		-- Collapse the player's quests
		for zone, questList in pairs(tree) do 
			if ( self ) then 
				if ( not PartyQuests_CharSettings.self[zone] ) then
					PartyQuests_CharSettings.self[zone] = {};
					PartyQuests_CharSettings.self[zone].collapsed = PartyQuests_CharSettings.self.collapsed;
				end
				tree[zone].collapsed = PartyQuests_CharSettings.self[zone].collapsed;
			else
				if ( not PartyQuests_CharSettings.myQuests[zone] ) then
					PartyQuests_CharSettings.myQuests[zone] = {};
					PartyQuests_CharSettings.myQuests[zone].collapsed = PartyQuests_CharSettings.myQuests.collapsed;
				end
				tree[zone].collapsed = PartyQuests_CharSettings.myQuests[zone].collapsed;
			end

			-- Show the level/shared indicator
			if ( self ) then
				questList.showLevel = PartyQuests_Settings.self.showLevel
				questList.showShared = PartyQuests_Settings.self.showShared
			else
				questList.showLevel = PartyQuests_Settings.myQuests.showLevel
				questList.showShared = PartyQuests_Settings.myQuests.showShared
			end
		end

		local eTree = PartyQuests.ConvertQuestTreeToEnhancedTree(tree, funcList);

		if ( #(eTree) > 1 ) then
			-- Sort the eTree
			if ( self ) then
				eTree = PartyQuests.SortQuestTree(eTree, "self");
			else
				eTree = PartyQuests.SortQuestTree(eTree, "myQuests");
		      
				-- Strip out "All Quests" tag from myQuests tree.
				if ( eTree[1] ) then
					if ( #(eTree) == 1 and eTree[1].title == PARTYQUESTS_ALLQUESTS ) then 
						eTree = eTree[1].children;
					end
				end
			end
		end
		playerTree.children = eTree;
	end

	return playerTree;
end

--[[ Sets the collapsed value for a player's tree ]]--
function PartyQuests.Self.StoreCollapsed( collapsed ) 
	PartyQuests_CharSettings.self.collapsed = collapsed;
end

function PartyQuests.myQuests.StoreCollapsed( collapsed ) 
	PartyQuests_CharSettings.myQuests.collapsed = collapsed;
end

--[[ Sets the collapsed value for a player's zone tree ]]--
function PartyQuests.Self.StoreZoneCollapsed( collapsed, value ) 
	if value == nil then
		return
	end

	if ( not PartyQuests_CharSettings.self[value.zone] ) then 
		PartyQuests_CharSettings.self[value.zone] = {};
	end
	PartyQuests_CharSettings.self[value.zone].collapsed = collapsed;
end

function PartyQuests.myQuests.StoreZoneCollapsed( collapsed, value ) 
	if value == nil then
		return
	end
	
	if ( not PartyQuests_CharSettings.myQuests[value.zone] ) then 
		PartyQuests_CharSettings.myQuests[value.zone] = {};
	end
	PartyQuests_CharSettings.myQuests[value.zone].collapsed = collapsed;
end

--[[ Converts the common quest tree into an enhanced tree ]]--
function PartyQuests.Common.ConvertQuestTreeToEnhancedTree(tree)
	local commonTree = {};
	commonTree.title = PARTYQUESTS_COMMONQUESTS;

	-- Set collapsed state
	commonTree.collapsed = PartyQuests_CharSettings.common.collapsed;
	commonTree.showLevel = PartyQuests_CharSettings.common.showLevel;
	commonTree.showShared = PartyQuests_CharSettings.common.showShared;

	commonTree.onCollapseClick = function ( collapsed ) 
					PartyQuests_CharSettings.common.collapsed = collapsed;
				end
	
	if ( tree ) then
		local funcList = {
			onClick=PartyQuests.Common.OnQuestClick;
			onZoneClick=PartyQuests.Common.OnQuestClick;
			onCollapseClick=PartyQuests.Common.StoreZoneCollapse;
			tooltipFunc = PartyQuests.Common.CreateTooltip; 
		};

		-- Load the collapse structure
		for zone,list in pairs(tree) do 
			if ( PartyQuests_CharSettings.common[zone] ) then 
				if ( PartyQuests_CharSettings.common[zone].collapsed ) then 
					tree[zone].collapsed = PartyQuests_CharSettings.common[zone].collapsed;
				end
			end
			list.showLevel = PartyQuests_Settings.common.showLevel
			list.showShared = PartyQuests_Settings.common.showShared
		end

		local eTree = PartyQuests.ConvertQuestTreeToEnhancedTree(tree, funcList);

		-- Sort the eTree
		eTree = PartyQuests.SortQuestTree(eTree, "common");

		-- Strip out All Quests for Common tree.=
		if ( eTree[1] ) then 
			if ( #(eTree) == 1 and eTree[1].title == PARTYQUESTS_ALLQUESTS ) then 
				eTree = eTree[1].children;
			end
		end

		if ( #(eTree) > 0 ) then 
			commonTree.children = eTree;
		end
	end
	
	commonTree.right = PartyQuests.Common.GetTreeTag;
	commonTree.rightColor = BLUE_FONT_COLOR;

	return commonTree;
end

--[[ Returns a string saying if the common tree is waiting ]]--
function PartyQuests.Common.GetTreeTag()
	-- Am I waiting on anything
	if ( PartyQuests.Telepathy.AmIWaiting() ) then 
		PartyQuests_TempSettings.common.waitCount = math.fmod(PartyQuests_TempSettings.common.waitCount+1, 4);

		return getglobal("PARTYQUESTS_BUILDING_"..PartyQuests_TempSettings.common.waitCount);
	end
end

--[[ Converts the specified ally's quest tree to an enhanced tree ]]--
function PartyQuests.Ally.ConvertQuestTreeToEnhancedTree(username, allyData)
	local allyTree = {};
	allyTree.title = string.format(PARTYQUESTS_XQUESTS, username);

	if ( allyData.quests ) then 
		-- Check if collapsed
		if (PartyQuests_TempSettings.party[username] == nil ) then
			allyTree.collapsed = PartyQuests_CharSettings.party.collapsed;
			PartyQuests_TempSettings.party[username] = {};
			PartyQuests_TempSettings.party[username].collapsed = PartyQuests_CharSettings.party.collapsed;
		else
			allyTree.collapsed = PartyQuests_TempSettings.party[username].collapsed;
		end
		allyTree.onCollapseClick = function ( collapsed ) 
						PartyQuests_TempSettings.party[username].collapsed = collapsed;
					end

		-- Do Something
		local funcList = {};
		funcList.onClick = function(value)
					PartyQuests.Party.OnQuestClick(username, value.title, value.level);
				end

--		funcList.onZoneClick = function(value)
--			PartyQuests.Party.OnQuestClick(username, value.title, value.level);
--		end

		funcList.onCollapseClick = function ( collapsed, value ) 
						if ( not value.zone ) then 
							return;
						end

						if ( PartyQuests_TempSettings.party[username][value.zone] == nil ) then
							PartyQuests_TempSettings.party[username][value.zone] = {};
						end
						PartyQuests_TempSettings.party[username][value.zone].collapsed = collapsed;
					end

		-- Collapse the ally's quests
		for zone,questList in pairs(allyData.quests) do 
			if ( PartyQuests_TempSettings.party[username][zone] ) then 
				allyData.quests[zone].collapsed = PartyQuests_TempSettings.party[username][zone].collapsed;
			end

			questList.showLevel = PartyQuests_Settings.party.showLevel;
		end
		
		-- Convert the quests over
		local eTree = PartyQuests.ConvertQuestTreeToEnhancedTree(allyData.quests, funcList);

		-- Sort the eTree
		eTree = PartyQuests.SortQuestTree(eTree, "party", username);

		-- Strip out All Quests for party members.=
		if ( eTree[1] ) then 
			if ( #(eTree) == 1 and eTree[1].title == PARTYQUESTS_ALLQUESTS ) then 
				eTree = eTree[1].children;
			end
		end
		allyTree.children = eTree;
	end

	allyTree.right = function() return PartyQuests.Ally.GetTreeTag ( username ); end;
	allyTree.rightColor = BLUE_FONT_COLOR;

	return allyTree;
end

--[[ Gets the tag for the allyTree ]]--
function PartyQuests.Ally.GetTreeTag(username)
	local allyData = PartyQuests.PartyQuests[username];
	if ( allyData ) then 
		if ( allyData.waiting ) then	
			if ( allyData.waiting == PARTYQUESTS_FAILED ) then 
				return PARTYQUESTS_FAILED_TEXT, RED_FONT_COLOR;
			else
				if ( not allyData.waitCount ) then
					allyData.waitCount = 0;
				end

				allyData.waitCount = math.fmod(allyData.waitCount+1, 4);				
				return getglobal("PARTYQUESTS_WAITING_"..allyData.waitCount);		
			end
		elseif ( allyData.version == false ) then 
			return PARTYQUESTS_NOT_PQ_USER_TEXT;
		elseif ( allyData.version ) then
			return "V "..allyData.version;			
		end
	end
end

--[[ Create the Player Tooltip ]]--
function PartyQuests.Player.CreateTooltip(quest)
	local tooltip = nil;

	if ( type( quest ) == "table" and
	    PartyQuests_Settings.showDetailedTooltip ) then 
		if ( quest.id ) then 
			local questInfo = PartyQuests.Player.GetQuestInfo(quest.id);

			-- An un-nessecarily long block of text 
			if ( questInfo and questInfo.objective ) then
				local text = questInfo.objective;
			        tooltip = ""
				if ( string.len(text) > PARTYQUESTS_TOOLTIP_MAX_CHAR_WIDTH ) then 
					local i = 1;
					while ( text and text ~= "" and i < 30) do
						local e = 1;
						if ( string.find(text, " ",  PARTYQUESTS_TOOLTIP_MAX_CHAR_WIDTH ) ) then 
							e = string.find(text, " ",  PARTYQUESTS_TOOLTIP_MAX_CHAR_WIDTH );
						else
							e = string.len(text);
						end

						local small = string.sub(text, 1, e);
						if ( small ) then 
							tooltip = tooltip..small.."\n";
						else
							tooltip = tooltip..text.."\n";
						end
						text = string.sub(text, e+1 );
						i = i + 1;
					end
				else
					tooltip = text;
				end
			end
			if ( questInfo and questInfo.objectives and #(questInfo.objectives) > 0 ) then
				if ( not tooltip ) then tooltip = ""; end 
				tooltip = tooltip..string.format(PARTYQUESTS_NBOBJ, #(questInfo.objectives) ) .. "\n";
				for k,v in pairs(questInfo.objectives) do
					tooltip = tooltip..v.text.."\n";

					if ( questInfo.complete == 1 ) then 
						tooltip = tooltip.." "..string.format(PARTYQUESTS_QUEST_TITLE_TAG,COMPLETE);
					elseif ( questInfo.complete == -1 ) then
						tooltip = tooltip.." "..string.format(PARTYQUESTS_QUEST_TITLE_TAG,FAILED);
					end
				end
			end
		end
	end

	if type( quest ) == "table" and quest.id then
		-- Add other party members on the same quest to tooltip, if showShared is turned on
		local displayShared
		if ( PartyQuestsFrame.display == "self" ) then
			displayShared = PartyQuests_Settings.self.showShared;
		else
			displayShared = PartyQuests_Settings.myQuests.showShared
		end

		if ( displayShared ) then
			local numPartyMembers = GetNumPartyMembers();
			local membersOnQuest = "";
			local count = 2;
			local addtotip = false
			if ( numPartyMembers > 0 ) then
				for i=1, numPartyMembers do
					-- Who else has this quest:
					-- Aylene    Miravlix
					-- Noddy    Hansi
					-- Lisa
					if ( IsUnitOnQuest( quest.id, "party"..i ) ) then
						if ( count == 2 ) then 
							membersOnQuest = membersOnQuest.."\n";
							count = 0;
						elseif ( count == 1 ) then
							membersOnQuest = membersOnQuest.."    ";
						end
						count = count + 1;
						membersOnQuest = membersOnQuest .. UnitName("party"..i);
						addtotip = true;
					end
				end
				if ( addtotip ) then
					if tooltip ~= nil then
						tooltip = tooltip .. "\n" .. PARTYQUESTS_WHOELSE .. membersOnQuest;
					else
						tooltip = PARTYQUESTS_WHOELSE .. membersOnQuest;
					end
				end
			end
		end
	end

	return tooltip;
end

--[[ Create the Common Tooltip ]]--
function PartyQuests.Common.CreateTooltip(quest)
 	if ( type( quest ) == "table"
	    and type( quest.allies ) == "table"
	    and PartyQuests_Settings.showDetailedTooltip ) then 

		local tooltip = "";
		local allies = "";
		local count = 2;

		tooltip = tooltip..PARTYQUESTS_WHOELSE;

		for k,ally in pairs(quest.allies) do 
			if ( count == 2 ) then 
				tooltip = tooltip.."\n";
				count = 0;
			elseif ( count == 1 ) then
				tooltip = tooltip.."    ";
			end
			tooltip = tooltip..ally;
			count = count + 1;
		end

		-- Format it nicely
		if (quest.completeCount == 1) then
			tooltip = format(PARTYQUESTS_STNOCOMPLETED, tooltip, quest.completeCount);
		elseif (quest.completeCount == 0) then
			tooltip = format(PARTYQUESTS_STNOONECOMPLETED, tooltip);
		elseif (quest.completeCount == #(quest.allies) ) then
			tooltip = format(PARTYQUESTS_STALLCOMPLETED, tooltip);
		elseif (type(quest.completeCount) == "number" ) then
			tooltip = format(PARTYQUESTS_STXCOMPLETED, tooltip, quest.completeCount);
		else
			return;
		end

		return tooltip;
	end
end

--[[ Store the collapsed zones ]]--
function PartyQuests.Common.StoreZoneCollapse (collapsed, value)
	if ( not value ) then 
		return 
	end

	if ( PartyQuests_CharSettings.common[value.zone] == nil ) then 
		PartyQuests_CharSettings.common[value.zone] = {};
	end
	PartyQuests_CharSettings.common[value.zone].collapsed = collapsed;
end

--[[ Sort the Quest Tree ]]--
function PartyQuests.SortQuestTree(enhancedTree, mtype)
	-- Sort Off
	local sort = nil;

	-- Determine if the quests are flat
	if ( PartyQuests_Settings.indentOff == true ) then
		for k,v in pairs(enhancedTree) do
			for k2,v2 in pairs(v.children) do 
				v2.noTextIndent = true;
			end
		end
	end

	-- Determine if Zones are On
	if ( not PartyQuests_Settings[mtype].zonesOn ) then 
		-- Flatten the Tree
		local temp = {};
		for k,v in pairs(enhancedTree) do
			for k2,v2 in pairs(v.children) do 
				table.insert(temp,v2);
			end
		end

		-- Replace the old tree		
		enhancedTree = temp;
	end

	-- Sort by Zone
	local zoneSorter = function(a,b) return a.zone < b.zone; end;

	-- Sort by Quest
	local titleSorter = function(a,b) return a.questTitle < b.questTitle; end;

	-- Sort by Level
	local levelSorter = function(a,b) return a.level < b.level; end;

	-- Sort Zones by First Quest Name
	local childTitleSorter = function(a,b) 		
					 if ( a.children and b.children ) then
						 table.sort(a.children, function(a,b) return a.questTitle < b.questTitle end );
						 table.sort(b.children, function(a,b) return a.questTitle < b.questTitle end );
						 if a.children[1] and b.children[1] then
							 return a.children[1].questTitle < b.children[1].questTitle;
						 else
							 return false
						 end
					 end			
				 end;

	-- Sort Zones By Quest Level
	local childLevelSorter = function(a,b)
					 if ( a.children and b.children ) then
						 table.sort(a.children, function(a,b) return a.level < b.level end );
						 table.sort(b.children, function(a,b) return a.level < b.level end );

						 -- attempt to index field ? (a nil value)  hack fix! I've no clue why it generates a nil error here.
						 if a.children[1] and b.children[1] then
							 return a.children[1].level < b.children[1].level;
						 else
							 return false
						 end
					 end		
				 end;
	
	-- Choose the sort method
	if ( PartyQuests_Settings[mtype].zonesOn ) then 
		if ( PartyQuests_Settings[mtype].sortMethod == "level" ) then 
			sort = childLevelSorter; 
		elseif ( PartyQuests_Settings[mtype].sortMethod == "title" ) then 
			sort = childTitleSorter;
		elseif ( PartyQuests_Settings[mtype].sortMethod == "zone" ) then 
			sort = zoneSorter;
		end
	else
		if ( PartyQuests_Settings[mtype].sortMethod == "level" ) then 
			sort = levelSorter; 
		elseif ( PartyQuests_Settings[mtype].sortMethod == "title" ) then 
			sort = titleSorter;
		elseif ( PartyQuests_Settings[mtype].sortMethod == "zone" ) then 
			sort = zoneSorter;
		end
	end
	
	-- Check if we're sorting
	if ( sort ) then 
		-- Perform Sort
		table.sort(enhancedTree,sort);
	end

	-- Wrap it in an All Quests tag
	if ( not PartyQuests_Settings[mtype].zonesOn ) then
		if mtype == "self" then
			enhancedTree = {{title=PARTYQUESTS_ALLQUESTS;
					collapsed=PartyQuests_CharSettings[mtype].AllQuests.collapsed;
					onCollapseClick=PartyQuests.Self.StoreAllQuestsCollapsed;
					children=enhancedTree}};
		end
	end

	return enhancedTree;
end

function PartyQuests.Self.StoreAllQuestsCollapsed( collapsed ) 
	PartyQuests_CharSettings.self.AllQuests.collapsed = collapsed;
end

-- [[ Attempts to open a cached quest ]]--
function PartyQuests.Party.OnQuestClick(username, questTitle, questLevel) 
	local button = arg1;
	if ( button == "LeftButton" ) then
		if ( IsShiftKeyDown() and ChatFrameEditBox:IsVisible() ) then
			ChatFrameEditBox:Insert(this:GetText());
		else
			-- Set the active quest ID #
			PartyQuestsLogFrameQuestCount:SetText(username);
			PartyQuestsLogFrameCountMiddle:SetWidth(PartyQuestsLogFrameQuestCount:GetWidth());

			PartyQuests.CurrentQuest_SetFromCache (username, questTitle, questLevel);
			-- Show the current quest
			PartyQuests.Frame.ShowCurrentQuest();
		end
	elseif ( button == "RightButton" ) then 
		OpenMenu("party");
	end
end

--[[ Attempts to open a Common Quest ]]--
function PartyQuests.Common.OnQuestClick(value)
	local id = nil;
	if ( type (value) == "table" ) then 
		id = value.id;
	end
	if ( id and id > GetNumQuestLogEntries() ) then
	else
		local button = arg1;
		if ( button == "LeftButton" ) then
			if ( IsShiftKeyDown() and ChatFrameEditBox:IsVisible() ) then
				ChatFrameEditBox:Insert(this:GetText());
			elseif ( id ) then 
				local numEntries, numQuests = GetNumQuestLogEntries();

				-- Set the active quest ID #
				PartyQuestsLogFrameQuestCount:SetText(string.format(PARTYQUESTS_LOG_ID_TEMPLATE, id));
				PartyQuestsLogFrameCountMiddle:SetWidth(PartyQuestsLogFrameQuestCount:GetWidth());

				-- Update the log frame
				PartyQuests.CurrentQuest_SetFromID(id, true);

				-- Mark this as shared
				if ( PartyQuests.CurrentQuest ) then 
					PartyQuests.CurrentQuest.shared = true;
					PartyQuests.AddSharedStatusToCurrentQuest();
				end

				-- Show the current quest
				PartyQuests.Frame.ShowCurrentQuest();
			end
		elseif ( button == "RightButton" ) then 
			OpenMenu("common")
		end
	end
end

-- Adds shared quest statuses to current quest 
function PartyQuests.AddSharedStatusToCurrentQuest ()
	-- Add the party statuses to the Common Quest log!
	if ( PartyQuests.CurrentQuest and PartyQuests.CurrentQuest.quest ) then 
		for player,quests in pairs(PartyQuests.PartyQuests) do 
			local questData = PartyQuests.GetQuestData(player, PartyQuests.CurrentQuest.quest.title, PartyQuests.CurrentQuest.quest.level ); 

			if ( questData and questData.status ) then 
				if ( not PartyQuests.CurrentQuest.quest.objectives ) then 
					PartyQuests.CurrentQuest.quest.objectives = {};
				end

				table.insert(PartyQuests.CurrentQuest.quest.objectives, {text="\n"..string.format(PARTYQUESTS_XOBJ,player)});
				for k,objective in pairs(questData.status) do
					table.insert(PartyQuests.CurrentQuest.quest.objectives, objective);
				end
			elseif ( questData ) then
				-- Insert Dummy Data
				table.insert(PartyQuests.CurrentQuest.quest.objectives, {text="\n"..string.format(PARTYQUESTS_XOBJ,player)});
				table.insert(PartyQuests.CurrentQuest.quest.objectives, {text=PARTYQUESTS_REQUESTING});

				-- If they have this quest, ask them for their status
				if ( not PartyQuests.PartyQuests[player].waitingStatus ) then 
					PartyQuests.Telepathy.RequestQuestStatusFromPlayer(player, PartyQuests.CurrentQuest.quest.title, PartyQuests.CurrentQuest.quest.level );
					PartyQuests.Telepathy.StartWaiting(player);
					PartyQuests.CurrentQuest.waitingStatus = true;
				end
				Sea.io.dprint(PARTYQUESTS_DEBUG, "Requested status on ", PartyQuests.CurrentQuest.quest.title, " ", PartyQuests.CurrentQuest.quest.level, " from ", player, ".");
			end
		end
	end

	-- Start Refreshing
	PartyQuests.Frame.RefreshPartyGuiIfWaiting();
end

-- Open the specified quest log
function PartyQuests.Player.OnQuestClick(value) 
	local id = nil;
	if ( type (value) == "table" ) then 
		id = value.id;
	end
	if ( id and id <= GetNumQuestLogEntries() ) then
		local button = arg1;
		if ( button == "LeftButton" ) then
			if ( IsShiftKeyDown() and ChatFrameEditBox:IsVisible() ) then
				ChatFrameEditBox:Insert(this:GetText());
			elseif ( id ) then 
				local numEntries, numQuests = GetNumQuestLogEntries();

				-- Set the active quest ID #
				PartyQuestsLogFrameQuestCount:SetText(string.format(PARTYQUESTS_LOG_ID_TEMPLATE, id));
				PartyQuestsLogFrameCountMiddle:SetWidth(PartyQuestsLogFrameQuestCount:GetWidth());

				-- Update the log frame
				PartyQuests.CurrentQuest_SetFromID(id, true);

				-- Show the current quest
				PartyQuests.Frame.ShowCurrentQuest()
			end
		elseif ( button == "RightButton" ) then
			if ( PartyQuestsFrame.display == "self" ) then
				PartyQuests.Frame.OpenMenu("self");
			else
				PartyQuests.Frame.OpenMenu("myQuests");
			end
		end
	end
end

function PartyQuests.Player.OnQuestCheck(checked, value)
	local id = nil;
	if ( type (value) == "table" ) then 
		id = value.id;
	else 
		return;
	end
	if ( IsQuestWatched(id) ) then
		RemoveQuestWatch(id);
		QuestWatch_Update();
		return false;
	else
		-- Set error if no objectives
		if ( GetNumQuestLeaderBoards(id) == 0 ) then
			UIErrorsFrame:AddMessage(QUEST_WATCH_NO_OBJECTIVES, 1.0, 0.1, 0.1, 1.0, UIERRORS_HOLD_TIME);
			return false;
		end
		-- Set an error message if trying to show too many quests
		if ( GetNumQuestWatches() >= MAX_WATCHABLE_QUESTS ) then
			UIErrorsFrame:AddMessage(format(QUEST_WATCH_TOO_MANY, MAX_WATCHABLE_QUESTS), 1.0, 0.1, 0.1, 1.0, UIERRORS_HOLD_TIME);
			return false;
		end
		AddQuestWatch(id);
		QuestWatch_Update();

		return true;
	end
end

--[[ Get Quest Information ]]--
function PartyQuests.Player.GetQuestInfo(questID)
	local myQuestRecord = {};
	local realRecord = Sea.table.copy( Libram.requestRecord(questID) );

	if ( not realRecord ) then 
		return;
	end
	-- Duplicate the record to prevent tampering. 
	for k,v in pairs(realRecord) do 
		if ( type(v) ~= "table" ) then 
			myQuestRecord[k] = v;
		else
			myQuestRecord[k] = {};

			for k2,v2 in pairs(v) do 
				myQuestRecord[k][k2] = v2;
			end
		end
	end

	return myQuestRecord;
end

--[[ Finds a QuestID Matching Title and Level Specified ]]--
function PartyQuests.MatchQuestToPlayerQuestID(questTitle, questLevel ) 
	local id = nil;
	local log = PartyQuests.Player.GetQuestTree();

	Sea.io.dprint(PARTYQUESTS_DEBUG, "Matching ", questTitle, " ", questLevel );
	for zone, questList in pairs(log) do
		for k2, quest in pairs(questList) do
			if ( type ( quest ) == "table" ) then 
				if ( quest.title == questTitle and quest.level == questLevel ) then 
					id = quest.id;
					break;
				end
			end
		end
	end

	return id;
end


--[[ Load a Single Quest Log From Cache ]]--
function PartyQuests.CurrentQuest_SetFromCache (username, questTitle, questLevel, shared)
	PartyQuests.InitializeUser(username)

	local ally = PartyQuests.PartyQuests[username];
	local quest = PartyQuests.GetCachedQuest(questTitle, questLevel)

	if ( not quest ) then 
		quest = {};
		PartyQuests.Telepathy.RequestQuestDetailsFromPlayer(username, questTitle, questLevel);
		PartyQuests.Telepathy.StartWaiting(username);
		Sea.io.dprint(PARTYQUESTS_DEBUG, "Requested details on ", questTitle, " ", questLevel, " from ", username, ".");
		quest.title = questTitle; 
		quest.level = questLevel; 
		quest.description = string.format(PARTYQUESTS_QUESTSTATUS_WAITING, username);

		-- Mark as waiting for a username
		quest.waitingDetails = true;
		quest.username = username;
	end

	if ( ally.quests ) then
		questData = PartyQuests.GetQuestData(username, questTitle, questLevel ); 

		if ( not questData or not questData.status ) then 
			-- Mark as waiting for a username
			quest.waitingStatus = true;
			quest.username = username;
			quest.title = questTitle; 
			quest.level = questLevel; 
			PartyQuests.Telepathy.RequestQuestStatusFromPlayer(username, questTitle, questLevel);
			Sea.io.dprint(PARTYQUESTS_DEBUG, "Requested status on ", questTitle, " ", questLevel, " from ", username, ".");
		else
			quest.objectives = questData.status;
		end
	elseif ( ally.waiting ) then
		Sea.io.dprint(PARTYQUESTS_DEBUG, "Ally is waiting or failed on ", questTitle, " ", questLevel, " from ", username, ".");

		if ( ally.waiting == PARTYQUESTS_WAITING ) then 
			-- Show Waiting message 
			quest.description = string.format(PARTYQUESTS_QUESTSTATUS_WAITING, username);
		elseif ( ally.waiting == PARTYQUESTS_FAILED ) then 
			-- Show Failed message
			quest.description = string.format(PARTYQUESTS_QUESTSTATUS_FAILED, username);
		end
	else
		-- If we have no quest log for that user (How did we get here??)
		-- Ask them for that log
		PartyQuests.Telepathy.StartWaiting(username);
		quest.title = string.format(PARTYQUESTS_QUESTSTATUS_WAITING, username);

		PartyQuests.Telepathy.RequestLogFromPlayer(username);	
	end

	-- Determine if we're going to load the quest or hide the frame
	if ( not  PartyQuests.CurrentQuest
	    or not PartyQuests.CurrentQuest.quest
	    or PartyQuests.CurrentQuest.quest.waitingDetails
	    or PartyQuests.CurrentQuest.quest.waitingStatus
	    or not ( PartyQuests.CurrentQuest.quest.title == quest.title)
	    or not ( PartyQuestsLogFrame:IsVisible()) ) then

		PartyQuests.CurrentQuest = {
			questType = "cache";
			quest = quest;
			visible = true;
		};
	else
		PartyQuests.CurrentQuest = nil;
	end

	if ( shared ) then 
		PartyQuests.AddSharedStatusToCurrentQuest();
	end

	-- Start Refreshing
	PartyQuests.Frame.RefreshPartyGuiIfWaiting();
end

function PartyQuests.CurrentQuest_UpdateFromCache (username, questTitle, questLevel, shared)
	PartyQuests.InitializeUser(username);

	local ally = PartyQuests.PartyQuests[username];
	local quest = PartyQuests.GetCachedQuest(questTitle, questLevel);

	if ( quest ) then 
		PartyQuests.CurrentQuest.quest = quest;
		PartyQuests.CurrentQuest.visible = true;
		PartyQuests.CurrentQuest.waitingDetails = nil;
	end
	
	if ( ally.quests ) then
		questData = PartyQuests.GetQuestData(username, questTitle, questLevel ); 

		if ( not questData or not questData.status ) then 
			return;
		else
			PartyQuests.CurrentQuest.quest.objectives = questData.status;
			PartyQuests.CurrentQuest.visible = true;
			PartyQuests.CurrentQuest.quest.waitingStatus = nil;
			-- Add the prefix for that player
			table.insert(PartyQuests.CurrentQuest.quest.objectives, 1 , {text=string.format(PARTYQUESTS_XOBJ,username);finished=questData.complete});
		end
	end

	if ( shared ) then 
		PartyQuests.AddSharedStatusToCurrentQuest ();
	end
end

function PartyQuests.CurrentQuest_SetFromID ( id, toggle )
	local questInfo = PartyQuests.Player.GetQuestInfo(id);

	if ( questInfo ) then 
		if ( toggle ) then 
			if ( not PartyQuests.CurrentQuest ) then 
				PartyQuests.CurrentQuest = {};
				PartyQuests.CurrentQuest.visible = true;
			elseif ( PartyQuests.CurrentQuest.quest ) then
				if ( questInfo.title == PartyQuests.CurrentQuest.quest.title and questInfo.level == PartyQuests.CurrentQuest.quest.level ) then 
					PartyQuests.CurrentQuest.visible = not PartyQuests.CurrentQuest.visible;
				end
			end

			PartyQuests.CurrentQuest.quest = questInfo;
		else
			if ( not PartyQuests.CurrentQuest ) then 
				PartyQuests.CurrentQuest = {};
			end
			PartyQuests.CurrentQuest.quest = questInfo;
			PartyQuests.CurrentQuest.visible = true;			
		end
	end
end

--[[ Creates a Menu ]]--
function PartyQuests.Frame.CreateMenu(mtype)
	local info = { };
	local i=1;
	
	info[i] = { text = TEXT(PARTYQUESTS_MENU_REFRESH), func = PartyQuests.Party.ProcessChange; };
	i=i+1;
	info[i] = { text = TEXT(CANCEL), func = function () end; };
	i=i+1;
	info[i] = { text = "|cFFCCCCCC------------|r", disabled = 1, notClickable = 1 };
	i=i+1;
	
	info[i] = { text = TEXT(PARTYQUESTS_MENU_GLOBAL), isTitle = true};
	i=i+1;
	
	info[i] = { text = TEXT(PARTYQUESTS_MENU_SHOWINDENT), keepShownOnClick = 1 };
	info[i].func = function (checked) PartyQuests_Settings.indentOff = checked; PartyQuests.Frame.ShowQuestLog(); end
	info[i].checked = PartyQuests_Settings.indentOff;
	i=i+1;
	
	info[i] = { text = TEXT(PARTYQUESTS_MENU_SHOWCOMPLETEDBANNER), keepShownOnClick = 1 };
	info[i].func = function (checked) PartyQuests_Settings.showcompletedBanner = checked; PartyQuests.Frame.ShowQuestLog(); end
	info[i].checked = PartyQuests_Settings.showCompletedBanner;
	i=i+1;
	
	info[i] = { text = TEXT(PARTYQUESTS_MENU_SHOWFAILEDBANNER), keepShownOnClick = 1 };
	info[i].func = function (checked) PartyQuests_Settings.showFailedBanner = checked; PartyQuests.Frame.ShowQuestLog(); end
	info[i].checked = PartyQuests_Settings.showFailedBanner;
	i=i+1;
	
	info[i] = { text = TEXT(PARTYQUESTS_MENU_SHOWDETAILEDTIP), keepShownOnClick = 1 };
	info[i].func = function (checked) PartyQuests_Settings.showDetailedTooltip = checked; PartyQuests.Frame.ShowQuestLog(); end
	info[i].checked = PartyQuests_Settings.showDetailedTooltip;
	i=i+1;
	
	info[i] = { text = PartyQuests.SettingsString[mtype], isTitle = true};
	i=i+1;
	
	info[i] = { text = TEXT(PARTYQUESTS_MENU_SHOWZONES), keepShownOnClick = 1 };
	info[i].func = function (checked) PartyQuests_Settings[mtype].zonesOn = checked; PartyQuests.Frame.ShowQuestLog(); end
	info[i].checked = PartyQuests_Settings[mtype].zonesOn;
	i=i+1;
	
	info[i] = { text = TEXT(PARTYQUESTS_MENU_SHOWLEVEL), keepShownOnClick = 1 };
	info[i].func = function (checked) PartyQuests_Settings[mtype].showLevel = checked; PartyQuests.Frame.ShowQuestLog(); end
	info[i].checked = PartyQuests_Settings[mtype].showLevel;
	i=i+1;
	
	if ( mtype ~= "party" ) then
		info[i] = { text = TEXT(PARTYQUESTS_MENU_SHOWSHARED), keepShownOnClick = 1 };
		info[i].func = function (checked) PartyQuests_Settings[mtype].showShared = checked; PartyQuests.Frame.ShowQuestLog(); end
		info[i].checked = PartyQuests_Settings[mtype].showShared;
		i=i+1;
	end
	
	-- Submenu 
	info[i] = { text = TEXT(PARTYQUESTS_SORTING), hasArrow = 1, value = 1};
	info[i][1] = { text = TEXT(PARTYQUESTS_SORTING), isTitle = 1 };
	info[i][2] = { text = TEXT(PARTYQUESTS_SORT_LEVEL), keepShownOnClick = false, func = function() PartyQuests_Settings[mtype].sortMethod = "level"; PartyQuests.Frame.ShowQuestLog(); end };
	info[i][3] = { text = TEXT(PARTYQUESTS_SORT_TITLE), keepShownOnClick = false, func = function() PartyQuests_Settings[mtype].sortMethod = "title"; PartyQuests.Frame.ShowQuestLog(); end };
	info[i][4] = { text = TEXT(PARTYQUESTS_SORT_ZONE),  keepShownOnClick = false, func = function() PartyQuests_Settings[mtype].sortMethod = "zone"; PartyQuests.Frame.ShowQuestLog(); end };
	i=i+1;
	-- Missing selection?

	return info;
end

--[[ Opens a Menu ]]--
--
function PartyQuests.Frame.OpenMenu(mtype)
	local menulist = PartyQuests.Frame.CreateMenu(mtype);
	EarthMenu_MenuOpen(menulist, this:GetName(), 0, 0, "MENU");
end

--[[ Periodically Update the Gui If Waiting ]]--
function PartyQuests.Frame.RefreshPartyGuiIfWaiting() 
	if ( Chronos.isScheduledByName("PartyQuestsWaitingUpdate" ) ) then 
		return; 
	end
	
	if ( PartyQuestsFrame.display and PartyQuestsFrame.display == "party" ) then 
		-- All this does right now is power the waiting spinner!
		PartyQuests.Frame.ShowQuestLog()
	end
	
	if ( PartyQuests.Telepathy.AmIWaiting() ) then 
		Chronos.scheduleByName("PartyQuestsWaitingUpdate", PARTYQUESTS_WAITING_UPDATE_DELAY, PartyQuests.Frame.RefreshPartyGuiIfWaiting );
	end
end


--[[ Detects if you're waiting for anyone ]]--
function PartyQuests.Telepathy.AmIWaiting()
	local waiting = false;
	for username, memberData in pairs(PartyQuests.PartyQuests) do
		if ( memberData.waiting and memberData.waiting == PARTYQUESTS_WAITING ) then 
			waiting = true;
		end
	end

	return waiting;
end

--[[ Start Waiting ]]--
function PartyQuests.Telepathy.StartWaiting(username)
	PartyQuests.PartyQuests[username].waiting = PARTYQUESTS_WAITING;

	if ( Chronos.isScheduledByName( "PartyQuestsWaitingOn"..username ) ) then 
		return; 
	end

	-- Register a failure timer
	Chronos.scheduleByName(
			       "PartyQuestsWaitingOn"..username, 
			       PARTYQUESTS_WAITING_FAILURE_DELAY,
			       function() 
				       if ( PartyQuests.PartyQuests[username] and
					   PartyQuests.PartyQuests[username].waiting and
					   PartyQuests.PartyQuests[username].waiting == PARTYQUESTS_WAITING ) then 
					       PartyQuests.PartyQuests[username].waiting = PARTYQUESTS_FAILED;
				       end
			       end
		       );
end

--[[ Telepathy-Version Callback ]]--
function PartyQuests.Telepathy.VersionCallback(name, vers)
	if (name == UnitName("player")) then
		return;
	end
	PartyQuests.InitializeUser(name);
	PartyQuests.PartyQuests[name].version = (vers.PartyQuests or false);
	--PartyQuests.Telepathy.MarkNo
	--PartyQuests.PartyQuests[username].noTelepathy
end


--[[
--	Configuration Stuff
--
--
--]]
function PartyQuests.Register.Earth()
	if ( EarthFeature_AddButton ) then 
		EarthFeature_AddButton ( 
					{
						id="PartyQuests";
						name=PARTYQUESTS_TITLE_TEXT;
						tooltip=PARTYQUESTS_BUTTON_TOOLTIP;
						icon="Interface\\Icons\\INV_Misc_Book_04";
						callback=PartyQuests.TogglePartyQuests;
					}
				);
	end
end

--[[
--
--	Communications Stuff
--
--
--
--]]
function PartyQuests.Register.Telepathy()
	Telepathy.registerListener(PARTYQUESTS_TELEPATHY_ID, {"PARTY", "RAID", "WHISPER"}, PartyQuests.Telepathy.readMessage);
	Telepathy.Versions.RegisterAddon(PARTYQUESTS_TELEPATHY_ID, PartyQuests.Version);
	Telepathy.Versions.RegisterCallback(PARTYQUESTS_TELEPATHY_ID, PartyQuests.Telepathy.VersionCallback);
end

function PartyQuests.Telepathy.readMessage(data, sender, method)
	if (type (data) ~= "table" or sender == UnitName("player")) then
		return;
	end
	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Receiving from ", sender, ": ");
	if ( getglobal(PARTYQUESTS_DEBUG_I) ) then 
		Sea.io.printTable(data);
	end
	local time = GetTime();
	if ( string.find(data.action, PARTYQUESTS_ANNOUNCE ) == 1 ) then 
		PartyQuests.RecordData( data, sender, time ); 
		PartyQuests.ReportAnnouncement( data, sender, time ); 
		PartyQuests.Frame.ShowQuestLog()
	elseif ( string.find(data.action, PARTYQUESTS_RECORD ) == 1 ) then 
		PartyQuests.RecordData( data, sender, time );
		PartyQuests.Frame.ShowQuestLog()
	elseif ( string.find(data.action, PARTYQUESTS_NACK ) == 1 ) then 
		PartyQuests.Telepathy.StopWaiting( data, sender, time );
		PartyQuests.Frame.ShowQuestLog()
	elseif ( string.find(data.action, PARTYQUESTS_REQUEST ) == 1 ) then 
		PartyQuests.HandleRequest( data, sender, time ); 
	end
end

--[[ Report Announcements ]]-- 
function PartyQuests.ReportAnnouncement(data, username, time)
	--Sea.io.printComma(username, time, GetTime(), PARTYQUESTS_BLOCK_UPDATES_OLDER_THAN, data.action);
	--if ( (GetTime() - time) > PARTYQUESTS_BLOCK_UPDATES_OLDER_THAN ) then
	--	return
	--end

	-- Announce Status Updates
	if ( data.action == PARTYQUESTS_ANNOUNCE_PROGRESS ) then 
		Sea.io.bannerc( PARTYQUESTS_BANNER_UPDATE_COLOR, string.format(PARTYQUESTS_QPROGRESS, username, data.info) ); 

		-- Erase status
		local qData = PartyQuests.GetQuestData(username, data.questTitle, data.questLevel ); 
		if ( qData ) then 
			qData.status = nil;
		end

	-- Announce Completions
	elseif ( data.action == PARTYQUESTS_ANNOUNCE_COMPLETE ) then 
		if ( data.questTitle and data.questLevel ) then
			if ( PartyQuests_Settings.showCompletedBanner ) then 
				Sea.io.bannerc( PARTYQUESTS_BANNER_COMPLETE_COLOR, 
					       string.format( PARTYQUESTS_XHASCOMPLETED, username, data.questTitle )
				       );
			end
			-- Check for completed
			local total = 0;
			local completed = 0;
			for player,v in pairs(PartyQuests.PartyQuests) do 
				local data = PartyQuests.GetQuestData(player, data.questTitle, data.questLevel );

				if ( data ) then 
					total = total + 1;
					if ( data.complete == 1 ) then
						completed = completed + 1; 
					end
				end
			end

			-- If everyone is done, announce it
			if ( total == completed and completed > 0 ) then 
				Sea.io.bannerc( PARTYQUESTS_BANNER_COMPLETE_COLOR, string.format(PARTYQUESTS_PARTYCOMPLETED, data.questTitle) );					
			end
		elseif ( data.objective ) then
			Sea.io.bannerc( PARTYQUESTS_BANNER_COMPLETED_COLOR, string.format ( PARTYQUESTS_OBJCOMPLETED, username, data.objectiveName ) );			
		end

	elseif ( data.action == PARTYQUESTS_ANNOUNCE_FAILED ) then 
		if ( data.questTitle and data.questLevel ) then
			if ( PartyQuests_Settings.showFailedBanner ) then 
				Sea.io.bannerc( PARTYQUESTS_BANNER_FAILED_COLOR, 
					       string.format( PARTYQUESTS_XHASFAILED, username, data.questTitle )
				       );
			end
			-- Check for failed
			local total = 0;
			local failed = 0;
			for player,v in pairs(PartyQuests.PartyQuests) do 
				local data = PartyQuests.GetQuestData(player, data.questTitle, data.questLevel );

				if ( data ) then 
					total = total + 1;
					if ( data.complete == -1 ) then
						failed = failed + 1; 
					end
				end
			end

			-- If everyone is done, announce it
			if ( total == failed and failed > 0 ) then 
				Sea.io.bannerc( PARTYQUESTS_BANNER_FAILED_COLOR, string.format(PARTYQUESTS_PARTYFAILED, data.questTitle) );					
			end
		end
	end
end

--[[ Record Data ]]--
function PartyQuests.RecordData(data, username, time)
	Sea.io.dprint(PARTYQUESTS_DEBUG, "Recording data from ", username, " at ", time);
	if ( string.find(data.action, PARTYQUESTS_ANNOUNCE) == 1 ) then 
		if ( data.action == PARTYQUESTS_ANNOUNCE_COMPLETE ) then 
			if ( data.questTitle and data.questLevel ) then 
				local questData = PartyQuests.GetQuestData(username, data.questTitle, data.questLevel ); 
				if ( questData ) then 
					questData.complete = 1;
				end
			end

		elseif ( data.action == PARTYQUESTS_ANNOUNCE_FAILED ) then 
			if ( data.questTitle and data.questLevel ) then 
				local questData = PartyQuests.GetQuestData(username, data.questTitle, data.questLevel ); 
				if ( questData ) then 
					questData.complete = -1;
				end
			end
		end

	elseif ( string.find(data.action, PARTYQUESTS_RECORD) == 1 ) then 
		if ( data.action == PARTYQUESTS_RECORD_LOG ) then
			if ( data.log ) then 
				PartyQuests.RecordQuestLog (username, data.log, time);
			end
		elseif ( data.action == PARTYQUESTS_RECORD_DETAILS ) then 
			if ( data.questTitle and data.questLevel ) then 
				if ( data.details ) then 
					PartyQuests.RecordQuestDetails (username, data.questTitle, data.questLevel, data.details);
				end			
			end
		elseif ( data.action == PARTYQUESTS_RECORD_ADD ) then 
			if ( data.quest and data.quest.zone ) then 
				PartyQuests.RecordQuestAddition (username, data.quest);
			end
		elseif ( data.action == PARTYQUESTS_RECORD_DELETE ) then 
			if ( data.questTitle and data.questLevel ) then 
				PartyQuests.RecordQuestDeletion (username, data.questTitle, data.questLevel);
			end
		elseif ( data.action == PARTYQUESTS_RECORD_STATUS ) then 
			if ( data.questTitle and data.questLevel and data.status ) then 
				PartyQuests.RecordQuestStatus (username, data.questTitle, data.questLevel, data.status);
			end
		end
	end
end

--[[ Handle Request ]]--
function PartyQuests.HandleRequest(data, username, time)
	if ( data.action == PARTYQUESTS_REQUEST_NEEDALL ) then 
		Chronos.scheduleByName("PartyQuestLogSend", 1, PartyQuests.Telepathy.SendQuestLogToParty());

	elseif ( data.action == PARTYQUESTS_REQUEST_NEED ) then 
		if ( Sea.list.isInList(data.need, UnitName("player") ) ) then
			Chronos.scheduleByName("PartyQuestLogSend", 1, PartyQuests.Telepathy.SendQuestLogToParty());
		end
	elseif ( data.action == PARTYQUESTS_REQUEST_STATUS ) then 
		if ( data.questTitle and data.questLevel ) then 
			PartyQuests.Telepathy.SendQuestStatusToPlayer(username, data.questTitle, data.questLevel);
		end
	elseif ( data.action == PARTYQUESTS_REQUEST_DETAILS ) then 
		if ( data.questTitle and data.questLevel ) then 
			PartyQuests.Telepathy.SendQuestDetailsToPlayer(username, data.questTitle, data.questLevel);
		end
	elseif ( data.action == PARTYQUESTS_REQUEST_SHARE ) then 
		if ( data.questTitle and data.questLevel ) then 
			PartyQuests.ShareQuest(data.questTitle, data.questLevel);
		end
	end
end

--[[ Stop Waiting for Data ]]--
function PartyQuests.Telepathy.StopWaiting(data, username, time)
	if ( not PartyQuests.PartyQuests[username] ) then
		PartyQuests.PartyQuests[username] = {};
	end
	PartyQuests.PartyQuests[username].waiting = PARTYQUESTS_FAILED;

	-- If we got naked, kill the quest
	if ( data.details == PARTYQUESTS_NOSUCHQUEST or data.status == PARTYQUESTS_NOSUCHQUEST ) then 
		local data = PartyQuests.GetQuestData(username, data.questTitle, data.questLevel );
		if ( data ) then 
			local zoneQuests = PartyQuests.PartyQuests[username].quests[data.zone];

			if ( zoneQuests ) then 
				for k,v in pairs(zoneQuests) do
					if ( v.title == data.questTitle and v.level == data.questLevel ) then 
						zoneQuests[k] = nil;
					end
				end
			end
		end
	end

	Sea.io.dprint(PARTYQUESTS_DEBUG, "Nack: User ", username, " does not have ", data.questTitle, " lvl ", data.questLevel );
end


--[[ Request Needed Logs ]]--
function PartyQuests.Telepathy.RequestNeededLogs()
	local numParty = GetNumPartyMembers();
	local numRaid = GetNumRaidMembers();	
	local needed = {};
	local data

	if (numParty > 0) or (numRaid > 0) then

		-- Debug only
		Sea.io.dprint(PARTYQUESTS_DEBUG, "Request Quest Logs from "..numParty.." party members...");
		local num, unit
		if (numRaid > 0) then
			num = numRaid;
			unit = "raid";
		else
			num = numParty;
			unit = "party";
		end
		local playerName = UnitName("player");
		for i=1, num do 
			local username = UnitName(unit..i)
			if (playerName ~= username) then
				PartyQuests.InitializeUser(username);
	
				if ((GetTime() - PartyQuests.PartyQuests[username].time > PARTYQUESTS_LOG_EXPIRATION  ) ) then 	
					-- Scan if they have PQ or not
					local vers = Telepathy.Versions.IsAddonUser(username, "PartyQuests");
					if ( vers ) then
						if ( not PartyQuests.PartyQuests[username].waiting or PartyQuests.PartyQuests[username].waiting == PARTYQUESTS_FAILED ) then 
							PartyQuests.Telepathy.StartWaiting(username);
							table.insert(needed, username);
						end
					elseif (vers == false) then
						--PartyQuests.Telepathy.MarkNo(username, true);
					end
				end
			end
		end

		-- Request those logs
		if ( #(needed) > 3 ) then 
			data = {};
			data.action = PARTYQUESTS_REQUEST_NEEDALL;

			Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action);
			-- Broadcast it
			Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "RAID", nil, "NORMAL");

			-- Start Refreshing
			PartyQuests.Frame.RefreshPartyGuiIfWaiting();

		elseif ( #(needed) > 0 ) then 
			data = {};
			data.action = PARTYQUESTS_REQUEST_NEED;
			data.need = needed;

			Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action);
			if ( getglobal(PARTYQUESTS_DEBUG_I) ) then 
				Sea.io.printTable(data.need);
			end

			-- Broadcast it
			Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "RAID", nil, "NORMAL");

			-- Start Refreshing
			PartyQuests.Frame.RefreshPartyGuiIfWaiting();
		end
	end
end

--[[ Request a single player's quest log ]]--
function PartyQuests.Telepathy.RequestLogFromPlayer(player)
	local data = {};
	data.action = PARTYQUESTS_REQUEST_NEED;
	data.need = {player};

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", player);
	if ( getglobal(PARTYQUESTS_DEBUG_I) ) then 
		Sea.io.printTable(data.need);
	end
	-- Whisper it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "WHISPER", player, "NORMAL");
end

--[[ Request Quest Details from a Player ]]--
function PartyQuests.Telepathy.RequestQuestDetailsFromPlayer(player, questTitle, questLevel)
	local data = {};
	data.action = PARTYQUESTS_REQUEST_DETAILS;
	data.questTitle = questTitle;
	data.questLevel = questLevel;

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", data.questLevel, " ", data.questTitle, " ", player);

	-- Whisper it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "WHISPER", player, "NORMAL");
end

--[[ Request Quest Status From a Player ]]--
function PartyQuests.Telepathy.RequestQuestStatusFromPlayer(player, questTitle, questLevel)
	local data = {};
	data.action = PARTYQUESTS_REQUEST_STATUS;
	data.questTitle = questTitle;
	data.questLevel = questLevel;

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", data.questLevel, " ", data.questTitle, " ", player);

	-- Whisper it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "WHISPER", player, "NORMAL");
end

--[[ Request Quest Status From a Player ]]--
function PartyQuests.Telepathy.RequestQuestShareFromPlayer(player, questTitle, questLevel)
	local data = {};
	data.action = PARTYQUESTS_REQUEST_SHARE;
	data.questTitle = questTitle;
	data.questLevel = questLevel;

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", data.questLevel, " ", data.questTitle, " ", player);

	-- Whisper it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "WHISPER", player, "NORMAL");
end

--[[
--
--	Communications Database Tools
--
--
--
--
--]]

--[[ Initializes a user if needed, otherwise does nothing ]]--
function PartyQuests.InitializeUser(username)
	if ( not PartyQuests.PartyQuests[username] ) then 
		PartyQuests.PartyQuests[username]  = {};
		PartyQuests.PartyQuests[username].time = 0;				
	end
end

--[[ Get a quest table ]]--
function PartyQuests.GetQuestData(username, questTitle, level ) 
	PartyQuests.InitializeUser(username);

	if ( not PartyQuests.PartyQuests[username].quests ) then 
		PartyQuests.PartyQuests[username].quests = {};
	end

	for zone,questList in pairs(PartyQuests.PartyQuests[username].quests) do
		-- Check, it might be collapsed, not a zone
		if ( type ( questList ) == "table" ) then 
			for i=1,#(questList) do 
				quest = questList[i];
				if ( type (quest) == "table" ) then 
					if ( quest.title == questTitle and quest.level == level ) then 
						return quest;
					end
				end
			end
		end
	end

	return nil;
end

--[[ Get a cached quest ]]--
function PartyQuests.GetCachedQuest(quest, level )
	if ( PartyQuests.PartyQuestsCache[quest] ) then 
		return PartyQuests.PartyQuestsCache[quest][level];
	end
	return nil;
end

--[[ Store a quest log ]]--
function PartyQuests.RecordQuestLog (username, questLog, time ) 
	PartyQuests.InitializeUser(username);

	if ( questLog ) then 
		PartyQuests.PartyQuests[username].quests = questLog;
		PartyQuests.PartyQuests[username].time = time;
		PartyQuests.PartyQuests[username].waiting = nil;
	end
	
	Sea.io.derror(PARTYQUESTS_DEBUG, "Recorded log for ", username);
end

--[[ Store a quest data cache ]]--
function PartyQuests.RecordQuestDetails (username, questTitle, questLevel, details)
	if ( not PartyQuests.PartyQuestsCache[questTitle] ) then 
		PartyQuests.PartyQuestsCache[questTitle] = {};
	end
	if ( not PartyQuests.PartyQuestsCache[questTitle][questLevel] ) then 
		PartyQuests.PartyQuestsCache[questTitle][questLevel] = {};
	end

	if ( details.objectives ) then 
		PartyQuests.RecordQuestStatus (username, questTitle, questLevel, details.objectives );
		details.objectives = nil;
	end

	-- Records the details
	PartyQuests.PartyQuestsCache[questTitle][questLevel] = details;
	PartyQuests.PartyQuestsCache[questTitle][questLevel].username = username;

	Sea.io.dprint(PARTYQUESTS_DEBUG, "Recorded Quest Details for ", questTitle, " (",questLevel,") from ", username);

	PartyQuests.CurrentQuest_SetFromCache(username, questTitle, questLevel, nil);
	PartyQuests.Frame.ShowCurrentQuest()
end


--[[ Store a quest data cache ]]--
function PartyQuests.RecordQuestAddition (username, quest)
	-- Erase the quest from their record
	PartyQuests.InitializeUser(username);

	if ( not PartyQuests.PartyQuests[username]["quests"] ) then 
		PartyQuests.PartyQuests[username]["quests"] = {};
	end

	if ( not PartyQuests.PartyQuests[username]["quests"][quest.zone] ) then 
		PartyQuests.PartyQuests[username]["quests"][quest.zone] = {};
	end

	table.insert(PartyQuests.PartyQuests[username]["quests"][quest.zone], quest);

	Sea.io.dprint(PARTYQUESTS_DEBUG, "Recorded Quest Addition for ", quest.title, " (",quest.level,") from ", username);
end

--[[ Delete a quest record ]]--
function PartyQuests.RecordQuestDeletion (username, questTitle, questLevel ) 
	-- Wipe out any details sent by that person
	if ( not PartyQuests.PartyQuestsCache ) then 
		PartyQuests.PartyQuestsCache = {};
	end
	if ( not PartyQuests.PartyQuestsCache[questTitle] ) then 
		PartyQuests.PartyQuestsCache[questTitle] = {};
	end
	if ( PartyQuests.PartyQuestsCache[questTitle][questLevel] ) then 
		if ( PartyQuests.PartyQuestsCache[questTitle][questLevel].username == username ) then 
			PartyQuests.PartyQuestsCache[questTitle][questLevel] = nil;
		end
		if ( #(PartyQuests.PartyQuestsCache[questTitle]) == 0 ) then 
			PartyQuests.PartyQuestsCache[questTitle] = nil;
		end
	end

	-- Erase the quest from their record
	PartyQuests.InitializeUser(username);

	if ( not PartyQuests.PartyQuests[username]["quests"] ) then 
		PartyQuests.PartyQuests[username]["quests"] = {};
	end

	-- Wipe out the quest entry
	for zone,questList in pairs(PartyQuests.PartyQuests[username].quests) do
		for i=1,#(questList) do 
			if ( type (questList[i]) == "table" ) then 
				if ( questList[i].title == questTitle and questList[i].level == questLevel ) then 
					questList[i] = nil;
				end
			end
		end
	end

	Sea.io.dprint(PARTYQUESTS_DEBUG, "Deleted Quest for ", questTitle, " (",questLevel,") from ", username);
end

--[[ Record Quest Status ]]--
function PartyQuests.RecordQuestStatus (username, questTitle, questLevel, status ) 
	PartyQuests.InitializeUser(username);

	if ( not PartyQuests.PartyQuests[username].quests ) then 
		PartyQuests.PartyQuests[username].quests = {};
	end

	local recorded = false;

	-- Scan their quests
	for zone,questList in pairs(PartyQuests.PartyQuests[username].quests) do
		if ( type ( questList ) == "table" ) then 
			for i=1,#(questList) do 
				quest = questList[i];
				if ( type (quest) == "table" ) then 
					if ( quest.title == questTitle and quest.level == questLevel ) then 
						questList[i].status = status;
						recorded = true;
						break;
					end
				end
			end
		end
	end

	-- Clear the waiting flag
	PartyQuests.PartyQuests[username].waiting = nil;
	-- Refresh QuestDetailView now we got new data.
	PartyQuests.Frame.ShowCurrentQuest()
	
	Sea.io.dprint(PARTYQUESTS_DEBUG, "Recorded Quest Status for ", questTitle, " (",questLevel,") from ", username, " ? ", recorded);
end

--
-- Outgoing
-- 

--[[ Send an update on all objectives for that quest ]]--
function PartyQuests.Telepathy.SendQuestStatusToPlayer(username, questTitle, questLevel )
	Sea.io.dprint(PARTYQUESTS_DEBUG, "Sending Quest Status To Player ", username);
	local id = PartyQuests.MatchQuestToPlayerQuestID(questTitle, questLevel );
	local details = PartyQuests.Player.GetQuestInfo(id);

	local data = {};
	
	-- If such a quest exists, send only objectives
	if ( details ) then 
		data.action = PARTYQUESTS_RECORD_STATUS;
		data.questTitle = details.title;
		data.questLevel = details.level;
		data.status = details.objectives; 
	else
		data.action = PARTYQUESTS_NACK_STATUS;
		data.questTitle = questTitle;
		data.questLevel = questLevel; 
		data.status = PARTYQUESTS_NOSUCHQUEST;
	end

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", data.questLevel, " ", data.questTitle, " ", username);
	if ( getglobal(PARTYQUESTS_DEBUG_I) ) then 
		if (type (data.status) == "table" ) then
			Sea.io.printTable(data.status);
		else
			Sea.io.dprint(PARTYQUESTS_DEBUG_I, data.status);
		end
	end

	-- Queue it up
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "WHISPER", username, "NORMAL");
end

--[[ Send an update on all objectives for that quest ]]--
function PartyQuests.Telepathy.SendQuestStatusToParty(questTitle, questLevel )
	Sea.io.dprint(PARTYQUESTS_DEBUG, "Sending Quest Status To Party");
	local id = PartyQuests.MatchQuestToPlayerQuestID(questTitle, questLevel );
	local details = PartyQuests.Player.GetQuestInfo(id);

	local data = {};

	-- If such a quest exists, send only objectives
	if ( details ) then 
		data.action = PARTYQUESTS_RECORD_STATUS;
		data.questTitle = details.title;
		data.questLevel = details.level;
		data.status = details.objectives; 
	else
		data.action = PARTYQUESTS_NACK_STATUS;
		data.questTitle = questTitle;
		data.questLevel = questLevel; 
		data.status = PARTYQUESTS_NOSUCHQUEST;
	end

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", data.questLevel, " ", data.questTitle);
	if ( getglobal(PARTYQUESTS_DEBUG_I) ) then 
		if (type (data.status) == "table" ) then
			Sea.io.printTable(data.status);
		else
			Sea.io.dprint(PARTYQUESTS_DEBUG_I, data.status);
		end
	end
	-- Queue it up
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "RAID", nil, "NORMAL");
end

--[[ Send a full list of the details required to display that quest ]]-- 
function PartyQuests.Telepathy.SendQuestDetailsToPlayer(username, questTitle, questLevel )
	Sea.io.dprint(PARTYQUESTS_DEBUG, "Sending Quest Details To Player ", username );
	local id = PartyQuests.MatchQuestToPlayerQuestID(questTitle, questLevel );
	local details = PartyQuests.Player.GetQuestInfo(id);

	local data = {};

	-- If such a quest exists, send only objectives
	if ( details ) then 
		data.action = PARTYQUESTS_RECORD_DETAILS;
		data.questTitle = details.title;
		data.questLevel = details.level;
		data.details = details; 
	else
		data.action = PARTYQUESTS_NACK_DETAILS;
		data.questTitle = questTitle;
		data.questLevel = questLevel; 
		data.details = PARTYQUESTS_NOSUCHQUEST;
	end

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", data.questLevel, " ", data.questTitle, " ", username);
	if ( getglobal(PARTYQUESTS_DEBUG_I) ) then 
		if (type (data.status) == "table" ) then
			Sea.io.printTable(data.details);
		else
			Sea.io.dprint(PARTYQUESTS_DEBUG_I, data.details);
		end
	end

	-- Queue it up
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "WHISPER", username, "BULK");
end

--[[ Send a full quest log to the specified player ]]--
function PartyQuests.SendQuestLogToPlayer(username)
	local log = PartyQuests.Player.GetQuestTree();

	local data = {};
	data.action = PARTYQUESTS_RECORD_LOG;
	data.log = log;

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", username);
	if ( getglobal(PARTYQUESTS_DEBUG_I) ) then 
		Sea.io.printTable(data.log);
	end

	-- Whisper it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "WHISPER", username, "BULK");
end

--[[ Send a full quest log to the party ]]--
function PartyQuests.Telepathy.SendQuestLogToParty()
	local log = PartyQuests.Player.GetQuestTree();

	local data = {};
	data.action = PARTYQUESTS_RECORD_LOG;
	data.log = log;

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action);
	if ( getglobal(PARTYQUESTS_DEBUG_I) ) then 
		Sea.io.printTable(data.log);
	end
	-- Broadcast it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "RAID", nil, "BULK");
end

--[[ Send Quest Progress Updates ]]--
function PartyQuests.Telepathy.SendQuestProgressUpdateToParty(updateText)
	local data = {};
	data.action = PARTYQUESTS_ANNOUNCE_PROGRESS;
	data.info = updateText;

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", data.info);
	-- Broadcast it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "RAID", nil, "NORMAL");
end

--[[ Send Quest Completion Notice ]]-- 
function PartyQuests.Telepathy.SendQuestCompleteUpdateToParty(questTitle, questLevel)
	local data = {};
	data.action = PARTYQUESTS_ANNOUNCE_COMPLETE;
	data.questTitle = questTitle;
	data.questLevel = questLevel;

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", data.questLevel, " ", data.questTitle);
	-- Broadcast it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "RAID", nil, "NORMAL");
end

--[[ Send Quest Failed Notice ]]-- 
function PartyQuests.Telepathy.SendQuestFailedUpdateToParty(questTitle, questLevel)
	local data = {};
	data.action = PARTYQUESTS_ANNOUNCE_FAILED;
	data.questTitle = questTitle;
	data.questLevel = questLevel;

	-- Broadcast it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "RAID", nil, "NORMAL");
end

--[[ Send Quest Deletion Notice ]]-- 
function PartyQuests.Telepathy.SendQuestDeletionUpdateToParty(questTitle, questLevel)
	local data = {};
	data.action = PARTYQUESTS_RECORD_DELETE;
	data.questTitle = questTitle;
	data.questLevel = questLevel;

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", data.questLevel, " ", data.questTitle);
	-- Broadcast it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "RAID", nil, "NORMAL");
end

--[[ Send Quest Deletion Notice ]]-- 
function PartyQuests.Telepathy.SendQuestAdditionUpdateToParty(quest)
	local data = {};
	data.action = PARTYQUESTS_RECORD_ADD;
	data.quest = quest;

	Sea.io.dprint(PARTYQUESTS_DEBUG_I, "Sending ", data.action, " ", data.zone);
	if ( getglobal(PARTYQUESTS_DEBUG_I) ) then 
		Sea.io.printTable(data.quest);
	end
	-- Broadcast it
	Telepathy.sendTable(PARTYQUESTS_TELEPATHY_ID, data, "RAID", nil, "NORMAL");
end

function PartyQuests.ShareQuest(questTitle, questLevel)
	local id = PartyQuests.MatchQuestToPlayerQuestID(questTitle, questLevel );
	if (id) then
		PartyQuests.ShareQuestByID(id);
	end
end

function PartyQuests.ShareQuestByID(id)
	if ( not Sea.wow.questLog.protectQuestLog() ) or (not id) then 
		return false; 
	end

	-- Expand everything
	ExpandQuestHeader(0);

	-- Select it
	SelectQuestLogEntry(id);	

	QuestLogPushQuest();

	-- Unprotect
	Sea.wow.questLog.unprotectQuestLog();
end


--
--
--	Gui Updating Functions Here
--
--
--

function PartyQuests.Frame.ShowCurrentQuest()
	if ( PartyQuests.CurrentQuest and PartyQuests.CurrentQuest.visible ) then 
		if ( PartyQuests.CurrentQuest.quest) then 
			local id = PartyQuests.MatchQuestToPlayerQuestID(PartyQuests.CurrentQuest.quest.title, PartyQuests.CurrentQuest.quest.level);
			if ( not PartyQuests.CurrentQuest.quest.id ) or ( not id ) then 
				PartyQuestsLogFrameAbandonButton:Disable();
			else
				PartyQuestsLogFrameAbandonButton:Enable();
			end
			if ( PartyQuests.CurrentQuest.quest.pushable and GetNumPartyMembers() > 0) then
				PartyQuestsLogFrameShareButton:Enable();
			else
				PartyQuestsLogFrameShareButton:Disable();
			end

			Sea.io.dprint(PARTYQUESTS_DEBUG, "Reloading Quest...");

			-- Load the quest and update
			EarthQuestLog_LoadQuest("PartyQuestsLog", PartyQuests.CurrentQuest.quest);
			EarthQuestLog_Update("PartyQuestsLog");

			-- Show the other frame
			if ( PartyQuestsFrame:IsVisible() ) then 
				ShowUIPanel(getglobal("PartyQuestsLogFrame"));
			end
		else
			EarthQuestLog_Clear("PartyQuestsLog");
		end
	else
		EarthQuestLog_Clear("PartyQuestsLog");
		HideUIPanel(getglobal("PartyQuestsLogFrame"));
	end
end

function PartyQuests.Libram.callback( action, questInfo, updateInfo ) 
	local updateTree = false;
	
	if ( action == LIBRAM_QUEST_INIT ) then 
		updateTree = true;

	elseif ( action == LIBRAM_QUEST_COMPLETED ) then
		PartyQuests.Telepathy.SendQuestCompleteUpdateToParty(questInfo.title, questInfo.level);

		-- Show "You have completed Quest Of Doom" messages
		if ( PartyQuests_Settings.showCompletedBanner ) then 
			Sea.io.bannerc( PARTYQUESTS_BANNER_COMPLETE_COLOR, 
				       string.format( PARTYQUESTS_YOUHAVECOMPLETED,  questInfo.title )
			       );
			PartyQuests.Frame.ShowQuestLog()
		end
		updateTree = true;

	elseif ( action == LIBRAM_QUEST_FAILED ) then
		PartyQuests.Telepathy.SendQuestFailedUpdateToParty(questInfo.title, questInfo.level);

		-- Show "You have failed Quest Of Doom" messages
		if ( PartyQuests_Settings.showFailedBanner ) then 
			Sea.io.bannerc( PARTYQUESTS_BANNER_FAILED_COLOR, 
				       string.format( PARTYQUESTS_YOUHAVEFAILED,  questInfo.title )
			       );
			PartyQuests.Frame.ShowQuestLog()
		end
		updateTree = true;

	elseif ( action == LIBRAM_QUEST_ADD ) then 
		PartyQuests.Telepathy.SendQuestAdditionUpdateToParty(questInfo.newQuest);
		PartyQuests.Frame.ShowQuestLog()
		updateTree = true;

	elseif ( action == LIBRAM_QUEST_REMOVE ) then 
		PartyQuests.Telepathy.SendQuestDeletionUpdateToParty(questInfo.title, questInfo.level);

		if ( PartyQuests.CurrentQuest and PartyQuests.CurrentQuest.quest) then 
			if ( PartyQuests.CurrentQuest.quest.id == questInfo.id ) then 
				PartyQuests.CurrentQuest.quest = nil;
				updateCurrentQuest = true;
			end
		end
		updateTree = true;

	elseif ( action == LIBRAM_QUEST_UPDATE ) then 
		PartyQuests.Telepathy.SendQuestStatusToParty(questInfo.title, questInfo.level );

		if ( PartyQuests.CurrentQuest and PartyQuests.CurrentQuest.quest) then
			if ( PartyQuests.CurrentQuest.quest.id == questInfo.id ) then 
				PartyQuests.CurrentQuest.quest = questInfo;
				updateCurrentQuest = true;
			end
		end

	elseif ( action == LIBRAM_QUEST_TIMER ) then 
		updateCurrentQuest = true;

	elseif ( action == LIBRAM_QUEST_CHANGED ) then 
		if ( PartyQuests.CurrentQuest and PartyQuests.CurrentQuest.quest) then 
			if ( PartyQuests.CurrentQuest.quest.id == updateInfo.oldid ) then 
				PartyQuests.CurrentQuest.quest.id = updateInfo.newid;
			end
		end
		updateTree = true;

	end

	if ( updateTree ) then
		if ( not Chronos.isScheduledByName("PartyQuestScholarQuestLogRefresh") ) then
			Chronos.scheduleByName(
					       "PartyQuestScholarQuestLogRefresh", 
					       PARTYQUESTS_GUI_REFRESH_DELAY, 
					       PartyQuests.Frame.ShowQuestLog
				       );
		end
	end

	if ( updateCurrentQuest ) then 
		if ( not Chronos.isScheduledByName("PartyQuestScholarQuestRefresh") ) then
			Chronos.scheduleByName(
					       "PartyQuestScholarQuestRefresh", 
					       PARTYQUESTS_GUI_REFRESH_DELAY, 
					       PartyQuests.Frame.ShowCurrentQuest
				       );
		end
	end
end

--[[ Shows the Quest Log ]]--
function PartyQuests.Frame.ShowQuestLog()
	-- Do nothing if we don't have a window to update
	if not PartyQuestsFrame:IsShown() then
		return
	end

	-- Set the total quests
	local numEntries, numQuests = GetNumQuestLogEntries()
	PartyQuestsFrameQuestCount:SetText(string.format(QUEST_LOG_COUNT_TEMPLATE, numQuests, MAX_QUESTLOG_QUESTS));
	PartyQuestsFrameCountMiddle:SetWidth(PartyQuestsFrameQuestCount:GetWidth());

	if ( PartyQuestsFrame.display == "self" ) then
		local tree = PartyQuests.Player.GetQuestTree()
		local eTree = PartyQuests.Player.ConvertQuestTreeToEnhancedTree(tree)

		Sea.io.dprint(PARTYQUESTS_DEBUG, "Loading self data... ")

		-- Update the live tree
		if ( eTree.children ) then 
			EarthTree_LoadEnhanced(PartyQuestsFrameTree, eTree.children)
		else
			EarthTree_LoadEnhanced(PartyQuestsFrameTree, {})
		end

	elseif ( PartyQuestsFrame.display == "party" ) then 
		local ctree = nil
		local ecTree = nil
		local fullTree = {}

		-- Add Common
		ctree = PartyQuests.Common.GetQuestsTree()
		ecTree = PartyQuests.Common.ConvertQuestTreeToEnhancedTree(ctree)

		table.insert(fullTree, ecTree )

		-- Add Player
		local ptree = PartyQuests.Player.GetQuestTree()
		local epTree = PartyQuests.Player.ConvertQuestTreeToEnhancedTree(ptree)

		table.insert(fullTree, epTree )

		Sea.io.dprint(PARTYQUESTS_DEBUG, "Loading party data...")

		-- Get all party member quests
		tree = PartyQuests.Party.GetQuestTree()
		if ( tree ) then
			-- Make eTrees for everyone
			for k,partyMember in pairs(tree) do
				if ( type (partyMember) == "table" ) then 
					local eat = PartyQuests.Ally.ConvertQuestTreeToEnhancedTree( k, partyMember )
					if ( eat ) then
						table.insert(fullTree, eat)
					end
				end
			end
		end

		-- Update the live tree
		EarthTree_LoadEnhanced(PartyQuestsFrameTree, fullTree)
	end
	EarthTree_UpdateFrame(PartyQuestsFrameTree)
end


--[[
--
--	XML Event Handlers Below this point
--
--	Do Not Enter!
--
--]]

--------------------------------------------------
-- Help Icon
--------------------------------------------------
function PartyQuests.Frame.HelpIcon_OnLoad()
	this:SetScript("OnEnter", PartyQuests.Frame.HelpIcon_OnEnter);
	this:SetScript("OnLeave", PartyQuests.Frame.HelpIcon_OnLeave);
end

function PartyQuests.Frame.HelpIcon_OnEnter()
	EarthTooltip:SetOwner(this, "ANCHOR_RIGHT");
	EarthTooltip:SetText(PARTYQUESTS_HELP_TIP);
end	

function PartyQuests.Frame.HelpIcon_OnLeave()
	EarthTooltip:Hide();
end


-- Register with Khaos
function PartyQuests.Register.Khaos()
	if ( Khaos ) then
		Khaos.registerOptionSet(
			"quest",
			{
				id="PartyQuests";
				text=PARTYQUESTS_TITLE_TEXT;
				helptext=PARTYQUESTS_HELPTEXT;
				difficulty=1;
				default=true;
				options={
					{
						id="ReplaceQuestLog";
						text=PARTYQUESTS_REPLACE_TEXT;
						difficulty=1;
						helptext=PARTYQUESTS_REPLACE_HELPTEXT;
						callback=PartyQuests.ReplaceDefaultQuestWindow;
						feedback=PartyQuests.ReplacedDefaultFeedback;
						check=true;
						type=K_TEXT;
						default={
							checked=true;
						};
						disabled={
							checked=false;
						};
					}
				};
			}
		);
	end
end

-- Register with Meteorologist
function PartyQuests.Register.Meteorologist()
	if ( Meteorologist_RegisterAddon ) then
		Meteorologist_RegisterAddon( PARTYQUESTS_TITLE_TEXT, TogglePartyQuests );
	end
end


--[[ Removes the Party log button ]]--
function PartyQuests.RemovePartyButton()
	QuestLogFramePartyButton:Hide();
	QuestLogFrameAbandonButton:SetWidth(125);
	QuestLogFrameAbandonButton:SetText(ABANDON_QUEST);
		
	QuestFramePushQuestButton:ClearAllPoints();
	QuestFramePushQuestButton:SetPoint("RIGHT","QuestFrameExitButton","LEFT",0,0);
	QuestFramePushQuestButton:SetWidth(123);
	QuestFramePushQuestButton:SetText(SHARE_QUEST);
end

--[[ Tabs ]]--
function PartyQuests.Frame.SelectTab(id)
	if ( id == 1 ) then 
		PartyQuests.Player.DisplayQuests();
	else
		PartyQuests.Party.DisplayQuests();
	end
end

function PartyQuests.Party.DisplayQuests()
	PartyQuestsFrame.display = "party";
	PartyQuests.Telepathy.RequestNeededLogs()
	PartyQuests.Frame.ShowQuestLog()
end

function PartyQuests.Player.DisplayQuests()
	PartyQuestsFrame.display = "self"
	PartyQuests.Frame.ShowQuestLog()
end

-- Set the Icon for the Log Frame
function PartyQuests.Frame.LogFrame_OnLoad()
	-- Erase the log when you close it.
	this:SetScript("OnHide",
		function()
			UpdateMicroButtons();
			if ( PartyQuestsFrame:IsVisible() ) then
				PartyQuests.CurrentQuest = nil;
			else
				PlaySound("igQuestLogClose");
			end
		end
	);
	
	-- Make us pushable
	UIPanelWindows[this:GetName()] =		{ area = "left",	pushable = 6 };

	--
	-- Set the icon to a boring default
	-- 
	getglobal(this:GetName().."MainIcon"):SetTexture("Interface\\QuestFrame\\UI-QuestLog-BookIcon");
	getglobal(this:GetName().."TitleText"):SetText(PARTYQUESTS_QUESTINFO_TEXT);
end

--[[ Log Frame Abandon Button ]]--
function PartyQuests.Frame.LogFrameAbandon_OnLoad()
	this:SetScript("OnClick", PartyQuests.Frame.LogFrameAbandon_OnClick);
end

function PartyQuests.Frame.LogFrameAbandon_OnClick()
	local id = PartyQuests.MatchQuestToPlayerQuestID(PartyQuestsLog.questInfo.title, PartyQuestsLog.questInfo.level);
	if ( ( not id ) or ( not Sea.wow.questLog.protectQuestLog() ) ) then 
		return false;
	end

	-- Expand everything
	ExpandQuestHeader(0);

	if ( PartyQuestsLog.questInfo ) then 	
		-- Select it
		SelectQuestLogEntry(id);	
	
		SetAbandonQuest();
		StaticPopup_Show("ABANDON_QUEST", GetAbandonQuestName());
	end

	-- Unprotect
	Sea.wow.questLog.unprotectQuestLog();
	
	if (PartyQuestsLogFrame:IsVisible()) then
		HideUIPanel( PartyQuestsLogFrame );
	end
end

--[[ Log Frame Share Button ]]--
function PartyQuests.Frame.LogFrameShare_OnLoad()
	this:SetScript("OnClick", PartyQuests.Frame.LogFrameShare_OnClick);
end

function PartyQuests.Frame.LogFrameShare_OnClick()
	if ( PartyQuestsLog.questInfo ) then 
		if (PartyQuests.MatchQuestToPlayerQuestID(PartyQuestsLog.questInfo.title, PartyQuestsLog.questInfo.level)) then
			PartyQuests.ShareQuestByID(PartyQuestsLog.questInfo.id);
		else
			PartyQuests.Telepathy.RequestQuestShareFromPlayer(PartyQuestsLog.questInfo.username, PartyQuestsLog.questInfo.title, PartyQuestsLog.questInfo.level)
		end
	end
end

--[[ Old Log Frame Party Button ]]--
function PartyQuests.Frame.QuestLogFramePartyButton_OnLoad()
	this:SetScript("OnEnter", PartyQuests.Frame.QuestLogFramePartyButton_OnEnter);
	this:SetScript("OnLeave", PartyQuests.Frame.QuestLogFramePartyButton_OnLeave);
	this:SetScript("OnClick", PartyQuests.Frame.QuestLogFramePartyButton_OnClick);
end

function PartyQuests.Frame.QuestLogFramePartyButton_OnEnter()
	GameTooltip:SetOwner(this,"ANCHOR_RIGHT");
	GameTooltip:SetText(TEXT(PARTYQUESTS_BUTTON_TOOLTIP), 1.0, 1.0, 1.0);
end

function PartyQuests.Frame.QuestLogFramePartyButton_OnLeave()
	GameTooltip:Hide();
end

function PartyQuests.Frame.QuestLogFramePartyButton_OnClick()
	PartyQuests.TogglePartyQuests();
end

--[[ On and off ]]--
function PartyQuests.TogglePartyQuests() 
	if ( PartyQuestsFrame:IsVisible() ) then 
		HideUIPanel(PartyQuestsFrame);
		HideUIPanel(PartyQuestsLogFrame); 		
	elseif ( PartyQuestsLogFrame:IsVisible() ) then 
		HideUIPanel(PartyQuestsLogFrame);	
	else
		ShowUIPanel(PartyQuestsFrame);
	end		
end

function PartyQuests.UpdateMicroButtons()
	if ( PartyQuestsFrame:IsVisible() or PartyQuestsLogFrame:IsVisible() or QuestLogFrame:IsVisible() ) then
		QuestLogMicroButton:SetButtonState("PUSHED", 1);
	else
		QuestLogMicroButton:SetButtonState("NORMAL");
	end
end

function PartyQuests.ReplaceDefaultQuestWindow ( state )
	if ( state.checked ) then 
		Sea.util.hook("ToggleQuestLog", "PartyQuests.TogglePartyQuests", "replace");
		Sea.util.hook("UpdateMicroButtons", "PartyQuests.UpdateMicroButtons", "after");
	else
		Sea.util.unhook("ToggleQuestLog", "PartyQuests.TogglePartyQuests", "replace");
		Sea.util.unhook("UpdateMicroButtons", "PartyQuests.UpdateMicroButtons", "after");
	end
end

function PartyQuests.ReplacedDefaultFeedback ( state ) 
	if ( state.checked ) then 
		return PARTYQUESTS_REPLACE_FEEDBACK_TRUE;
	else
		return PARTYQUESTS_REPLACE_FEEDBACK_FALSE;
	end
end


--[[To fix a bug with double display]]--
PartyQuests.OldAddQuestWatch = AddQuestWatch
function PartyQuests.NewAddQuestWatch(index)
    if IsQuestWatched(index) then
        return
    else
        PartyQuests.OldAddQuestWatch(index)
    end
end

AddQuestWatch = PartyQuests.NewAddQuestWatch