local tablet = AceLibrary("Tablet-2.0")
local compost = AceLibrary("Compost-2.0")
local deformat = AceLibrary("Deformat-2.0")
local babbleclass = AceLibrary("Babble-Class-2.0")
local crayon = AceLibrary("Crayon-2.0")
local tourist = AceLibrary("Tourist-2.0")

local L = AceLibrary("AceLocale-2.0"):new("QuestsFu")

QuestsFu = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceHook-2.0", "AceConsole-2.0", "AceDB-2.0", "FuBarPlugin-2.0")
QuestsFu:RegisterDB("QuestsFuDB")
QuestsFu:RegisterDefaults('profile', {
	showLevelsGame = true,
	showLevelsTablet = true,
	showLevelsZone = true,
	showDifficulty = true,
	showImpossible = true,
	showArea = true,
	showCurrentAreaOnly = false,
	showClassAnyway = true,
	showCurrentAreaDescriptionOnly = false,
	showDescription = true,
	showCompletedObjectives = false,
	showInItemTooltip = true,
	colorObjectives = true,
	wrapQuests = true,
	showTextOpt = {
		total = true,
		current = true,
		complete = false,
		lastmessage = true,
	},
})
QuestsFu:RegisterDefaults('char', {
	hidden = {},
	watchedQuests = {},
})

QuestsFu.version = "2.0." .. string.sub("$Revision: 11583 $", 12, -3)
QuestsFu.date = string.sub("$Date: 2006-09-20 21:07:20 -0400 (Wed, 20 Sep 2006) $", 8, 17)
QuestsFu.hasIcon = true
QuestsFu.clickableTooltip = true
QuestsFu.cannotHideText = true

local optionsTable = {
	handler = QuestsFu,
	type = 'group',
	args = {
		show = {
			type = 'group', 
			name = L["OPT_show"],
			desc = L["OPT_show_d"],
			args = {
				text = {
					type = 'group', 
					name = L["OPT_text"],
					desc = L["OPT_text_d"],
					args = {
						current = {
							type = 'toggle', 
							name = L["OPT_current"],
							desc = L["OPT_current_d"],
							get = "IsShowingTextCurrent",
							set = "ToggleShowingTextCurrent",
						},
						complete = {
							type = 'toggle', 
							name = L["OPT_complete"],
							desc = L["OPT_complete_d"],
							get = "IsShowingTextComplete",
							set = "ToggleShowingTextComplete",
						},
						total = {
							type = 'toggle', 
							name = L["OPT_total"],
							desc = L["OPT_total_d"],
							get = "IsShowingTextTotal",
							set = "ToggleShowingTextTotal",
						},
						lastmessage = {
							type = 'toggle', 
							name = L["OPT_lastmessage"],
							desc = L["OPT_lastmessage_d"],
							get = "IsShowingTextLastMessage",
							set = "ToggleShowingTextLastMessage",
						},
					}
				},
				levels = {
					type = 'group',
					name = L["OPT_levels"],
					desc = L["OPT_levels_d"],
					args = {
						game = {
							type = "toggle", name = L["OPT_levels_game"],
							desc = L["OPT_levels_game_d"],
							get = "IsShowingLevelsGame",
							set = "ToggleShowingLevelsGame",
						},
						tablet = {
							type = "toggle", name = L["OPT_levels_tablet"],
							desc = L["OPT_levels_tablet_d"],
							get = "IsShowingLevelsTablet",
							set = "ToggleShowingLevelsTablet",
						},
						zone = {
							type = "toggle", name = L["OPT_levels_zone"],
							desc = L["OPT_levels_zone_d"],
							get = "IsShowingLevelsZone",
							set = "ToggleShowingLevelsZone",
						},
					},
				},
				diff = {
					type = 'toggle', 
					name = L["OPT_diff"],
					desc = L["OPT_diff_d"],
					get = "IsShowingDifficulty",
					set = "ToggleShowingDifficulty",
				},
				impossible = {
					type = 'toggle', 
					name = L["OPT_impossible"],
					desc = L["OPT_impossible_d"],
					get = "IsShowingImpossible",
					set = "ToggleShowingImpossible",
				},
				area = {
					type = 'toggle', 
					name = L["OPT_area"],
					desc = L["OPT_area_d"],
					get = "IsShowingArea",
					set = "ToggleShowingArea",
				},
				caonly = {
					type = 'toggle', 
					name = L["OPT_caonly"],
					desc = L["OPT_caonly_d"],
					get = "IsShowingCurrentAreaOnly",
					set = "ToggleShowingCurrentAreaOnly",
				},
				classanyway = {
					type = 'toggle', 
					name = L["OPT_classanyway"],
					desc = L["OPT_classanyway_d"],
					get = "IsShowingClassAnyway",
					set = "ToggleShowingClassAnyway",
				},
				description = {
					type = 'toggle', 
					name = L["OPT_description"],
					desc = L["OPT_description_d"],
					get = "IsShowingDescription",
					set = "ToggleShowingDescription",
				},
				cadonly = {
					type = 'toggle', 
					name = L["OPT_cadonly"],
					desc = L["OPT_cadonly_d"],
					get = "IsShowingCurrentAreaDescriptionOnly",
					set = "ToggleShowingCurrentAreaDescriptionOnly",
				},
				completed= {
					type = 'toggle', 
					name = L["OPT_completed"],
					desc = L["OPT_completed_d"],
					get = "IsShowingCompletedObjectives",
					set = "ToggleShowingCompletedObjectives",
				},
				tooltip = {
					type = 'toggle', 
					name = L["OPT_tooltip"],
					desc = L["OPT_tooltip_d"],
					get = "IsShowingInItemTooltip",
					set = "ToggleShowingInItemTooltip",
				},
				colorobj = {
					type = 'toggle', 
					name = L["OPT_colorobj"],
					desc = L["OPT_colorobj_d"],
					get = "IsColoringObjectives",
					set = "ToggleColoringObjectives",
				},
				wrap = {
					type = 'toggle', 
					name = L["OPT_wrap"],
					desc = L["OPT_wrap_d"],
					get = "IsWrappingQuests",
					set = "ToggleWrappingQuests",
				},
			}
		}
	}
}

QuestsFu:RegisterChatCommand({"/questsfu", "/fuq" }, optionsTable)
QuestsFu.OnMenuRequest = optionsTable

function QuestsFu:ToggleShowingTextComplete()
	self.db.profile.showTextOpt.complete = not self.db.profile.showTextOpt.complete
	self:UpdateText()
	return self.db.profile.showTextOpt.complete
end
function QuestsFu:ToggleShowingTextCurrent()
	self.db.profile.showTextOpt.current = not self.db.profile.showTextOpt.current
	self:UpdateText()
	return self.db.profile.showTextOpt.current
end
function QuestsFu:ToggleShowingTextTotal()
	self.db.profile.showTextOpt.total = not self.db.profile.showTextOpt.total
	self:UpdateText()
	return self.db.profile.showTextOpt.total
end
function QuestsFu:ToggleShowingTextLastMessage()
	self.db.profile.showTextOpt.lastmessage = not self.db.profile.showTextOpt.lastmessage
	self:UpdateText()
	return self.db.profile.showTextOpt.lastmessage
end

function QuestsFu:IsShowingTextComplete()
	return self.db.profile.showTextOpt.complete
end
function QuestsFu:IsShowingTextCurrent()
	return self.db.profile.showTextOpt.current
end
function QuestsFu:IsShowingTextTotal()
	return self.db.profile.showTextOpt.total
end
function QuestsFu:IsShowingTextLastMessage()
	return self.db.profile.showTextOpt.lastmessage
end

function QuestsFu:IsShowingLevelsGame()
	return self.db.profile.showLevelsGame
end

function QuestsFu:IsShowingLevelsTablet()
	return self.db.profile.showLevelsTablet
end

function QuestsFu:IsShowingLevelsZone()
	return self.db.profile.showLevelsZone
end

function QuestsFu:ToggleShowingLevelsGame(silent)
	self.db.profile.showLevelsGame = not self.db.profile.showLevelsGame
	if self.db.profile.showLevelsGame then
		self:RegisterEvent("GOSSIP_SHOW", "OnGossipShow")
		self:RegisterEvent("QUEST_GREETING", "OnQuestGreeting")
		self:Hook("GetQuestLogTitle")
	else
		self:UnregisterEvent("GOSSIP_SHOW", "OnGossipShow")
		self:UnregisterEvent("QUEST_GREETING", "OnQuestGreeting")
		self:Unhook("GetQuestLogTitle")
	end
	self:UpdateData()
	return self.db.profile.showLevelsGame
end

function QuestsFu:ToggleShowingLevelsTablet(silent)
	self.db.profile.showLevelsTablet = not self.db.profile.showLevelsTablet
	self:UpdateTooltip()
	return self.db.profile.showLevelsTablet
end

function QuestsFu:ToggleShowingLevelsZone(silent)
	self.db.profile.showLevelsZone = not self.db.profile.showLevelsZone
	self:UpdateTooltip()
	return self.db.profile.showLevelsZone
end

function QuestsFu:IsShowingDifficulty()
	return self.db.profile.showDifficulty
end

function QuestsFu:ToggleShowingDifficulty(silent)
	self.db.profile.showDifficulty = not self.db.profile.showDifficulty
	self:UpdateTooltip()
	return self.db.profile.showDifficulty
end

function QuestsFu:IsShowingImpossible()
	return self.db.profile.showImpossible
end

function QuestsFu:ToggleShowingImpossible(silent)
	self.db.profile.showImpossible = not self.db.profile.showImpossible
	self:UpdateTooltip()
	return self.db.profile.showImpossible
end

function QuestsFu:IsShowingArea()
	return self.db.profile.showArea
end

function QuestsFu:ToggleShowingArea(silent)
	self.db.profile.showArea = not self.db.profile.showArea
	self:UpdateTooltip()
	return self.db.profile.showArea
end

function QuestsFu:IsShowingCurrentAreaOnly()
	return self.db.profile.showCurrentAreaOnly
end

function QuestsFu:ToggleShowingCurrentAreaOnly(silent)
	self.db.profile.showCurrentAreaOnly = not self.db.profile.showCurrentAreaOnly
	self:UpdateTooltip()
	return self.db.profile.showCurrentAreaOnly
end

function QuestsFu:IsShowingClassAnyway()
	return self.db.profile.showClassAnyway
end

function QuestsFu:ToggleShowingClassAnyway(silent)
	self.db.profile.showClassAnyway = not self.db.profile.showClassAnyway
	self:UpdateTooltip()
	return self.db.profile.showClassAnyway
end

function QuestsFu:IsShowingCurrentAreaDescriptionOnly()
	return self.db.profile.showCurrentAreaDescriptionOnly
end

function QuestsFu:ToggleShowingCurrentAreaDescriptionOnly(silent)
	self.db.profile.showCurrentAreaDescriptionOnly = not self.db.profile.showCurrentAreaDescriptionOnly
	self:UpdateTooltip()
	return self.db.profile.showCurrentAreaDescriptionOnly
end

function QuestsFu:IsShowingDescription()
	return self.db.profile.showDescription
end

function QuestsFu:ToggleShowingDescription(silent)
	self.db.profile.showDescription = not self.db.profile.showDescription
	self:UpdateTooltip()
	return self.db.profile.showDescription
end

function QuestsFu:IsColoringObjectives()
	return self.db.profile.colorObjectives
end

function QuestsFu:ToggleColoringObjectives(silent)
	self.db.profile.colorObjectives = not self.db.profile.colorObjectives
	self:UpdateTooltip()
	return self.db.profile.colorObjectives
end

function QuestsFu:IsShowingCompletedObjectives()
	return self.db.profile.showCompletedObjectives
end

function QuestsFu:ToggleShowingCompletedObjectives(silent)
	self.db.profile.showCompletedObjectives = not self.db.profile.showCompletedObjectives
	self:UpdateTooltip()
	return self.db.profile.showCompletedObjectives
end

function QuestsFu:IsShowingInItemTooltip()
	return self.db.profile.showInItemTooltip
end

function QuestsFu:ToggleShowingInItemTooltip(silent)
	self.db.profile.showInItemTooltip = not self.db.profile.showInItemTooltip
	self:UpdateTooltip()
	return self.db.profile.showInItemTooltip
end

function QuestsFu:IsWrappingQuests()
	return self.db.profile.wrapQuests
end

function QuestsFu:ToggleWrappingQuests(silent)
	self.db.profile.wrapQuests = not self.db.profile.wrapQuests
	self:UpdateTooltip()
	return self.db.profile.wrapQuests
end

function QuestsFu:OnInitialize()
	if type(self.db.profile.showText) == 'table' then
		--FuBarPlugin-2.0 appropriated .showText.  Switch it out.
		self.db.profile.showTextOps = self.db.profile.showText
		self.db.profile.showText = nil
	end
	
	self.numQuests = 0
	self.numEntries = 0
	self.zones = {}
	self.zone_quests = {}
	self.quests = {}
	self.items = {}
	self.mobs = {}
	self.lastuimessage = ""
	self.lastquestmessage = ""

	self.allowedToUpdate = true
	
	self.loadedWatchedQuests = false
end

function QuestsFu:OnEnable()
	self:RegisterEvent("QUEST_LOG_UPDATE", "OnQuestLogUpdate")
	self:RegisterEvent("PLAYER_LEVEL_UP", "UpdateTooltip") -- Quest difficulty colors can change on level; just redraw the tooltip in case they have it open while levelling.
	self:RegisterEvent("ZONE_CHANGED_NEW_AREA", "UpdateTooltip")
	self:RegisterEvent("UPDATE_MOUSEOVER_UNIT", "OnMouseOverUnit")
	self:RegisterEvent("UI_INFO_MESSAGE","OnUIInfoMessage")
	self:Hook("ContainerFrameItemButton_OnEnter", "OnItemTooltip")
	self:Hook("AddQuestWatch", "OnAddQuestWatch")
	self:Hook("RemoveQuestWatch", "OnRemoveQuestWatch")
	
	self:ScheduleEvent(self.LoadWatchedQuests, 1, self)
	
	if self:IsShowingLevelsGame() then
		self:RegisterEvent("GOSSIP_SHOW", "OnGossipShow")
		self:RegisterEvent("QUEST_GREETING", "OnQuestGreeting")
		self:Hook("GetQuestLogTitle")
	end
end

function QuestsFu:Disable()
	metro:Unregister(self.name)
end

function QuestsFu:MakeTag(level, tag)
	if tag then
		if tag == L["ELITE"] then tag = L["TAG_ELITE"]
		elseif tag == L["DUNGEON"] then tag = L["TAG_DUNGEON"]
		elseif tag == L["PVP"] then tag = L["TAG_PVP"]
		elseif tag == L["RAID"] then tag = L["TAG_RAID"]
		end
	else
		tag = ''
	end
	return "[" .. level .. tag .. "] "
end

function QuestsFu:GetQuestLogTitle(index)
	local questLogTitleText, level, questTag, isHeader, isCollapsed, isComplete = self.hooks["GetQuestLogTitle"].orig(index)
	if not isHeader and level then
		if questLogTitleText then
			questLogTitleText = self:MakeTag(level, questTag) .. questLogTitleText
		end
	end
	return questLogTitleText, level, questTag, isHeader, isCollapsed, isComplete
end

--This is pretty much how Minimalist does it, which it credits to AutoSelect.
function QuestsFu:OnGossipShow()
	local buttonindex = 1
	local list, button
	if (GetGossipAvailableQuests()) then
		list = compost:Acquire(GetGossipAvailableQuests())
		for i = 2,getn(list),2 do
			button = getglobal("GossipTitleButton"..(buttonindex))
			button:SetText(format('[%d] %s',list[i],list[i-1]))
			buttonindex = buttonindex + 1
		end
		buttonindex = buttonindex + 1
		compost:Reclaim(list)
	end
	if (GetGossipActiveQuests()) then
		list = compost:Acquire(GetGossipActiveQuests())
		for i = 2,getn(list),2 do
			button = getglobal("GossipTitleButton"..(buttonindex))
			button:SetText(format('[%d] %s',list[i],list[i-1]))
			buttonindex = buttonindex + 1
		end
		compost:Reclaim(list)
	end
end

--This is pretty much how Minimalist does it, which it credits to AutoSelect.
function QuestsFu:OnQuestGreeting()
	local numactive,numavailable = GetNumActiveQuests(), GetNumAvailableQuests()
	local title, level, button
	local o,GetTitle,GetLevel = 0,GetActiveTitle,GetActiveLevel
	for i=1, numactive + numavailable do
		if i == numactive + 1 then
			o,GetTitle,GetLevel = numactive,GetAvailableTitle,GetAvailableLevel
		end
		title,level = GetTitle(i-o), GetLevel(i-o)
		button = getglobal("QuestTitleButton"..i)
		button:SetText(format('[%d] %s',level,title))
	end
end

function QuestsFu:OnAddQuestWatch(id)
	self.hooks["AddQuestWatch"].orig(id)
	self:SaveWatchedQuests()
end

function QuestsFu:OnRemoveQuestWatch(id)
	self.hooks["RemoveQuestWatch"].orig(id)
	self:SaveWatchedQuests()
end

function QuestsFu:OnUIInfoMessage()
	self.lastuimessage = arg1
	self:ScheduleEvent(self.ClearUIInfoMessage, 0.5, self)
end

function QuestsFu:ClearUIInfoMessage()
	self.lastuimessage = ""
end

function QuestsFu:OnQuestLogUpdate()
	if self.allowedToUpdate then self.lastquestmessage = self.lastuimessage end
	self.allowedToUpdate = false
	self:ScheduleEvent("ThrottleUpdate", function(self)
		self.allowedToUpdate = true
		self:Update()
	end, 1, self)
end

function QuestsFu:OnMouseOverUnit()
	-- Make sure that this is wanted, and that the tooltip is actually read/writeable.
	if self:IsShowingInItemTooltip() and self:CanMessWithGameTooltip() then
		--Comes with color codes.  Strip 'em out.
		local thisMob = string.gsub(getglobal('GameTooltipTextLeft1'):GetText(),"|c........(.*)|?r?","%1")
		if self.mobs[thisMob] then
			self:AddToGameTooltipAndFixHeight(self.mobs[thisMob].quest..": "..self.mobs[thisMob].needed, self.mobs[thisMob].colorr, self.mobs[thisMob].colorg, self.mobs[thisMob].colorb)
		end
	end
end

function QuestsFu:OnItemTooltip()
	self.hooks["ContainerFrameItemButton_OnEnter"].orig()
	if self:IsShowingInItemTooltip() and self:CanMessWithGameTooltip() then
		--Comes with color codes.  Strip 'em out.
		local thisItem = string.gsub(getglobal('GameTooltipTextLeft1'):GetText(),"|c........(.*)|r","%1")
		if self.items[thisItem] then
			self:AddToGameTooltipAndFixHeight(self.items[thisItem].quest..": "..self.items[thisItem].needed, self.items[thisItem].colorr, self.items[thisItem].colorg, self.items[thisItem].colorb)
		end
	end
end

function QuestsFu:CanMessWithGameTooltip()
	if GameTooltip and getglobal('GameTooltipTextLeft1'):IsVisible() and getglobal('GameTooltipTextLeft1'):GetText() ~= nil then
		return true
	else
		return false
	end
end

function QuestsFu:AddToGameTooltipAndFixHeight(text, r, g, b)
	GameTooltip:AddLine(text, r, g, b)

	-- Fix up the width of the tooltip
	length = getglobal(GameTooltip:GetName() .. "TextLeft" .. GameTooltip:NumLines()):GetStringWidth()
	-- Space for right-border:
	length = length + 22

	GameTooltip:SetHeight(GameTooltip:GetHeight() + 14)
	if length > GameTooltip:GetWidth() then
		GameTooltip:SetWidth(length)
	end
end

function QuestsFu:OnDataUpdate()
	--UpdateText and UpdateTooltip both need to know things about the questlog.  Better to do this once.
	compost:Reclaim(self.quests, 3)
	compost:Reclaim(self.zone_quests, 1)
	self.quests, self.zone_quests = nil, nil

	local startingQuestLogSelection = GetQuestLogSelection()
	local numEntries, numQuests = GetNumQuestLogEntries()
	local numQuestsDone = 0
	local quests = compost:Acquire()
	local zones = compost:Erase(self.zones)
	--LEVELS--local level_quests = {}
	local zone_quests = compost:Acquire()
	local items = compost:Erase(self.items)
	local mobs = compost:Erase(self.mobs)
	local zoneIndex = "OMG NONE" --The first item in the quest log should always be a header, so this should always be replaced.  But just in case.

	if numEntries > 0 and self.allowedToUpdate then
		local questid
		for questid = 1, numEntries do
			local strQuestLogTitleText, strQuestLevel, strQuestTag, isHeader, isCollapsed, isComplete = GetQuestLogTitle(questid)
			local q = compost:Acquire()

			if isHeader then
				zoneIndex = strQuestLogTitleText
				table.insert(zones, zoneIndex)
				zone_quests[zoneIndex] = compost:Acquire()
			else
				q.zone = zoneIndex
				q.tag = strQuestTag

				SelectQuestLogEntry(questid)

				if isComplete == 1 then
					isComplete = L["QUEST_DONE"]
					numQuestsDone = numQuestsDone + 1
				elseif isComplete == -1 then
					isComplete = L["QUEST_FAILED"]
				else
					isComplete = nil
				end

				q.title = strQuestLogTitleText
				q.level = strQuestLevel
				q.complete = isComplete

				local lb = compost:Acquire()
				if GetNumQuestLeaderBoards() > 0 then
					local ii
					for ii=1, GetNumQuestLeaderBoards() do
						local desc, qtype, done = GetQuestLogLeaderBoard(ii)
						local itemName,mobName,numNeeded,numItems,factionName,factionCurrent,factionNeeded

						if qtype == 'item' then
							itemName,numItems,numNeeded = deformat(desc, QUEST_OBJECTS_FOUND)
							desc = itemName
						elseif qtype == 'monster' then
							mobName,numItems,numNeeded = deformat(desc, QUEST_MONSTERS_KILLED)
							if mobName == nil or numItems == nil or numNeeded == nil then
								--Sometimes we get objectives like "Find Mankrik's Wife: 0/1", which are listed as "monster".
								mobName, numItems, numNeeded = deformat(desc, QUEST_OBJECTS_FOUND)
							end
							desc = mobName
						elseif qtype == 'reputation' then
							factionName,factionCurrent,factionNeeded = deformat(desc, QUEST_FACTION_NEEDED)
							numItems = self:GetReactionLevel(factionCurrent)
							numNeeded = self:GetReactionLevel(factionNeeded)
							desc = factionName
						end

						local r,g,b
						if numItems ~= nil then
							-- This quest involves items!
							r, g, b = crayon:GetThresholdColor(numItems / numNeeded)
						end

						if qtype == 'item' then
							items[itemName] = compost:AcquireHash('quest', strQuestLogTitleText, 'item', itemName, 'needed', string.format("%s/%s",numItems,numNeeded), 'colorr', r, 'colorg', g, 'colorb', b)
						elseif qtype == 'monster' then
							mobs[mobName] = compost:AcquireHash('quest', strQuestLogTitleText, 'mob', mobName, 'needed', string.format("%s/%s",numItems,numNeeded), 'colorr', r, 'colorg', g, 'colorb', b)
						end

						if done then
							done = L["QUEST_DONE"]
						elseif numItems and numNeeded then
							if qtype == 'reputation' then
								done = string.format("%s/%s",factionCurrent,factionNeeded)
							else
								done = string.format("%s/%s",numItems,numNeeded)
							end
						else
							--I think this shouldn't happen.  ...but I haven't seen every quest out there.
							done = ""
						end

						table.insert(lb, compost:AcquireHash('description', desc, 'done', done, 'colorr', r, 'colorg', g, 'colorb', b))
					end
				else
					local strQuestDescription, strQuestObjectives = GetQuestLogQuestText(questid)
					--q.objective = self.superwrap(strQuestObjectives, 50, 0, 2)
					q.objective = strQuestObjectives
				end
				q.leaderboard = lb
				q.id = questid

				quests[questid] = q
				table.insert(zone_quests[zoneIndex], questid)
				--LEVELS--table.insert(level_quests[tonumber(strQuestLevel)], questid)
			end
		end
	end

	self.quests = quests
	self.zones = zones
	--LEVELS--self.level_quests = level_quests
	self.zone_quests = zone_quests
	self.items = items
	self.mobs = mobs
	self.numQuests = numQuests
	self.numEntries = numEntries
	self.numQuestsDone = numQuestsDone

	SelectQuestLogEntry(startingQuestLogSelection)

	self:SaveWatchedQuests() --A watched quest may have gone away because of the update.  (The game doesn't call RemoveQuestWatch for it...)
end

function QuestsFu:OnTextUpdate()
	local text = ""
	local maxQuests = 20
	if self.allowedToUpdate then
		local r,g,b = GameFontNormal:GetTextColor()
		local color = string.format("%02X%02X%02X", r * 255, g * 255, b * 255)
		if self:IsShowingTextComplete() then
			color = crayon:GetThresholdHexColor(self.numQuestsDone / self.numQuests)
			text = text .. crayon:Colorize(color,self.numQuestsDone)
		end
		if self:IsShowingTextComplete() and (self:IsShowingTextCurrent() or self:IsShowingTextTotal()) then
			text = text .. crayon:Colorize(color,"/")
		end
		if self:IsShowingTextCurrent() then
			color = crayon:GetThresholdHexColor((maxQuests - self.numQuests) / maxQuests)
			text = text .. crayon:Colorize(color,self.numQuests)
		end
		if self:IsShowingTextCurrent() and self:IsShowingTextTotal() then
			text = text .. crayon:Colorize(color,"/")
		end
		if self:IsShowingTextTotal() then
			text = text .. crayon:Colorize(color,maxQuests)
		end
		if (self:IsShowingTextTotal() or self:IsShowingTextComplete() or self:IsShowingTextCurrent()) and self:IsShowingTextLastMessage() and self.lastquestmessage ~= "" then
			text = text .. " m: "
		end
		if self:IsShowingTextLastMessage() and self.lastquestmessage ~= "" then
			text = text .. self.lastquestmessage
		end
	else
		text = L["TEXT_LOADING"]
	end
	self:SetText(text)
end

function QuestsFu:OnTooltipUpdate()
	if self.numQuests > 0 and self.allowedToUpdate then
		if table.getn(self.zones) then
			local currentZone = GetZoneText()
			local zonename, zone
			local cat
			if not self:IsShowingArea() then
				cat = tablet:AddCategory('columns', 2)
				local sortedQuests = self:SortQuests(self.quests)
				for _,quest in pairs(sortedQuests) do
					self:AddQuestToTooltip(cat, quest, true)
				end
				compost:Reclaim(sortedQuests)
			else
				for _,zone in pairs(self.zones) do
					-- If showing area headers and either not showing only the current area's quests or this is the current area.
					if self:IsShowingArea() and ((currentZone == zone) or not self:IsShowingCurrentAreaOnly() or (self:IsShowingClassAnyway() and UnitClass("player") == zone)) then
						local r,g,b = 0.749,1,0.749
						zonename = zone
						if zone == UnitClass("player") then 
							r, g, b = babbleclass:GetColor(zone)
						else
							if self:IsShowingLevelsZone() and tourist:IsZoneOrInstance(zone) then
								local high, low = tourist:GetLevel(zone)
								if high > 0 and low > 0 then
									zonename = string.format("%s (%d-%d)", zone, high, low)
								end
							end
							if self:IsShowingDifficulty() and tourist:IsZoneOrInstance(zone) then
								r, g, b = tourist:GetLevelColor(zone)
							end
						end
						cat = tablet:AddCategory(
							'id', zone,
							'text', zonename,
							'columns', 2,
							'hideBlankLine', true,
							'showWithoutChildren', true,
							'checked', true, 'hasCheck', true, 'checkIcon', self.db.char.hidden[zone] and "Interface\\Buttons\\UI-PlusButton-Up" or "Interface\\Buttons\\UI-MinusButton-Up",
							'func', 'ToggleCategory', 'arg1', self, 'arg2', zone,
							'textR', r,
							'textG', g,
							'textB', b,
							'child_func', 'QuestClick',
							'child_arg1', self
						)
					end
					-- If we're not hiding the zone and it's either the current zone or we're not showing only the current zone.
					if (not self.db.char.hidden[zone]) and ((not self:IsShowingCurrentAreaOnly()) or (currentZone == zone) or (self:IsShowingClassAnyway() and UnitClass("player") == zone)) and (table.getn(self.zone_quests[zone]) > 0) then
						local questid
						for _,questid in pairs(self.zone_quests[zone]) do
							self:AddQuestToTooltip(cat, self.quests[questid], false)
						end
					end
				end
			end
		end
	end
	tablet:SetHint(L["TOOLTIP_HINT"])
end

function QuestsFu:MembersOnQuest(questid)
	--Return the number of party members on a quest.  Returns zero if not in a party.
	local n = 0
	local party = GetNumPartyMembers()
	if party > 0 then
		for i = 1,party do
			n = n + (IsUnitOnQuest(questid, "party"..i) and 1 or 0)
		end
	end
	return n
end

function QuestsFu:AddQuestToTooltip(cat, quest, appendZone)
	--Are we hiding impossible quests, and is this quest impossible?
	local questImpossible = (GetDifficultyColor(quest.level) == QuestDifficultyColor.impossible)
	if not questImpossible or (questImpossible and self:IsShowingImpossible()) then
		local thisQuest, thisQuestColor
		local questLevel, questZone = "",""
		local currentZone = GetZoneText()

		local questShared = QuestsFu:MembersOnQuest(quest.id)
		
		if questShared == 0 then
			questShared = ""
		end
		
		if appendZone then
			questZone = format(" (%s)", quest.zone)
		end
		
		if self:IsShowingLevelsTablet() and not self:IsShowingLevelsGame() then
			questLevel = self:MakeTag(quest.level, quest.tag)
		end
		
		thisQuest = questLevel..quest.title..questZone..((questShared ~= "") and "|c0000ff00<"..questShared..">|r" or '')

		if self:IsShowingDifficulty() then
			thisQuestColor = GetDifficultyColor(quest.level)
		end

		cat:AddLine(
			'text', thisQuest, 'wrap', self:IsWrappingQuests(),
			'text2', quest.complete,
			'textR', (thisQuestColor and thisQuestColor.r) or 1, 'textG', (thisQuestColor and thisQuestColor.g) or 1, 'textB', (thisQuestColor and thisQuestColor.b) or 1,
			'text2R', (quest.complete == L["QUEST_FAILED"]) and 1 or 0, 'text2G', (quest.complete == L["QUEST_DONE"]) and 1 or 0, 'text2B', 0,
			'func', 'QuestClick',
			'arg1', self,
			'arg2', quest.id,
			'indentation', 6,
			'checked', IsQuestWatched(quest.id),
			'hasCheck', IsQuestWatched(quest.id)
		)

		-- If we're showing descriptions/objectives and it's either the current zone or we're not showing descriptions only for the current zone.
		if self:IsShowingDescription() and ((currentZone == quest.zone) or not self:IsShowingCurrentAreaDescriptionOnly()) then
			-- If we know of leaderboard objectives:
			if table.getn(quest.leaderboard) > 0 then
				local i, goal
				for i,goal in pairs(quest.leaderboard) do
					if not (goal.done == L["QUEST_DONE"] and not self:IsShowingCompletedObjectives()) then
						local r,g,b
						if self:IsColoringObjectives() then
							r,g,b = goal.colorr, goal.colorg, goal.colorb
						end
						cat:AddLine(
							'text', '  '..goal.description,
							'text2', goal.done,
							'textR', r or ((thisQuestColor and thisQuestColor.r) or 1), 'textG', g or ((thisQuestColor and thisQuestColor.g) or 1), 'textB', b or ((thisQuestColor and thisQuestColor.b) or 1),
							'text2R', r or ((thisQuestColor and thisQuestColor.r) or 1), 'text2G', g or ((thisQuestColor and thisQuestColor.g) or 1), 'text2B', b or ((thisQuestColor and thisQuestColor.b) or 1),
							'size', tablet:GetNormalFontSize()-2, 'size2', tablet:GetNormalFontSize()-2,
							'arg2', quest.id,
							'indentation', 12
						)
					end
				end
			-- Otherwise, if the quest is incomplete (or complete and we're showing completed objectives), add the generic objective.
			elseif not (quest.complete and not self:IsShowingCompletedObjectives()) then
				cat:AddLine(
					'text', quest.objective,
					'wrap', true,
					'textR', (thisQuestColor and thisQuestColor.r) or 1,
					'textG', (thisQuestColor and thisQuestColor.g) or 1,
					'textB', (thisQuestColor and thisQuestColor.b) or 1,
					'size', tablet:GetNormalFontSize()-2,
					'arg2', quest.id,
					'indentation', 12
				)
			end
		end
	end
end

function QuestsFu:ToggleCategory(id, button)
	if self.db.char.hidden[id] then
		self.db.char.hidden[id] = false
	else
		self.db.char.hidden[id] = true
	end
	-- Refresh in place
	self:UpdateTooltip()
end

--TheFly contributed the base of this, and it's still using much of the logic he provided
function QuestsFu:QuestClick(questid, button)
	if IsAltKeyDown() then
		-- Shift-click toggles quest-watch on this quest.
		if IsQuestWatched(questid) then
			RemoveQuestWatch(questid)
			QuestWatch_Update()
		else
			-- Set error if no objectives
			if GetNumQuestLeaderBoards(questid) == 0 then
				UIErrorsFrame:AddMessage(QUEST_WATCH_NO_OBJECTIVES, 1.0, 0.1, 0.1, 1.0, UIERRORS_HOLD_TIME)
				return
			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
			end
			AddQuestWatch(questid)
			QuestWatch_Update()
		end
	elseif IsShiftKeyDown() and IsControlKeyDown() and ChatFrameEditBox:IsVisible() then
		-- Add the quest objectives to the chat editbox, if it's open.
		for i,goal in pairs(self.quests[questid].leaderboard) do
			ChatFrameEditBox:Insert(string.format("{%s %s} ", goal.description, goal.done))
		end
	elseif IsShiftKeyDown() and ChatFrameEditBox:IsVisible() then
		-- Add quest title to the chat editbox if it's open.
		ChatFrameEditBox:Insert(self.quests[questid].title)
	elseif IsControlKeyDown() then
		-- Share the quest with party members.
		local wasSelected = GetQuestLogSelection()
		SelectQuestLogEntry(questid)
		if (GetQuestLogPushable() and GetNumPartyMembers() > 0) then
			QuestLogPushQuest()
		end
		SelectQuestLogEntry(questid)
	else
		if QuestLogFrame:IsVisible() then
			if self.lastIndex == questid then
				HideUIPanel(QuestLogFrame)
			end
		else
			ShowUIPanel(QuestLogFrame)
		end
		if (self.numEntries > QUESTS_DISPLAYED) then
			if (questid < self.numEntries - QUESTS_DISPLAYED) then
				FauxScrollFrame_SetOffset(QuestLogListScrollFrame, questid - 1)
				QuestLogListScrollFrameScrollBar:SetValue((questid - 1) * QUESTLOG_QUEST_HEIGHT)
			else
				FauxScrollFrame_SetOffset(QuestLogListScrollFrame, self.numEntries - QUESTS_DISPLAYED)
				QuestLogListScrollFrameScrollBar:SetValue((self.numEntries - QUESTS_DISPLAYED) * QUESTLOG_QUEST_HEIGHT)
			end
		end

		SelectQuestLogEntry(questid)
		QuestLog_SetSelection(questid)
		self.lastIndex = questid
	end
end

function QuestsFu:OnClick()
	ToggleQuestLog()
end

function QuestsFu:GetReactionLevel(leveltext)
	if leveltext == FACTION_STANDING_LABEL1 then --Hated
		return 1
	elseif leveltext == FACTION_STANDING_LABEL2 then --Hostile
		return 2
	elseif leveltext == FACTION_STANDING_LABEL3 then --Unfriendly
		return 3
	elseif leveltext == FACTION_STANDING_LABEL4 then --Neutral
		return 4
	elseif leveltext == FACTION_STANDING_LABEL5 then --Friendly
		return 5
	elseif leveltext == FACTION_STANDING_LABEL6 then --Honored
		return 6
	elseif leveltext == FACTION_STANDING_LABEL7 then --Revered
		return 7
	elseif leveltext == FACTION_STANDING_LABEL8 then --Exalted
		return 8
	end
end

function QuestsFu:LoadWatchedQuests()
	if not (GetNumQuestWatches() > 0) then
		for _,questId in pairs(self.db.char.watchedQuests) do
			AddQuestWatch(questId)
		end
	end
	QuestWatch_Update()
	
	self.loadedWatchedQuests = true
end

function QuestsFu:SaveWatchedQuests()
	if self.loadedWatchedQuests then
		self.db.char.watchedQuests = compost:Erase(self.db.char.watchedQuests)
		for i = 1, GetNumQuestWatches() do
			table.insert(self.db.char.watchedQuests, GetQuestIndexForWatch(i))
		end
	end
end

-- Sorts a table of quests by level, with quests of the same level ordered
-- by elite, dungeon or raid tags, i.e. normal < elite < dungeon < raid.
-- Quests of the same level and tag are sorted alphabetically by title.
-- Returns a new table of the sorted quests. Original table is unchanged.
function QuestsFu:SortQuests(quests)
	-- Make a copy of the table, without keys
	local sortedQuests = compost:Acquire()
	table.foreach(self.quests,
		function(k,v)
			table.insert(sortedQuests, v)
		end
	)

	table.sort(sortedQuests,
		function(a,b)
			local aa = a.level*4
			local bb = b.level*4
			if a.tag == L["TAG_ELITE"] then aa = aa+1 end
			if a.tag == L["TAG_DUNGEON"] then aa = aa+2 end
			if a.tag == L["TAG_RAID"] then aa = aa+3 end
			if b.tag == L["TAG_ELITE"] then bb = bb+1 end
			if b.tag == L["TAG_DUNGEON"] then bb = bb+2 end
			if b.tag == L["TAG_RAID"] then bb = bb+3 end
			if aa == bb then
				return a.title < b.title
			end
			return aa < bb;
		end
	)

	return sortedQuests
end
