--
--	Grail
--	Written by scott@mithrandir.com
--
--	Version History
--		001	Initial version.
--		002	Converted to using a hooked function to register completed quests.
--			Made it so quests that never appear in the quest log can be marked completed assuming the quest data is up to date.
--			Condensed the debug statements.
--			Changed the architecture so extra information can be returned for failure conditions.
--			Switched ProfessionExceeds to be able to use localized names of professions.
--		003	Made it so Darkmoon Faire NPCs return the location based on where the Darkmoon Faire currently is.
--			Removed the QUEST_AUTOCOMPLETE event handling since it seems to be unneeded.
--			Added specialZones which allow mapping of GetZoneText() to things we prefer.
--			Removed the check for IsDaily() and IsWeekly() from the Status routine since they are marked as non-complete when reset happens.
--			Added IsYearly() because there are holiday quests that can be completed only once.
--			Resettable quests (daily/weekly/yearly) are now recorded specially so quests can be queried as to whether they have ever been completed using HasQuestEverBeenCompleted().
--			Added a notification system for accepting and completing quests.
--			Added API to get quests that are available during an event (holiday).
--		004	Corrected a problem where resettable quests could not be saved for initial use.
--			Augmented level checking to maximum level is checked as well.
--			Added a targetLevel parameter to filtering quests.
--			Made it so "Near" NPCs can have a specific zone associated with them which makes their return location table entry have the zone name and the word "Near".
--			Removed the need for specialZones since GetRealZoneText() does what we need.  Switched the use of GetZoneText() to GetRealZoneText().
--			ProfessionExceeds() now returns success and skill level, where skill level can be Grail.NO_SKILL if the player does not have that skill at all.
--			LocationNPC() now has more parameters to refine the locations returned.
--			LocationQuest() now makes use of LocationNPC() changes and can return the NPC name as well.
--		005	Quest titles that do not match our internal database are recorded, which helpfully gives us localizations as well.
--			Made it so repeatable quests are also recorded in the resettable quests list.
--			Did a little optimization by declaring some LUA functions local.
--			Made some quest traversal routines take an optional argument to force garbage collection, which greatly increases the time to return the desired data, but brings the footprint back down.
--			Added a routine to get the riding skill level.
--			Made it so QueryQuestsCompleted() is called at startup because the earlier assumption did not take into account that there was still another add-on that did it.
--			Made it so we call QueryQuestsCompleted() if GetQuestResetTime() indicates that quests have been reset.  LIMITATION: The check that triggers this only happens upon accepting or completing a quest.
--			Corrected a problem in ProfessionExceeds() where the comparison was incorrect.  Also made sure the skill exists before API is called.  Changed the value of Grail.NO_SKILL.
--			IsNPCAvailable() now can work with heroic NPCs in their instances.
--		006	Corrected a problem where the questResetTime variable was misspelled.
--			Made it so the SpecialQuests are cleaned out of the GrailDatabase properly.
--			Switched City of Ironforge to Ironforge to match GetRealZoneText() return value.
--			Added a table that contains the quests per zone to allow QuestsInZone() to return the cached information immediately.
--			Made it so a callback can be registered for quest abandoning.
--		007	Corrected a problem where a mismatch in title would cause a LUA error when attempting to record bad quest data.
--			Made it so the GrailDatabase gets its NewQuests and NewNPCs cleaned out properly.
--			Added a QuestName function so the name can be gotten without need for internal data structure knowledge.
--			Added Quest and NPC localizations for French, German, Russian and Spanish.
--		008	Changed hooking quest completing to actually get the current script associated instead of the global name for the script.
--			Corrected some problems where LUA errors would occur if the internal database did not know about a quest.
--			Added support for a quest to have a prerequisite quest in the quest log and not complete.
--			Added support for automatic quests to indicate the NPC that needs to be killed to initiate quest acceptance.
--			Added support for automatic quests that are obtained from entering a zone.
--			Added a zoneMapping table that maps zone IDs returned by GetCurrentMapAreaID() to those used internally.
--			Made it so Status can ignore prerequisite requirements of a quest.
--			Made LocationQuest also return the NPC ID as well as the other information it returns.
--			Restructured the posting of the abandon notification to be about 0.75 seconds after the button click because it seems the quest log does not actually remove it immediately.
--			Added capability to handle indirect items where another NPC drops the one that starts the quest.  The NPC name returned is an NPC that drops the item followed by the item name in parentheses.
--		009	Added more mappings from GetCurrentMapAreaID().
--			Corrected a problem where some parameter names in Status() were not the same as in the implementation, thereby ignoring their values.
--			Added a QuestLevel() function.
--			MeetsRequirementLevel() now returns the levels used to determine success.
--			Added some quest interrogation routines IsEscort(), IsDungeon(), IsRaid(), IsPVP(), IsGroup() and IsHeroic().
--			Added a QuestsInMap() function that uses a map ID.
--			Added a convenience function SingleMapLocationQuest().
--		010	Added an NPCName() function.
--			Added an IsTooltipNPC() which indicates what type of NPC we are dealing with for those that modify tooltips.
--			Added an AvailableBreadcrumbs() which returns breadcrumb quests available to be gotten for the specified quest.
--		011	Corrected an issue where ensuring all prerequisite quests were confirmed could have been inaccurate.
--			Added AncestorStatus() and made Status() call it so prerequisite quests are checked to ensure they can be completed otherwise the Status will be false.  For example, this makes
--			an entire quest chain unavailable if the race does not permit the first quest to be accepted.
--			Debugging has now been turned off by default.
--			A new feature called tracking has been provided to keep a little history of basic quest activity, but is off by default.
--			Changed the posting of abandon notifications to be about 1.0 seconds after the button click since there were times when 0.75 seconds was not enough, and made it a variable.
--			Added some clearing out of BadQuestData that has been added to the database.
--			Made clearing out of NewQuest data more robust.
--			Changed to have NPC locations use map area IDs.
--			Removed the zoneMapping and zones tables as part of the move to using map area IDs for all locations.
--			Made the quest index per map area computed at runtime for the latest most accurate data.
--		012	Made it so marking a quest complete only does so if the quest is not already complete.  This is just a precaution to handle an edge case.
--			Added the "/grail backup" and "/grail compare" commands to help find quest IDs for quests that do not enter the quest log.
--			Made it so special quests that never appear in the quest log can be recorded as complete when there is more than one quest with the same name as long as the NPC ID is different.
--			Made it so NPC locations return the dungeon level and the alias map ID.
--			Added a lot of quest/NPC information for the Midsummer Fire Festival.
--		013	Updated quests and NPCs for Firelands.
--			Did a bunch of localization.
--		014	Corrected some localization issues.
--			Updates quests and NPCs for Mount Hyjal and Firelands.
--			Corrected quest prerequisite information to remove cycles to make a DAG.
--			Added AncestorQuests() which returns a complicated table structure of prerequisite quests.
--		015	Made low level comparisons use Blizzard's own routine so grey quests appear properly.
--			Added more quest level information.
--			Updates to quests/NPCs for Firelands, Alliance Grizzly Hills, and Kezan.
--			Added processing to have holiday quests stored in their own map areas (besides where the quest givers are) so they can be viewed as a group.
--			Added the ability to handle PH: prerequisites that require a quest to have been completed sometime in the past (used with dailies that are triggers).
--			Added the ability to handle Xc codes which exclude classes (basically the opposite of Cc codes).
--		016	Updates to quests/NPCs for Gilneas, and Durotar.
--			Corrected a problem in MultipleUniqueMapLocationQuest() where the accept or turn in parameter was not being passed along properly.
--		017	Updated quest and NPC data to minimize problems with stack overflows, etc.
--			Made it so questgiver locations with NearXXX codes are ignored like Near.
--			Made it so AncestorStatus is now passed the same ignore flags as Status so any subsequent calls to Status will get passed them as well.
--			Added tables for the five continents' dungeons.
--		018	Set up basic structures to start support for ptBR localization when Brazilian version comes on line.
--			Updated a large number of localizations for quest names.
--			Updates to some Firelands quest/NPC information, as well as Zul'Drak, Tirisfal Glades, Redridge Mountains, Duskwood and Northern Stranglethorn.
--			Implemented support for Sx quest codes which are the logical opposite of Rx codes.
--			Corrected the implementation of Xx codes.
--			Made it so a nil value we sometimes get will no longer crash, but output something helpful.
--		019	Added support for PC: quest codes.
--			Updated some Alliance quest information for Northern Stranglethorn, Cape of Stranglethorn, Dustwallow Marsh, Dun Morogh, Loch Modan, Wetlands, Arathi Highlands, The Hinterlands, Western Plaguelands, Badlands, Searing Gorge, Burning Steppes, Swamp of Sorrows, Darkshore, Teldrassil, Hillsbrad Foothills, Azuremyst, Bloodmyst Isle, Zul'Drak and the capital cities.
--			Updated some quest information for Mulgore, Tirisfal Glades and Silverpine Forest.
--			Made it so AvailableBreadcrumbs() will return breadcrumbs that have prerequisites that can be fulfilled as well as ones that are currently available.
--			K codes for cooking, fishing, and Brewfest quests have been changed to level 0 to indicate the actual level is the same as the player accepting the quest.
--			The zone-specific Self NPCs are now automatically generated for each zone.
--			Changed Status() to return a "Level" failure last of all the checks.
--			Corrected a probem where DEATHKNIGHT was not properly used as the class type.
--			Made it so there is a "map area" that contains all the daily quests.
--			Made "map areas" to contain each of the quests only available to specific classes.
--			Made "map areas" to contain each of the quests only available to specific professions.
--			Made "map areas" to contain each of the quests only available to those with specific reputations with factions.
--			Added support for OAC: quest codes.
--			Added a StatusCode() routine that returns a bitmask of quest status.
--		020	Updates some quest/NPC information for Durotar, Desolace, Southern Barrens, Ironforge, Stonetalon Mountains, Eversong Woods, Eastern Plaguelands, Badlands, Zul'Drak and Ashenvale.
--			Added more support for StatusCode() to support some more bit values plus values from prerequisites.
--			When using StatusCode() quest status values are cached to avoid recomputing values.  The cached values are invalidated as appropriate based on environment and the values of the status.
--			Made IsLowLevel() never consider quests whose level is 0 as low-level since those quests' levels change to match the player level.
--			Removed the Ahn'Qiraj War Effort from the list of world events.
--			Marked Status() as deprecated API which will be removed in the future.
--			Changed the method by which abandoned quests have their notifications posted so the variable abandonedQuestId no longer exists.
--			Added support for LoremasterMapArea() API which provides the map area of the Loremaster achievement for which the quest qualifies.  Also added Grail.loremasterQuests[mapAreaId] tables which list the quests that are used for each Loremaster achievement.
--		021	Updates some quest/NPC information for Feralas, Northern Stranglethorn, Un'Goro Crater, Stormwind City, Ghostlands, Silvermoon City and Cape of Stranglethorn.
--			Updates quest/NPC information for Hallow's End and Day of the Dead.
--			Created caching structure for accessing some quest information to help reduce runtime footprint and increase speed.
--			Added support for OCC:, PLT: and PCT: quest codes.
--			Made QuestsInMap() able to return only quests that qualify for Loremaster.
--			Removed a number of debug slash commands and the functions that were supporting them.
--			Added the CreateRaceNameLocalizedGenderized() routine so race names can be displayed nicely.
--			Removed AncestorStatus(), QuestsWithCode() and Status() and some support routines.
--		022	Updates some quest/NPC information for Mugore, Thunder Bluff, Silverpine Forest, Durotar, Bloodmyst Isle and Azshara.
--			Updates quest/NPC information for Pilgrim's Bounty.
--			Corrected the Gnomeregan reputation name to not include Exiles.
--			Started recording found defects in a new format.
--			Created a system to record when reputation changes do not match what the internal database has.
--			Added the achievement information where quests are associated with specific achievements.
--			Updated the TOC to support Interface 40300.
--		023	Corrects the detection of the Mr Popularity guild perks.
--			Updates some quest/NPC information for Darkmoon Faire, Azshara, Elwynn Forest, class-specific ones and the Bwemba's Spirit line.
--			Adds the missing reputation names to the non-English clients (whose lack was causing addons that use reputation to fail).
--			Updates a lot of Portuguese data.
--			Fixes a problem where unknown quests were not being recorded correctly, causing a LUA error.
--			Fixes a problem where event handlers were not installed properly because Blizzard events cannot arrive in a guaranteed order.
--			Fixes a problem where AZ codes were not being processed properly, thereby resulting in quests with those codes to appear in the current map area instead of their proper one.
--			Fixes a problem where the new Darkmoon Faire quests would not be available on Darkmoon Island unless the UI was reloaded.
--		024	Updates some quest/NPC information for Azshara, Ashenvale, Stonetalon Mountains, Southern Barrens, Dalaran, Shattrath City and some dungeons.
--			Updates some Portuguese localizations.
--			Updates the CleanDatabase() routine to do more cleaning.
--			Makes it so slash commands are not forced to lower case.
--			Changes the way StatusCode() works to not mark a quest complete if it does not meet race, class, gender and/or faction requirements.  This is to work around Blizzard behavior where the server marks quests complete that could not possibly be done by a player.
--			Changes the way StatusCode() works to not mark level problems or invalidation problems with quests that are marked complete.
--			Fixes a problem where CleanDatabase() could attempt to access data that does not exist.
--			Fixes an infinite loop that is sometimes encountered using Blizzard's GetFactionInfo(), found by ArcaneTourist.
--		025	Updates some quest/NPC information for Southern Barrens, Durotar, Northern Barrens, Desolace and Dustwallow Marsh.
--			Adds a Christmas Week holiday that handles the quests in Winter Veil that only start appearing on Christmas Day.
--			Adds a feature to record NPC names that do not match those in the database.
--			Updates some Portuguese localizations.
--			Updates some other localizations, for Winter Veil.
--		026	Updates some quest/NPC information for Desolace, Azuremyst Isle, The Exodar, Azshara, Hillsbrad Foothills and Feralas.
--			Cleans up some Blizzard event handling, and moved some event handling Wholly was doing into here because it is the right place for them.
--			Updates some Portuguese localizations.
--			Fixes a problem where a LUA error was being thrown when invalidating part of the status cache when evaluating a quest status.
--			Adds support for world events achievements.
--		027	Updates some quest/NPC information for Feralas, Northern Barrens, Thousand Needles, Tanaris, Zul'Drak, Sholazar Basin, Storm Peaks, some dungeons and Uldum.
--			Updates some quest/NPC information for the Lunar Festival.
--			Updates some Portuguese localizations.
--		028	*** Will not work with Wholly 15 or older ***
--			Corrects the mapAreaMaximumReputationChange constant.
--			Revamps the location providing routines so only the new QuestLocations() and NPCLocations() are needed, REMOVING the older ones. 
--			Updates some quest/NPC information for Un'Goro Crater, Silithus, Burning Steppes, Kezan, The Lost Isles, Northern Barrens, Ashenvale, some dungeons and Winterspring.
--			Fixes detection of European servers to remove non-existent quests.
--			Updates some Portuguese localizations.
--			Makes _CleanDatabase() a little more intense with its cleaning.
--			Makes the system than checks for reputation gains a little more accurate.
--			Records actual quest completion for those quests that Blizzard marks complete with others in the server, so clients can know really which quest was done.
--			Implements a way to know when Blizzard uses internal marking mechanics (which differ from flag quests) to specify when quests are available.
--			Adds an architecture to support information about quests that are bugged.
--		029 *** Will not work with Wholly 16 or older ***
--			Splits out two load on demand addons to handle achievements and reputation gains.
--			Updates some quest/NPC information for the Lost Isles, Feralas and some dungeons.
--			Updates some localizations, primarily Portuguese, Korean and Simplified Chinese.
--			Corrects the problem where some daily quests that also have another aspect (e.g., PVP or dungeon) were not being shown as daily quests.
--			Updates the automatic quest level verification system to ensure quests that are considered to have a dynamic level actually do.
--          Adds basic structural support for the Italian localization.
--			Consolidates the internal use of prerequisite quest types into a unified technique, causing all QuestPrerequisite* API to be REMOVED other than QuestPrerequisites.
--			Fixes the problem where quests with AZ codes were not being added to the proper zone.
--			Fixes the problem where the status of quests that require other quests being in the quest log was not being displayed properly.
--			Adds the Kalu'ak Fishing Derby holiday.
--			Updates some quest/NPC information for the fishing contests.
--		030	Corrects a problem that manifests itself when running the ElvUI addon.
--		031	Corrects the internal checking of reputation gains to not include modifications when the reputation is lost.
--			Adds the verifynpcs slash command option.
--			Updates some localizations, primarily Portuguese and Korean.
--			Updates some quest/NPC information for Dun Morogh, Loch Modan, Wetlands, Vash'jir and Kelp'thar Forest.
--			Corrects the problem where quests with breadcrumbs were being marked as not complete after a reload.
--			Adds processing to startup to ensure Grail attempts to get the server quest status automatically.
--			Corrects AncestorStatusCode() to ignore non-quest prerequisites.
--			Adds the ability to have quests have items or lack of items as prerequisites.
--			Adds support for ODC: quest codes, which are used to mark other quests complete when a quest is turned in.
--			Adds the ability to have quests use the abandoned state of quests as prerequisites.
--		032	Adds some German translation from polzi.
--			Augments CanAcceptQuest() to include a parameter to ignore holiday requirements.
--			Updates some quest/NPC information for some dungeons, Oracles/Frenzyheart, Worgen starting areas, Tol Barad and others.
--			Changes the comparisons to completed quests to be more mathematically robust.
--			Corrects a problem where cleaning the database can cause a LUA error.
--		033	Updates some quest/NPC information for Blasted Lands, Eastern Plaguelands, Tirisfal Glades, Undercity, Winterspring, Zul'Aman and professions.
--			Adds some Spanish translation from Trisquite.
--			Changes the implementation of _ReputationExceeds() to use GetFactionInfoByID() instead of GetFactionInfo() since it seems there are times when the latter does not return proper values at startup.
--		034	Updates some quest/NPC information for Wandering Isle.
--			Creates new Grail.reputationExpansionMapping table to replace the original four tables which are deprecated and will be removed in version 035.
--			Updates Midsummer Fire Festival quest/NPC data, primarily the Portuguese localization.
--		035	Updates Midsummer Fire Festival localization for Korean, Spanish and German.
--			Updates more NPC/quest localizations.
--			Updates the quest recording subsystem to generate basic K codes.
--			Changes the reputation system to no longer use indirection, but Blizzard faction IDs.
--			Updates the quest recording subsystem to record faction rewards on quest acceptance, and turns off recording faction rewards when quests are turned in.
--			Corrects the problem where quests that start automatically when entering a zone can appear improperly in the current zone (based on the current zone name).
--			Changes the technique by which the server is queried for completed quests since API has been changed for MoP.
--			Updates some quest/NPC information for Valley of the Four Winds and Krasarang Wilds.
--			Makes it so B codes are automatically generated from the quests with O codes, so the vast majority of B codes need not be present in the data file.
--			Adds the ability to create profession prerequisite codes (vice the normally supported profession requirements).
--		036	Fixes the problem where accepting and abandoning a quest with a breadcrumb was not setting the breadcrumb status properly.
--			Fixes the problem where quests could be considered to fail prerequisites if the only prerequisites were quests requiring presence in the quest log.
--			Updates some quest/NPC information for MoP beta, including Night Elf and Draenei starter zones.
--			Updates quest information to allow marking quests Scenario and Legendary.
--			Removes Grail.bitMaskQuestNonLevel as the internal data structures have changed, no longer requiring this.
--			Adds HasQuestEverBeenAccepted() to be able to handle O type prerequisites.
--			Removes Grail.reputationBlizzardMapping since it is no longer needed because of the use of Blizzard faction IDs.
--		037	Updates some quest/NPC information for Twilight Highlands, Deepholm, Uldum, Sholazar Basin and Mount Hyjal.
--			Adds DisplayableQuestPrerequisites() so flag quests can be bypassed, showing their requirements instead.
--			Adds some Italian localization.
--			Adds support for account-wide quests.
--
--	Known Issues
--
--			The use of GetQuestResetTime() is not adequate, nor is the API good enough to provide us accurate information for weeklies (and possibly yearlies depending on when they actually reset compared to dailies).
--				The check is only made when a quest is accepted or completed, and this means the reset could happen during play and the Blizzard-provided data would be out of date until a restart or one of our
--				monitored events occurs.  This is the price one pays for not using something like OnUpdate.
--			Support for Neutral faction for starting Pandarens does not exist as it need not.  Quests are marked with a racial requirement, and the system
--				should handle the situation when the Pandaren chooses the desired faction.
--
--			Update the "BadQuestData" data recording/cleaning to handle the rest of the failure possibilities.
--			Need to make it so special quests with the same name AND same NPC ID can be handled.  For the Consortium gem quests I believe we will have to check the levels of the Consortium rep to know how to distinguish.
--			Need a time detection system because we need to know when we cross boundaries for things like the fishing holidays so we can turn the quests on or off appropriately.  This will also allow us to handle other time-based quests.  It means we will most likely use OnUpdate and the above comment will go away and we can actually put in a timer for the next quest reset time so we know when dailies reset.  Of course this means we may want to study the calendar to know when an upcoming event boundary will be crossed as well other than fishing (like Darkmoon Faire, etc.).
--			Need to be able to set Grail.playerFactionBitMask for a Pandaren if they start out playing before they select a faction, and then select a faction during play.  Otherwise they will be defaulted to Alliance which could prove problematic.
--
--			Celebratingholiday() does not check the time when a holiday starts and stops, and assumes presence of a holiday on a day means the entire day has the holiday.  This is incorrect for holidays, that for example, start or end at 02h00.
--
--			Should probably change the handling of UNIT_AURA and UNIT_QUEST_LOG_CHANGED to use the new RegisterUnitEvent() API with the "player" parameter as it should be more efficient than the current technique where we will get more events than we need.
--
--			Determine if it is possible to notice when a faction is marked "at war" by the user so reputation checks against it take that into account because when one is "at war" the NPCs will not give the quests as expected.
--
--	UTF-8 file
--

local directoryName, _ = ...
local versionFromToc = GetAddOnMetadata(directoryName, "Version")
local _, _, versionValueFromToc = strfind(versionFromToc, "(%d+)")
local Grail_File_Version = tonumber(versionValueFromToc)

if nil == Grail or Grail.versionNumber < Grail_File_Version then

	local tinsert, tContains = tinsert, tContains
	local strsplit, strfind, strformat, strsub = strsplit, string.find, string.format, string.sub
	local pairs = pairs

	--	Even though it is documented that UNIT_QUEST_LOG_CHANGED is preferable to QUEST_LOG_UPDATE, in practice UNIT_QUEST_LOG_CHANGED fails
	--	to do what it is supposed to do.  In fact, processing cannot properly happen using it and not QUEST_LOG_UPDATE, even with proper
	--	priming of the data structures.  Therefore, this addon makes use of QUEST_LOG_UPDATE instead.  Actually, this has proven to be a
	--	little unreliable as well, so a hooked function is now used instead.

	--	It would be really convenient to be able not to store the localized names of the quests and the NPCs.  However, the only real way
	--	to get any arbitrary one (that is not in the quest log) is to populate the tooltip with a hyperlink.  However, that will not normally
	--	return results immediately from a server query, so another attempt at tooltip population is needed.  In the case of quests, this
	--	works pretty well.  However, with NPCs the results are less than satisfactory.  In reality, we want the information to be readily
	--	available for when someone needs it, so polling the server is not convenient.  Therefore, we will continue to store the localized
	--	names of these objects so they are available immediately to the caller.  This means the size of the add-on in memory is going to
	--	be constant and not growing overtime if we were to attempt to populate the information in the background (which we would want to do
	--	to make the information available).

	--	Instead of trying to deal with the concept of having NPCs who have unique IDs to be associated with each other but only be available
	--	in specific "phases", the availability of an NPC should probably be checked through the use of determining whether a quest can be
	--	obtained.  Normally, the prerequisite structure of the quests will indicate specific quests cannot yet be obtained, and those are
	--	likely to be associated with the NPCs that will be in new "phases".  Therefore, nothing special needs be done in this library, but
	--	the onus can be put on the user of this library to ensure only quest givers for available quests are listed/shown.

	--	The Blizzard quest log list cannot reliably be queried upon startup until after the PLAYER_ALIVE event has been received.  However,
	--	setting a flag during that event processing will not work since reloading the UI will not cause PLAYER_ALIVE to be sent again, but
	--	will cause the flag to be reset.  It appears under brief testing that QUEST_LOG_UPDATE fires after PLAYER_ALIVE on normal login, and
	--	fires sometime after PLAYER_LOGIN after a UI reload.  Therefore, the flag will be set in QUEST_LOG_UPDATE event processing.

	--	Another issue is the fact that the calendar API cannot be properly used to get real data until OpenCalendar() has returned something
	--	useful, which cannot occur until later in the login sequence.  And trying to call OpenCalendar() without calling CalendarSetAbsMonth()
	--	beforehand makes it so the call does nothing and the CALENDAR_UPDATE_EVENT_LIST event is never sent.

	--
	--	Caching of the quest status.
	--
	--	If the status of a quest is requested, and that status already exists in the cache, then the cache results
	--	should be returned.  When the status of a quest is computed it is added to the quest status cache.  The
	--	cache of a quest status can be invalidated based on what happens in the environment and the status of the
	--	quest.  For example, if a quest was marked as being too high for the player to obtain, but the player gains
	--	a level, that quest status in the cache needs to be removed so it can be recomputed when needed.
	--

	--	For some quests Blizzard marks others complete when you complete one.  For example, Firelands dailies are in groups and when you
	--	finish one, the others are marked complete on the server.  This tends not to be a problem.  However, Blizzard also does this with
	--	quests the player would never be able to acquire, like the starting zone class-specific quests.  So, when a mage completes its
	--	class quest the server marks the class quests for hunter, warrior, etc. also complete.  This seems idiotic as Blizzard already has
	--	other mechanisms to limit a mage from getting a hunter quest, for example.  This causes a problem with the way Grail evaluates the
	--	status of a quest, since it is done "live" because quests have so many relationships.  In general, the quests that one could never
	--	aquire are evaluated such during play, and in the future when they are marked complete on the server they will be both marked
	--	complete since we must believe what the server reports, and will be marked unobtainable for whatever reasons are appropriate.
	--	This works well except for when we attempt to evaluate prerequisites because part of prerequisites is to see if the required quest
	--	is complete.  However, we also check to see whether the quest can be obtained.  The flaw that we currently have is that we are
	--	evaluating whether the quest can be obtained currently, which is technically incorrect because it should be can the quest be
	--	obtained at the time the quest is marked complete.  Of course we can only know this if we keep track of which specific quests are
	--	marked complete when any other is done.  This is yet another level of annoyance that Blizzard causes that it need not.  So, Grail
	--	is going to approximate this for the time being with evaluating the current ability to accept a quest that was complete.

	--	Blizzard seems to have some internal method of determining state with regard to quests that is not flagged using another quest.
	--	They do use other quests sometimes, but not all the time.  Therefore, to ensure we keep a similar state bogus quests are used within
	--	the database, but these are not going to be present from the server query.  Therefore, this state is kept in controlCompletedQuests
	--	which will be checked every time the results from the server query are processed to ensure the internally kept master completed
	--	quests include them.

	--	Database of stored information per character.
	GrailDatabasePlayer = {}
	GrailDatabase = { }
	--	The completedQuests is a table of 32-bit integers.  The index in the table indicates which set of 32 bits are being used and the value at that index
	--	is a bit representation of completed quests in that 32 quest range.  For example, quest 7 being the only one completed in the quests from 1 to 32
	--	would mean table entry 0 would have a value of 64.  Quest 33 being done would mean [1] = 1, while quests 33 and 35 would mean [1] = 5.  The user need
	--	not know any of this since the API to access this information takes care of the dirty work.
	--	The completedResettableQuests is just like completedQuests except it records only those quests that Blizzard resets like dailies and weeklies.  This
	--	is used for API that can determine if a quest has ever been completed (since a daily could have been completed in the past, but Blizzard's API would
	--	indicate that it is currently not completed (because it has been reset)).
	--	There are four possible tables of interest:  NewNPCs, NewQuests, SpecialQuests and BadQuestData.
	--	These tables could be used to provide feedback which can be used to update the internal database to provide more accurate quest information.

	Grail = {
		versionNumber = Grail_File_Version,
		questsVersionNumber = 0,
		npcsVersionNumber = 0,
		zonesVersionNumber = 0,
		zonesIndexedVersionNumber = 0,
		achievementsVersionNumber = 0,
		reputationsVersionNumber = 0,
		buggedQuestsVersionNumber = 0,
		INFINITE_LEVEL = 100000,
		NO_SKILL = -1,
		NPC_TYPE_BY = 'BY',
		NPC_TYPE_DROP = 'DROP',
		NPC_TYPE_KILL = 'KILL',
		abandonPostNotificationDelay = 1.0,
		abandoningQuestIndex = nil,

		-- Bit mask system for quest status
		-- First bits are "good" bits
		bitMaskNothing							= 0x00000000,
		bitMaskCompleted						= 0x00000001,
		bitMaskRepeatable						= 0x00000002,
		bitMaskResettable						= 0x00000004,
		bitMaskEverCompleted					= 0x00000008,
		bitMaskInLog							= 0x00000010,
		bitMaskLevelTooLow						= 0x00000020,		-- the player's level is too low for the quest currently
		bitMaskLowLevel							= 0x00000040,		-- the quest is a low-level quest compared to the player's level
		-- These are really failure bits
		bitMaskClass							= 0x00000080,
		bitMaskRace								= 0x00000100,
		bitMaskGender							= 0x00000200,
		bitMaskFaction							= 0x00000400,
		bitMaskInvalidated						= 0x00000800,
		bitMaskProfession						= 0x00001000,
		bitMaskReputation						= 0x00002000,
		bitMaskHoliday							= 0x00004000,
		bitMaskLevelTooHigh						= 0x00008000,		-- the player's level is too high for the quest
		-- This next one indicates no prerequisites have been fulfilled
		bitMaskPrerequisites					= 0x00010000,
		-- These are failure bits for ancestor quests if bitMaskPrerequisites is set.  They are the same
		-- as the previous set of failure bits * 1024
		bitMaskAncestorClass					= 0x00020000,
		bitMaskAncestorRace						= 0x00040000,
		bitMaskAncestorGender					= 0x00080000,
		bitMaskAncestorFaction					= 0x00100000,
		bitMaskAncestorInvalidated				= 0x00200000,
		bitMaskAncestorProfession				= 0x00400000,
		bitMaskAncestorReputation				= 0x00800000,
		bitMaskAncestorHoliday					= 0x01000000,
		bitMaskAncestorLevelTooHigh				= 0x02000000,
		-- Informational bits
		bitMaskInLogComplete					= 0x04000000,
		bitMaskInLogFailed						= 0x08000000,
		bitMaskResettableRepeatableCompleted	= 0x10000000,
		bitMaskBugged							= 0x20000000,
		-- These basically represent internal errors within the database
		bitMaskNonexistent						= 0x40000000,
		bitMaskError							= 0x80000000,
		-- Some convenience values precomputed
		bitMaskQuestFailure = 0xff80,	-- from bitMaskClass to bitMaskLevelTooHigh
		bitMaskQuestFailureWithAncestor = 0x03feff80,	-- bitMaskQuestFailure + (bitMaskAncestorClass to bitMaskAncestorLevelTooHigh)
		bitMaskAcceptableMask = 0xefffffb1,	-- all bits except bitMaskRepeatable, bitMaskResettable, bitMaskEverCompleted, bitMaskResettableRepeatableCompleted and bitMaskLowLevel
		-- End of Bit mask values


		-- Bit mask system for other quest information indicating who can get a quest
		-- Faction
		bitMaskFactionAlliance	=	0x00000001,
		bitMaskFactionHorde		=	0x00000002,
		-- Class
		bitMaskClassDeathKnight	=	0x00000004,
		bitMaskClassDruid		=	0x00000008,
		bitMaskClassHunter		=	0x00000010,
		bitMaskClassMage		=	0x00000020,
		bitMaskClassMonk		=	0x00000040,
		bitMaskClassPaladin		=	0x00000080,
		bitMaskClassPriest		=	0x00000100,
		bitMaskClassRogue		=	0x00000200,
		bitMaskClassShaman		=	0x00000400,
		bitMaskClassWarlock		=	0x00000800,
		bitMaskClassWarrior		=	0x00001000,
		-- Gender
		bitMaskGenderMale		=	0x00002000,
		bitMaskGenderFemale		=	0x00004000,
		-- Race
		bitMaskRaceHuman		=	0x00008000,
		bitMaskRaceDwarf		=	0x00010000,
		bitMaskRaceNightElf		=	0x00020000,
		bitMaskRaceGnome		=	0x00040000,
		bitMaskRaceDraenei		=	0x00080000,
		bitMaskRaceWorgen		=	0x00100000,
		bitMaskRaceOrc			=	0x00200000,
		bitMaskRaceScourge		=	0x00400000,
		bitMaskRaceTauren		=	0x00800000,
		bitMaskRaceTroll		=	0x01000000,
		bitMaskRaceBloodElf		=	0x02000000,
		bitMaskRaceGoblin		=	0x04000000,
		bitMaskRacePandaren		=	0x08000000,
		-- Some convenience values
		bitMaskFactionAll		=	0x00000003,
		bitMaskClassAll			=	0x00001ffc,
		bitMaskGenderAll		=	0x00006000,
		bitMaskRaceAll			=	0x0fff8000,
		-- End of bit mask values


		-- Bit mask system for information about type of quest
		bitMaskQuestRepeatable	=	0x00000001,
		bitMaskQuestDaily		=	0x00000002,
		bitMaskQuestWeekly		=	0x00000004,
		bitMaskQuestMonthly		=	0x00000008,
		bitMaskQuestYearly		=	0x00000010,
		bitMaskQuestEscort		=	0x00000020,
		bitMaskQuestDungeon		=	0x00000040,
		bitMaskQuestRaid		=	0x00000080,
		bitMaskQuestPVP			=	0x00000100,
		bitMaskQuestGroup		=	0x00000200,
		bitMaskQuestHeroic		=	0x00000400,
		bitMaskQuestScenario	=	0x00000800,
		bitMaskQuestLegendary	=	0x00001000,
		bitMaskQuestAccountWide	=	0x00002000,
		-- End of bit mask values


		-- Bit mask system for information about level of quest
		-- Eight bits are used to be able to represent a level value from 0 - 255.
		-- Three sets of those eight bits are used to represent the actual level
		-- of the quest, the minimum level required for the quest, and the maximum
		-- level allowed to accept the quest.
		bitMaskQuestLevel		=	0x000000ff,
		bitMaskQuestMinLevel	=	0x0000ff00,
		bitMaskQuestMaxLevel	=	0x00ff0000,
		bitMaskQuestLevelOffset	=	0x00000001,
		bitMaskQuestMinLevelOffset =0x00000100,
		bitMaskQuestMaxLevelOffset =0x00010000,
		-- End of bit mask values


		-- Bit mask system for holidays
		bitMaskHolidayLove		=	0x00000001,
		bitMaskHolidayBrewfest	=	0x00000002,
		bitMaskHolidayChildren	=	0x00000004,
		bitMaskHolidayDead		=	0x00000008,
		bitMaskHolidayDarkmoon	=	0x00000010,
		bitMaskHolidayHarvest	=	0x00000020,
		bitMaskHolidayLunar		=	0x00000040,
		bitMaskHolidayMidsummer	=	0x00000080,
		bitMaskHolidayNoble		=	0x00000100,
		bitMaskHolidayPirate	=	0x00000200,
		bitMaskHolidayNewYear	=	0x00000400,
		bitMaskHolidayWinter	=	0x00000800,
		bitMaskHolidayHallow	=	0x00001000,
		bitMaskHolidayPilgrim	=	0x00002000,
		bitMaskHolidayChristmas	=	0x00004000,
		bitMaskHolidayFishing	=	0x00008000,
		bitMaskHolidayKaluak    =   0x00010000,
		-- End of bit mask values

		checksReputationRewardsOnAcceptance = true,
		--	The followins is false because it will be more pleasant recording the reputation changes when accepting quests instead of attempting to parse 
		--	messages and take into account all the modifications that can happen to reputation values to come up with the value in the message.
		checksReputationRewardsOnTurnin = false,

		classMapping = { ['K'] = 'DEATHKNIGHT', ['D'] = 'DRUID', ['H'] = 'HUNTER', ['M'] = 'MAGE', ['O'] = 'MONK', ['P'] = 'PALADIN', ['T'] = 'PRIEST', ['R'] = 'ROGUE', ['S'] = 'SHAMAN', ['L'] = 'WARLOCK', ['W'] = 'WARRIOR', },
		classToBitMapping = { ['K'] = 0x00000004, ['D'] = 0x00000008, ['H'] = 0x00000010, ['M'] = 0x00000020, ['O'] = 0x00000040, ['P'] = 0x00000080, ['T'] = 0x00000100, ['R'] = 0x00000200, ['S'] = 0x00000400, ['L'] = 0x00000800, ['W'] = 0x00001000, },
		classToMapAreaMapping = { ['CK'] = 200011, ['CD'] = 200004, ['CH'] = 200008, ['CM'] = 200013, ['CO'] = 200015, ['CP'] = 200016, ['CT'] = 200020, ['CR'] = 200018, ['CS'] = 200019, ['CL'] = 200012, ['CW'] = 200023, },
		completingQuest = nil,
		completingQuestTitle = nil,
		continents = {},
		continentKalimdor = 1,
		continentEasternKingdoms = 2,
		continentOutland = 3,
		continentNorthrend = 4,
		continentMaelstrom = 5,
		continentPandaria = 6,
		currentlyProcessingStatus = {},
		currentlyVerifying = false,
		currentMortalIssues = {},
		currentQuestIndex = nil,
		debug = false,
		delayBagUpdate = 0.5,
		emergencyOutlet = 0,
		eventDispatch = {			-- table of functions whose keys are the events

			['ACHIEVEMENT_EARNED'] = function(self, frame, arg1)
				local achievementNumber = tonumber(arg1)
				if nil ~= achievementNumber and nil ~= self.questStatusCache['A'][achievementNumber] then
					self:_StatusCodeInvalidate(self.questStatusCache['A'][achievementNumber])
				end
			end,

			['ADDON_LOADED'] = function(self, frame, arg1)
				if "Grail" == arg1 then
					-- We have loaded GrailDatabase at this point, but we need to ensure the structure is set up for first-time players as we rely on at least an empty structure existing
					if nil == GrailDatabasePlayer then GrailDatabasePlayer = {} end

					--	For users prior to the release version 028, the GrailDatabase held personal quest information.  Now we move that information into the
					--	new structure GrailDatabasePlayer so it can be separated from the information that would be reported.
					if GrailDatabase[self.playerRealm] then
						if GrailDatabase[self.playerRealm][self.playerName] then
							GrailDatabasePlayer["completedQuests"] = GrailDatabase[self.playerRealm][self.playerName]["completedQuests"]
							GrailDatabasePlayer["completedResettableQuests"] = GrailDatabase[self.playerRealm][self.playerName]["completedResettableQuests"]
							GrailDatabasePlayer["actuallyCompletedQuests"] = GrailDatabase[self.playerRealm][self.playerName]["actuallyCompletedQuests"]
							GrailDatabasePlayer["controlCompletedQuests"] = GrailDatabase[self.playerRealm][self.playerName]["controlCompletedQuests"]
							GrailDatabase[self.playerRealm][self.playerName] = nil
						end
						local realmCount = 0
						for n, v in pairs(GrailDatabase[self.playerRealm]) do
							if nil ~= v then realmCount = realmCount + 1 end
						end
						if 0 == realmCount then GrailDatabase[self.playerRealm] = nil end
					end

					if nil == GrailDatabasePlayer["completedQuests"] then GrailDatabasePlayer["completedQuests"] = {} end
					if nil == GrailDatabasePlayer["completedResettableQuests"] then GrailDatabasePlayer["completedResettableQuests"] = {} end
					if nil == GrailDatabasePlayer["actuallyCompletedQuests"] then GrailDatabasePlayer["actuallyCompletedQuests"] = {} end
					if nil == GrailDatabasePlayer["controlCompletedQuests"] then GrailDatabasePlayer["controlCompletedQuests"] = {} end
					if nil == GrailDatabasePlayer["abandonedQuests"] then GrailDatabasePlayer["abandonedQuests"] = {} end

					--	Ensure the tooltip is not messed up
					if not com_mithrandir_grailTooltip:IsOwned(UIParent) then com_mithrandir_grailTooltip:SetOwner(UIParent, "ANCHOR_NONE") end

					self:RegisterSlashOption("debug", "|cFF00FF00debug|r => toggles debug on and off, printing new value", function()
						self.debug = not self.debug
						print(format("Grail Debug now %s", self.debug and "ON" or "OFF"))
					end)
					self:RegisterSlashOption("target", "|cFF00FF00target|r => gets target information (NPC ID and your current location)", function()
						local targetName, npcId, coordinates = self:TargetInformation()
						if targetName == nil then targetName = 'nil target' end
						if npcId == nil then npcId = -1 end
						if coordinates == nil then coordinates = 'no coords' end
						local message = format("%s (%d) %s", targetName, npcId, coordinates)
						print(message)
						self:_AddTrackingMessage(message)
					end)
					self:RegisterSlashOption("c ", "|cFF00FF00c|r |cFFFF8C00msg|r => adds the |cFFFF8C00msg|r to the tracking data", function(msg)
						self:_AddTrackingMessage(strsub(msg, 3))
					end)
					self:RegisterSlashOption("comment ", "|cFF00FF00comment|r |cFFFF8C00msg|r => adds the |cFFFF8C00msg|r to the tracking data", function(msg)
						self:_AddTrackingMessage(strsub(msg, 9))
					end)
					self:RegisterSlashOption("tracking", "|cFF00FF00tracking|r => toggles tracking on and off, printing new value", function()
						self.tracking = not self.tracking
						print(format("Grail Tracking now %s", self.tracking and "ON" or "OFF"))
						if self.tracking then
							self:RegisterObserverQuestAbandon(Grail._AddTrackingCallback)
							self:RegisterObserverQuestAccept(Grail._AddTrackingCallback)
							self:RegisterObserverQuestComplete(Grail._AddTrackingCallback)
						else
							self:UnregisterObserverQuestAbandon(Grail._AddTrackingCallback)
							self:UnregisterObserverQuestAccept(Grail._AddTrackingCallback)
							self:UnregisterObserverQuestComplete(Grail._AddTrackingCallback)
						end
					end)
					self:RegisterSlashOption("help", "|cFF00FF00help|r => print out this list of commands", function()
						print("|cFFFF0000Grail|r slash commands:")
						for option, value in pairs(self.slashCommandOptions) do
							print("|cFFFF0000/grail|r",value['help'])
						end
						print("|cFFFF0000/grail|r => initiates a database query to get completed quests [this happens at startup normally]")
					end)
					self:RegisterSlashOption("backup", "|cFF00FF00backup|r => creates a backup copy of the completed quests used for comparison", function()
						self:_ProcessServerBackup()
					end)
					self:RegisterSlashOption("compare", "|cFF00FF00compare|r => compares the current completed quest list to the backup copy", function()
						self:_ProcessServerCompare()
					end)
					--	Add a command for MoP that makes comparison of completed quests a little easier.  Only for MoP since before that the server
					--	needs to be queried and that means the return result will not happen before we compare.
					if self.blizzardRelease >= 15640 then
						self:RegisterSlashOption("cb", "|cFF00FF00cb|r => compares the latest server status quest list to the backup copy, and makes the backup become current", function()
							print("|cFFFFFF00Grail|r initiating server database query")
							QueryQuestsCompleted()
							self:_ProcessServerCompare()
							self:_ProcessServerBackup()
						end)
					end
					self:RegisterSlashOption("verifynpcs", "|cFF00FF00verifynpcs|r => starts a verification of every NPC name against those seen in play", function()
						if Grail.currentlyVerifying then print("Already verifying...please wait until completed") return end
						Grail.currentlyVerifying = true
						Grail.currentQuestIndex = nil
						Grail.timeSinceLastUpdate = 0
						Grail.verifyTable = {}
						Grail.verifyTableCount = 0
						Grail.verifyTable2 = {}
						Grail.verifyTableCount2 = 0
						Grail.verifyTable3 = {}
						Grail.verifyTableCount3 = 0
						Grail.npcCountForVerification = 0
						Grail.doneProcessingList = false
						if GrailDatabase['NPC_ISSUES'] == nil then GrailDatabase['NPC_ISSUES'] = {} end
						GrailDatabase['NPC_ISSUES'][Grail.playerLocale] = {
														release = Grail.blizzardRelease,
														version = self.versionNumber
														}		-- clear out the current values for this locale since we are about to write over them
						if nil == Grail.npcNotificationFrame then Grail.npcNotificationFrame = CreateFrame("Frame") end
						Grail.npcNotificationFrame:SetScript("OnUpdate", function(myself, elapsed) Grail:_VerifyNPCList(elapsed) end)
						print("Starting total NPC verification")
					end)
					self:RegisterSlashOption("clearstatuses", "|cFF00FF00clearstatuses|r => clears the status of all quests allowing them to be recomputed", function()
						for questId, v in pairs(self.quests) do
							if v then v[7] = nil end
						end
						self:_CoalesceDelayedNotification("Status", 0)
					end)

					frame:RegisterEvent("ACHIEVEMENT_EARNED")		-- e.g., quest 29452 can be gotten if certain achievements are complete
					frame:RegisterEvent("CRITERIA_EARNED")		-- for debugging to see when criteria are earned in MoP
					frame:RegisterEvent("CHAT_MSG_COMBAT_FACTION_CHANGE")	-- needed for quest status caching
					frame:RegisterEvent("CHAT_MSG_SKILL")	-- needed for quest status caching
					frame:RegisterEvent("PLAYER_LEVEL_UP")	-- needed for quest status caching
					frame:RegisterEvent("QUEST_ACCEPTED")
					frame:RegisterEvent("QUEST_COMPLETE")
					frame:RegisterEvent("QUEST_LOG_UPDATE")	-- just to indicate we are now available to read the Blizzard quest log without issues
					frame:RegisterEvent("QUEST_QUERY_COMPLETE")
					frame:RegisterEvent("UNIT_AURA")				-- it seems we need to know when a specific buff happens for quest 28656 at a minimum
					frame:RegisterEvent("UNIT_QUEST_LOG_CHANGED")	-- so we can know when a quest is complete or failed
					frame:RegisterEvent("ZONE_CHANGED_NEW_AREA")	-- only to get the first time logging in so the GetQuestResetTime() actually returns a real value
					self:_CleanDatabase()

					if self.checksReputationRewardsOnTurnin then
						self:RegisterObserver("TimeBomb", self._TimeBomb)
					end
					self:RegisterObserver("Bags", self._BagUpdates)

				end

			end,

			['BAG_UPDATE'] = function(self, frame, bagId)
				if bagId ~= -2 and bagId < 5 then		-- a normal bag that is not the special (-2) backpack
					self:_CoalesceDelayedNotification("Bags", self.delayBagUpdate)
				end
			end,

			['CALENDAR_UPDATE_EVENT_LIST'] = function(self, frame)
				self.receivedCalendarUpdateEventList = true
				frame:UnregisterEvent("CALENDAR_UPDATE_EVENT_LIST")
				self:_UpdateQuestResetTime()	-- moved here from ADDON_LOADED in the hopes that here GetQuestResetTime() will always return a real value
			end,

			['CHAT_MSG_COMBAT_FACTION_CHANGE'] = function(self, frame, message)
				if nil ~= self.questStatusCache then
					self:_StatusCodeInvalidate(self.questStatusCache["R"])
					self.questStatusCache["R"] = {}
				end
				if self.checksReputationRewardsOnTurnin then
					self:_RecordReputation(message)
				end
			end,

			['CHAT_MSG_SKILL'] = function(self, frame)
				if nil ~= self.questStatusCache then
					self:_StatusCodeInvalidate(self.questStatusCache["P"])
					self.questStatusCache["P"] = {}
				end
			end,

			['CRITERIA_EARNED'] = function(self, frame, ...)
				local achievementId, criterionId = ...
				local _, achievementName = GetAchievementInfo(achievementId)
				local criterionName = GetAchievementCriteriaInfoByID(achievementId, criterionId)
				self:_AddTrackingMessage("Criterion earned: "..criterionName.." ("..criterionId..") for achievement "..achievementName.." ("..achievementId..")")
			end,

			-- Note that the new level is recorded here, because during processing of this event calls to UnitLevel('player')
			-- will not return the new level.
			['PLAYER_LEVEL_UP'] = function(self, frame, newLevel)
				self.levelingLevel = tonumber(newLevel)
				if nil ~= self.questStatusCache then
					self:_StatusCodeInvalidate(self.questStatusCache["L"])
					self.questStatusCache["L"] = {}
					self:_StatusCodeInvalidate(self.questStatusCache["V"])
					self.questStatusCache["V"] = {}
				end
			end,

			['QUEST_ACCEPTED'] = function(self, frame, questIndex)
				local questTitle, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily, questId, startEvent = GetQuestLogTitle(questIndex)
				local npcId = nil
				local version = self.versionNumber.."/"..self.questsVersionNumber.."/"..self.npcsVersionNumber.."/"..self.zonesVersionNumber

				if nil == questTitle then questTitle = "NO TITLE PROVIDED BY BLIZZARD" end

				-- Get the target information to ensure the target exists in the database of NPCs
				local targetName, npcId, coordinates = self:TargetInformation()
				self:_UpdateTargetDatabase(targetName, npcId, coordinates, version)

				--	If this quest is not in our internal database attempt to record some information about it so we have a chance the
				--	user can provide this to us to update the database.
				if not isHeader then
					local baseValue = 0
					if isDaily then baseValue = baseValue + 2 end
					if suggestedGroup and suggestedGroup > 1 then baseValue = baseValue + 512 end
					-- at the moment we ignore questTag since it is localized
					local kCode = strformat("K%03d%d", level, baseValue)
					self:_UpdateQuestDatabase(questId, questTitle, npcId, isDaily, 'A', version, kCode)

					-- Ask Blizzard API to provide us with the reputation rewards for this quest
					if self.checksReputationRewardsOnAcceptance then
						SelectQuestLogEntry(questIndex)
						local reputationRewardsCount = GetNumQuestLogRewardFactions()
						local factionId, reputationAmount, repChangeString
						for i = 1, reputationRewardsCount do
							factionId, reputationAmount = GetQuestLogRewardFactionInfo(i)
							repChangeString = strformat("%s%d", self:_HexValue(factionId, 3), math.floor(reputationAmount / 100))
							if nil ~= repChangeString and nil ~= questId and nil ~= self.quests[questId] and (nil == self.quests[questId][6] or not tContains(self.quests[questId][6], repChangeString)) then
								self:_RecordBadQuestData('G' .. self.versionNumber .. '|' .. questId .. "|0|Rep:" .. repChangeString)
							end
						end
					end

				end

				--	If we think we should not have been able to accept this quest we should record some information that may help us update our faulty database.
				local statusCode = self:StatusCode(questId)
				local errorString = 'G' .. self.versionNumber .. '|' .. questId .. '|' .. statusCode
				if not self:CanAcceptQuest(questId, false, false, true) then
					-- look at the reason and record the reason and contrary information for that reason
					if bit.band(statusCode, self.bitMaskLevelTooLow + self.bitMaskLevelTooHigh) > 0 then errorString = errorString .. "|L:" .. UnitLevel('player') end
					if bit.band(statusCode, self.bitMaskClass) > 0 then errorString = errorString .. "|C:" .. self.playerClass end
-- TODO: Correct the fact that |R results in the loss of the R because it is a code used in their strings
					if bit.band(statusCode, self.bitMaskRace) > 0 then errorString = errorString .. "|R:" .. self.playerRace end
					if bit.band(statusCode, self.bitMaskGender) > 0 then errorString = errorString .. "|G:" .. self.playerGender end
					if bit.band(statusCode, self.bitMaskFaction) > 0 then errorString = errorString .. "|F:" .. self.playerFaction end
					if bit.band(statusCode, self.bitMaskInvalidated) > 0 then

					end
					if bit.band(statusCode, self.bitMaskProfession) > 0 then
-- TODO: Need to look at all the professions associated with the quest and record the actual profession values the user currently has for them

					end
					if bit.band(statusCode, self.bitMaskReputation) > 0 then
-- TODO: Same as professions, but with reputations instead

					end
					if bit.band(statusCode, self.bitMaskHoliday) > 0 then
-- TODO: Determine if we actually need to mark which holiday caused the problem because when CleanDatabase comes across this without the specific one, it can only remove this if there is NO holiday associated with the quest.
						errorString = errorString .. "HOL"
					end
					if bit.band(statusCode, self.bitMaskPrerequisites) > 0 then

					end
					self:_RecordBadQuestData(errorString)
				end

				--	If the questTitle is different from what we have recorded, note that as BadQuestData (even though it could just be a localization issue)
				if self:DoesQuestExist(questId) and questTitle ~= self:QuestName(questId) then
					errorString = errorString .. "|Title:" .. questTitle .. "|Locale:" .. self.playerLocale
					self:_RecordBadQuestData(errorString)
				end

				--	If the level as reported by Blizzard API does not match our internal database we should note that fact
				if self:DoesQuestExist(questId) then
					local internalQuestLevel = self:QuestLevel(questId)
					if (0 ~= internalQuestLevel and (internalQuestLevel or 1) ~= level) or (0 == internalQuestLevel and level ~= UnitLevel('player')) then
						errorString = errorString .. "|Level:" .. level
						self:_RecordBadQuestData(errorString)
					end
				end

				self:_PostNotification("Accept", questId)

				-- If there is an OAC: code associated with the quest we need to complete all the quests listed there.
				local oacCodes = self:QuestOnAcceptCompletes(questId)
				if nil ~= oacCodes then
					for i = 1, #oacCodes do
						self:_MarkQuestComplete(oacCodes[i], true, false, false)
					end
				end

				if self.debug then
					local debugMessage = "Grail Debug: Accepted quest: ".. questTitle .. " (" .. questId .. ") from "
					if nil ~= targetName then debugMessage = debugMessage .. targetName .. " (" .. npcId .. ") " .. coordinates else debugMessage = debugMessage .. "no target" end
					if not self:CanAcceptQuest(questId, false, false, true) then debugMessage = debugMessage .. " but should not accept because of: " .. errorString else debugMessage = debugMessage .. " without problems" end
					DEFAULT_CHAT_FRAME:AddMessage(debugMessage)
				end
				self:_UpdateQuestResetTime()
			end,

			['QUEST_COMPLETE'] = function(self, frame)
				local titleText = GetTitleText()
				self.completingQuest = self:QuestInQuestLogMatchingTitle(titleText)
				self.completingQuestTitle = titleText
				if nil == self.completingQuest then	-- if we still do not have it, mark it in the saved variables for possible future inclusion
					if nil == GrailDatabase["SpecialQuests"] then GrailDatabase["SpecialQuests"] = { } end
					if nil == GrailDatabase["SpecialQuests"][titleText] then GrailDatabase["SpecialQuests"][titleText] = true end
				end
				self:_UpdateQuestResetTime()
			end,

			-- This is used solely to indicate to the system that the Blizzard quest log is available to be read properly.  Early in the startup
			-- this is not the case prior to receiving PLAYER_ALIVE, but since that event is never received in a UI reload this event is used as
			-- a replacement which seems to work properly.
			['QUEST_LOG_UPDATE'] = function(self, frame)
				frame:UnregisterEvent("QUEST_LOG_UPDATE")
				self.receivedQuestLogUpdate = true
				frame:RegisterEvent("CALENDAR_UPDATE_EVENT_LIST")		-- to indicate the calendar is primed and can be accurately read
				frame:RegisterEvent("BAG_UPDATE")						-- we need to know when certain items are present or not (for quest 28607 e.g.)
				-- The intention is to receive the CALENDAR_UPDATE_EVENT_LIST event
				-- and to do so, one calls OpenCalendar(), but it seems if one does
				-- not call the other calendar functions beforehand, the call to
				-- OpenCalendar() will do nothing useful.
				local weekday, month, day, year = CalendarGetDate();
				CalendarSetAbsMonth(month, year);
				OpenCalendar()
			end,

			['QUEST_QUERY_COMPLETE'] = function(self, frame, arg1)
				self:_ProcessServerQuests()
			end,

			['UNIT_AURA'] = function(self, frame, arg1)
				if arg1 == "player" then
					if nil == self.spellsToHandle then self.spellsToHandle = {} end
					self.spellsJustHandled = {}
					local name, spellId
					local i = 1
					while (true) do
						name,_,_,_,_,_,_,_,_,_,spellId = UnitAura(arg1, i)
						if name then
							spellId = tonumber(spellId)
							if nil ~= spellId and nil ~= self.questStatusCache['B'][spellId] then
								self:_StatusCodeInvalidate(self.questStatusCache['B'][spellId])
								self.spellsToHandle[spellId] = true
								self.spellsJustHandled[spellId] = true
							end
							i = i + 1
						else
							break
						end
					end
					for spellId, _ in pairs(self.spellsToHandle) do
						if not self.spellsJustHandled[spellId] then
							self:_StatusCodeInvalidate(self.questStatusCache['B'][spellId])
							self.spellsToHandle[spellId] = nil
						end
					end
				end
			end,

			['UNIT_QUEST_LOG_CHANGED'] = function(self, frame, arg1)
				if arg1 == "player" then
					-- Get all the quests in the Blizzard quest log and invalidate their status cache values if they have changed with regard to completed/failed
					local questsToInvalidate = {}
					local quests = self:_QuestsInLog()
					local bitsToCheckAgainst = self.bitMaskInLog + self.bitMaskInLogComplete + self.bitMaskInLogFailed
					for questId, t in pairs(quests) do
						local cachedStatus = self.quests[questId] and self.quests[questId][7] or nil
						if nil ~= cachedStatus then
							local soughtBitMask = self.bitMaskInLog
							local foundComplete = false
							if t[2] then
								if t[2] > 0 then soughtBitMask = soughtBitMask + self.bitMaskInLogComplete foundComplete = true end
								if t[2] < 0 then soughtBitMask = soughtBitMask + self.bitMaskInLogFailed end
							end
							if bit.band(cachedStatus, bitsToCheckAgainst) ~= soughtBitMask then
								tinsert(questsToInvalidate, questId)
								if foundComplete then
									local occCodes = self.quests[questId]['OCC']
									if nil ~= occCodes then
										for i = 1, #occCodes do
											self:_MarkQuestComplete(occCodes[i], true, false, false)
											tinsert(questsToInvalidate, occCodes[i])
										end
									end
								end
							end
						end
					end
					self:_StatusCodeInvalidate(questsToInvalidate)
				end
			end,

			['ZONE_CHANGED_NEW_AREA'] = function(self, frame)
				self:_UpdateQuestResetTime()	-- moved here from ADDON_LOADED in the hopes that here GetQuestResetTime() will always return a real value
				frame:UnregisterEvent("ZONE_CHANGED_NEW_AREA")
			end,

			},
		factionMapping = { ['A'] = 'Alliance', ['H'] = 'Horde', },
		genderMapping = { ['M'] = 2, ['F'] = 3, },
		holidayMapping = { ['A'] = 'Love is in the Air', ['B'] = 'Brewfest', ['C'] = "Children's Week", ['D'] = 'Day of the Dead', ['F'] = 'Darkmoon Faire',
				['H'] = 'Harvest Festival', ['K'] = "Kalu'ak Fishing Derby", ['L'] = 'Lunar Festival', ['M'] = 'Midsummer Fire Festival', ['N'] = 'Noblegarden', ['P'] = "Pirates' Day",
				['U'] = 'New Year', ['V'] = 'Feast of Winter Veil', ['W'] = "Hallow's End", ['X'] = 'Stranglethorn Fishing Extravaganza', ['Y'] = "Pilgrim's Bounty", ['Z'] = "Christmas Week", },
		holidayToBitMapping = { ['A'] = 0x00000001, ['B'] = 0x00000002, ['C'] = 0x00000004, ['D'] = 0x00000008, ['F'] = 0x00000010,
				['H'] = 0x00000020, ['K'] = 0x00010000, ['L'] = 0x00000040, ['M'] = 0x00000080, ['N'] = 0x00000100, ['P'] = 0x00000200,
				['U'] = 0x00000400, ['V'] = 0x00000800, ['W'] = 0x00001000, ['X'] = 0x00008000, ['Y'] = 0x00002000, ['Z'] = 0x00004000, },
		holidayToMapAreaMapping = { ['HA'] = 100001, ['HB'] = 100002, ['HC'] = 100003, ['HD'] = 100004, ['HF'] = 100006, ['HH'] = 100008, ['HK'] = 100011, ['HL'] = 100012, ['HM'] = 100013,
				['HN'] = 100014, ['HP'] = 100016, ['HQ'] = 100017, ['HU'] = 100021, ['HV'] = 100022, ['HW'] = 100023, ['HX'] = 100024, ['HY'] = 100025, ['HZ'] = 100026, },
		indexedQuests = {},
		indexedQuestsExtra = {},
		levelingLevel = nil,	-- this is set during the PLAYER_LEVEL_UP event because UnitLevel() does not work during it
		mapAreaBaseAchievement = 500000,
		mapAreaBaseClass = 200000,
		mapAreaBaseDaily = 400000,
		mapAreaBaseHoliday = 100000,
		mapAreaBaseProfession = 300000,
		mapAreaBaseReputation = 400000,	-- note that 400000 is used for Daily
		mapAreaBaseReputationChange = 600000,
		mapAreaMapping = {},
		mapAreaMaximumAchievement = 599999,
		mapAreaMaximumClass = 299999,
		mapAreaMaximumDaily = 400000,	-- not used since Daily really is only every one area
		mapAreaMaximumHoliday = 199999,
		mapAreaMaximumProfession = 399999,
		mapAreaMaximumReputation = 499999,
		mapAreaMaximumReputationChange = 699999,
		observers = { },
		origAbandonQuestFunction,
		origConfirmAbandonQuestFunction,
		origHookFunction,
		playerClass,
		playerFaction,
		playerGender,
		playerLocale,
		playerName,
		playerRace,
		playerRealm,
		professionMapping = { ['A'] = 'Alchemy', ['B'] = 'Blacksmithing', ['C'] = 'Cooking', ['E'] = 'Enchanting', ['F'] = 'Fishing', ['H'] = 'Herbalism',
				['I'] = 'Inscription', ['J'] = 'Jewelcrafting', ['L'] = 'Leatherworking', ['M'] = 'Mining', ['N'] = 'Engineering',
				['R'] = 'Riding', ['S'] = 'Skinning', ['T'] = 'Tailoring', ['X'] = 'Archaeology', ['+'] = 'First Aid', },
		-- might be able to make use of the following global strings that have the values associated PROFESSIONS_ARCHAEOLOGY PROFESSIONS_COOKING PROFESSIONS_FIRST_AID PROFESSIONS_FISHING
		professionToMapAreaMapping = { ['PA'] = 300001, ['PB'] = 300002, ['PC'] = 300003, ['PE'] = 300005, ['PF'] = 300006, ['PH'] = 300008, ['PI'] = 300009, ['PJ'] = 300010, ['PL'] = 300012, ['PM'] = 300013, ['PN'] = 300014, ['PP'] = 300016, ['PR'] = 300018, ['PS'] = 300019, ['PT'] = 300020, ['PU'] = 300021, ['PX'] = 300024, ['P+'] = 300043, },
		questNPCId = nil,
		questResetTime = 0,
		races = {
			-- [1] is Blizzard API return (non-localized)
			-- [2] is localized male
			-- [3] is localized female
			-- [4] is bitmap value
			['H'] = { 'Human', 'Human', 'Human', 0x00008000 },
			['F'] = { 'Dwarf', 'Dwarf', 'Dwarf', 0x00010000 },
			['E'] = { 'NightElf', 'Night Elf', 'Night Elf', 0x00020000 },
			['N'] = { 'Gnome', 'Gnome', 'Gnome', 0x00040000 },
			['D'] = { 'Draenei', 'Draenei', 'Draenei', 0x00080000 },
			['W'] = { 'Worgen', 'Worgen', 'Worgen', 0x00100000 },
			['O'] = { 'Orc', 'Orc', 'Orc', 0x00200000 },
			['U'] = { 'Scourge', 'Undead', 'Undead', 0x00400000 },
			['T'] = { 'Tauren', 'Tauren', 'Tauren', 0x00800000 },
			['L'] = { 'Troll', 'Troll', 'Troll', 0x01000000 },
			['B'] = { 'BloodElf', 'Blood Elf', 'Blood Elf', 0x02000000 },
			['G'] = { 'Goblin', 'Goblin', 'Goblin', 0x04000000 },
			['A'] = { 'Pandaren', 'Pandaren', 'Pandaren', 0x08000000 },
			},
		raceMapping = { ['H'] = 'Human', ['F'] = 'Dwarf', ['E'] = 'NightElf', ['N'] = 'Gnome', ['D'] = 'Draenei', ['W'] = 'Worgen', ['O'] = 'Orc', ['U'] = 'Scourge', ['T'] = 'Tauren', ['L'] = 'Troll', ['B'] = 'BloodElf', ['G'] = 'Goblin', ['A'] = 'Pandaren', },
		raceNameFemaleMapping = { ['H'] = 'Human', ['F'] = 'Dwarf', ['E'] = 'Night Elf', ['N'] = 'Gnome', ['D'] = 'Draenei', ['W'] = 'Worgen', ['O'] = 'Orc', ['U'] = 'Undead', ['T'] = 'Tauren', ['L'] = 'Troll', ['B'] = 'Blood Elf', ['G'] = 'Goblin', ['A'] = 'Pandaren', },
		raceNameMapping = { ['H'] = 'Human', ['F'] = 'Dwarf', ['E'] = 'Night Elf', ['N'] = 'Gnome', ['D'] = 'Draenei', ['W'] = 'Worgen', ['O'] = 'Orc', ['U'] = 'Undead', ['T'] = 'Tauren', ['L'] = 'Troll', ['B'] = 'Blood Elf', ['G'] = 'Goblin', ['A'] = 'Pandaren', },
		raceToBitMapping = { ['H'] = 0x00008000, ['F'] = 0x00010000, ['E'] = 0x00020000, ['N'] = 0x00040000, ['D'] = 0x00080000, ['W'] = 0x00100000, ['O'] = 0x00200000, ['U'] = 0x00400000, ['T'] = 0x00800000, ['L'] = 0x01000000, ['B'] = 0x02000000, ['G'] = 0x04000000, ['A'] = 0x08000000, },
		receivedCalendarUpdateEventList = false,
		receivedQuestLogUpdate = false,

		--	The reputation values are the actual faction values used by Blizzard.
		reputationExpansionMapping = {
			[1] = { 69, 54, 47, 72, 930, 1134, 530, 76, 81, 68, 911, 1133, 509, 890, 730, 510, 729, 889, 21, 577, 369, 470, 910, 609, 749, 990, 270, 529, 87, 909, 92, 989, 93, 349, 809, 70, 59, 576, 922, 967, 589, 469, 67, },
			[2] = { 942, 946, 978, 941, 1038, 1015, 970, 933, 947, 1011, 1031, 1077, 932, 934, 935, 1156, 1012, },
			[3] = { 1037, 1106, 1068, 1104, 1126, 1067, 1052, 1073, 1098, 1105, 1119, 1064, 1050, 1085, 1091, 1090, 1094, 1124, },
			[4] = { 1158, 1173, 1135, 1171, 1174, 1178, 1172, 1177, 1204, },
			[5] = { 1216, 1351, 1270, 1277, 1275, 1283, 1282, 1228, 1281, 1269, 1279, 1243, 1273, 1358, 1276, 1271, 1242, 1278, 1302, 1341, 1337, 1345, 1272, 1280, 1352, 1357, 1353, 1359, },
			},

		--	The keys are the actual faction values used by Blizzard converted into a 3-character hexidecimal value.
		reputationMapping = {
			["015"] = 'Booty Bay',
			["02F"] = 'Ironforge',
			["036"] = 'Gnomeregan',
			["03B"] = 'Thorium Brotherhood',
			["043"] = 'Horde',
			["044"] = 'Undercity',
			["045"] = 'Darnassus',
			["046"] = 'Syndicate',
			["048"] = 'Stormwind',
			["04C"] = 'Orgrimmar',
			["051"] = 'Thunder Bluff',
			["057"] = 'Bloodsail Buccaneers',
			["05C"] = 'Gelkis Clan Centaur',
			["05D"] = 'Magram Clan Centaur',
			["0A9"] = 'Steamwheedle Cartel',
			["10E"] = 'Zandalar Tribe',
			["15D"] = 'Ravenholdt',
			["171"] = 'Gadgetzan',
			["1D5"] = 'Alliance',
			["1D6"] = 'Ratchet',
			["1FD"] = 'The League of Arathor',
			["1FE"] = 'The Defilers',
			["211"] = 'Argent Dawn',
			["212"] = 'Darkspear Trolls',
			["240"] = 'Timbermaw Hold',
			["241"] = 'Everlook',
			["24D"] = 'Wintersaber Trainers',
			["261"] = 'Cenarion Circle',
			["2D9"] = 'Frostwolf Clan',
			["2DA"] = 'Stormpike Guard',
			["2ED"] = 'Hydraxian Waterlords',
			["329"] = "Shen'dralar",
			["379"] = 'Warsong Outriders',
			["37A"] = 'Silverwing Sentinels',
			["38D"] = 'Darkmoon Faire',
			["38E"] = 'Brood of Nozdormu',
			["38F"] = 'Silvermoon City',
			["39A"] = 'Tranquillien',
			["3A2"] = 'Exodar',
			["3A4"] = 'The Aldor',
			["3A5"] = 'The Consortium',
			["3A6"] = 'The Scryers',
			["3A7"] = "The Sha'tar",
			["3AD"] = "The Mag'har",
			["3AE"] = 'Cenarion Expedition',
			["3B2"] = 'Honor Hold',
			["3B3"] = 'Thrallmar',
			["3C7"] = 'The Violet Eye',
			["3CA"] = 'Sporeggar',
			["3D2"] = 'Kurenai',
			["3DD"] = 'Keepers of Time',
			["3DE"] = 'The Scale of the Sands',
			["3F3"] = 'Lower City',
			["3F4"] = 'Ashtongue Deathsworn',
			["3F7"] = 'Netherwing',
			["407"] = "Sha'tari Skyguard",
			["40D"] = 'Alliance Vanguard',
			["40E"] = "Ogri'la",
			["41A"] = 'Valiance Expedition',
			["41C"] = 'Horde Expedition',
			["428"] = 'The Taunka',
			["42B"] = 'The Hand of Vengeance',
			["42C"] = "Explorers' League",
			["431"] = "The Kalu'ak",
			["435"] = 'Shattered Sun Offensive',
			["43D"] = 'Warsong Offensive',
			["442"] = 'Kirin Tor',
			["443"] = 'The Wyrmrest Accord',
			["446"] = 'The Silver Covenant',
			["44A"] = 'Knights of the Ebon Blade',
			["450"] = 'Frenzyheart Tribe',
			["451"] = 'The Oracles',
			["452"] = 'Argent Crusade',
			["45F"] = 'The Sons of Hodir',
			["464"] = 'The Sunreavers',
			["466"] = 'The Frostborn',
			["46D"] = 'Bilgewater Cartel',
			["46E"] = 'Gilneas',
			["46F"] = 'The Earthen Ring',
			["484"] = 'The Ashen Verdict',
			["486"] = 'Guardians of Hyjal',
			["490"] = 'Guild',
			["493"] = 'Therazane',
			["494"] = "Dragonmaw Clan",
			["495"] = 'Ramkahen',
			["496"] = 'Wildhammer Clan',
			["499"] = "Baradin's Wardens",
			["49A"] = "Hellscream's Reach",
			["4B4"] = "Avengers of Hyjal",
			["4C0"] = "Shang Xi's Academy",
			["4CC"] = 'Forest Hozen',
			["4DA"] = 'Pearlfin Jinyu',
			["4DB"] = 'Hozen',
			["4F5"] = 'Golden Lotus',
			["4F6"] = 'Shado-Pan',
			["4F7"] = 'Order of the Cloud Serpent',
			["4F8"] = 'The Tillers',
			["4F9"] = 'Jogu the Drunk',
			["4FB"] = 'Ella',
			["4FC"] = 'Old Hillpaw',
			["4FD"] = 'Chee Chee',
			["4FE"] = 'Sho',
			["4FF"] = 'Haohan Mudclaw',
			["500"] = 'Tina Mudclaw',
			["501"] = 'Gina Mudclaw',
			["502"] = 'Fish Fellreed',
			["503"] = 'Farmer Fung',
			["516"] = 'The Anglers',
			["539"] = 'The Klaxxi',
			["53D"] = 'The August Celestials',
			["541"] = 'The Lorewalkers',
			["547"] = 'The Brewmasters',
			["548"] = 'Huojin Pandaren',
			["549"] = 'Tushui Pandaren',
			["54D"] = 'Nomi',
			["54E"] = 'Nat Pagle',
			["54F"] = 'The Black Prince',
			},

		slashCommandOptions = {},
		specialQuests = { },
		statusMapping = { ['C'] = "Completed", ['F'] = 'Faction', ['G'] = 'Gender', ['H'] = 'Holiday', ['I'] = 'Invalidated', ['L'] = "InLog",
			['P'] = 'Profession', ['Q'] = 'Prerequisites', ['R'] = 'Race', ['S'] = 'Class', ['T'] = 'Reputation', ['V'] = "Level", },
		timeBombDelay = 0.75,
		timeSinceLastUpdate = 0,
		tooltip = nil,
		tracking = false,
		trackingStarted = false,
		useAncestor = true,
		verifyTable = {},
		verifyTableCount = 0,
		zoneNameMapping = {},	-- maps zone names into map IDs

		---
		--	Returns whether the specified achievement is complete.
		--	@param soughtAchievementId The standard numeric achievement ID representing an achievement.
		--	@return true is the achievement is complete, false otherwise.
		AchievementComplete = function(self, soughtAchievementId)
			local _, _, _, achievementComplete = GetAchievementInfo(soughtAchievementId)
			return achievementComplete
		end,

		---
		--	Internal Use.
		--	Updates the internal database to associate the specified quest with the specified map area,
		--	optionally setting the title for the map area.
		--	@param questId The standard numeric questId representing a quest.
		--	@param mapAreaId The standard numeric map are ID representing the map area.
		--	@param title The localized name of the map area.
		AddQuestToMapArea = function(self, questId, mapAreaId, title)
			if nil ~= questId and nil ~= mapAreaId then
				if nil == self.indexedQuests[mapAreaId] then self.indexedQuests[mapAreaId] = {} end
				if not tContains(self.indexedQuests[mapAreaId], questId) then tinsert(self.indexedQuests[mapAreaId], questId) end
				if nil == self.mapAreaMapping[mapAreaId] then self.mapAreaMapping[mapAreaId] = title end
			end
		end,

		--	This routine is registered to be called when any of the notifications this addon can post are posted.
		--	It formats a message that is stored in the tracking system.
		--	@param callbackType The string representing the type of callback as posted by the notification system.
		--	@param questId The standard questId posted by the notification system.
		_AddTrackingCallback = function(callbackType, questId)
			local message = strformat("%s %s(%d)", callbackType, Grail:QuestName(questId) or "NO NAME", questId)
			if "Accept" == callbackType or "Complete" == callbackType then
				local targetName, npcId, coordinates = Grail:TargetInformation()
				if nil ~= targetName then
					if nil == npcId then npcId = -123 end
					if nil == coordinates then coordinates = "NO COORDS" end
					message = strformat("%s %s %s(%d) %s", message, ("Accept" == callbackType) and "from" or "to", targetName, npcId, coordinates)
				end
			end
			Grail:_AddTrackingMessage(message)
		end,

		--	This adds the provided message to the tracking system.  The first time this is called, a timestamp with some player
		--	information is logged into the tracking system as well.
		--	@param msg The string that will be added to the tracking system.
		_AddTrackingMessage = function(self, msg)
			if nil == GrailDatabase["Tracking"] then GrailDatabase["Tracking"] = {} end
			if not self.trackingStarted then
				local hour, minute = GetGameTime()
				local weekday, month, day, year = CalendarGetDate()
				tinsert(GrailDatabase["Tracking"], strformat("%4d-%02d-%02d %02d:%02d %s/%s/%s/%s/%s/%s/%s/%s/%d", year, month, day, hour, minute, self.playerRealm, self.playerName, self.playerFaction, self.playerClass, self.playerRace, self.playerGender, self.playerLocale, GetCVar("portal"), self.blizzardRelease))
				self.trackingStarted = true
			end
			tinsert(GrailDatabase["Tracking"], msg)
		end,

		AliasQuestId = function(self, questId)
			return self:_QuestGenericAccess(questId, 'Y')
		end,

		AncestorStatusCode = function(self, questId, baseStatusCode)
			local prerequisites = self:QuestPrerequisites(questId)
			if nil ~= prerequisites then
				local possibleFailure = nil
				local foundSuccess = false
				local valueToUse
				for key, value in pairs(prerequisites) do
					valueToUse = ("table" == type(value)) and value or {value}
					local anyFailure = nil
					for _, qId in pairs(valueToUse) do
						local currentQuestId = tonumber(qId)
						local code = ''
						if currentQuestId == nil then
							currentQuestId = tonumber(strsub(qId, 2))
							code = strsub(qId, 1, 1)
						end
						if code ~= 'I' and code ~= 'J' and code ~= 'K' and code ~= 'L' and code ~= 'M' and code ~= 'N' and code ~= 'P' then
							if nil == self.questStatusCache["Q"][currentQuestId] then self.questStatusCache["Q"][currentQuestId] = {} end
							if not tContains(self.questStatusCache["Q"][currentQuestId], questId) then tinsert(self.questStatusCache["Q"][currentQuestId], questId) end
							if nil == anyFailure then
								local subCode = self:StatusCode(currentQuestId)
								local failureBits = bit.band(subCode, self.bitMaskQuestFailure)
								if failureBits > 0 then
									-- this means this specific quest has bits in it that would cause failure (need not check prerequisites for it at all since it fails by itself)
									-- and that means since this is part of an AND clause (which could be a sole quest technically), the AND clause fails
									anyFailure = failureBits
								elseif bit.band(subCode, self.bitMaskPrerequisites) > 0 then
									-- this means the quest itself does not immediately fail, but it fails because of prerequisites, so that reason needs to be checked in
									-- case it is one of the hard reasons for failure
									failureBits = bit.band(subCode, self.bitMaskQuestFailureWithAncestor)
									if failureBits > 0 then
										-- this means the quest has a prerequisite quest that fails in one of the hard ways
										anyFailure = failureBits / 1024
									end
								end
							end
						end
					end
					if nil ~= anyFailure and nil == possibleFailure then
						possibleFailure = anyFailure
					end
					if nil == anyFailure then	-- normally we would break out of the loop here, but we need to register all the quests to be able to invalidate properly
						foundSuccess = true
					end
				end
				if not foundSuccess and nil ~= possibleFailure then
					baseStatusCode = baseStatusCode + (1024 * possibleFailure)
				end
			end
			return baseStatusCode
		end,

		--	This looks at each of the quests in the quests table, and makes the appropriate checks to see
		--	whether they have been completed, are in the log, etc. based on the parameters used.  If all
		--	of the quests pass muster true is returned, otherwise false.
		_AllEvaluateTrue = function(self, quests, masterQuestId, dangerous)
			local stillGood = true
			if nil ~= quests then
				local questCompleted, questInLog, questStatus, questEverCompleted, canAcceptQuest, spellPresent, achievementComplete, itemPresent, questEverAbandoned, professionGood, questEverAccepted
				local checkLog, checkEver, checkStatusComplete, shouldCheckTurnin, checkSpell, checkAchievement, checkItem, checkItemLack, checkEverAbandoned, checkNeverAbandoned, checkProfession, checkEverAccepted
				local code, value, position, subcode

				for _, questId in pairs(quests) do
					questCompleted, questInLog, questStatus, questEverCompleted, canAcceptQuest, spellPresent, achievementComplete, professionGood, questEverAccepted = false, false, false, false, true, false, false, false, false
					checkLog, checkEver, checkStatusComplete, shouldCheckTurnin, checkSpell, checkAchievement, checkItem, checkItemLack, checkEverAbandoned, checkNeverAbandoned, checkProfession, checkEverAccepted = false, false, false, false, false, false, false, false, false, false, false, false
					position = 1
					code = strsub(questId, 1, 1)
					if nil == tonumber(code) then
						-- code is already a letter, so leave it alone
						position = 2
					elseif dangerous then	-- we are checking I:
						code = 'C'
					else					-- we are checking P:
						code = 'A'
					end
					if 'P' == code then
						subcode = strsub(questId, 2, 2)
						position = 3
					else
						subcode = nil
					end
					value = tonumber(strsub(questId, position))

					-- Now to figure out what needs to be checked based on the code
					if code == 'A' then
						shouldCheckTurnin = true
					elseif code == 'B' then
						checkLog = true
					elseif code == 'C' then
						shouldCheckTurnin = true
						checkLog = true
					elseif code == 'D' then
						checkStatusComplete = true
					elseif code == 'E' then
						shouldCheckTurnin = true
						checkStatusComplete = true
					elseif code == 'H' then
						checkEver = true
					elseif code == 'I' then
						checkSpell = true
					elseif code == 'J' then
						checkAchievement = true
					elseif code == 'K' then
						checkItem = true
					elseif code == 'L' then
						checkItemLack = true
					elseif code == 'M' then
						checkEverAbandoned = true
					elseif code == 'N' then
						checkNeverAbandoned = true
					elseif code == 'O' then
						checkEverAccepted = true
					elseif code == 'P' then
						checkProfession = true
					else
						print("|cffff0000Grail|r found _AllEvaluateTrue quest ID", questId, "with unknown code", code)
					end

					if shouldCheckTurnin then questCompleted = self:IsQuestCompleted(value) end
					if checkLog or checkStatusComplete then questInLog, questStatus = self:IsQuestInQuestLog(value) end
					if checkEver then questEverCompleted = self:HasQuestEverBeenCompleted(value) end
					if (shouldCheckTurnin and questCompleted) or (checkEver and questEverCompleted) then
						if dangerous then
							if nil == self.currentMortalIssues[value] then self.currentMortalIssues[value] = {} end
							if not tContains(self.currentMortalIssues[value], masterQuestId) then tinsert(self.currentMortalIssues[value], masterQuestId) end
						else
							canAcceptQuest = self:CanAcceptQuest(value, true)
						end
					end
					if checkSpell then spellPresent = self:SpellPresent(value) end
					if checkAchievement then achievementComplete = self:AchievementComplete(value) end
					if checkItem or checkItemLack then itemPresent = self:ItemPresent(value) end
					if checkEverAbandoned or checkNeverAbandonded then questEverAbandoned = self:HasQuestEverBeenAbandoned(value) end
					if checkProfession then professionGood = self:ProfessionExceeds(subcode, value) end
					if checkEverAccepted then questEverAccepted = self:HasQuestEverBeenAccepted(value) end

					stillGood = stillGood and (
						(shouldCheckTurnin and questCompleted and canAcceptQuest) or
						(checkLog and questInLog) or
						(checkEver and questEverCompleted and canAcceptQuest) or
						(checkStatusComplete and questInLog and questStatus ~= nil and questStatus > 0) or
						(checkSpell and spellPresent) or
						(checkAchievement and achievementComplete) or
						(checkItem and itemPresent) or
						(checkItemLack and not itemPresent) or
						(checkEverAbandoned and questEverAbandoned) or
						(checkNeverAbandoned and not questEverAbandoned) or
						(checkProfession and professionGood) or
						(checkEverAccepted and questEverAccepted)
						)

				end
				
			end
			return stillGood
		end,

		--	This looks at the code with appropriate prefix from the specified log and analyzes it to determine if any of the quests
		--	the code contains have been completed, or if checkLog is true, are in the quest log.  The format for the code is a comma
		--	separated list of single questIds that match or if more than one is required to match, they are separated by a plus.  So:
		--	<br>123,456,789+1122,3344<br>means and of the following quests would match:<br>123<br>456<br>789 and 1122<br>3344<br>
		--	@param questId The standard numeric questId representing a quest.
		--	@param codePrefix An prefix used to determine which type of internal code to process.
		--	@return True if any of the codes quests are completed (or appropriately in the quest log), false otherwise.
		--	@return True is there actually is a code that needed checking, false otherwise.
		_AnyEvaluateTrue = function(self, questId, codePrefix)
			questId = tonumber(questId)
			if nil == questId or nil == self.quests[questId] then return false end
			local anyEvaluateTrue = false
			local requirementPresent = false
			local codeValues = self.quests[questId][codePrefix]
			local dangerous = (codePrefix == 'I' or codePrefix == 'B')
			if nil ~= codeValues then
				local valueToUse
				requirementPresent = true
				for key, value in pairs(codeValues) do
					valueToUse = ("table" == type(value)) and value or {value}
					anyEvaluateTrue = self:_AllEvaluateTrue(valueToUse, questId, dangerous)
					if anyEvaluateTrue then break end
				end

			end
			return anyEvaluateTrue, requirementPresent
		end,

		---
		--	Returns a table of questIds that are available breadcrumb quests for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return A table of questIds for available breadcrumb quests for this quest, or nil if there are none.
		AvailableBreadcrumbs = function(self, questId)
			local retval = {}
			local possible = self:QuestBreadcrumbs(questId)
			if nil ~= possible then
				for _, qid in pairs(possible) do
					if self:CanAcceptQuest(qid, false, true) then
						tinsert(retval, qid)
					end
				end
			end
			if 0 == #retval then retval = nil end
			return retval
		end,

		_BagUpdates = function(type, ignored)
			local self = Grail
			-- we cheat and instead of doing any work here we just invalidate all the quests associated with
			-- items that need to be present or need not be present because the evaluation of status will
			-- check whether items are present
			for itemId in pairs(self.questStatusCache['C']) do
				self:_StatusCodeInvalidate(self.questStatusCache['C'][itemId])
			end
			for itemId in pairs(self.questStatusCache['E']) do
				self:_StatusCodeInvalidate(self.questStatusCache['E'][itemId])
			end
		end,

		---
		--	Returns true is the specified quest can be accepted based on the other parameters.  Otherwise returns false.
		--	@param questId The standard numeric questId representing a quest.
		--	@param ignoreCompleted	Ignores the status of the quest being completed.
		--	@param ignorePrerequisites	Ignores whether the quest has met all its prerequisites.
		--	@param ignoreInLog	Ignores whether the quest is already in the Blizzard quest log.
		--	@param ignoreLevelTooLow	Ignores whether the quest is too high for the player to obtain currently.
		--	@param ignoreHolidayRequirement	Ignores whether the quest is only available during specific holidays.
		CanAcceptQuest = function(self, questId, ignoreCompleted, ignorePrerequisites, ignoreInLog, ignoreLevelTooLow, ignoreHolidayRequirement)
			local bitValue = self.bitMaskAcceptableMask
			if ignoreCompleted then bitValue = bitValue - self.bitMaskCompleted end
			if ignorePrerequisites then bitValue = bitValue - self.bitMaskPrerequisites end
			if ignoreInLog then bitValue = bitValue - self.bitMaskInLog - self.bitMaskInLogComplete end
			if ignoreLevelTooLow then bitValue = bitValue - self.bitMaskLevelTooLow end
			if ignoreHolidayRequirement then bitValue = bitValue - self.bitMaskHoliday end
			return (0 == bit.band(self:StatusCode(questId), bitValue))
		end,

		---
		--	Determines whether the soughtHolidayName is currently being celebrated.
		--	@param soughtHolidayName The localized name of a holiday, like Brewfest or Darkmoon Faire.
		--	@return true if the holiday is being celebrated currently, or false otherwise
		CelebratingHoliday = function(self, soughtHolidayName)
			local retval = false
			local weekday, month, day, year = CalendarGetDate()
			local hour, minute = GetGameTime()
			local i = 1
			local needsChristmasOrLater = false
			if self.holidayMapping['Z'] == soughtHolidayName then
				needsChristmasOrLater = true
				soughtHolidayName = self.holidayMapping['V']
			end
-- TODO: This needs to check the time as well because, e.g., a holiday may stop at 02h00 and just checking the title shows it valid for the entire day, which is not correct.
			while CalendarGetDayEvent(0, day, i) do
				local title, hour, minute, calendarType, sequenceType, eventType, texture, modStatus, inviteStatus, invitedBy, difficulty, inviteType = CalendarGetDayEvent(0, day, i)
				if eventType == 0 and calendarType == 'HOLIDAY' then
					if title == soughtHolidayName then
						retval = true
					end
				end
				i = i + 1
			end
			if retval and needsChristmasOrLater then
				if 12 == month and 25 > day then
					retval = false
				end
			end
			if self.holidayMapping['U'] == soughtHolidayName then
				if (12 == month and 31 == day) then
					retval = true
				end
			end

			-- Stranglethorn Fishing Extravaganza quest givers appear on Saturday and Sunday
			if self.holidayMapping['X'] == soughtHolidayName then
				if weekday == 1 or weekday == 7 then
					retval = true
				end
			end

			-- Kalu'ak Fishing Derby quest giver appears on Sunday between 14h00 and 16h00 server
			if self.holidayMapping['K'] == soughtHolidayName then
				if weekday == 7 then
					local minutes = hour * 60 + minute
					if minutes >= (14 * 60) and minutes <= (16 * 60) then
						retval = true
					end
				end
			end

			return retval
		end,

		--	This routine attempts to remove items from the special tables that are stored in the GrailDatabase table
		--	when they have been added to the internal database.  These special tables are populated when Grail discovers
		--	something lacking in its internal database as game play proceeds.  This routine is called upon startup.
		_CleanDatabase = function(self)

			-- Remove quests from SpecialQuests that have been marked as special in our internal database.
			if nil ~= GrailDatabase["SpecialQuests"] then
				for questName, _ in pairs(GrailDatabase["SpecialQuests"]) do
					local questId = self:QuestWithName(questName)
					if self.quests[questId] and  self.quests[questId]['SP'] then
						GrailDatabase["SpecialQuests"][questName] = nil
					end
				end
			end

			-- Remove quests from NewQuests that have been added to our internal database.
			-- If the name matches and all the codes are in our internal datbase we remove.
			if nil ~= GrailDatabase["NewQuests"] then
				for questId, q in pairs(GrailDatabase["NewQuests"]) do
					if self:DoesQuestExist(questId) then
						if q[self.playerLocale] == self:QuestName(questId) or q[self.playerLocale] == "No Title Stored" then
							local allCodesGood = true
							if nil ~= q[1] then
								local codeArray = { strsplit(" ", q[1]) }
								for _, code in pairs(codeArray) do
									if code ~= "" then
										if "A:" == strsub(code, 1, 2) then
											local npcId = tonumber(strsub(code, 3))
											if 0 == npcId then
												local foundAny = false
												if nil ~= self.quests[questId]['A'] then
													for _, n in pairs(self.quests[questId]['A']) do
														local n1 = tonumber(n)
														if n1 ~= nil and n1 <= 0 then foundAny = true end
													end
												end
												if not foundAny then allCodesGood = false end
											elseif nil == self.quests[questId]['A'] or (not tContains(self.quests[questId]['A'], npcId) and not self:_ContainsAliasNPC(self.quests[questId]['A'], npcId)) then
												allCodesGood = false
											end
										elseif "T:" == strsub(code, 1, 2) then
											local npcId = tonumber(strsub(code, 3))
											if 0 == npcId then
												local foundAny = false
												if nil ~= self.quests[questId]['T'] then
													for _, n in pairs(self.quests[questId]['T']) do
														local n1 = tonumber(n)
														if n1 ~= nil and n1 <= 0 then foundAny = true end
													end
												end
												if not foundAny then allCodesGood = false end
											elseif nil == self.quests[questId]['T'] or (not tContains(self.quests[questId]['T'], npcId) and not self:_ContainsAliasNPC(self.quests[questId]['T'], npcId)) then
												allCodesGood = false
											end
										elseif "+D" == code then
											if not self:IsDaily(questId) then
												allCodesGood = false
											end
										elseif "K0" == strsub(code, 1, 2) then
-- At the moment we ignore this code instead of verifying it exists in the current database.
										else
											print("|cffff0000Grail|r found NewQuests quest ID", questId, "with unknown code", code)
										end
									end
								end
							end
							if allCodesGood then GrailDatabase["NewQuests"][questId] = nil end
						end
					end
				end
			end

			-- Remove NPCs from NewNPCs that have been added to our internal database
			-- Basically, if the name matches and we have a location in our internal database we remove
			if nil ~= GrailDatabase["NewNPCs"] then
				for npcId, n in pairs(GrailDatabase["NewNPCs"]) do
					local locations = self:_RawNPCLocations(npcId)
					if nil ~= locations then
						for _, npc in pairs(locations) do
							if nil ~= npc.name and n[self.playerLocale] == npc.name and ((nil ~= npc.x and nil ~= npc.y) or npc.near) then
								GrailDatabase["NewNPCs"][npcId] = nil
							end
						end
					else	-- it seems we do not have the NPC or we have no information about it
						-- if the version of this entry is so old we will just nuke it
						local startPos, endPos, grailVersion, restOfString = strfind(n[2], "(%d+)/(.*)")
						if nil ~= startPos then
							grailVersion = tonumber(grailVersion)
							if nil ~= grailVersion and grailVersion < self.versionNumber - 4 then
								GrailDatabase["NewNPCs"][npcId] = nil
							end
						end
					end
				end
			end

			-- BadNPCData is processed like BadQuestData (which follows)
			if nil ~= GrailDatabase["BadNPCData"] then
				local newBadNPCData = {}
				for k, v in pairs(GrailDatabase["BadNPCData"]) do
					local startPos, endPos, grailVersion, npcId, restOfString = strfind(v, "G(%d+)|(%d+)(.*)")
					local writables = {}
					if nil ~= startPos then
						npcId = tonumber(npcId)
						if nil ~= restOfString then
							local codes = { strsplit('|', restOfString) }
							if nil ~= codes then
								local nameValue = nil	-- used in conjunction with localeValue
								local localeValue = nil	-- used in conjunction with nameValue
								for _, v in pairs(codes) do
									if nil == v or "" == v then
										-- skip it
									elseif "Locale:" == strsub(v, 1, 7) then
										localeValue = strsub(v, 8)
										if nil ~= nameValue then
											if localeValue ~= self.playerLocale or nameValue ~= self:NPCName(npcId) then
												tinsert(writables, "Name:" .. nameValue)
												tinsert(writables, "Locale:" .. localeValue)
											end
										end
									elseif "Name:" == strsub(v, 1, 5) then
										nameValue = strsub(v, 6)
										if nil ~= localeValue then
											if localeValue ~= self.playerLocale or nameValue ~= self:NPCName(npcId) then
												tinsert(writables, "Name:" .. nameValue)
												tinsert(writables, "Locale:" .. localeValue)
											end
										end
									else
										tinsert(writables, v)
									end
								end
							end
						end
					end
					if 0 < #writables then
						local whatToWrite = 'G' .. grailVersion .. '|' .. npcId
						for _, w in pairs(writables) do
							whatToWrite = whatToWrite .. '|' .. w
						end
						tinsert(newBadNPCData, whatToWrite)
					end
				end
				GrailDatabase["BadNPCData"] = newBadNPCData
			end

			-- The BadQuestData will be analyzed against the current database and things that have been fixed
			-- in the current database will be removed from BadQuestData.  This is done by creating a new table
			-- and only putting things that are not fixed into it.
			if nil ~= GrailDatabase["BadQuestData"] then
				local newBadQuestData = {}
				for k, v in pairs(GrailDatabase["BadQuestData"]) do
					if "table" ~= type(v) then
						local startPos, endPos, grailVersion, questId, statusCode, restOfString = strfind(v, "G(%d+)|(%d+)|(%d+)(.*)")
						local writables = {}

						if nil ~= startPos then
							questId = tonumber(questId)
							statusCode = tonumber(statusCode)
							if nil ~= restOfString then
								local codes = { strsplit('|', restOfString) }
								if nil ~= codes then
									local titleValue = nil	-- used in conjunction with localeValue
									local localeValue = nil	-- used in conjunction with titleValue
									for _, v in pairs(codes) do
										if nil == v or "" == v then
											-- skip it
										elseif "Rep:" == strsub(v, 1, 4) and 4 < strlen(v) then
											if nil == self.quests[questId] or nil == self.quests[questId][6] or not tContains(self.quests[questId][6], strsub(v, 5)) then
												tinsert(writables, v)
											end
										elseif "UnknownRep:" == strsub(v, 1, 11) then
											local startPos2, endPos2, reputationName, changeAmount = strfind(strsub(v, 12), "(.*) (-?%d+)")
											local shouldWrite = true
											local whatToWrite = v
											if nil ~= startPos2 then
												local reputationIndex = self.reverseReputationMapping[reputationName]
												if nil ~= reputationIndex then
													if "490" == reputationIndex then	-- remove the Guild reputation indexes that beta testers may have since we do not want them
														shouldWrite = false
													else
														local repChangeString = strformat("%s%d", reputationIndex, changeAmount)
														if nil ~= self.quests[questId][6] and tContains(self.quests[questId][6], repChangeString) then
															shouldWrite = false
														else
															whatToWrite = strformat("Rep:%s", repChangeString)
														end
													end
												end
											end
											if shouldWrite then tinsert(writables, whatToWrite) end
										elseif "C:" == strsub(v, 1, 2) then
											if not self:MeetsRequirementClass(questId, strsub(v, 3)) then tinsert(writables, v) end
										elseif "F:" == strsub(v, 1, 2) then
											if not self:MeetsRequirementFaction(questId, strsub(v, 3)) then tinsert(writables, v) end
										elseif "G:" == strsub(v, 1, 2) then
											if not self:MeetsRequirementGender(questId, strsub(v, 3)) then tinsert(writables, v) end
										elseif "L:" == strsub(v, 1, 2) then
											if not self:MeetsRequirementLevel(questId, tonumber(strsub(v, 3))) then tinsert(writables, v) end
										elseif "R:" == strsub(v, 1, 2) then
											if not self:MeetsRequirementRace(questId, strsub(v, 3)) then tinsert(writables, v) end
										elseif "Level:" == strsub(v, 1, 6) then
											local internalLevel = self:QuestLevel(questId)
											local actualLevel = tonumber(strsub(v, 7))
											if 0 ~= internalLevel and (internalLevel or 1) ~= actualLevel then
												tinsert(writables, v)
											end
										elseif "Locale:" == strsub(v, 1, 7) then
											localeValue = strsub(v, 8)
											if nil ~= titleValue then
												if localeValue ~= self.playerLocale or titleValue ~= self:QuestName(questId) then
													tinsert(writables, "Title:" .. titleValue)
													tinsert(writables, "Locale:" .. localeValue)
												end
											end
										elseif "Title:" == strsub(v, 1, 6) then
											titleValue = strsub(v, 7)
											if nil ~= localeValue then
												if localeValue ~= self.playerLocale or titleValue ~= self:QuestName(questId) then
													tinsert(writables, "Title:" .. titleValue)
													tinsert(writables, "Locale:" .. localeValue)
												end
											end
										else
											tinsert(writables, v)
										end
									end
								end
							end
						end
						if 0 < #writables and tonumber(grailVersion) + 4 >= self.versionNumber then
							local whatToWrite = 'G' .. grailVersion .. '|' .. questId .. '|' .. statusCode
							for _, w in pairs(writables) do
								whatToWrite = whatToWrite .. '|' .. w
							end
							tinsert(newBadQuestData, whatToWrite)
						end
					end
				end
				GrailDatabase["BadQuestData"] = newBadQuestData
			end
		end,

		--	This routine adds a notification to the delayed notification system if a notification
		--	of that type does not already exist in the system.  Using this allows the code to effectively
		--	post as many of a type of notification as it wants, but when the delayed notifications are
		--	processed only one type of notification will be sent to observers.
		_CoalesceDelayedNotification = function(self, notificationName, delay)
			local needToPost = true
			if nil ~= self.delayedNotifications then
				for i = 1, #(self.delayedNotifications) do
					if notificationName == self.delayedNotifications[i]["n"] then
						needToPost = false
					end
				end
			end
			if needToPost then
				self:_PostDelayedNotification(notificationName, nil, delay)
			end
		end,

		--	Populates the internal caches for all the fixed codes that are derived from quest data.
		--	@param questId The standard numeric questId representing a quest.
		_CodeAllFixed = function(self, questId)
			questId = tonumber(questId)

			if nil ~= questId then

				--	We just need to use one of the caches as the signal to compute them since they are all done together
				if nil ~= self.quests[questId] and nil == self.quests[questId][2] then
					local typeValue = 0
					local holidayValue = 0
					local obtainersValue = 0
					local levelValue = 0

					local codeString = self.quests[questId][1] or nil
					if nil ~= codeString then
						local codeArray = { strsplit(" ", codeString) }
						local c
						local code
						local codeValue
						local bitValue
						local foundTFaction = false
						local foundAFaction = false
						for i = 1, #codeArray do
							c = codeArray[i]
							code = strsub(c, 1, 1)
							codeValue = strsub(c, 2, 2)

							if 'C' == code then
								bitValue = self.classToBitMapping[codeValue]
								if nil ~= bitValue then
									obtainersValue = obtainersValue + bitValue
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'B' == code then
								if ':' == codeValue then
									self.quests[questId]['B'] = self:_FromList(strsub(c, 3))
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'J' == code then
								if ':' == codeValue then
									self.quests[questId]['J'] = self:_FromPattern(strsub(c, 3))
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'X' == code then
								--	The inherent nature of an X code makes is such that only one has meaning, and C codes should not be combined
								bitValue = self.classToBitMapping[codeValue]
								if nil ~= bitValue then
									obtainersValue = bit.band(obtainersValue, bit.bnot(self.bitMaskClassAll))
									obtainersValue = obtainersValue + self.bitMaskClassAll - bitValue
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'F' == code then
								if 'A' == codeValue then
									obtainersValue = obtainersValue + self.bitMaskFactionAlliance
								elseif 'H' == codeValue then
									obtainersValue = obtainersValue + self.bitMaskFactionHorde
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'G' == code then
								if 'M' == codeValue then
									obtainersValue = obtainersValue + self.bitMaskGenderMale
								elseif 'F' == codeValue then
									obtainersValue = obtainersValue + self.bitMaskGenderFemale
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'R' == code then
								bitValue = self.raceToBitMapping[codeValue]
								if nil ~= bitValue then
									obtainersValue = obtainersValue + bitValue
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'S' == code then
								if 'P' ~= codeValue then
									--	The inherent nature of an S code makes is such that only one has meaning, and R codes should not be combined
									bitValue = self.raceToBitMapping[codeValue]
									if nil ~= bitValue then
										obtainersValue = bit.band(obtainersValue, bit.bnot(self.bitMaskRaceAll))
										obtainersValue = obtainersValue + self.bitMaskRaceAll - bitValue
									else
										print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
									end
								else
									self.quests[questId]['SP'] = true
								end

							elseif 'K' == code then
								levelValue = levelValue + (tonumber(strsub(c, 2, 4)) * self.bitMaskQuestLevelOffset)
								if strlen(c) > 4 then
									typeValue = tonumber(strsub(c, 5)) or 0
								end

							elseif 'L' == code then
								levelValue = levelValue + ((tonumber(strsub(c, 2)) or 1) * self.bitMaskQuestMinLevelOffset)

							elseif 'M' == code then
								levelValue = levelValue + ((tonumber(strsub(c, 2)) or 127) * self.bitMaskQuestMaxLevelOffset)

							elseif 'H' == code then
								bitValue = self.holidayToBitMapping[codeValue]
								if nil ~= bitValue then
									holidayValue = holidayValue + bitValue
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'V' == code or 'W' == code then
								local reputationIndex = strsub(c, 2, 4)
								local reputationValue = tonumber(strsub(c, 5))
								if nil == self.quests[questId]['rep'] then self.quests[questId]['rep'] = {} end
								if nil == self.quests[questId]['rep'][reputationIndex] then self.quests[questId]['rep'][reputationIndex] = {} end
								self.quests[questId]['rep'][reputationIndex][('V' == code) and 'min' or 'max'] = reputationValue

							elseif 'O' == code then
								if ':' == codeValue then
									self.quests[questId]['O'] = self:_FromPattern(strsub(c, 3))
									self:_FromStructure(self.quests[questId]['O'], questId, 'B')
								elseif 'A' == codeValue and strlen(c) > 4 and 'OAC:' == strsub(c, 1, 4) then
									self.quests[questId]['OAC'] = self:_FromList(strsub(c, 5))
								elseif 'B' == codeValue and strlen(c) > 4 and 'OBC:' == strsub(c, 1, 4) then
									self.quests[questId]['OBC'] = self:_FromList(strsub(c, 5))
								elseif 'C' == codeValue and strlen(c) > 4 and 'OCC:' == strsub(c, 1, 4) then
									self.quests[questId]['OCC'] = self:_FromList(strsub(c, 5))
								elseif 'D' == codeValue and strlen(c) > 4 and 'ODC:' == strsub(c, 1, 4) then
									self.quests[questId]['ODC'] = self:_FromList(strsub(c, 5))
								elseif 'E' == codeValue and strlen(c) > 4 and 'OEC:' == strsub(c, 1, 4) then
									self.quests[questId]['OEC'] = self:_FromList(strsub(c, 5))
								elseif 'P' == codeValue and strlen(c) > 4 and 'OPC:' == strsub(c, 1, 4) then
									self.quests[questId]['OPC'] = self:_FromPattern(strsub(c, 5))
								elseif 'T' == codeValue and strlen(c) > 4 and 'OTC:' == strsub(c, 1, 4) then
									self.quests[questId]['OTC'] = self:_FromPattern(strsub(c, 5))
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'Y' == code then
								if ':' == codeValue then
									self.quests[questId]['Y'] = tonumber(strsub(c, 3))
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'T' == code then
								if ':' == codeValue then
									if not foundTFaction then self.quests[questId]['T'] = self:_FromList(strsub(c, 3)) end
								elseif 'A' == codeValue or 'H' == codeValue then
									if (('Horde' == self.playerFaction) and 'H' or 'A') == codeValue then
										self.quests[questId]['T'] = self:_FromList(strsub(c, 4))
										foundTFaction = true
									end
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'I' == code then
								if ':' == codeValue then
									self.quests[questId]['I'] = self:_FromPattern(strsub(c, 3))
									self:_ProcessQuestsForHandlers(questId, self.quests[questId]['I'])
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'A' == code then
								if ':' == codeValue then
									if not foundAFaction then self.quests[questId]['A'] = self:_FromList(strsub(c, 3)) end
								elseif 'A' == codeValue or 'H' == codeValue then
									if (('Horde' == self.playerFaction) and 'H' or 'A') == codeValue then
										self.quests[questId]['A'] = self:_FromList(strsub(c, 4))
										foundAFaction = true
									end
								elseif 'K' == codeValue then
									self.quests[questId]['AK'] = self:_FromList(strsub(c, 4))
								elseif 'Z' == codeValue then
									self.quests[questId]['AZ'] = tonumber(strsub(c, 4))
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end

							elseif 'B' == code then
								--	we call _FromList with the current value of the 'B' table because processing 'O:' codes before
								--	may have created a 'B' table, so we would want to add to it instead of overwriting it
								self.quests[questId]['B'] = self:_FromList(strsub(c, 2), nil, self.quests[questId]['B'])

							elseif 'P' == code then
								local colonCheck = strsub(c, 3, 3) 
								if ':' == codeValue then
									self.quests[questId]['P'] = self:_FromPattern(strsub(c, 3))
									self:_ProcessQuestsForHandlers(questId, self.quests[questId]['P'])
								elseif nil ~= self.professionMapping[codeValue] then
									if nil == self.quests[questId]['prof'] then self.quests[questId]['prof'] = {} end
									self.quests[questId]['prof'][codeValue] = tonumber(strsub(c, 3))
								else
									print("|cFFFF0000Grail Error|r: Quest",questId,"has unknown code:",c)
								end
							end

						end

						--	Since the assumption is if there is a lack of code present to limit those permitted to
						--	obtain quests, checks must be done to see whether any limitations are present, and if
						--	none, the values need to be altered to permit all of those subset.
						if 0 == bit.band(obtainersValue, self.bitMaskFactionAll) then obtainersValue = obtainersValue + self.bitMaskFactionAll end
						if 0 == bit.band(obtainersValue, self.bitMaskClassAll) then obtainersValue = obtainersValue + self.bitMaskClassAll end
						if 0 == bit.band(obtainersValue, self.bitMaskGenderAll) then obtainersValue = obtainersValue + self.bitMaskGenderAll end
						if 0 == bit.band(obtainersValue, self.bitMaskRaceAll) then obtainersValue = obtainersValue + self.bitMaskRaceAll end

						--	And the levels are assumed to have minimum and maximum values that are reasonable if none present
						if 0 == bit.band(levelValue, self.bitMaskQuestMinLevel) then levelValue = levelValue + self.bitMaskQuestMinLevelOffset end
						if 0 == bit.band(levelValue, self.bitMaskQuestMaxLevel) then levelValue = levelValue + self.bitMaskQuestMaxLevel end

					end

					self.quests[questId][2] = typeValue
					self.quests[questId][3] = holidayValue
					self.quests[questId][4] = obtainersValue
					self.quests[questId][13] = levelValue

				end

			end

		end,

		---
		--	Returns a bit mask indicating the type of the holidays that limit who can get the quest.
		--	@return An integer that should be interpreted as a bit mask containing information about what holiday .
		CodeHoliday = function(self, questId)
			questId = tonumber(questId)
			self:_CodeAllFixed(questId)
			return nil ~= questId and self.quests[questId] and self.quests[questId][3] or 0
		end,

		---
		--	Returns a bit mask indicating the levels of who can get the quest.
		--	@return An integer that should be interpreted as a bit mask containing information about levels of the quest.
		CodeLevel = function(self, questId)
			questId = tonumber(questId)
			self:_CodeAllFixed(questId)
			return nil ~= questId and self.quests[questId] and self.quests[questId][13] or 0
		end,

		---
		--	Returns a bit mask indicating the type of the obtainers who can get the quest.
		--	@return An integer that should be interpreted as a bit mask containing information about who can get the quest.
		CodeObtainers = function(self, questId)
			questId = tonumber(questId)
			self:_CodeAllFixed(questId)
			return nil ~= questId and self.quests[questId] and self.quests[questId][4] or 0
		end,

		---
		--	Internal Use.
		--	Returns a table of codes from the victim string that match the sought prefix.
		--	@param victim The string that contains codes separated by spaces.
		--	@param soughtPrefix The prefix of the desired matching codes.
		--	@return A table of the matching codes or nil if there are none.
		CodesWithPrefix = function(self, victim, soughtPrefix)
			local retval = { }
			local codeArray = { strsplit(" ", victim) }
			if nil ~= codeArray then
				for i = 1, #(codeArray), 1 do
					if 1 == strfind(codeArray[i], soughtPrefix) then
						tinsert(retval, codeArray[i])
					end
				end
			end
			-- return nil if there is nothing in the table of matching codes
			if 0 == #(retval) then
				retval = nil
			end
			return retval
		end,

		---
		--	Internal Use.
		--	Returns a table of codes from the NPC that match the sought prefix.
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@param soughtPrefix The prefix of the desired matching codes.
		--	@return A table of the matching codes or nil if there are none.
		CodesWithPrefixNPC = function(self, npcId, soughtPrefix)
			local retval = nil
			if nil == npcId or not tonumber(npcId) then return nil end
			npcId = tonumber(npcId)
			if nil ~= self.npcs[npcId] then
				retval = self:CodesWithPrefix(self.npcs[npcId][1], soughtPrefix)
			end
			return retval
		end,

		---
		--	Returns a bit mask indicating the type of the quest.
		--	@return An integer that should be interpreted as a bit mask containing information about the type of quest.
		CodeType = function(self, questId)
			questId = tonumber(questId)
			self:_CodeAllFixed(questId)
			return nil ~= questId and self.quests[questId] and self.quests[questId][2] or 0
		end,

		-- This checks to see if the npcList contains an NPC that is an alias for the soughtNPC
		_ContainsAliasNPC = function(self, npcList, soughtNPC)
			local retval = false
			if nil ~= npcList and nil ~= soughtNPC then
				for _, npcId in pairs(npcList) do
					local locations = self:_RawNPCLocations(npcId)
					if nil ~= locations then
						for _, npc in pairs(locations) do
							if npc.alias == soughtNPC then
								retval = true
							end
						end
					end
				end
			end
			return retval
		end,

		---
		--	Returns the localized and gender specific name of the player's class.
		--	@param englishName The Blizzard internal name of the class.  If nil, the player's class will be used.
		--	@param desiredGender The numeric value for the desired gender (2 is male and 3 is female).  If nil, the player's gender will be used.
		--	@return A string whose value is the localized name of the class using the appropriate gender where applicable.
		CreateClassNameLocalizedGenderized = function(self, englishName, desiredGender)
			local nameToUse = englishName or self.playerClass
			local genderToUse = desiredGender or self.playerGender
			return (genderToUse == 3) and LOCALIZED_CLASS_NAMES_FEMALE[nameToUse] or LOCALIZED_CLASS_NAMES_MALE[nameToUse]
		end,

		---
		--	Internal Use.
		--	Populates internal quest lists based on location of NPCs that can start
		--	the quests.  In normal mode, the API will use this indexed list instead
		--	of accessing the NPC information, thereby speeding up queries.
		CreateIndexedQuestList = function(self)
			if self.debug then debugprofilestart() end
			self.indexedQuests = {}
			self.indexedQuestsExtra = {}
			self.loremasterQuests = {}
			self.specialQuests = {}

			local locations
			local mapId
			local bitMask
			local questName

			for questId in pairs(self.quests) do

				self:_CodeAllFixed(questId)

				--	Add the quests to the map areas based on the locations of the starting NPCs
--				locations = self:QuestLocations(questId, 'A')
				locations = self:QuestLocations(questId, 'A', nil, nil, nil, nil, nil, nil, true)
				if nil ~= locations then
					for _, npc in pairs(locations) do
						if nil ~= npc.mapArea then
							local mapId = npc.mapArea
							if npc.realArea then
								if nil == self.indexedQuestsExtra[mapId] then self.indexedQuestsExtra[mapId] = {} end
								if not tContains(self.indexedQuestsExtra[mapId], questId) then tinsert(self.indexedQuestsExtra[mapId], questId) end
							else
								if nil == self.indexedQuests[mapId] then self.indexedQuests[mapId] = {} end
								if not tContains(self.indexedQuests[mapId], questId) then tinsert(self.indexedQuests[mapId], questId) end
							end
						else
							if self.debug then print("Quest", questId, "has nil mapId for NPC", npc.name, npc.id) end
						end
					end
				end

				-- Add this quest if it automatically starts entering a map area
				if nil ~= self.quests[questId]['AZ'] then
					self:AddQuestToMapArea(questId, self.quests[questId]['AZ'], self.mapAreaMapping[self.quests[questId]['AZ']])
				end

				--	Add this quest to holiday quests
				bitMask = self.quests[questId][3]
				if 0 ~= bitMask then
					for bitValue,code in pairs(self.holidayBitToCodeMapping) do
						if bit.band(bitMask, bitValue) > 0 then
							self:AddQuestToMapArea(questId, tonumber(self.holidayToMapAreaMapping['H'..code]), self.holidayMapping[code])
						end
					end
				end

				--	Add this quest to class quests
				bitMask = bit.band(self.quests[questId][4], self.bitMaskClassAll)
				if bitMask ~= self.bitMaskClassAll then
					for bitValue,code in pairs(self.classBitToCodeMapping) do
						if bit.band(bitMask, bitValue) > 0 then
							self:AddQuestToMapArea(questId, tonumber(self.classToMapAreaMapping['C'..code]), self:CreateClassNameLocalizedGenderized(self.classMapping[code]))
						end
					end
				end
				
				--	Add this quest to profession quests
				if nil ~= self.quests[questId]['prof'] then
					for code in pairs(self.quests[questId]['prof']) do
						self:AddQuestToMapArea(questId, tonumber(self.professionToMapAreaMapping['P'..code]), self.professionMapping[code])
					end
				end
				
				--	Add this quest to daily quests
				if bit.band(self.quests[questId][2], self.bitMaskQuestDaily) > 0 then
					self:AddQuestToMapArea(questId, self.mapAreaBaseDaily, DAILY)
				end
				
				--	Add this quest to reputation quests
				if nil ~= self.quests[questId]['rep'] then
					for reputationIndex in pairs(self.quests[questId]['rep']) do
						self:AddQuestToMapArea(questId, self.mapAreaBaseReputation + tonumber(reputationIndex, 16), REPUTATION .. " - " .. self.reputationMapping[reputationIndex])
					end
				end
				
				--	Deal with SPecial and repeatable quests to allow them to be accepted even when they do not appear in the quest log
				if nil ~= self.quests[questId]['SP'] or bit.band(self.quests[questId][2], self.bitMaskQuestRepeatable) > 0 then
					questName = self:QuestName(questId)
					if nil == self.specialQuests[questName] then self.specialQuests[questName] = {} end
					-- Now we go through and get the NPCs that give this quest and add them to the name table matching this quest
					local npcs = self:_TableAppendCodes(nil, self.quests[questId], { 'A', 'AK' })
					if nil ~= npcs then
						for _, questGiverId in pairs(npcs) do
							tinsert(self.specialQuests[questName], { questGiverId, questId })
						end
					end
				end

			end
			if self.debug then print("Done creating indexed quest list with elapsed milliseconds: "..debugprofilestop()) end
		end,

		---
		--	Returns the localized and gender specific name of the player's race.
		--	@param englishName The Blizzard internal name of the class.  If nil, the player's class will be used.
		--	@param desiredGender The numeric value for the desired gender (2 is male and 3 is female).  If nil, the player's gender will be used.
		--	@return A string whose value is the localized name of the race using the appropriate gender where applicable.
		CreateRaceNameLocalizedGenderized = function(self, englishName, desiredGender)
			local retval = nil
			local nameToUse = englishName or self.playerClass
			local genderToUse = desiredGender or self.playerGender
			local codeToUse = nil
			for code,raceName in pairs(self.raceMapping) do
				if raceName == nameToUse then
					codeToUse = code
				end
			end
			if nil ~= codeToUse then
				retval = (genderToUse == 3) and self.raceNameFemaleMapping[codeToUse] or self.raceNameMapping[codeToUse]
			end
			return retval
		end,

		---
		--	Determines whether the internal database contains the NPC specified by npcId.
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@return true if the NPC is known to the internal database, false otherwise
		DoesNPCExist = function(self, npcId)
			npcId = tonumber(npcId)
			return nil ~= npcId and nil ~= self.npcs[npcId] and true or false
		end,

		---
		--	Determines whether the internal database contains the quest specified by questId.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is known to the internal database, false otherwise
		DoesQuestExist = function(self, questId)
			questId = tonumber(questId)
			return nil ~= questId and nil ~= self.quests[questId] and true or false
		end,

		--	This takes a string of items representing an OR structure and returns a list where
		--	each element in the list is one of the OR items.
		--	@param list The string representing a list of OR items
		--	@param splitter An optional splitter string, with comma being the default
		--	@param oldTable An optional table to use to populate, otherwise a new one is created
		--	@return A table where each OR item is an entry in the table
		_FromList = function(self, list, splitter, oldTable)
			local retval = oldTable or {}
			local splitterToUse = splitter or ','
			local items = { strsplit(splitterToUse, list) }
			for i = 1, #items do
				if nil ~= tonumber(strsub(items[i], 1, 1)) then
					tinsert(retval, tonumber(items[i]))
				else
					tinsert(retval, items[i])
				end
			end
			return retval
		end,

		--	This takes a string of items representing an AND/OR structure and returns a list where
		--	each element in the list is one of the OR items, and tables within the list elements
		--	are the AND items.
		--	@param list The string representing a list of OR items
		--	@param orSplitter An optional splitter string, with comma being the default
		--	@param andSplitter An optional splitter string, with plus being the default
		--	@param oldTable An optional table to use to populate, otherwise a new one is created
		--	@return A table where each OR item is an entry in the table
		_FromPattern = function(self, pattern, orSplitter, andSplitter, oldTable)
			local retval = oldTable or {}
			local orSplitterToUse = orSplitter or ','
			local andSplitterToUse = andSplitter or '+'
			local items = { strsplit(orSplitterToUse, pattern) }
			local andItems
			for i = 1, #items do
				andItems = self:_FromList(items[i], andSplitterToUse)
				if 1 == #andItems then
					tinsert(retval, andItems[1])
				else
					tinsert(retval, andItems)
				end
			end
			return retval
		end,

		--	This takes the structure which represents OR/AND combinations and for
		--	each quest value contained, will associate that quest's code with the
		--	provided questId.
		_FromStructure = function(self, structure, questId, code)
			for _, value in pairs(structure) do
				if "table" == type(value) then
					self:_FromStructure(value, questId, code)
				else
					local qId = tonumber(value)
					if nil == self.quests[qId][code] then self.quests[qId][code] = {} end
					tinsert(self.quests[qId][code], questId)
				end
			end
		end,

		---
		--	Gets the NPC ID and name of an NPC indicated using the supplied parameters.  If
		--	useMouseoverOnly is true, the only NPC checked is mouseover.  If useTargetFirst
		--	is true, the list of NPCs to check uses target first.  Normally, the list of NPCs
		--	to check just contains npc and questnpc in that order.  The first NPC in the list
		--	that returns a name is used.  The NPC ID that is returned will be modified to
		--	meet the Grail requirements, which means if the NPC is really a world object the
		--	number one million will be added to Blizzard's NPC ID.
		--	@param useTargetFirst If non-nil target is first checked in the normal list
		--	@param useMouseoverOnly If non-nil only mouseover is checked
		--	@return The NPC ID (Grail modified for world objects)
		--	@return The name of the NPC
		GetNPCId = function(self, useTargetFirst, useMouseoverOnly)
			local used
			local targetName = nil
			local npcId = nil
			local searchTable = {}
			if useMouseoverOnly then
				tinsert(searchTable, "mouseover")
			else
				if useTargetFirst then tinsert(searchTable, "target") end
				tinsert(searchTable, "npc")
				tinsert(searchTable, "questnpc")
			end
			for k, v in pairs(searchTable) do
				used = v
				targetName = UnitName(used)
				if nil ~= targetName then break end
			end
			if nil ~= targetName then
				local gid = UnitGUID(used)
				if nil ~= gid then
					local targetType = tonumber(gid:sub(5,5), 16)
					npcId = tonumber(gid:sub(6,10), 16)
					if 1 == targetType then npcId = npcId + 1000000 end		-- our representation of a world object
				end
			end
			return npcId, targetName
		end,

		_GetOTCQuest = function(self, questId, npcId)
			questId = tonumber(questId)
			npcId = tonumber(npcId)
			local retval = questId
			if nil ~= questId and nil ~= self.quests[questId] and nil ~= self.quests[questId]['OTC'] then
				local sets = self.quests[questId]['OTC']
				for i = 1, #sets do
					if npcId == sets[i][1] then retval = sets[i][2] end
				end
			end
			return retval
		end,

		---
		--	Indicates whether the character has ever abandoned the specified quest.  This information is only valid
		--	as long as Grail has been used to record this information.  This information cannot be known prior to
		--	Grail being used.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest has been marked abandoned at any time, otherwise false
		HasQuestEverBeenAbandoned = function(self, questId)
			return self:_IsQuestMarkedInDatabase(questId, GrailDatabasePlayer["abandonedQuests"])
		end,

		---
		--	Indicates whether the character has ever accepted the specified quest.  This information is only valid
		--	as long as Grail has been used to record this information.  This information cannot be known perfectly
		--	prior to Grail being used.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest has been accepted at any time, otherwise false
		HasQuestEverBeenAccepted = function(self, questId)
			return self:HasQuestEverBeenAbandoned(questId) or self:HasQuestEverBeenCompleted(questId) or self:IsQuestInQuestLog(questId)
		end,

		---
		--	Indicates whether the character has ever completed the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest has been marked complete, or if the quest has been completed and is one that Blizzard resets daily/weekly/yearly, otherwise false
		HasQuestEverBeenCompleted = function(self, questId)
			return self:IsQuestCompleted(questId) or self:IsResettableQuestCompleted(questId)
		end,

		--	This turns a number into its hexidecimal equivalent.
		--	@param aNumber The integer to convert to hexidecimal
		--	@param minDigits An optional minimum number of hexidecimal digits to return, 0 padding at front
		--	@return A hexidecimal string representing the provided integer
		_HexValue = function(self, aNumber, minDigits)
			local codes = "0123456789ABCDEF"
			local retval = ""
			local position
			while aNumber > 0 do
				aNumber, position = math.floor(aNumber / 16), mod(aNumber, 16) + 1
				retval = strsub(codes, position, position) .. retval
			end
			if nil ~= minDigits then
				while (strlen(retval) < minDigits) do
					retval = '0' .. retval
				end
			end
			return retval
		end,

		---
		--	Indicates whether the character is in a heroic instance with the specified NPC.
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@return true if the character is in a heroic instance where the NPC is located, otherwise false
		InWithHeroicNPC = function(self, npcId)
			local retval = false
			local isHeroic, instanceName = self:IsInHeroicInstance()
			if isHeroic then
				local locations = self:NPCLocations(npcId, false, false, true)	-- only return things that match the current map area
				if nil ~= locations and 0 < #(locations) then
					retval = true
				end
			end
			return retval
		end,

		---
		--	Indicates whether the quest is an account-wide quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is an account-wide quest, otherwise false
		IsAccountWide = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestAccountWide) > 0)
		end,

		---
		--	Indicated whether Grail thinks the quest is bugged, meaning it cannot be completed
		--	because of a Blizzard server problem.
		--	@param questId The standard numeric questId representing a quest.
		--	@return nil if the quest is not bugged, otherwise a string describing the problem.
		IsBugged = function(self, questId)
			return self:_QuestGenericAccess(questId, 'bugged')
		end,

		---
		--	Indicates whether the quest is a daily quest as indicated by the internal database.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is a daily quest, otherwise false
		IsDaily = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestDaily) > 0)
		end,

		---
		--	Indicates whether the quest is a dungeon quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is a dungeon quest, otherwise false
		IsDungeon = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestDungeon) > 0)
		end,

		---
		--	Indicates whether the quest is an escort quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is an escort quest, otherwise false
		IsEscort = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestEscort) > 0)
		end,

		---
		--	Indicates whether the quest is a group quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is a group quest, otherwise false
		IsGroup = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestGroup) > 0)
		end,

		---
		--	Indicates whether the quest is a heroic quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is a heroic quest, otherwise false
		IsHeroic = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestHeroic) > 0)
		end,

		---
		--	Indicates whether the NPC is only to be found in heroic instances.
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@return True if the NPC is only found in heroic instances, false otherwise.
		IsHeroicNPC = function(self, npcId)
			retval = false
			local codes = self:CodesWithPrefixNPC(npcId, 'X')
			if nil ~= codes and 0 < #(codes) then
				retval = true
			end
			return retval
		end,

		---
		--	Indicates whether the character is in a heroic instance.
		--	@return true if the character is in a heroic instance, otherwise false
		--	@return the name of the instance the character is in
		--	@usage isHeroic, instanceName = Grail:IsInHeroicInstance()
		IsInHeroicInstance = function(self)
			local retval = false
			local name, type, difficultyIndex, difficultyName, maxPlayers, dynamicDifficulty, isDynamic = GetInstanceInfo()
			if "none" ~= type then
				if 3 == difficultyIndex or 4 == difficultyIndex or (2 == difficultyIndex and "raid" ~= type) then
					retval = true
				end
			end
			return retval, name
		end,

		---
		--	Indicates whether the quest is invalidated (meaning it cannot be accepted based on other completed quests or those in the quest log).
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the quest cannot be accepted because of a quest in the log or one already completed, false otherwise.
		--	@return A table of failure reasons, or nil if there are none.
		IsInvalidated = function(self, questId, ignoreBreadcrumb)
			local retval = false
			local any, present, failures = self:_AnyEvaluateTrue(questId, "I")
			if present then
				retval = any
			end

			if not retval and not ignoreBreadcrumb then

				-- Check to see whether this quest is a breadcrumb quest for something that is already completed or in the quest log.

				any, present, failures = self:_AnyEvaluateTrue(questId, "B")
				if present then retval = any end

			end

			if not retval then

				-- Examine the P codes to determine if any of them require the presence in the log.  If there is a code that does
				-- not, then we are ok.  If the only P codes require in the log presence check the status of those quests.  If they
				-- are unobtainable or already completed (turned in) then this quest is invalidated.

				local prerequisites = self:QuestPrerequisites(questId)
				if nil ~= prerequisites then
					
					local inLogRequirementCount = 0
					local inLogFailedRequirementCount = 0
					local foundNonInLogRequirement = false
					for key, value in pairs(prerequisites) do

						-- This is conceptually an AND situation, so all the items in the valueToUse must match
						valueToUse = ("table" == type(value)) and value or {value}
						for _, qId in pairs(valueToUse) do
							local code = strsub(qId, 1, 1)
							if 'B' == code or 'D' == code then
								inLogRequirementCount = inLogRequirementCount + 1
								local status = self:StatusCode(tonumber(strsub(qId, 2)))
								if bit.band(status, self.bitMaskQuestFailureWithAncestor + self.bitMaskCompleted) > 0 then
									inLogFailedRequirementCount = inLogFailedRequirementCount + 1
								end
							else
								foundNonInLogRequirement = true
							end
						end
						if foundNonInLogRequirement then
							break
						end

					end
					if inLogRequirementCount > 0 and not foundNonInLogRequirement then
						if inLogFailedRequirementCount == inLogRequirementCount then
							retval = true
						end
					end
				end
			end

			return retval, failures
		end,

		---
		--	Indicates whether the quest is a legendary quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is a legendary quest, otherwise false
		IsLegendary = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestLegendary) > 0)
		end,

		---
		--	Returns whether the quest is a low level quest in comparison to the specified level
		--	or that of the player if none specified.
		--	@param questId The standard numeric questId representing a quest.
		--	@param comparisonLevel The level used to make a comparison against the quest level.
		--	@return True if the comparisonLevel (or player level) is more than the quest's level plus Blizzard's green range comparison
		IsLowLevel = function(self, questId, comparisonLevel)
			local retval = false
			comparisonLevel = tonumber(comparisonLevel) or UnitLevel("player")
			local questLevel = self:QuestLevel(questId) or 1
			if 0 ~= questLevel then		-- 0 is the special marker indicating the quest is actually the same level as the player
				retval = (comparisonLevel > (questLevel + (GetQuestGreenRange() or 8)))	-- 8 is the return value from GetQuestGreenRange() for anyone level 60 or higher (at least)
			end
			return retval
		end,

		---
		--	Indicates whether the quest is a monthly quest as indicated by the internal database.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is a monthly quest, otherwise false
		IsMonthly = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestMonthly) > 0)
		end,

		---
		--	Returns whether the NPC should be available to the character.
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@return True if the NPC is available based on holidays currently celebrated and presence in a heroic instance, false otherwise.
		IsNPCAvailable = function(self, npcId)
			if nil == npcId or not tonumber(npcId) then return false end
			npcId = tonumber(npcId)
			local retval = true
			local codes = self:CodesWithPrefixNPC(npcId, 'H')
			if nil ~= codes then
				local holidayGood = true
				for i = 1, #(codes), 1 do
					if holidayGood then
						holidayGood = self:CelebratingHoliday(self.holidayMapping[strsub(codes[i], 2, 2)])
					end
				end
				retval = holidayGood
			end
			if retval and self:IsHeroicNPC(npcId) then
				retval = self:InWithHeroicNPC(npcId)
			end
			return retval
		end,

		---
		--	Returns whether Grail is ready to properly respond to status information about quests.
		IsPrimed = function(self)
			return self.receivedCalendarUpdateEventList and self.receivedQuestLogUpdate
		end,

		---
		--	Indicates whether the quest is a PVP quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is a PVP quest, otherwise false
		IsPVP = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestPVP) > 0)
		end,

		---
		--	Returns whether the quest is considered completed.
		--	Note that certain types of quests can be reset (e.g., dailies) and when they are, this routine will return false.  These types of
		--	quests can be completed and this routine will return true until they are once again reset.
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the quest is completed, false otherwise.
		--	@see HasQuestEverBeenCompleted
		--	@see IsResettableQuestCompleted
		IsQuestCompleted = function(self, questId)
			return self:_IsQuestMarkedInDatabase(questId)
		end,

		---
		--	Returns whether the quest is in the quest log.
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the quest is in the quest log, false otherwise.
		--	@return True if the quest is marked as complete in the quest log, false otherwise.
		--	@use inLog, isComplete = Grail:IsQuestInQuestLog(11)
		IsQuestInQuestLog = function(self, questId)
			local retval, retvalComplete = false, nil
			local quests = self:_QuestsInLog()
			questId = tonumber(questId)
			if nil ~= questId and nil ~= quests[questId] then
				retval, retvalComplete = true, quests[questId][2]
			end
			return retval, retvalComplete
		end,

		_IsQuestMarkedInDatabase = function(self, questId, db)
			questId = tonumber(questId)
			if nil == questId then return false end
			db = db or GrailDatabasePlayer["completedQuests"]
			local retval = false
			local index = math.floor((questId - 1) / 32)
			local offset = questId - (index * 32) - 1
			if nil ~= db[index] then
				if bit.band(db[index], 2^offset) > 0 then
					retval = true
				end
			end
			return retval
		end,

		---
		--	Indicates whether the quest is a raid quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is a raid quest, otherwise false
		IsRaid = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestRaid) > 0)
		end,

		---
		--	Returns whether the quest is a repeatable quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the quest is a repeatable quest, false otherwise.
		IsRepeatable = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestRepeatable) > 0)
		end,

		---
		--	Returns whether the quest is a resettable quest and has been completed in the past.
		--	This routine can return true and IsQuestCompleted() can return false as the quest can be reset.
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the quest is resettable and has ever been completed, false otherwise.
		--	@see HasQuestEverBeenCompleted
		--	@see IsQuestCompleted
		IsResettableQuestCompleted = function(self, questId)
			return self:_IsQuestMarkedInDatabase(questId, GrailDatabasePlayer["completedResettableQuests"])
		end,

		---
		--	Indicates whether the quest is a scenario quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return true if the quest is a scenario quest, otherwise false
		IsScenario = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestScenario) > 0)
		end,

		---
		--	Returns whether this is a special type of NPC that has information useful for tooltips and a table of that information
		--	where each item in the table is a table with the type of NPC and the associated NPC/quest/item ID.
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@return True if there is any table data being returned, false otherwise
		--	@return Table data containing tables of NPC type and associated ID.
		IsTooltipNPC = function(self, npcId)
			local retval = {}
			local dropCodes = self:CodesWithPrefixNPC(npcId, 'D:')
			if nil ~= dropCodes then
				for i = 1, #(dropCodes), 1 do
					local npcs = { strsplit(",", strsub(dropCodes[i], 3)) }
					if nil ~= npcs then
						for j = 1, #(npcs), 1 do
							tinsert(retval, { self.NPC_TYPE_BY, npcs[j] } )
						end
					end
				end
			end
			local killCodes = self:CodesWithPrefixNPC(npcId, 'K:')
			if nil ~= killCodes then
				for i = 1, #(killCodes), 1 do
					local quests = { strsplit(",", strsub(killCodes[i], 3)) }
					if nil ~= quests then
						for j = 1, #(quests), 1 do
							tinsert(retval, { self.NPC_TYPE_KILL, quests[j] } )
						end
					end
				end
			end
			local hasCodes = self:CodesWithPrefixNPC(npcId, 'H:')
			if nil ~= hasCodes then
				for i = 1, #(hasCodes), 1 do
					local items = { strsplit(",", strsub(hasCodes[i], 3)) }
					if nil ~= items then
						for j = 1, #(items), 1 do
							tinsert(retval, { self.NPC_TYPE_DROP, items[j] } )
						end
					end
				end
			end
			return (0 < #(retval)), retval
		end,

		---
		--	Returns whether the quest is a weekly quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the quest is a weekly quest, false otherwise.
		IsWeekly = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestWeekly) > 0)
		end,

		---
		--	Returns whether the quest is a yearly quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the quest is a yearly quest, false otherwise.
		IsYearly = function(self, questId)
			return (bit.band(self:CodeType(questId), self.bitMaskQuestYearly) > 0)
		end,

		---
		--	Returns whether the specifid item is present in the player's bags.
		--	Normally the itemId passed in is a Grail representation of a Blizzard
		--	item ID, but this routine should be able to handle a pure Blizzard ID
		--	as well.
		--	@param itemId Either the Grail representation of an item or a Blizzard one.
		--	@return True if an item with the same ID is in the player's bags, or false otherwise.
		ItemPresent = function(self, itemId)
			local retval = false
			itemId = tonumber(itemId)
			if nil == itemId then return false end

			--	The itemId is really our NPC representation of the item so its value
			--	needs to be adjusted back to Blizzard values.
			if itemId > 100000000 then
				itemId = itemId - 100000000
			end

			--	Now check the bags for an item with this ID
			local id
			for bag = 0, 4 do
				local numSlots = GetContainerNumSlots(bag)
				for slot = 1, numSlots do
					id = GetContainerItemID(bag, slot)
					if nil ~= id and itemId == id then
						retval = true
					end
				end
			end
			return retval
		end,

		_LogNameIssue = function(self, npcOrQuest, id, properTitle)
			if GrailDatabase[npcOrQuest] == nil then GrailDatabase[npcOrQuest] = {} end
			if GrailDatabase[npcOrQuest][self.playerLocale] == nil then GrailDatabase[npcOrQuest][self.playerLocale] = {} end
			GrailDatabase[npcOrQuest][self.playerLocale][id] = properTitle
		end,

		---
		--	This returns the map area to which the specified quest belongs for Loremaster purposes.  If the quest does not
		--	belong to any Loremaster, or the achievements addon is not loaded nil is returned.
		--	@param questId The standard numeric questId representing a quest.
		--	@return The map area for Loremaster or nil if not a Loremaster quest.
		LoremasterMapArea = function(self, questId)
			local retval = nil
			questId = tonumber(questId)
			if nil ~= questId and nil ~= self.quests[questId] and nil ~= self.quests[questId][5] then
				for _, achievementId in pairs(self.quests[questId][5]) do
					if achievementId < self.mapAreaBaseAchievement then
						retval = achievementId
					end
				end
			end
			return retval
		end,

		--	This marks the specified quest as complete in the internal database.  Optionally it will attempt to update the NewNPCs and NewQuests.
		--	@param questId The standard numeric questId representing a quest.
		--	@param updateDatabase If true the NewNPCs and NewQuests will be updated as well as posting the Complete notification.
		_MarkQuestComplete = function(self, questId, updateDatabase, updateActual, updateControl)
			local v = tonumber(questId)
			local index = math.floor((v - 1) / 32)
			local offset = v - (index * 32) - 1
			local db = GrailDatabasePlayer["completedQuests"]
			local db2 = GrailDatabasePlayer["actuallyCompletedQuests"]
			local db3 = GrailDatabasePlayer["controlCompletedQuests"]
			if (nil == db[index]) then
				db[index] = 0
			end
			if bit.band(db[index], 2^offset) == 0 then
				db[index] = db[index] + (2^offset)
			else
				if self.debug then print(format("Quest %d is already marked completed", v)) end
			end

			if updateControl then
				if nil == db3[index] then db3[index] = 0 end
				if bit.band(db3[index], 2^offset) == 0 then
					db3[index] = db3[index] + (2^offset)
				else
					if self.debug then print(format("Quest %d is already marked control completed", v)) end
				end
			end

			if updateActual then
				if nil == db2[index] then db2[index] = 0 end
				if bit.band(db2[index], 2^offset) == 0 then
					db2[index] = db2[index] + (2^offset)
				else
					if self.debug then print(format("Quest %d is already marked actually completed", v)) end
				end
				-- Make sure any I: quests are marked as incomplete
				local iQuests = self:QuestInvalidates(v)
				if nil ~= iQuests then
					for _, qId in pairs(iQuests) do
						self:_MarkQuestNotComplete(qId, db2)
					end
				end
			end

			if updateDatabase then
				local status = self:StatusCode(questId)
				if not self:IsResettableQuestCompleted(questId) and bit.band(status, self.bitMaskRepeatable + self.bitMaskResettable) > 0 then
					local rdb = GrailDatabasePlayer["completedResettableQuests"]
					if (nil == rdb[index]) then
						rdb[index] = 0
					end
					rdb[index] = rdb[index] + (2^offset)
				end

				-- Get the target information to ensure the target exists in the database of NPCs
				local version = self.versionNumber.."/"..self.questsVersionNumber.."/"..self.npcsVersionNumber.."/"..self.zonesVersionNumber
				local targetName, npcId, coordinates = self:TargetInformation()
				self:_UpdateTargetDatabase(targetName, npcId, coordinates, version)
				if self.debug then
					if nil ~= targetName then
						DEFAULT_CHAT_FRAME:AddMessage("Grail Debug: Marked questId "..questId.." complete, turned in to: "..targetName.."("..npcId..") "..coordinates)
					else
						DEFAULT_CHAT_FRAME:AddMessage("Grail Debug: Turned in quest "..questId.." with no target")
					end
				end
				self:_UpdateQuestDatabase(questId, 'No Title Stored', npcId, false, 'T', version)
				self:_PostNotification("Complete", questId)
			end

		end,

		--	This marks the specified quest as complete in the specified database.
		--	@param questId The standard numeric questId representing a quest.
		--	@param db The database to use for marking the quest complete.  If none provided, the completed quests database is used.
		--	@param notComplete If true, the quest is marked not complete, otherwise the quest is marked complete.
		--	@return	True if the database is updated, otherwise false is returned if the quest is already marked the desired value.
		_MarkQuestInDatabase = function(self, questId, db, notComplete)
			local v = tonumber(questId)
			if nil == v then return false end
			db = db or GrailDatabasePlayer["completedQuests"]
			local retval = true
			local index = math.floor((v - 1) / 32)
			local offset = v - (index * 32) - 1
			if nil == db[index] then
				db[index] = 0
			end
			if notComplete then
				if bit.band(db[index], 2^offset) > 0 then
					db[index] = db[index] - (2^offset)
				else
					retval = false
				end
			else
				if bit.band(db[index], 2^offset) == 0 then
					db[index] = db[index] + (2^offset)
				else
					retval = false
				end
			end
			return retval
		end,

		_MarkQuestNotComplete = function(self, questId, db)
			self:_MarkQuestInDatabase(questId, db, true)
		end,

		---
		--	Returns whether the character meets prerequisites for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the character meets the prerequisites for the specified quest or false otherwise.
		--	@return A table of failures if any, nil otherwise.
		MeetsPrerequisites = function(self, questId, code)
			local retval = true
			code = code or 'P'
			local any, present, failures = self:_AnyEvaluateTrue(questId, code)
			if present then
				retval = any
			end
			return retval, failures
		end,

		_MeetsRequirement = function(self, questId, requirementCode, soughtParameter)
			if nil == questId or not tonumber(questId) then return false end
			local retval = true
			questId = tonumber(questId)
			self:_CodeAllFixed(questId)
			local bitMaskToUse

			if 'G' == requirementCode then
				if nil == soughtParameter then
					bitMaskToUse = self.playerGenderBitMask
				elseif 3 == tonumber(soughtParameter) then
					bitMaskToUse = self.bitMaskGenderFemale
				else
					bitMaskToUse = self.bitMaskGenderMale
				end
				retval = (bit.band(self.quests[questId][4], bitMaskToUse) > 0)

			elseif 'F' == requirementCode then
				if nil == soughtParameter then
					bitMaskToUse = self.playerFactionBitMask
				elseif 'Horde' == soughtParameter then
					bitMaskToUse = self.bitMaskFactionHorde
				else
					bitMaskToUse = self.bitMaskFactionAlliance
				end
				retval = (bit.band(self.quests[questId][4], bitMaskToUse) > 0)

			elseif 'C' == requirementCode or 'X' == requirementCode then
				bitMaskToUse = (nil == soughtParameter) and self.playerClassBitMask or self.classNameToBitMapping[soughtParameter]
				retval = (bit.band(self.quests[questId][4], bitMaskToUse) > 0)

			elseif 'H' == requirementCode then
				if 0 ~= self.quests[questId][3] then
					local found = false
					local comparisonValue = self.quests[questId][3]
					for bitMask,code in pairs(self.holidayBitToCodeMapping) do
						if bit.band(comparisonValue, bitMask) > 0 then		-- this bitValue is one that is required by the quest
							if self:CelebratingHoliday(self.holidayMapping[code]) then
								found = true
							end
						end
					end
					retval = found
				end
			elseif 'R' == requirementCode or 'S' == requirementCode then
				bitMaskToUse = (nil == soughtParameter) and self.playerRaceBitMask or self.raceNameToBitMapping[soughtParameter]
				retval = (bit.band(self.quests[questId][4], bitMaskToUse) > 0)

-- TODO: Should convert these over to the new way of doing things
			elseif 'V' == requirementCode or 'W' == requirementCode or 'P' == requirementCode then
				local codeArray = { strsplit(" ", self.quests[questId][1]) }
				local controlCode
				local controlValue
				for i = 1, #codeArray do
					controlCode = strsub(codeArray[i], 1, 1)
					controlValue = strsub(codeArray[i], 2, 2)
					if controlCode == requirementCode then
						if 'V' == requirementCode or 'W' == requirementCode then
							local repIndex = strsub(codeArray[i], 2, 4)
							local repValue = tonumber(strsub(codeArray[i], 5))
							local exceeds, earnedValue = self:_ReputationExceeds(self.reputationMapping[repIndex], repValue)
							local success = exceeds
							if ('V' == requirementCode) then success = not success end
							if success then
								retval = false
--								if nil ~= earnedValue then
--									tinsert(failures, codeArray[i].." actual: "..earnedValue)
--								end
							end
						elseif 'P' == requirementCode then
							local colonCheck = strsub(codeArray[i], 3, 3)
							if ':' == controlValue or
							('L' == controlValue and ':' == colonCheck) or
							('H' == controlValue and ':' == colonCheck) or
							('C' == controlValue and ':' == colonCheck) or
							('C' == controlValue and 'T' == colonCheck and ':' == strsub(codeArray[i], 4, 4)) or
							('L' == controlValue and 'T' == colonCheck and ':' == strsub(codeArray[i], 4, 4))
							then
								-- we ignore these because they are not profession requirements.
							else
								local profValue = tonumber(strsub(codeArray[i], 3, 5))
								local exceeds, skillLevel = self:ProfessionExceeds(controlValue, profValue)
								if not exceeds then
									retval = false
--									tinsert(failures, codeArray[i].." actual: "..skillLevel)
								end
							end

						end
					end
				end
			end
			return retval
		end,

		---
		--	Returns whether the character meets class requirements for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@soughtClass The desired class to be matched, or if nil the player's class will be used
		--	@return True if the character meets the class requirements for the specified quest or false otherwise.
		--	@return A table of failures if any, nil otherwise.
		--	@see StatusCode
		MeetsRequirementClass = function(self, questId, soughtClass)
			return self:_MeetsRequirement(questId, 'C', soughtClass)
		end,

		---
		--	Returns whether the character meets faction requirements for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@soughtFaction The desired faction to be matched, or if nil the player's faction will be used
		--	@return True if the character meets the faction requirements for the specified quest or false otherwise.
		--	@return A table of failures if any, nil otherwise.
		--	@see StatusCode
		MeetsRequirementFaction = function(self, questId, soughtFaction)
			return self:_MeetsRequirement(questId, 'F', soughtFaction)
		end,

		---
		--	Returns whether the character meets gender requirements for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@param soughtGender The desired gender to be matched, or if nil the player's gender will be used
		--	@return True if the character meets the gender requirements for the specified quest or false otherwise.
		--	@return A table of failures if any, nil otherwise.
		--	@see StatusCode
		MeetsRequirementGender = function(self, questId, soughtGender)
			return self:_MeetsRequirement(questId, 'G', soughtGender)
		end,

		---
		--	Returns whether the character meets holiday requirements for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the character meets the holiday requirements for the specified quest or false otherwise.
		--	@return A table of failures if any, nil otherwise.
		--	@see StatusCode
		MeetsRequirementHoliday = function(self, questId)
			return self:_MeetsRequirement(questId, 'H')
		end,

		---
		--	Returns whether the level requirements are met for the specified quest.  This handles both minimum and maximum level requirements.
		--	@param questId The standard numeric questId representing a quest.
		--	@param optionalComparisonLevel A comparison level to use.  If nil the character's level is used.
		--	@return True if the level requirements for the specified quest are met or false otherwise.
		--	@return The level used in making comparisons to the requirements of the quest.
		--	@return The minimum level required for the quest.
		--	@return The maximum level permitted for the quest or Grail.INFINITE_LEVEL if there is none.
		--	@see StatusCode
		MeetsRequirementLevel = function(self, questId, optionalComparisonLevel)
			questId = tonumber(questId)
			if nil == questId then return false end
			local bitMask = self:CodeLevel(questId)
			local retval = true
			local levelToCompare = optionalComparisonLevel or UnitLevel('player')
			local levelRequired = bit.band(bitMask, self.bitMaskQuestMinLevel) / self.bitMaskQuestMinLevelOffset
			local levelNotToExceed = bit.band(bitMask, self.bitMaskQuestMaxLevel) / self.bitMaskQuestMaxLevelOffset
			if levelToCompare < levelRequired or levelToCompare > levelNotToExceed then
				retval = false
			end
			return retval, levelToCompare, levelRequired, levelNotToExceed
		end,

		---
		--	Returns whether the character meets profession requirements for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the character meets the profession requirements for the specified quest or false otherwise.
		--	@return A table of failures if any, nil otherwise.
		--	@see StatusCode
		MeetsRequirementProfession = function(self, questId)
			return self:_MeetsRequirement(questId, 'P')
		end,

		---
		--	Returns whether the character meets race requirements for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@soughtRace The desired race to be matched, or if nil the player's race will be used
		--	@return True if the character meets the race requirements for the specified quest or false otherwise.
		--	@return A table of failures if any, nil otherwise.
		--	@see StatusCode
		MeetsRequirementRace = function(self, questId, soughtRace)
			return self:_MeetsRequirement(questId, 'R', soughtRace)
		end,

		---
		--	Returns whether the character meets reputation requirements for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return True if the character meets the reputation requirements for the specified quest or false otherwise.
		--	@return A table of failures if any, nil otherwise.
		--	@see StatusCode
		MeetsRequirementReputation = function(self, questId)
			local first, failures = self:_MeetsRequirement(questId, 'V')
			local second, failures2 = self:_MeetsRequirement(questId, 'W')
			local retval = first and second
			if nil == failures then
				failures = failures2
			else
				failures = self:_TableAppend(failures, failures2)
			end
			return retval, failures
		end,

		---
		--	Returns a table of NPC records filtered by the provided parameters where each record contains
		--	informaion about the NPC's location containing values whose keys are described in this table:
		--		name		the localized name of the NPC
		--		id			the npcId (passed in to the function)
		--		mapArea		the map area ID where the NPC is located
		--		mapLevel	if present the dungeon level within the mapArea
		--		near		true if the NPC is considered nearby
		--		x			the x coordinate of the NPC location
		--		y			the y coordinate of the NPC location
		--		realArea	the map area ID of the real area where the NPC is located
		--		heroic		true if the NPC needs to be in a heroic dungeon
		--		kill		true if the NPC needs to be killed to start a quest
		--		notes		non-nil if there are notes associated with the NPC
		--		alias		if exists is the actual NPC ID of the Blizzard NPC
		--		dropName	if exists is the name of the item dropped
		--		dropId		if exists is the NPC ID of the item dropped
		--		questId		if exists is the quest associated with the dropped item
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@param requiresNPCAvailable If true, the NPC must be available.
		--	@param onlySingleReturn If true, only one location will be in the returned table, otherwise all matching locations will be there.
		--	@param onlyMapReturn If true, only locations matching the appropriate map area will be returned.
		--	@param preferredMapAreaId The map area ID to be used, and if nil the character's current map area is used.
		--	@param dungeonLevel The dungeon level to be used
		--	@return A table of locations where the NPC can be found or nil if there are none.
		--	@see IsNPCAvailable
		NPCLocations = function(self, npcId, requiresNPCAvailable, onlySingleReturn, onlyMapReturn, preferredMapAreaId, dungeonLevel)
			local retval = {}
			local npcs = self:_RawNPCLocations(npcId)
			if nil ~= npcs then
				local mapIdToUse = tonumber(preferredMapAreaId) or GetCurrentMapAreaID()
				for _, npc in pairs(npcs) do
					if not requiresNPCAvailable or self:IsNPCAvailable(npc.id) then
						if not onlyMapReturn or (onlyMapReturn and mapIdToUse == npc.mapArea) then
							if not dungeonLevel or (dungeonLevel == npc.mapLevel) then
								tinsert(retval, npc)
							end
						end
					end
				end
				if onlySingleReturn and 1 < #retval then
					retval = { retval[1] }		-- pick the first item for no better algorithm to use to decide
				end
			end
			if 0 == #retval then
				retval = nil
			end
			return retval
		end,

		---
		--	Returns the localized name of the NPC represented by the specified NPC ID.
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@return The localized string for the specific NPC, or nil if the NPC is not found in the database.
		NPCName = function(self, npcId)
			npcId = tonumber(npcId)
			return nil ~= npcId and nil ~= self.npcs[npcId] and self.npcs[npcId][0] or nil
		end,

		--	Checks the ensure the prerequisites present are those that have the specified questCode
		_OnlyHasPrerequisites = function(self, questId, questCode)
			local retval = true
			local codeValues = self:QuestPrerequisites(questId)

			if nil ~= codeValues then
				local valueToUse
				for key, value in pairs(codeValues) do
					valueToUse = ("table" == type(value)) and value or {value}
					for _, innerValue in pairs(valueToUse) do
						if questCode ~= strsub(innerValue, 1, 1) then
							retval = false
						end
					end
				end
			end
			return retval
		end,

		--	Routine used to put notifications into the system that will be posted in a routine called by OnUpdate after
		--	the spcified delay.  If there were no notifications in the queue prior to this call the notificationFrame
		--	will have an OnUpdate script set.
		--	@param notificationName The name of the notification that will eventually be posted.  E.g., Abandon, Accept, etc.
		--	@param questId The questId associated with the notification.
		--	@param delay The delay time in seconds which will probably be a floating point number less than one.
		_PostDelayedNotification = function(self, notificationName, questId, delay)
			if nil == self.delayedNotifications then self.delayedNotifications = {} end
			if 0 == #(self.delayedNotifications) then	-- the assumption is when the table has 0 notifications we pull the handler off
				self.notificationFrame:SetScript("OnUpdate", function(myself, elapsed) self:_ProcessDelayedNotifications(elapsed) end)
			end
			tinsert(self.delayedNotifications, { ["n"] = notificationName, ["q"] = questId, ["f"] = GetTime() + delay })
		end,

		--	This routine is used to post notifications to observers.
		--	@param eventName The name of the notification.
		--	@param questId The questId associated with the notification.
		_PostNotification = function(self, eventName, questId)
			if nil ~= self.observers[eventName] then
				for _,f in pairs(self.observers[eventName]) do
					f(eventName, questId)
				end
			end
		end,

		--	Internal Use.
		--	Routine used by the OnUpdate system to process notifications that have been put into a queue for delayed
		--	processing.  When the last notification is removed from the queue, the notificationFrame will have its
		--	OnUpdate script removed.
		_ProcessDelayedNotifications = function(self, ignoredElapsed)
			local now = GetTime()	-- if now > "fire trigger" we post the associated notification
			local newNotificationTable = {}
			for _, t in pairs(self.delayedNotifications) do
				if now > t["f"] then
					self:_PostNotification(t["n"], t["q"])
				else
					tinsert(newNotificationTable, t)
				end
			end
			self.delayedNotifications = newNotificationTable
			if 0 == #(self.delayedNotifications) then
				self.notificationFrame:SetScript("OnUpdate", nil)
			end
		end,

		--	Internal Use.
		--	This looks at the quest codes in the table to determine whether any of them require entries to be made in the
		--	questStatusCache structure which is used to invalidate quests based on how quests interrelate and happenings
		--	in the environment.  It calls a support routine to do the dirty work as this just iterates through the table
		--	contents.  The support routine uses a mapping to take quest codes and assign them to the proper internal table
		--	entries.
		_ProcessQuestsForHandlers = function(self, questId, table)
			for key, value in pairs(table) do
				if "table" == type(value) then
					for key2, value2 in pairs(value) do
						self:_ProcessQuestsForHandlersSupport(questId, value2)
					end
				else
					self:_ProcessQuestsForHandlersSupport(questId, value)
				end
			end

		end,

		_ProcessQuestsForHandlersMapping = { ["B"] = 'D', ["D"] = 'D', ["I"] = 'B', ["J"] = 'A', ["K"] = 'C', ["L"] = 'E', ["M"] = 'F', ["N"] = 'F', },

		_ProcessQuestsForHandlersSupport = function(self, questId, value)
			local code = strsub(value, 1, 1)
			local rest = tonumber(strsub(value, 2))
			local mappedCode = self._ProcessQuestsForHandlersMapping[code]
			if nil ~= mappedCode then
				if nil == self.questStatusCache[mappedCode][rest] then self.questStatusCache[mappedCode][rest] = {} end
				tinsert(self.questStatusCache[mappedCode][rest], questId)
			elseif 'P' == code then
				if nil == self.quests[questId]['prof'] then self.quests[questId]['prof'] = {} end
				self.quests[questId]['prof'][strsub(value, 2, 2)] = tonumber(strsub(value, 3))
			end
		end,

		_ProcessServerBackup = function(self)
			GrailDatabasePlayer["backupCompletedQuests"] = {}
			for i, v in pairs(GrailDatabasePlayer["completedQuests"]) do
				GrailDatabasePlayer["backupCompletedQuests"][i] = v
			end
			print("|cFFFFFF00Grail|r: A backup of the completed quests has been made")
		end,

		_ProcessServerCompare = function(self)
			local db = GrailDatabasePlayer
			if nil == db["backupCompletedQuests"] then print("|cFFFF0000Grail|r: Please do |cFF00FF00/grail backup|r first") return
			else print("|cFF00FF00Grail|r: Starting quest comparison between completed quests and backup") end
			local indexesToCheck = {}
			for index, value in pairs(db["completedQuests"]) do
				if not tContains(indexesToCheck, index) then tinsert(indexesToCheck, index) end
			end
			for index in pairs(db["backupCompletedQuests"]) do
				if not tContains(indexesToCheck, index) then tinsert(indexesToCheck, index) end
			end
			local backup, current, diff, base, message
			for _, index in pairs(indexesToCheck) do
				backup = db["backupCompletedQuests"][index] or 0
				current = db["completedQuests"][index] or 0
				if current ~= backup then
					diff = bit.bxor(current, backup)
					-- index 0 covers 1 - 32
					-- index 1 covers 33 - 64
					-- index 2 covers 65 - 96
					base = index * 32
					for i = 0, 31 do
						if bit.band(diff, 2^i) > 0 then		-- this means there is a bit difference between backup and current
							if bit.band(current, 2^i) then	-- this means current is marked complete
								message = strformat("New quest completed %d", base + i + 1)
							else
								message = strformat("New quest LOST %d", base + i + 1)
							end
							print(message)
							self:_AddTrackingMessage(message)
						end
					end
				end
			end
			print("|cFFFF0000Grail|r: End quest comparison")
		end,

		_ProcessServerQuests = function(self)
			DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF00Grail|r: starting to process completed query results")

			local db = GrailDatabasePlayer

			--	First make a temporary backup of what we think is completed
			local temporaryBackupQuests = {}
			for i, v in pairs(db["completedQuests"]) do
				temporaryBackupQuests[i] = v
			end

			--	Now process the completed quests from the server query results
			local completedQuests = { }
			GetQuestsCompleted(completedQuests)
			local hour, minute = GetGameTime()
			local weekday, month, day, year = CalendarGetDate()
			db["serverUpdated"] = strformat("%4d-%02d-%02d %02d:%02d", year, month, day, hour, minute)
			db["completedQuests"] = { }
			if nil ~= completedQuests then		-- normally should always be non-nil, but just to make sure
				for v,_ in pairs(completedQuests) do
					self:_MarkQuestComplete(v)
				end
			end

			--	Now make sure each of the quests marked complete in controlCompletedQuests are also set
			local backup, current, diff, base
			for index, value in pairs(db["controlCompletedQuests"]) do
				if value ~= nil then
					base = index * 32
					for i = 0, 31 do
						if bit.band(value, 2^i) > 0 then
							self:_MarkQuestComplete(base + i + 1)
						end
					end
				end
			end

			--	Now process the actuallyCompletedQuests to ensure we have a good concept of what was actually done
			local actualToNuke = {}
			for index, value in pairs(db["actuallyCompletedQuests"]) do
				if value ~= nil then
					base = index * 32
					for i = 0, 31 do
						if bit.band(value, 2^i) > 0 then
							current = base + i + 1		-- this is a questId that is considered "actually" complete
							if self:IsQuestCompleted(current) then
							-- ensure all the I: quests are not considered complete from the server
								local iQuests = self:QuestInvalidates(current)
								if nil ~= iQuests then
									local shouldNuke
									for _, questId in pairs(iQuests) do
--										if questId contains an O: code with the value current we do not need to mark it NOT complete
										shouldNuke = true
										local oQuests = self:QuestBreadcrumbs(questId)
										if nil ~= oQuests then
											for _, oQuestId in pairs(oQuests) do
												if oQuestId == current then shouldNuke = false end
											end
										end
										if shouldNuke then self:_MarkQuestNotComplete(questId, db["completedQuests"]) end
									end
								end
							else
								-- remove the quest from the list of "actually" completed quests
								tinsert(actualToNuke, current)
--								self:_MarkQuestNotComplete(current, db["actuallyCompletedQuests"])
							end
						end
					end
				end
			end
			for _, questToNuke in pairs(actualToNuke) do
				self:_MarkQuestNotComplete(questToNuke, db["actuallyCompletedQuests"])
			end

-- TODO: Should contemplate performing a sanity check here to make sure that all the quests from completedQuests
--			actually can be completed by the player.  This means the gender, class, race and faction checks can
--			be used to mark incomplete those that should not be marked complete.

			--	Now invalidate any quests whose completed status from the backup does not match the server
			local indexesToCheck = {}
			for index in pairs(db["completedQuests"]) do
				if not tContains(indexesToCheck, index) then tinsert(indexesToCheck, index) end
			end
			for index in pairs(temporaryBackupQuests) do
				if not tContains(indexesToCheck, index) then tinsert(indexesToCheck, index) end
			end
			if 0 < #indexesToCheck then
				local questsToInvalidate = {}
				for _, index in pairs(indexesToCheck) do
					backup = temporaryBackupQuests[index] or 0
					current = db["completedQuests"][index] or 0
					if current ~= backup then
						diff = bit.bxor(current, backup)
						base = index * 32
						for i = 0, 31 do
							if bit.band(diff, 2^i) > 0 then
								tinsert(questsToInvalidate, base + i + 1)
							end
						end
					end
				end
				if 0 < #questsToInvalidate then self:_StatusCodeInvalidate(questsToInvalidate) end
			end

			--	Remove the temporary backup
			wipe(temporaryBackupQuests)

			DEFAULT_CHAT_FRAME:AddMessage("|cFFFF0000Grail|r: finished processing completed query results")
		end,

		--	Internal Use.
		--	Returns whether the character has the profession specified by the code exceeding the specified level.
		--	@param professionCode The code representing the profession as used in Grail.professionMapping
		--	@param professionValue The skill level to use in comparison.
		--	@return True when the character possesses the skill in excess of the indicated value, false otherwise.
		--	@return The actual skill level the character posseses or Grail.NO_SKILL if the character does not have the specified skill.
		--	@use hasSkill, skillLevel = Grail:ProfessionExceeds('+', 125)
		ProfessionExceeds = function(self, professionCode, professionValue)
			local retval = false
			local skillLevel = self.NO_SKILL
			local skillName = nil
			local prof1, prof2, archaeology, fishing, cooking, firstAid = GetProfessions();

			if "X" == professionCode and nil ~= archaeology then
				_, _, skillLevel = GetProfessionInfo(archaeology)
			elseif "F" == professionCode and nil ~= fishing then
				_, _, skillLevel = GetProfessionInfo(fishing)
			elseif "C" == professionCode and nil ~= cooking then
				_, _, skillLevel = GetProfessionInfo(cooking)
			elseif "+" == professionCode and nil ~= firstAid then
				_, _, skillLevel = GetProfessionInfo(firstAid)
			elseif "R" == professionCode then
				skillLevel = self:_RidingSkillLevel()
			else
				professionName = self.professionMapping[professionCode]
				if nil ~= prof1 then
					skillName, _, skillLevel = GetProfessionInfo(prof1)
				end
				if skillName ~= professionName then
					if nil ~= prof2 then
						skillName, _, skillLevel = GetProfessionInfo(prof2)
					end
					if skillName ~= professionName then
						skillLevel = self.NO_SKILL
					end
				end
			end
			if skillLevel >= professionValue then
				retval = true
			end
			return retval, skillLevel
		end,

		--	Internal Use.
		--	Routine used to hook the function for abandoning a quest.  This is needed because the events that Blizzard issues
		--	are not adequate for our desired tasks.  One of three needed for abandoning.
		_QuestAbandonStart = function(self)
			self.abandoningQuestIndex = GetQuestLogSelection()
			self.origAbandonQuestFunction()
		end,

		--	Internal Use.
		--	Routine used to hook the function for abandoning a quest.  This is needed because the events that Blizzard issues
		--	are not adequate for our desired tasks.  One of three needed for abandoning.
		_QuestAbandonStop = function(self)
			local questTitle, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily, questId = GetQuestLogTitle(self.abandoningQuestIndex)
			self.origConfirmAbandonQuestFunction()
			if nil ~= self.quests[questId] then
				self:_MarkQuestInDatabase(questId, GrailDatabasePlayer["abandonedQuests"])
			end
			if nil ~= self.quests[questId] and nil ~= self.quests[questId]['OBC'] then
				local questsToInvalidate = {}
				for _,clearQuestId in pairs(self.quests[questId]['OBC']) do
					self:_MarkQuestComplete(clearQuestId, true, false, true)
					tinsert(questsToInvalidate, clearQuestId)
				end
				self:_StatusCodeInvalidate(questsToInvalidate)
			end
			self:_PostDelayedNotification("Abandon", questId, self.abandonPostNotificationDelay)
		end,

		---
		--	Returns a table of questIds that are possible breadcrumbs for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return A table of questIds for possible breadcrumb quests for this quest, or nil if there are none.
		QuestBreadcrumbs = function(self, questId)
			return self:_QuestGenericAccess(questId, 'O')
		end,

		---
		--	Returns a tables of questIds for which this quest is a breadcrumb quest.
		QuestBreadcrumbsFor = function(self, questId)
			return self:_QuestGenericAccess(questId, 'B')
		end,

		---
		--	Returns a table of quests that are the causes for this quest to be a flag quest.
		QuestFlags = function(self, questId)
			return self:_QuestGenericAccess(questId, 'J')
		end,

		_QuestGenericAccess = function(self, questId, internalCode)
			questId = tonumber(questId)
			return nil ~= questId and nil ~= self.quests[questId] and self.quests[questId][internalCode] or nil
		end,

		---
		--	Returns the questId of the quest in the quest log with the sought title.
		--	@param soughtTitle The localized name of the quest sought in the quest log.
		--	@return The questId of the quest in the quest log matching the sought name or nil if none match.
		QuestInQuestLogMatchingTitle = function(self, soughtTitle)
			local retval = nil
			local cleanedTitle = strtrim(soughtTitle)
			local quests = self:_QuestsInLog()
			for questId, t in pairs(quests) do
				if cleanedTitle == t[1] then
					retval = questId
				end
			end
			return retval
		end,

		---
		--	Returns a table of questIds that invalidate the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return A table of questIds that invalidate this quest, or nil if there are none.
		QuestInvalidates = function(self, questId)
			return self:_QuestGenericAccess(questId, 'I')
		end,

		---
		--	Returns the level of the quest with the specified questId.
		--	@param questId The standard numeric questId representing a quest.
		--	@return The level of the quest matching the questId or nil if none found.
		QuestLevel = function(self, questId)
			return bit.band(self:CodeLevel(questId), self.bitMaskQuestLevel) / self.bitMaskQuestLevelOffset
		end,

		QuestLocations = function(self, questId, acceptOrTurnin, requiresNPCAvailable, onlySingleReturn, onlyMapAreaReturn, preferredMapId, acceptsMultipleUniques, dungeonLevel, isStartup)
			local retval = {}
			questId = tonumber(questId)
			if nil ~= questId and nil ~= self.quests[questId] then
				local npcCodes = self.quests[questId][acceptOrTurnin]
				if nil == npcCodes then
					npcCodes = self.quests[questId][acceptOrTurnin..'K']
				end
				if nil ~= npcCodes then
					for _, npcId in pairs(npcCodes) do
						local locations = self:NPCLocations(npcId, requiresNPCAvailable, onlySingleReturn, onlyMapAreaReturn, preferredMapId, dungeonLevel)
						if nil ~= locations then
							for _, npc in pairs(locations) do
								tinsert(retval, npc)
							end
						end
					end
				else
					local zoneId = self.quests[questId][acceptOrTurnin..'Z']
					if nil ~= zoneId and not isStartup then
						local mapId = tonumber(preferredMapId) or GetCurrentMapAreaID()
						if not onlyMapAreaReturn or (onlyMapAreaReturn and zoneId == mapId) then
							tinsert(retval, { ["id"] = 0, ["name"] = self:NPCName(0), ["mapArea"] = mapId, })
						end
					end
				end
			end
			-- Since the return values from NPCLocations will process things like onlySingleReturn properly, that means the retval should only have
			-- one location value per unique NPC and that means we can make use of acceptsMultipleUniques to ignore the onlySingleReturn value here.
			if onlySingleReturn and not acceptsMultipleUniques and 1 < #retval then retval = { retval[1] } end		-- pick the first item since no better algorithm
			if 0 == #retval then
				retval = nil
			end
			return retval
		end,

		---
		--	Returns the name of the quest with the specified questId.
		--	@param questId The standard numeric questId representing a quest.
		--	@return The localized name of the quest matching the questId or nil if none found.
		QuestName = function(self, questId)
			questId = tonumber(questId)
			return nil ~= questId and nil ~= self.quests[questId] and self.quests[questId][0] or nil
		end,

		---
		--	Returns a table of NPC IDs from which one can accept the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return A table of NPC ids, or nil if there are none.
		QuestNPCAccepts = function(self, questId)
			return self:_QuestGenericAccess(questId, 'A')
		end,

		---
		--	Returns a table of NPC IDs that can be killed to accept the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return A table of NPC ids, or nil if there are none.
		QuestNPCKills = function(self, questId)
			return self:_QuestGenericAccess(questId, 'AK')
		end,

		---
		--	Returns a table of NPC IDs to which one can turn in the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return A table of NPC ids, or nil if there are none.
		QuestNPCTurnins = function(self, questId)
			return self:_QuestGenericAccess(questId, 'T')
		end,

		QuestOnAcceptCompletes = function(self, questId)
			return self:_QuestGenericAccess(questId, 'OAC')
		end,

		QuestOnCompletionCompletes = function(self, questId)
			return self:_QuestGenericAccess(questId, 'OCC')
		end,

		QuestOnDoneCompletes = function(self, questId)
			return self:_QuestGenericAccess(questId, 'ODC')
		end,

		QuestOnTurninCompletes = function(self, questId)
			return self:_QuestGenericAccess(questId, 'OTC')
		end,

		---
		--	Returns a table of questIds that are simple prerequisites for the specified quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return A table of questIds that are simple prerequisites for this quest, or nil if there are none.
		QuestPrerequisites = function(self, questId)
			return self:_QuestGenericAccess(questId, 'P')
		end,

		---
		--	Returns a table of questIds that are simple prerequisites for the specified quest
		--	after they have been processed using any juxtaposed values.  The assumption is of
		--	course completion (turned in) of the juxtaposed quest.
		--	@param questId The standard numeric questId representing a quest.
		--	@return A table of questIds that are simple prerequisites for this quest, or nil if there are none.
		DisplayableQuestPrerequisites = function(self, questId)
			return self:_ProcessForFlagQuests(self:_QuestGenericAccess(questId, 'P'))
		end,

		--	This routine takes a structure of prerequisites in their AND/OR tables are processes them
		--	so any quests in the prerequisites that are in fact flag quests marked with J: codes will
		--	have them processed so no quests with J: codes will appear in the list of prerequisites.
		_ProcessForFlagQuests = function(self, preqs)
			local retval = {}

			if nil ~= preqs then
				for i = 1, #preqs do
					if type(preqs[i]) == "table" then
						local rettable = {{}}
						for j = 1, #(preqs[i]) do
							local flags = nil
							if nil ~= tonumber(strsub(preqs[i][j], 1, 1)) then
								flags = self:_ProcessForFlagQuests(self:QuestFlags(preqs[i][j]))
							end
							if nil ~= flags then
								local flagCount = #flags
								if 1 == flagCount then
									for k = 1, #rettable do
										self:_TableAppend(rettable[k], flags[1])
									end
								else	-- the flags are ORs, within an AND structure, so we need to add entries to rettable based on flagCount
									--	First we add to the table a number of extra rows based on flagCount
									local retCount = #rettable
									for k = 2, flagCount do
										for l = 1, retCount do
											tinsert(rettable, self:_TableCopy(rettable[l]))
										end
									end
									--	Then we append the values to the whole new list of tables
									for k = 1, flagCount do
										for l = 1, retCount do
											self:_TableAppend(rettable[(k - 1) * retCount + l], flags[k])
										end
									end
								end
							else
								for k = 1, #rettable do
									tinsert(rettable[k], preqs[i][j])
								end
							end
						end
						for j = 1, #rettable do
							tinsert(retval, rettable[j])
						end
					else	-- this will be a single entry, which means a single OR value
						local flags = nil
						if nil ~= tonumber(strsub(preqs[i], 1, 1)) then
							flags = self:_ProcessForFlagQuests(self:QuestFlags(preqs[i]))
						end
						if nil ~= flags then
							for j = 1, #flags do
								tinsert(retval, flags[j])
							end
						else
							tinsert(retval, preqs[i])
						end
					end
				end
			end

			if 0 == #retval then retval = nil end
			return retval
		end,

		---
		--	Returns the questId based on the parameters passed in by looking in the specialQuests
		--	for one that matches the either the specified NPC or the one that currently is the "npc"
		--	or "questnpc".  If a questId is not found using the specialQuests, one is returned that
		--	matches the provided name.
		--	@param questName The localized name of the quest whose questId is sought.
		--	@param optionalNPCIdToUse The npcId to use.  If nil, the defaul is looked up.
		--	@param shouldUseNameFallback If not nil, the questId is looked up by name as a fallback if none found using specialQuests.
		--	@return The sought questId.
		QuestIdFromNPCOrName = function(self, questName, optionalNPCIdToUse, shouldUseNameFallback)
			local retval = nil
			local npcId = optionalNPCIdToUse or self:GetNPCId(false)
			local questGivers = self.specialQuests[questName]
			if nil ~= questGivers then
				for i = 1, #questGivers do
					if tonumber(questGivers[i][1]) == npcId then
						retval = questGivers[i][2]
					end
				end
			end
			if nil == retval and shouldUseNameFallback then
				retval = self:QuestWithName(questName)
			end
			return retval
		end,

		--	Routine used to hook the function for completing a quest.  This is needed because the events that Blizzard issues
		--	are not adequate for our desired tasks.
		_QuestRewardCompleteButton_OnClick = function(self)
			self.questNPCId = self:GetNPCId(false)
			if nil == self.completingQuest then
				self.completingQuest = self:QuestIdFromNPCOrName(self.completingQuestTitle, self.questNPCId)
			end

			if self.completingQuest then
				self.completingQuest = self:_GetOTCQuest(self.completingQuest, self.questNPCId)
				self.reputationCompletingQuest = self.completingQuest
				if self.checksReputationRewardsOnTurnin then
					self:_PostDelayedNotification("TimeBomb", self.reputationCompletingQuest, self.timeBombDelay)
				end
				local shouldUpdateActual = (nil ~= self:QuestInvalidates(self.completingQuest))
				self:_MarkQuestComplete(self.completingQuest, true, shouldUpdateActual, false)

				if nil ~= self.quests[self.completingQuest] then
					local odcCodes = self.quests[self.completingQuest]['ODC']
					if nil ~= odcCodes then
						for i = 1, #odcCodes do
							self:_MarkQuestComplete(odcCodes[i], true, false, false)
						end
					end
					local oecCode = self.quests[self.completingQuest]['OEC']
if self.debug and nil ~= oecCodes then print("For quest", self.completingQuest, "we have OEC codes") end
if self.debug and nil ~= oecCodes and not self:MeetsPrerequisites(self.completingQuest, "OPC") then print("For quest", self.completingQuest, "we do not meet prerequisites for OPC") end
					if nil ~= oecCodes and self:MeetsPrerequisites(self.completingQuest, "OPC") then
						for i = 1, #oecCodes do
if self.debug then print("Marking OEC quest complete", oecCodes[i]) end
							self:_MarkQuestComplete(oecCodes[i], true, false, false)
						end
					end
				else
					print("|cffff0000Grail problem|r because completing quest which seems not to exist", self.completingQuest)
				end

				self.completingQuest = nil
			end
			self.origHookFunction()
		end,

		---
		--	Returns a table of quest IDs for quests that can start in the specified map area.
		--	@param mapId The map area to use, or if nil, the map area the character is currently in will be used.
		--	@param useDungeonAlso If true, dungeon quests inside the map area will also be included.
		--	@param useLoremasterOnly If true, only Loremaster quests will be used for the area, ignoring the normal entire quest list and ignoring the useDungeonAlso parameter.
		--	@return A table of questIds for quests that start in the map area or nil if none.
		QuestsInMap = function(self, mapId, useDungeonAlso, useLoremasterOnly)
			local retval = {}
			local mapIdToUse = mapId or GetCurrentMapAreaID()

			if nil ~= mapIdToUse then
				if useLoremasterOnly then
					retval = self.loremasterQuests[mapIdToUse]
				elseif useDungeonAlso then
					if nil == self.indexedQuestsExtra[mapIdToUse] then
						retval = self.indexedQuests[mapIdToUse]
					elseif nil == self.indexedQuests[mapIdToUse] then
						retval = self.indexedQuestsExtra[mapIdToUse]
					else
						for k,v in pairs(self.indexedQuests[mapIdToUse]) do
							tinsert(retval, v)
						end
						for k, v in pairs(self.indexedQuestsExtra[mapIdToUse]) do
							if not tContains(retval, v) then
								tinsert(retval, v)
							end
						end
					end
				else
					retval = self.indexedQuests[mapIdToUse]
				end
			end

			if nil ~= retval and 0 == #retval then retval = nil end
			return retval
		end,

		--	Returns a table whose key is the questId and whose value is a table made of the quest title and the completedness
		--	of the quest for each quest in the Blizzard quest log.  If there is nothing in the log, an empty table is returned.
		_QuestsInLog = function(self)
			local retval = {}
			local numEntries, numQuests = GetNumQuestLogEntries()
			for i = 1, numEntries do
				local questTitle, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily, questId, startEvent = GetQuestLogTitle(i)
				if not isHeader then
					retval[questId] = { questTitle, isComplete }
				end
			end
			return retval
		end,

		---
		--	Returns the questId for the quest with the specified name.
		--	@param soughtName The localized name of the quest.  If nil this will raise.
		--	@return The questId of the quest or nil if no quest with that name found.
		QuestWithName = function(self, soughtName)
			assert((nil ~= soughtName), "Grail Error: sought name cannot be nil")
			local retval = nil
			for questId, _ in pairs(self.quests) do
				if self:QuestName(questId) == soughtName then
					retval = questId
				end
			end
			return retval
		end,

		--	Returns a table of NPC records where each record indicates the location
		--	of the NPC.  Each record can contain information as described in the
		--	documentation for NPCLocations.
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@return A table of NPC records
		--	@see NPCLocations
		_RawNPCLocations = function(self, npcId)
			local retval = {}
			npcId = tonumber(npcId)
			if nil ~= npcId and nil ~= self.npcs[npcId] then
				local npcName = self:NPCName(npcId)
				local codes = self.npcs[npcId][1]
				if nil ~= codes then
					local codeArray = { strsplit(" ", codes) }
					local controlCode
					local t = {}
					t.name = npcName
					t.id = npcId
					t.notes = self.npcs[npcId][2]
					t.locations = {}
					t.droppers = {}
					for _, code in pairs(codeArray) do
						controlCode = strsub(code, 1, 1)
						if 'Z' == controlCode then
							tinsert(t.locations, { ["mapArea"]=tonumber(strsub(code, 2)) })
						elseif 'H' == controlCode then
						elseif 'K' == controlCode then
							t.kill = true
						elseif 'A' == controlCode then
							t.alias = tonumber(strsub(code, 3))
						elseif 'X' == controlCode then
							t.heroic = true
						elseif 'P' == controlCode		-- Preowned
							or 'C' == controlCode		-- Created
							or 'M' == controlCode		-- Mailbox
							or 'S' == controlCode then	-- Self
							tinsert(t.locations, {})
						elseif 'N' == controlCode then
							local t1 = { ["near"] = true }
							if strlen(code) > 4 then
								t1.mapArea = tonumber(strsub(code, 5))
							end
							tinsert(t.locations, t1)
						elseif 'D' == controlCode then
							-- the D: represents a list of NPCs that drop the item we are actually processing
							if strlen(code) > 2 and ':' == strsub(code, 2, 2) then
								local realNPCs = { strsplit(',', strsub(code, 3)) }
								for _, anNPCId in pairs(realNPCs) do
									local droppers = self:_RawNPCLocations(anNPCId)
									if nil ~= droppers then
										for _, dropper in pairs(droppers) do
											tinsert(t.droppers, dropper)
										end
									end
								end
							end
						elseif 'Q' == controlCode then
							if strlen(code) > 2 and ':' == strsub(code, 2, 2) then
								t.questId = tonumber(strsub(code, 3))
							end
						else	-- a real coordinate
							local mapId, rest = strsplit(':', code)
							local mapLevel = 0
							local mapLevelString
							mapId, mapLevelString = strsplit('[', mapId)
							local t1 = { ["mapArea"] = tonumber(mapId) }
							if nil ~= mapLevelString then
								mapLevel = tonumber(strsub(mapLevelString, 1, strlen(mapLevelString) - 1))
							end
							t1.mapLevel = mapLevel
							local coord, realArea = nil, nil
							if nil ~= rest then
								coord, realArea = strsplit('>', rest)
								local coordinates = { strsplit(',', coord) }
								t1.x = tonumber(coordinates[1])
								t1.y = tonumber(coordinates[2])
								if nil ~= realArea then
									t1.realArea = tonumber(realArea)
								end
							end
							tinsert(t.locations, t1)
						end
					end
					for _, t1 in pairs(t.locations) do
						local t2 = {}
						t2.name = t.name
						t2.id = t.id
						t2.notes = t.notes
						t2.kill = t.kill
						t2.alias = t.alias
						t2.heroic = t.heroic
						t2.mapArea = t1.mapArea
						t2.mapLevel = t1.mapLevel
						t2.near = t1.near
						t2.x = t1.x
						t2.y = t1.y
						t2.realArea = t1.realArea
						t2.questId = t.questId
						tinsert(retval, t2)
					end
					for _, t1 in pairs(t.droppers) do
						local t2 = {}
						t2.name = t1.name
						t2.id = t1.id
						t2.notes = t1.notes
						t2.kill = t.kill
						t2.alias = t.alias
						t2.heroic = t.heroic
						t2.mapArea = t1.mapArea
						t2.mapLevel = t1.mapLevel
						t2.near = t1.near
						t2.x = t1.x
						t2.y = t1.y
						t2.realArea = t1.realArea
						t2.dropName = t.name
						t2.dropId = t.id
						t2.questId = t.questId
						tinsert(retval, t2)
					end
				end
			end
			if 0 == #retval then
				retval = nil
			end
			return retval
		end,

		_RecordBadData = function(self, whichData, errorString)
			if nil == GrailDatabase[whichData] then GrailDatabase[whichData] = {} end
			if nil ~= errorString then tinsert(GrailDatabase[whichData], errorString) end
		end,

		_RecordBadNPCData = function(self, errorString)
			self:_RecordBadData("BadNPCData", errorString)
		end,

		_RecordBadQuestData = function(self, errorString)
			self:_RecordBadData("BadQuestData", errorString)
		end,

		--	This routine is used internally only when reputation is being recorded upon quest completion.
		--	It has its limitations in that is reads the message the system presents when a faction change
		--	event is sent by Blizzard, and in doing so must account for all the modifications to rewards
		--	that currently apply to the player at reward time.  Initially this routine only handles the
		--	changes from being Human or being part of a guild that has a certain level.  This does not
		--	take into account all the known modifications, nor does it handle any possible ones that may
		--	be introduced in MoP.  Other limitations also include having to process these messages when
		--	not actually turning in a quest and knowing a quest has not been turned in, as well as getting
		--	values that are not really quest-accurate, but player limited when a player reaches the last
		--	values possible for a faction. Therefore, this routine will not be used, as there is Blizzard
		--	API that can be used when accepting a quest that indicates the faction changes.
		--	@param message The localized message that indicates a change in faction.
		_RecordReputation = function(self, message)
			local startPos, endPos, reputationName, changeAmount = strfind(message, self.increasedPattern)
			local direction = 1
			if nil == startPos then
				startPos, endPos, reputationName, changeAmount = strfind(message, self.decreasedPattern)
				direction = -1
			end
			if nil ~= startPos then

				-- Figure out the modifications to reputation
				local bonus = 0
				local playerGuildLevel = GetGuildLevel()
				if playerGuildLevel >= 12 then
					bonus = bonus + 0.10
				elseif playerGuildLevel >= 4 then
					bonus = bonus + 0.05
				end
				if "Human" == self.playerRace then
					bonus = bonus + 0.10
				end
				
				if 0 ~= bonus and -1 ~= direction then
					changeAmount = floor((changeAmount / (1.00 + bonus)) + 0.5)
				end

				changeAmount = changeAmount * direction
				local questId = self.reputationCompletingQuest
				local reputationIndex = self.reverseReputationMapping[reputationName]
				local repChangeString = nil
				local message = nil
				if nil ~= reputationIndex then
					if "490" ~= reputationIndex then
						repChangeString = strformat("%s%d", reputationIndex, changeAmount)
					end
				else
					message = strformat("UnknownRep:%s %d", reputationName, changeAmount)
				end
				if nil ~= repChangeString and nil ~= questId and nil ~= self.quests[questId] and (nil == self.quests[questId][6] or not tContains(self.quests[questId][6], repChangeString)) then
					message = strformat("Rep:%s", repChangeString)
				end
				if nil ~= message and nil ~= questId then
					self:_RecordBadQuestData('G' .. self.versionNumber .. '|' .. questId .. "|0|" .. message)
				end
			end
			
		end,

-- TODO: Continue analyzing from here down...
		---
		--	Adds the callback to the observer queue for eventName.  Should use convenience API when possible.
		--	@see RegisterObserverQuestAbandon
		--	@see RegisterObserverQuestAccept
		--	@see RegisterObserverQuestComplete
		--	@see RegisterObserverQuestStatus
		--	@param eventName The name of the event to which the callback should be added.
		--	@param callback The callback that is to be added.
		RegisterObserver = function(self, eventName, callback)
			assert((nil ~= callback), "Grail Error: cannot register a nil callback")
			if nil == self.observers[eventName] then self.observers[eventName] = { } end
			tinsert(self.observers[eventName], callback)
		end,

		---
		--	Add the callback to receive quest Abandon notifications.
		--	When the notification is posted the callback will be called with two parameters, "Abandon" and the questId.
		--	@param callback The callback that is to be added.
		RegisterObserverQuestAbandon = function(self, callback)
			self:RegisterObserver("Abandon", callback)
		end,

		---
		--	Add the callback to receive quest Accept notifications.
		--	When the notification is posted the callback will be called with two parameters, "Accept" and the questId.
		--	@param callback The callback that is to be added.
		RegisterObserverQuestAccept = function(self, callback)
			self:RegisterObserver("Accept", callback)
		end,

		---
		--	Add the callback to receive quest Completion notifications.
		--	When the notification is posted the callback will be called with two parameters, "Complete" and the questId.
		--	@param callback The callback that is to be added.
		RegisterObserverQuestComplete = function(self, callback)
			self:RegisterObserver("Complete", callback)
		end,

		---
		--	Add the callback to receive quest Status notifications.
		--	When the notification is posted the callback will be called with two parameters, "Status" and the questId.
		--	@param callback The callback that is to be added.
		RegisterObserverQuestStatus = function(self, callback)
			self:RegisterObserver("Status", callback)
		end,

		RegisterSlashOption = function(self, option, helpDescription, theFunction)
			self.slashCommandOptions[option] = { ['help'] = helpDescription, ['func'] = theFunction }
		end,

		--	Returns whether the character has a reputation that exceeds the value specified for the reputation specified.
		--	@param reputationName The localized name of the sought reputation.
		--	@param reputationValue The reputation value that needs to be exceeded.  Note that internally all reputation values are the earned reputation value + 42000.
		--	@return True if the character has more reputation than was sought, or false otherwise.
		--	@return The reputation value the character actually has (earned value + 42000).
		--	@usage doesExceed, reputationValue = Grail:_ReputationExceeds("Lower City", 41999)
		_ReputationExceeds = function(self, reputationName, reputationValue)
			local retval = false
			local actualEarnedValue = nil
			reputationValue = tonumber(reputationValue)
			local reputationId = self.reverseReputationMapping[reputationName]
			local factionId = reputationId and tonumber(reputationId, 16) or nil
if factionId == nil then print("Rep nil issue:", reputationName, reputationId, reputationValue) end
			if nil ~= factionId and nil ~= reputationValue then
				local name, description, standingId, barMin, barMax, barValue = GetFactionInfoByID(factionId)
				actualEarnedValue = barValue + 42000	-- the reputationValue is stored with 42000 added to it so we do not have to deal with negative numbers, so we normalize here
				retval = (actualEarnedValue > reputationValue)
			end
			return retval, actualEarnedValue
		end,

		--	Returns the riding skill level of the character.
		--	@return The riding skill level of the character or Grail.NO_SKILL if no skill exists.
		_RidingSkillLevel = function(self)
			-- Need to search the spell book for the Riding skill
			local retval = self.NO_SKILL
			local spellIdMapping = { [33388] = 75, [33391] = 150, [34090] = 225, [34091] = 300, [90265] = 375 }
			local _, _, _, numberSpells = GetSpellTabInfo(1)
			for i = 1, numberSpells, 1 do
				local name = GetSpellBookItemName(i, BOOKTYPE_SPELL)
				local link = GetSpellLink(name)
				if link then
					local spellId = tonumber(link:match("^|c%x+|H(.+)|h%[.+%]"):match("(%d+)"))
					if spellId then
						local newLevel = spellIdMapping[spellId]
						if newLevel and newLevel > retval then
							retval = newLevel
						end
					end
				end
			end
			return retval
		end,

		--	The routine called when the /grail slash command is used.  For the most part the currently implemented commands are for testing only.
		--	@param frame The tooltip frame.
		--	@param msg The rest of the command line used with the /grail slash command.
		_SlashCommand = function(self, frame, msg)
			local executed = false
--			msg = strlower(msg)
			for option, value in pairs(self.slashCommandOptions) do
				if option == strsub(msg, 1, strlen(option)) then
					value['func'](msg, frame)
					executed = true
				end
			end
			if not executed then
				print("|cFFFFFF00Grail|r initiating server database query")
				QueryQuestsCompleted()
			end			
		end,

		SpellPresent = function(self, soughtSpellId)
			soughtSpellId = tonumber(soughtSpellId)
			if nil == soughtSpellId then return false end
			local retval = false
			local name, spellId
			local i = 1
			while (false == retval) do
				name,_,_,_,_,_,_,_,_,_,spellId = UnitAura('player', i)
				if name then
					if soughtSpellId == tonumber(spellId) then
						retval = true
					end
					i = i + 1
				else
					break
				end
			end
			return retval
		end,

		---
		--	Returns a bit mask indicating the status of the quest.
		--	@param questId The standard numeric questId representing the quest.
		--	@return An integer that should be interpreted as a bit mask containing information why the quest cannot be accepted (or 0 (or 2) if it can).
		StatusCode = function(self, questId)
			local retval = 0
			questId = tonumber(questId)

			if questId ~= nil and self.quests[questId] ~= nil then
				if nil == self.quests[questId][7] then
					tinsert(self.currentlyProcessingStatus, questId)
					if self:DoesQuestExist(questId) then
						if not self:MeetsRequirementClass(questId) then retval = retval + self.bitMaskClass end
						if not self:MeetsRequirementRace(questId) then retval = retval + self.bitMaskRace end
						if not self:MeetsRequirementGender(questId) then retval = retval + self.bitMaskGender end
						if not self:MeetsRequirementFaction(questId) then retval = retval + self.bitMaskFaction end
						-- Only set the completed if it actually could have been done based on class, race, gender and faction
						if 0 == retval and self:IsQuestCompleted(questId) then retval = retval + self.bitMaskCompleted end
						if self:IsRepeatable(questId) then retval = retval + self.bitMaskRepeatable end
						if self:IsDaily(questId) or self:IsWeekly(questId) or self:IsMonthly(questId) or self:IsYearly(questId) then retval = retval + self.bitMaskResettable end
						if self:HasQuestEverBeenCompleted(questId) then retval = retval + self.bitMaskEverCompleted end
						if self:IsResettableQuestCompleted(questId) then retval = retval + self.bitMaskResettableRepeatableCompleted end
						if nil ~= self:IsBugged(questId) then retval = retval + self.bitMaskBugged end
						if self:IsLowLevel(questId) then retval = retval + self.bitMaskLowLevel else tinsert(self.questStatusCache["V"], questId) end
						local inLog, inLogStatus = self:IsQuestInQuestLog(questId)
						if inLog and 0 == bit.band(retval, self.bitMaskCompleted) then retval = retval + self.bitMaskInLog end
						if inLogStatus then
							if inLogStatus > 0 then retval = retval + self.bitMaskInLogComplete end
							if inLogStatus < 0 then retval = retval + self.bitMaskInLogFailed end
						end
-- TODO: Determine if there is an issue evaluating a prerequisite quest whose only prerequisites are P:D codes.  Quest 9622 has a requirement including 9570 which shows issues.
						if not self:MeetsPrerequisites(questId) and not (self:IsQuestCompleted(questId) and self:_OnlyHasPrerequisites(questId, 'B')) then
							retval = retval + self.bitMaskPrerequisites
							retval = self:AncestorStatusCode(questId, retval)
						end
						-- Only set an invalidation if the quest is not already completed
						if 0 == bit.band(retval, self.bitMaskCompleted) and self:IsInvalidated(questId) then retval = retval + self.bitMaskInvalidated end
						if not self:MeetsRequirementProfession(questId) then retval = retval + self.bitMaskProfession tinsert(self.questStatusCache["P"], questId) end
						if not self:MeetsRequirementReputation(questId) then retval = retval + self.bitMaskReputation tinsert(self.questStatusCache["R"], questId) end
						if not self:MeetsRequirementHoliday(questId) then retval = retval + self.bitMaskHoliday end
						local success, levelToCompare, levelRequired, levelNotToExceed = self:MeetsRequirementLevel(questId, self.levelingLevel)
						-- Only set a level problem if the quest is not already completed
						if not success and 0 == bit.band(retval, self.bitMaskCompleted) then
							if levelToCompare < levelRequired then retval = retval + self.bitMaskLevelTooLow tinsert(self.questStatusCache["L"], questId) end
							if levelToCompare > levelNotToExceed then retval = retval + self.bitMaskLevelTooHigh end
						end
-- TODO: Think about moving this code out of here and to CodeAllFixed because this need not be reevaluated each time the quest is
						local iQuests = self.quests[questId]['I']
						if nil ~= iQuests then
							for _, iQuestId in pairs(iQuests) do
								if nil == self.questStatusCache["I"][iQuestId] then self.questStatusCache["I"][iQuestId] = {} end
								if not tContains(self.questStatusCache["I"][iQuestId], questId) then tinsert(self.questStatusCache["I"][iQuestId], questId) end
							end
						end

					else
						retval = self.bitMaskNonexistent + self.bitMaskError
					end
					self.quests[questId][7] = retval

					-- First we invalidate the cache for all the quests whose status is suspect
					if nil ~= self.currentMortalIssues[questId] then
						for _,victimQuestId in pairs(self.currentMortalIssues[questId]) do
							self.quests[victimQuestId][7] = nil
						end
						self.currentMortalIssues[questId] = nil
					end
					-- Now we remove ourselves from the stack of processing
					tremove(self.currentlyProcessingStatus)

				else
					retval = self.quests[questId][7]
				end
			else
				retval = self.bitMaskError
			end
			return retval
		end,

		_StatusCodeCallback = function(callbackType, questId)
			questId = tonumber(questId)
			if nil ~= questId then
				if nil ~= Grail.questStatusCache then
					if Grail.quests[questId] then Grail.quests[questId][7] = nil end
					Grail:_CoalesceDelayedNotification("Status", 0)
					Grail:_StatusCodeInvalidate(Grail.questStatusCache['D'][questId])
					Grail:_StatusCodeInvalidate(Grail.questStatusCache["I"][questId])
					Grail:_StatusCodeInvalidate(Grail.questStatusCache["Q"][questId])
					Grail:_StatusCodeInvalidate(Grail.questStatusCache["F"][questId]) -- technically this should only be done for abandon, but the size will be so small it matters not
					Grail.questStatusCache["Q"][questId] = {}	-- the list we nuked should be regenerated when descendants get their new StatusCode values
				end
				if nil ~= Grail.quests[questId] then
					Grail:_StatusCodeInvalidate(Grail.quests[questId]['O'])
				end
			end
		end,

		---
		--	
		_StatusCodeInvalidate = function(self, tableOfQuestIds)
			if nil ~= tableOfQuestIds then
				for _, questId in pairs(tableOfQuestIds) do
					if nil ~= self.quests[questId] and nil ~= self.quests[questId][7] then
						self.quests[questId][7] = nil
						self._StatusCodeCallback("bogus", questId)	-- we want to invalidate the cache for descendants
						self:_CoalesceDelayedNotification("Status", 0)
					end
				end
			end
		end,

		_TableAppend = function(self, t1, t2)
			if nil ~= t1 and nil ~= t2 then
				if type(t2) == "table" then
					for _, value in pairs(t2) do
						tinsert(t1, value)
					end
				else
					tinsert(t1, t2)
				end
			end
			return t1
		end,

		_TableAppendCodes = function(self, t, master, codes)
			local tableToUse = t or {}
			if nil ~= codes and nil ~= master then
				for _, code in pairs(codes) do
					tableToUse = self:_TableAppend(tableToUse, master[code])
				end
			end
			return tableToUse
		end,

		_TableCopy = function(self, t)
			if nil == t then return nil end
			local retval = {}
			for k, v in pairs(t) do
				retval[k] = v
			end
			return retval
		end,

		---
		--	Returns information about the currently selected target.
		--	@return The localized name of the target or nil if no target.
		--	@return The npcId of the target unless the target is a world object in which one million is added to its value.
		--	@return The coordinates of the player (since the target coordinates cannot be determined) in the format mapId*:xx.xx,yy.yy, where * is nothing or the dungeon level in []
		--	@usage targetName, npcId, coordinates = Grail:TargetInformation()
		TargetInformation = function(self)
			local coordinates = nil
			local npcId, targetName = self:GetNPCId(true)
			if nil ~= npcId then
				local x, y = GetPlayerMapPosition("player")	-- cannot get target x,y since Blizzard disabled that and returns 0,0 all the time for it
				local dungeonLevel = GetCurrentMapDungeonLevel()
				local dungeonIndicator = (dungeonLevel > 0) and "["..dungeonLevel.."]" or ""
				coordinates = strformat("%d%s:%.2f,%.2f", GetCurrentMapAreaID(), dungeonIndicator, x*100, y*100)
			end
			return targetName, npcId, coordinates
		end,

		--	An internal routine only used when processing faction changes upon quest turnin.  In current builds
		--	for Mop, this is not used.
		_TimeBomb = function(type, questId)
			if Grail.debug and questId ~= Grail.reputationCompletingQuest then print("_TimeBomb processing", type, "with mismatch in quest IDs", questId, Grail.reputationCompletingQuest) end
			Grail.reputationCompletingQuest = nil
		end,

		--	The routine called for event processing associated with the hidden tooltip.
		--	@param frame The tooltip frame.
		--	@param event The name of the event.
		--	@param ... Various parameters depending on the event.
		_Tooltip_OnEvent = function(self, frame, event, ...)
			if self.eventDispatch[event] then
				self.eventDispatch[event](self, frame, ...)
			end
		end,

		--	The routine called by the hidden tooltip used to receive events when it is loaded into the system.
		--	It is used to set up information about the player, register the /grail slash command, hook the routines
		--	for handling accepting and abandoning quests, updating database values, and registering for the
		--	VARIABLES_LOADED event.
		--	@param frame The tooltip frame.
		_Tooltip_OnLoad = function(self, frame)
			self.playerRealm = GetRealmName()
			self.playerName = UnitName('player')
			_, self.playerClass = UnitClass('player')
			_, self.playerRace = UnitRace('player')
			self.playerFaction = UnitFactionGroup('player')		-- for Pandaren who has not chosen results is "Neutral"
			self.playerGender = UnitSex('player')
			self.playerLocale = GetLocale()
			self.levelingLevel = UnitLevel('player')
			SlashCmdList["GRAIL"] = function(msg)
				self:_SlashCommand(frame, msg)
			end
			SLASH_GRAIL1 = "/grail"

			-- 15640 is the 5.0.1 release
			local _, release = GetBuildInfo()
			self.blizzardRelease = tonumber(release)

			if not frame:IsOwned(UIParent) then
				frame:SetOwner(UIParent, "ANCHOR_NONE")
			end

			--	For verification of NPC information the tooltips can return a string
			--	that indicates the server is being queried.
			if self.playerLocale == "enUS" or self.playerLocale == "enGB" then
				self.retrievingString = "Retrieving item information"
			elseif self.playerLocale == "deDE" then
				self.retrievingString = "Frage Gegenstandsinformationen ab"
			elseif self.playerLocale == "esES" or self.playerLocale == "esMX" then
				self.retrievingString = "Obteniendo información de objeto"
			elseif self.playerLocale == "frFR" then
				self.retrievingString = "Récupération des informations de l'objet"
			elseif self.playerLocale == "itIT" then
    		   self.retrievingString = "Recupero dati oggetto"
			elseif self.playerLocale == "koKR" then
				self.retrievingString = "아이템 정보 검색"
			elseif self.playerLocale == "ptBR" then
				self.retrievingString = "Recuperando informações do item"
			elseif self.playerLocale == "ruRU" then
				self.retrievingString = "Получение сведений о предмете"
			elseif self.playerLocale == "zhTW" then
				self.retrievingString = "讀取物品資訊"
			elseif self.playerLocale == "zhCN" then
				self.retrievingString = "正在获取物品信息"
			else
				self.retrievingString = "Unknown"
			end

			if nil == QueryQuestsCompleted then
				QueryQuestsCompleted = function() Grail:_ProcessServerQuests() end
			end
			if nil == GetQuestsCompleted then
				GetQuestsCompleted = function(t)
					for questId in pairs(Grail.quests) do
						if IsQuestFlaggedCompleted(questId) then
							t[questId] = true
						end
					end
				end
			end

			-- Now to hook the QuestRewardCompleteButton_OnClick function
			self.origHookFunction = QuestFrameCompleteQuestButton:GetScript("OnClick")
			QuestFrameCompleteQuestButton:SetScript("OnClick", function() self:_QuestRewardCompleteButton_OnClick() end);

			self.origAbandonQuestFunction = SetAbandonQuest
			SetAbandonQuest = function() self:_QuestAbandonStart() end

			self.origConfirmAbandonQuestFunction = AbandonQuest
			AbandonQuest = function() self:_QuestAbandonStop() end

			-- We set up the basic structure to cache quest status information
			if nil == self.questStatusCache then
				-- quests is a table whose indexes are questIds and values are the actual bit mask status
				-- A is a table whose key is an achievement ID and whose value is a table of quests assocaited with it
				-- B is a table whose key is a buff ID and whose value is a table of quests associated with it
				-- C is a table whose key is an item ID whose presence is needed and whose value is a table of quests associated with it
				-- D is a table whose indexes are questIds and values are tables of questIds that need to be invalidated when the index is no longer in the quest log
				-- E is a table whose key is an item ID whose presence is NOT wanted and whose value is a table of quests associated with it
				-- F is a table whose key is a questId that when abandoned needs to have the table of associated quests invalidated
				-- L is a table of questIds who fail because of bitMaskLevelTooLow
				-- P is a table of questIds who fail because of bitMaskProfession
				-- R is a table of questIds who fail because of bitMaskReputation
				-- I is a table whose indexes are questIds and values are tables of questIds that suffer bitMaskInvalidated from the quest that is the index
				-- Q is a table whose indexes are questIds and values are tables of questIds that suffer bitMaskPrerequisites from the quest that is the index
				-- V is a table of questIds for quests that are NOT marked bitMaskLowLevel because gaining levels can change that value
				self.questStatusCache = { ["L"] = {}, ["P"] = {}, ["R"] = {}, ["I"] = {}, ["Q"] = {}, ["V"] = {}, ["A"] = {}, ["B"] = {}, ["D"] = {}, ["C"] = {}, ["E"] = {}, ["F"] = {}, }
			end

			-- Create some convenience tables
			self.raceNameToBitMapping = {}
			for code,raceName in pairs(self.raceMapping) do
				self.raceNameToBitMapping[raceName] = self.raceToBitMapping[code]
			end
			self.classNameToBitMapping = {}
			self.classBitToCodeMapping = {}
			for code,className in pairs(self.classMapping) do
				self.classNameToBitMapping[className] = self.classToBitMapping[code]
				self.classBitToCodeMapping[self.classToBitMapping[code]] = code
			end
			self.holidayBitToCodeMapping = {}
			for code,bitValue in pairs(self.holidayToBitMapping) do
				self.holidayBitToCodeMapping[bitValue] = code
			end
			self.reverseHolidayMapping = {}
			for index, holidayName in pairs(self.holidayMapping) do
				self.reverseHolidayMapping[holidayName] = index
			end
			self.reverseProfessionMapping = {}
			for index, professionName in pairs(self.professionMapping) do
				self.reverseProfessionMapping[professionName] = index
			end

			-- Set up some reputation processing code
			-- We use the Blizzard API to get the names of the factions instead of maintaining them internally ourselves which we used to do
			local reputationIndex
			for hexIndex, _ in pairs(self.reputationMapping) do
				reputationIndex = tonumber(hexIndex, 16)
				local name = GetFactionInfoByID(reputationIndex)
				if nil == name then name = "*** UNKNOWN " .. reputationIndex .. " ***" end
				self.reputationMapping[hexIndex] = name
			end
			self.reverseReputationMapping = {}
			for index, repName in pairs(self.reputationMapping) do
				self.reverseReputationMapping[repName] = index
			end
			self.increasedPattern = string.gsub(string.gsub(FACTION_STANDING_INCREASED, "%%s", "(.*)"), "%%d", "(%%d+)")
			self.decreasedPattern = string.gsub(string.gsub(FACTION_STANDING_DECREASED, "%%s", "(.*)"), "%%d", "(%%d+)")

			-- Load Continent and Zone information
			-- There is an assumption that GetMapContinents() will always return the continents in the same order (Kalimdor,
			-- Eastern Kingdoms, etc.) no matter what the locale is.  The old code would be able to identify the continents
			-- based on the number of zones in each, but with the beta of MoP we now have two continents with ten zones each.
			-- Therefore, if we still do not want to maintain the localized list of all the zones ourselves we assume the
			-- API will behave as we expect.
			-- The continents structure that is generated after all is said and done is:
			-- An array of 5 (or 6 in MoP) tables whose indexes are 1 through 5 (or 6) representing each continent.
			-- Each table will have members:
			--		name	(the localized name of the continent)
			--		zones	(a table of all the zones in the continent)
			--		dungeons	(a table consisting of the mapIDs of all the dungeons considered to be in the continent)
			-- The zones table will contain a table representing each zone in the continent with members:
			--		name	(the localized name of the zone)
			--		mapID	(the mapID of the zone)
			local continentNames = { GetMapContinents() }
			local L
			local mapId
			self.mapToContinentMapping = {}
			for i = 1, #(continentNames), 1 do
				L = {}
				L["name"] = continentNames[i]
				L["zones"] = {}
				local zoneNames = { GetMapZones(i) }
				for j = 1, #(zoneNames), 1 do
					L["zones"][j] = {}
					L["zones"][j]["name"] = zoneNames[j]
					SetMapZoom(i, j)
					mapId = GetCurrentMapAreaID()
					L["zones"][j]["mapID"] = mapId
					self.zoneNameMapping[zoneNames[j]] = mapId
					self.mapToContinentMapping[mapId] = i
				end
				self.continents[i] = L
			end
			--	Add in the dungeons
			local maxContinents = 5
			self.continents[1].dungeons = { 761, 749, 688, 760, 686, 750, 718, 733, 680, 699, 766, 717, 521, 759, 769, 747, 734, }
			self.continents[2].dungeons = { 764, 690, 691, 762, 692, 687, 756, 721, 704, 765, 763, 755, 799, 781, 798, 753, 757, 767, 793, }
			self.continents[3].dungeons = { 797, 710, 727, 728, 724, 722, 732, 779, 731, 726, 725, 729, 730, 723, 796, }
			self.continents[4].dungeons = { 523, 524, 535, 534, 528, 526, 520, 529, 533, 530, 522, 601, 604, 602, 603, 609, 536 }
			self.continents[5].dungeons = { 768, }
			if self.blizzardRelease >= 15640 then
				maxContinents = 6
				self.continents[6].dungeons = { 876, }
			end
			--	Now compute the dungeonMapping master list based on all the dungeons for each continent
			self.dungeonMapping = {}
			for i = 1, maxContinents do
				for _, v in pairs(self.continents[i].dungeons) do
					tinsert(self.dungeonMapping, v)
					self.mapToContinentMapping[v] = i
				end
			end

			local selfName = self:NPCName(0)
			-- Now we need to make a reverse mapping table that maps map area IDs into localized zone names.
			for zoneName, mapId in pairs(self.zoneNameMapping) do
				if nil == self.mapAreaMapping[mapId] then self.mapAreaMapping[mapId] = zoneName end
				-- Also create the "self" NPCs that are specific to each zone
				self.npcs[0 - mapId]={[0]=selfName,[1]=format("Z%d",mapId)}
			end

			-- Now we need to update some information based on the server to which we are connected
			if GetCVar("portal") == "eu" then
				-- The following quests are not available on European servers
				local G = self.quests
				G[11117] = nil
				G[11118] = nil
				G[11120] = nil
				G[11431] = nil
			end

			-- Create the indexed quest list up front so future requests are much faster
			self:CreateIndexedQuestList()

			-- Precompute the bit masks associated with things that cannot change so future access will be faster
			self.playerClassBitMask = self.classNameToBitMapping[self.playerClass]
			self.playerRaceBitMask = self.raceNameToBitMapping[self.playerRace]
			self.playerFactionBitMask = ('Horde' == self.playerFaction) and self.bitMaskFactionHorde or self.bitMaskFactionAlliance
			self.playerGenderBitMask = (3 == self.playerGender) and self.bitMaskGenderFemale or self.bitMaskGenderMale

			-- We need to be notified when any of these happen so we can update the quest status caches properly
			self:RegisterObserverQuestAbandon(Grail._StatusCodeCallback)
			self:RegisterObserverQuestAccept(Grail._StatusCodeCallback)
			self:RegisterObserverQuestComplete(Grail._StatusCodeCallback)

			self.tooltip = frame

			self.notificationFrame = CreateFrame("Frame")
			self.notificationFrame:SetScript("OnEvent", function(frame, event, ...) self:_Tooltip_OnEvent(frame, event, ...) end)
			self.notificationFrame:RegisterEvent("ADDON_LOADED")
		end,

		---
		--	Internal Use.
		--	Removes the callback from the observer queue for eventName.  Should not be called directly, but through the use of convenience API.
		--	@see UnregisterObserverQuestAbandon
		--	@see UnregisterObserverQuestAccept
		--	@see UnregisterObserverQuestComplete
		--	@see UnregisterObserverQuestStatus
		--	@param eventName The name of the event from which the callback should be removed.
		--	@param callback The callback that is to be removed.
		UnregisterObserver = function(self, eventName, callback)
			if nil ~= callback and nil ~= self.observers[eventName] then
				for i = 1, #(self.observers[eventName]), 1 do
					if callback == self.observers[eventName][i] then
						tremove(self.observers[eventName], i)
						break
					end
				end
			end
		end,

		---
		--	Remove the callback from receiving quest Abandon notifications.
		--	@param callback The callback that is to be removed.
		UnregisterObserverQuestAbandon = function(self, callback)
			self:UnregisterObserver("Abandon", callback)
		end,

		---
		--	Remove the callback from receiving quest Accept notifications.
		--	@param callback The callback that is to be removed.
		UnregisterObserverQuestAccept = function(self, callback)
			self:UnregisterObserver("Accept", callback)
		end,

		---
		--	Remove the callback from receiving quest Completion notifications.
		--	@param callback The callback that is to be removed.
		UnregisterObserverQuestComplete = function(self, callback)
			self:UnregisterObserver("Complete", callback)
		end,

		---
		--	Remove the callback from receiving quest Status notifications.
		--	@param callback The callback that is to be removed.
		UnregisterObserverQuestStatus = function(self, callback)
			self:UnregisterObserver("Status", callback)
		end,

		--	Updates the NewQuests with data if the quest does not already exist in the internal database or adds the npcCode to the NewQuests data if it does not already exist.
		--	@param questId The standard numeric questId representing a quest.
		--	@param questTitle The localized name of the quest.
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@param isDaily Indicates whether the quest is a daily quest.
		--	@param npcCode A string value 'A:' or 'T:' indicating whether the NPC is for accepting a quest or turning one in.
		--	@param version A version string based on the current internal database versions.
		_UpdateQuestDatabase = function(self, questId, questTitle, npcId, isDaily, npcCode, version, kCode)
			questId = tonumber(questId)
			if nil == questId then return end
			if not self:DoesQuestExist(questId) or nil == self.quests[questId][npcCode] then
				if nil == GrailDatabase["NewQuests"] then GrailDatabase["NewQuests"] = { } end
				if nil == GrailDatabase["NewQuests"][questId] then
					GrailDatabase["NewQuests"][questId] = { }
					GrailDatabase["NewQuests"][questId][self.playerLocale] = questTitle
					local codes = kCode
					-- no longer need the following since kCode should have it
--					if isDaily then codes = "+D" end
					if nil ~= npcId then
						if nil == codes then codes = npcCode..':'..npcId else codes = codes.." "..npcCode..':'..npcId end
					end
					if nil ~= codes then GrailDatabase["NewQuests"][questId][1] = codes end
					GrailDatabase["NewQuests"][questId][2] = version
				else
					local codes = GrailDatabase["NewQuests"][questId][1] or ''
					if nil == self:CodesWithPrefix(codes, npcCode) then
						if nil ~= npcId then
							if nil == codes then codes = npcCode..':'..npcId else codes = codes.." "..npcCode..':'..npcId end
						end
						if nil ~= codes then GrailDatabase["NewQuests"][questId][1] = codes end
					end
				end
				if nil == self.quests[questId] then
					self.quests[questId] = { [0] = questTitle, [1] = '' }
				end
			end
		end,

		--	Updates the time until quests reset based on the GetQuestResetTime() API.  A side-effect is that if the reset time is past QueryQuestsCompleted() will be called.
		_UpdateQuestResetTime = function(self)
			local seconds = GetQuestResetTime()
			if seconds > self.questResetTime then
				print("|cFFFF0000Grail|r automatically initializing a server query for completed quests")
				QueryQuestsCompleted()
			end
			self.questResetTime = seconds
		end,

		--	Updates the NewNPCs with data if the NPC does not already exist in the internal database.
		--	@param targetName The localized name of the NPC.
		--	@param npcId The standard numeric npcId representing an NPC.
		--	@param coordinates The zone coordinates of the player.
		--	@param version A version string based on the current internal database versions.
		_UpdateTargetDatabase = function(self, targetName, npcId, coordinates, version)
			if nil ~= npcId then
				if nil == self.npcs[npcId] then
					if nil == GrailDatabase["NewNPCs"] then	GrailDatabase["NewNPCs"] = { } end
					if nil == GrailDatabase["NewNPCs"][npcId] then GrailDatabase["NewNPCs"][npcId] = { } end
					GrailDatabase["NewNPCs"][npcId][self.playerLocale] = targetName
					GrailDatabase["NewNPCs"][npcId][1] = coordinates
					GrailDatabase["NewNPCs"][npcId][2] = version
				elseif 0 ~= npcId and nil ~= targetName and targetName ~= self:NPCName(npcId) then
					self:_RecordBadNPCData('G' .. self.versionNumber .. '|' .. npcId .. "|Name:" .. targetName .. "|Locale:" .. self.playerLocale)
				end
			end
		end,

		_VerifyNPC = function(self, npcId)
			local retval = true
			local frame = com_mithrandir_grailTooltip
			npcId = tonumber(npcId)
			if not frame:IsOwned(UIParent) then frame:SetOwner(UIParent, "ANCHOR_NONE") end
			frame:ClearLines()

			-- A specific section of NPCs are considered alias NPCs so the actual NPC is checked
			-- for the name to ensure it matches the alias associated with it.	
			local npcIdToUse = npcId
			if npcId > 500000 and npcId < 600000 then
				local codes = self:CodesWithPrefixNPC(npcId, 'A:')
				if nil ~= codes and #(codes) == 1 then
					npcIdToUse = tonumber(strsub(codes[1], 3))
				end
			end

			if npcIdToUse > 100000000 then
				frame:SetHyperlink(format("item:%d:0:0:0:0:0:0:0", npcIdToUse - 100000000))
			elseif npcIdToUse > 1000000 then
				frame:SetHyperlink(format("unit:0xF51%05X00000000", npcIdToUse - 1000000))
			else
				frame:SetHyperlink(format("unit:0xF53%05X00000000", npcIdToUse))
			end
			local numLines = frame:NumLines()
			if nil == numLines or 0 == numLines then
				retval = false
			else
				local text = _G["com_mithrandir_grailTooltipTextLeft1"]
				if text then
					text = text:GetText()
					if text == self.retrievingString then
						retval = false
					elseif text ~= self:NPCName(npcId) then
						self:_LogNameIssue('NPC_ISSUES', npcId, text)
						if self.debug then print("Added entry for", npcId) end
					else
						if self.debug then print("NPC item seems fine", npcId) end
					end
				else
					retval = false
				end
			end
			return retval
		end,

		_VerifyNPCList = function(self, elapsed)
			self.timeSinceLastUpdate = self.timeSinceLastUpdate + elapsed
			if self.timeSinceLastUpdate > 2.0 then

				-- Everything in table two is checked and if fails is moved to table 3
				for npcId in pairs(self.verifyTable2) do
					if nil ~= npcId then
						if not self:_VerifyNPC(npcId) then
							self.verifyTable3[npcId] = true
							self.verifyTableCount3 = self.verifyTableCount3 + 1
						end
						self.verifyTable2[npcId] = nil
						self.verifyTableCount2 = self.verifyTableCount2 - 1
					end
				end

				-- Everything in table one is checked and if fails is moved to table 2
				for npcId in pairs(self.verifyTable) do
					if nil ~= npcId then
						if not self:_VerifyNPC(npcId) then
							self.verifyTable2[npcId] = true
							self.verifyTableCount2 = self.verifyTableCount2 + 1
						end
						self.verifyTable[npcId] = nil
						self.verifyTableCount = self.verifyTableCount - 1
					end
				end

				if not self.doneProcessingList then self.currentQuestIndex = next(self.npcs, self.currentQuestIndex) end
				while nil ~= self.currentQuestIndex and self.verifyTableCount <= 100 do
					self.currentQuestIndex = tonumber(self.currentQuestIndex)
					if self.npcs[self.currentQuestIndex] and self.currentQuestIndex > 6 then
						self.npcCountForVerification = self.npcCountForVerification + 1
						if not self:_VerifyNPC(self.currentQuestIndex) then
							self.verifyTable[self.currentQuestIndex] = true
							self.verifyTableCount = self.verifyTableCount + 1
						end
					end
					if self.verifyTableCount <=100 then
						self.currentQuestIndex = next(self.npcs, self.currentQuestIndex)
					end
				end
				if nil == self.currentQuestIndex then self.doneProcessingList = true end

				self.timeSinceLastUpdate = 0
				print("Verify NPC status:", self.verifyTableCount, "/", self.verifyTableCount2, "/", self.verifyTableCount3, "/", self.npcCountForVerification)

				if (0 == self.verifyTableCount and 0 == self.verifyTableCount2 and nil == self.currentQuestIndex) then
					self.npcNotificationFrame:SetScript("OnUpdate", nil)
					self.currentlyVerifying = false
					print("Total unverifiable NPCs is", self.verifyTableCount3)
					print("Total NPCs is", self.npcCountForVerification)
					print("Ended total NPC verification")
				end

			end

		end,

		}

end

local locale = GetLocale()

if locale == "deDE" then
	Grail.holidayMapping = { ['A'] = 'Liebe liegt in der Luft', ['B'] = 'Braufest', ['C'] = "Kinderwoche", ['D'] = 'Tag der Toten', ['F'] = 'Dunkelmond-Jahrmarkt', ['H'] = 'Erntedankfest', ['K'] = "Angelwettstreit der Kalu'ak", ['L'] = 'Mondfest', ['M'] = 'Sonnenwendfest', ['N'] = 'Nobelgarten', ['P'] = "Piratentag", ['U'] = 'Neujahr', ['V'] = 'Winterhauch', ['W'] = "Schlotternächte", ['X'] = 'Anglerwettbewerb im Schlingendorntal', ['Y'] = "Die Pilgerfreuden", ['Z'] = "Weihnachtswoche", }

	Grail.professionMapping = { ['A'] = 'Alchemie', ['B'] = 'Schmiedekunst', ['C'] = 'Kochkunst', ['E'] = 'Verzauberkunst', ['F'] = 'Angeln', ['H'] = 'Kräuterkunde', ['I'] = 'Inschriftenkunde', ['J'] = 'Juwelenschleifen', ['L'] = 'Lederverarbeitung', ['M'] = 'Bergbau', ['N'] = 'Ingenieurskunst', ['R'] = 'Reiten', ['S'] = 'Kürschnerei', ['T'] = 'Schneiderei', ['X'] = 'Archäologie', ['+'] = 'Erste Hilfe', }

	Grail.raceNameFemaleMapping = { ['H'] = 'Mensch', ['F'] = 'Zwerg', ['E'] = 'Nachtelfe', ['N'] = 'Gnom', ['D'] = 'Draenei', ['W'] = 'Worgen', ['O'] = 'Orc', ['U'] = 'Untote', ['T'] = 'Tauren', ['L'] = 'Troll', ['B'] = 'Blutelfe', ['G'] = 'Goblin', ['A'] = 'Pandaren', }

	Grail.raceNameMapping = { ['H'] = 'Mensch', ['F'] = 'Zwerg', ['E'] = 'Nachtelf', ['N'] = 'Gnom', ['D'] = 'Draenei', ['W'] = 'Worgen', ['O'] = 'Orc', ['U'] = 'Untoter', ['T'] = 'Tauren', ['L'] = 'Troll', ['B'] = 'Blutelf', ['G'] = 'Goblin', ['A'] = 'Pandaren', }

	local G = Grail.races
	G['H'][2] = 'Mensch'
	G['H'][3] = 'Mensch'
	G['F'][2] = 'Zwerg'
	G['F'][3] = 'Zwerg'
	G['E'][2] = 'Nachtelf'
	G['E'][3] = 'Nachtelfe'
	G['N'][2] = 'Gnom'
	G['N'][3] = 'Gnom'
		G['D'][2] = 'Draenei'
		G['D'][3] = 'Draenei'
		G['W'][2] = 'Worgen'
		G['W'][3] = 'Worgen'
		G['O'][2] = 'Orc'
		G['O'][3] = 'Orc'
	G['U'][2] = 'Untoter'
	G['U'][3] = 'Untote'
		G['T'][2] = 'Tauren'
		G['T'][3] = 'Tauren'
		G['L'][2] = 'Troll'
		G['L'][3] = 'Troll'
	G['B'][2] = 'Blutelf'
	G['B'][3] = 'Blutelfe'
		G['G'][2] = 'Goblin'
		G['G'][3] = 'Goblin'
		G['A'][2] = 'Pandaren'
		G['A'][3] = 'Pandaren'
elseif locale == "esES" then
	Grail.holidayMapping = { ['A'] = 'Amor en el aire', ['B'] = 'Fiesta de la cerveza', ['C'] = "Semana de los Niños", ['D'] = 'Festividad de los Muertos', ['F'] = 'Feria de la Luna Negra', ['H'] = 'Festival de la Cosecha', ['K'] = "Competición de pesca Kalu'ak", ['L'] = 'Festival Lunar', ['M'] = 'Festival de Fuego del Solsticio de Verano', ['N'] = 'Jardín Noble', ['P'] = "Día de los Piratas", ['U'] = 'Nochevieja', ['V'] = 'El festín del Festival del Invierno', ['W'] = "Halloween", ['X'] = 'Concurso de Pesca', ['Y'] = "Generosidad del Peregrino", ['Z'] = "Semana navideña", }

	Grail.professionMapping = { ['A'] = 'Alquimia', ['B'] = 'Herrería', ['C'] = 'Cocina', ['E'] = 'Encantamiento', ['F'] = 'Pesca', ['H'] = 'Hebalismo', ['I'] = 'Inscripción', ['J'] = 'Joyería', ['L'] = 'Peletería', ['M'] = 'Minería', ['N'] = 'Ingeniería', ['R'] = 'Equitación', ['S'] = 'Desuello', ['T'] = 'Sastrería', ['X'] = 'Arqueología', ['+'] = 'Primeros auxilios', }

	Grail.raceNameFemaleMapping = { ['H'] = 'Humana', ['F'] = 'Enana', ['E'] = 'Elfa de la noche', ['N'] = 'Gnoma', ['D'] = 'Draenei', ['W'] = 'Huargen', ['O'] = 'Orco', ['U'] = 'No-muerta', ['T'] = 'Tauren', ['L'] = 'Trol', ['B'] = 'Elfa de sangre', ['G'] = 'Goblin', ['A'] = 'Pandaren', }

	Grail.raceNameMapping = { ['H'] = 'Humano', ['F'] = 'Enano', ['E'] = 'Elfo de la noche', ['N'] = 'Gnomo', ['D'] = 'Draenei', ['W'] = 'Huargen', ['O'] = 'Orco', ['U'] = 'No-muerto', ['T'] = 'Tauren', ['L'] = 'Trol', ['B'] = 'Elfo de sangre', ['G'] = 'Goblin', ['A'] = 'Pandaren', }
	local G = Grail.races
	G['H'][2] = 'Humano'
	G['H'][3] = 'Humana'
	G['F'][2] = 'Enano'
	G['F'][3] = 'Enana'
	G['E'][2] = 'Elfo de la noche'
	G['E'][3] = 'Elfa de la noche'
	G['N'][2] = 'Gnomo'
	G['N'][3] = 'Gnoma'
	G['W'][2] = 'Huargen'
	G['W'][3] = 'Huargen'
	G['O'][2] = 'Orco'
	G['O'][3] = 'Orco'
	G['U'][2] = 'No-muerto'
	G['U'][3] = 'No-muerta'
	G['L'][2] = 'Trol'
	G['L'][3] = 'Trol'
	G['B'][2] = 'Elfo de sangre'
	G['B'][3] = 'Elfa de sangre'
elseif locale == "esMX" then
	Grail.holidayMapping = { ['A'] = 'Amor en el Aire', ['B'] = 'Fiesta de la Cerveza', ['C'] = "Semana de los Niños", ['D'] = 'Día de los Muertos', ['F'] = 'Feria de la Luna Negra', ['H'] = 'Festival de la Cosecha', ['K'] = "Competición de pesca Kalu'ak", ['L'] = 'Festival Lunar', ['M'] = 'Festival de Fuego del Solsticio de Verano', ['N'] = 'Jardín Noble', ['P'] = "Día de los Piratas", ['U'] = 'Nochevieja', ['V'] = 'Festival del Invierno', ['W'] = "Halloween", ['X'] = 'Concurso de Pesca', ['Y'] = "Generosidad del Peregrino", ['Z'] = "Semana navideña", }

	Grail.professionMapping = { ['A'] = 'Alquimia', ['B'] = 'Herrería', ['C'] = 'Cocina', ['E'] = 'Encantamiento', ['F'] = 'Pesca', ['H'] = 'Hebalismo', ['I'] = 'Inscripción', ['J'] = 'Joyería', ['L'] = 'Peletería', ['M'] = 'Minería', ['N'] = 'Ingeniería', ['R'] = 'Equitación', ['S'] = 'Desuello', ['T'] = 'Sastrería', ['X'] = 'Arqueología', ['+'] = 'Primeros auxilios', }

	Grail.raceNameFemaleMapping = { ['H'] = 'Humana', ['F'] = 'Enana', ['E'] = 'Elfa de la noche', ['N'] = 'Gnoma', ['D'] = 'Draenei', ['W'] = 'Huargen', ['O'] = 'Orco', ['U'] = 'No-muerta', ['T'] = 'Tauren', ['L'] = 'Trol', ['B'] = 'Elfa de sangre', ['G'] = 'Goblin', ['A'] = 'Pandaren', }

	Grail.raceNameMapping = { ['H'] = 'Humano', ['F'] = 'Enano', ['E'] = 'Elfo de la noche', ['N'] = 'Gnomo', ['D'] = 'Draenei', ['W'] = 'Huargen', ['O'] = 'Orco', ['U'] = 'No-muerto', ['T'] = 'Tauren', ['L'] = 'Trol', ['B'] = 'Elfo de sangre', ['G'] = 'Goblin', ['A'] = 'Pandaren', }
	local G = Grail.races
	G['H'][2] = 'Humano'
	G['H'][3] = 'Humana'
	G['F'][2] = 'Enano'
	G['F'][3] = 'Enana'
	G['E'][2] = 'Elfo de la noche'
	G['E'][3] = 'Elfa de la noche'
	G['N'][2] = 'Gnomo'
	G['N'][3] = 'Gnoma'
	G['W'][2] = 'Huargen'
	G['W'][3] = 'Huargen'
	G['O'][2] = 'Orco'
	G['O'][3] = 'Orco'
	G['U'][2] = 'No-muerto'
	G['U'][3] = 'No-muerta'
	G['L'][2] = 'Trol'
	G['L'][3] = 'Trol'
	G['B'][2] = 'Elfo de sangre'
	G['B'][3] = 'Elfa de sangre'
elseif locale == "frFR" then
	Grail.holidayMapping = { ['A'] = "De l'amour dans l'air", ['B'] = 'Fête des Brasseurs', ['C'] = "Semaine des enfants", ['D'] = 'Jour des morts', ['F'] = 'Foire de Sombrelune', ['H'] = 'Fête des moissons', ['K'] = "Tournoi de pêche kalu'ak", ['L'] = 'Fête lunaire', ['M'] = "Fête du Feu du solstice d'été", ['N'] = 'Le Jardin des nobles', ['P'] = "Jour des pirates", ['U'] = 'Nouvel an', ['V'] = "Voile d'hiver", ['W'] = "Sanssaint", ['X'] = 'Concours de pêche de Strangleronce', ['Y'] = "Bienfaits du pèlerin", ['Z'] = "Vacances de Noël", }

	Grail.professionMapping = { ['A'] = 'Alchimie', ['B'] = 'Forge', ['C'] = 'Cuisine', ['E'] = 'Enchantement', ['F'] = 'Pêche', ['H'] = 'Herboristerie', ['I'] = 'Calligraphie', ['J'] = 'Joaillerie', ['L'] = 'Travail du cuir', ['M'] = 'Minage', ['N'] = 'Ingénierie', ['R'] = 'Monte', ['S'] = 'Dépeçage', ['T'] = 'Couture', ['X'] = 'Archéologie', ['+'] = 'Secourisme', }

	Grail.raceNameFemaleMapping = { ['H'] = 'Humaine', ['F'] = 'Naine', ['E'] = 'Elfe de la nuit', ['N'] = 'Gnome', ['D'] = 'Draeneï', ['W'] = 'Worgen', ['O'] = 'Orque', ['U'] = 'Morte-vivante', ['T'] = 'Taurène', ['L'] = 'Trollesse', ['B'] = 'Elfe de sang', ['G'] = 'Gobeline', ['A'] = 'Pandaren', }

	Grail.raceNameMapping = { ['H'] = 'Humain', ['F'] = 'Nain', ['E'] = 'Elfe de la nuit', ['N'] = 'Gnome', ['D'] = 'Draeneï', ['W'] = 'Worgen', ['O'] = 'Orc', ['U'] = 'Mort-vivant', ['T'] = 'Tauren', ['L'] = 'Troll', ['B'] = 'Elfe de sang', ['G'] = 'Gobelin', ['A'] = 'Pandaren', }
elseif locale == "itIT" then
Grail.holidayMapping = {
    ['A'] = "Amore nell'Aria",
    ['B'] = 'Festa della Birra',
    ['C'] = "Settimana dei Bambini",
    ['D'] = 'Giorno dei Morti',
    ['F'] = 'Fiera di Lunacupa',
    ['H'] = 'Sagra del Raccolto',
	['K'] = "Gara di pesca dei Kalu'ak",
    ['L'] = 'Celebrazione della Luna',
    ['M'] = 'Fuochi di Mezza Estate',
    ['N'] = 'Festa di Nobiluova',
    ['P'] = "Giorno dei Pirati",
    ['U'] = 'New Year', -- LOCALIZE
    ['V'] = 'Vigilia di Grande Inverno',
    ['W'] = "Veglia delle Ombre",
    ['X'] = 'Gara di Pesca a Rovotorto',
    ['Y'] = "Ringraziamento del Pellegrino",
    ['Z'] = "Christmas Week", -- LOCALIZE
    }

Grail.professionMapping = {
    ['A'] = 'Alchemy', -- LOCALIZE
    ['B'] = 'Blacksmithing', -- LOCALIZE
    ['C'] = 'Cooking', -- LOCALIZE
    ['E'] = 'Incantamento',
    ['F'] = 'Fishing', -- LOCALIZE
    ['H'] = 'Herbalism', -- LOCALIZE
    ['I'] = 'Inscription', -- LOCALIZE
    ['J'] = 'Oreficeria',
    ['L'] = 'Leatherworking', -- LOCALIZE
    ['M'] = 'Mining', -- LOCALIZE
    ['N'] = 'Engineering', -- LOCALIZE
    ['R'] = 'Riding', -- LOCALIZE
    ['S'] = 'Skinning', -- LOCALIZE
    ['T'] = 'Tailoring', -- LOCALIZE
    ['X'] = 'Archaeology', -- LOCALIZE
    ['+'] = 'First Aid', -- LOCALIZE
    }

Grail.raceNameFemaleMapping = {
    ['H'] = 'Umana',
    ['F'] = 'Nana',
    ['E'] = 'Elfa della Notte',
    ['N'] = 'Gnoma',
    ['D'] = 'Draenei',
    ['W'] = 'Worgen',
    ['O'] = 'Orchessa',
    ['U'] = 'Non Morta',
    ['T'] = 'Tauren',
    ['L'] = 'Troll',
    ['B'] = 'Elfa del Sangue',
    ['G'] = 'Goblin',
    ['A'] = 'Pandaren',
    }

Grail.raceNameMapping = {
    ['H'] = 'Umano',
    ['F'] = 'Nano',
    ['E'] = 'Elfo della Notte',
    ['N'] = 'Gnomo',
    ['D'] = 'Draenei',
    ['W'] = 'Worgen',
    ['O'] = 'Orco',
    ['U'] = 'Non Morto',
    ['T'] = 'Tauren',
    ['L'] = 'Troll',
    ['B'] = 'Elfo del Sangue',
    ['G'] = 'Goblin',
    ['A'] = 'Pandaren',
    }
elseif locale == "koKR" then
	Grail.holidayMapping = { ['A'] = '온누리에 사랑을', ['B'] = '가을 축제', ['C'] = "어린이 주간", ['D'] = '망자의 날', ['F'] = '다크문 축제', ['H'] = '추수절', ['K'] = '칼루아크 낚시 대회', ['L'] = '달의 축제', ['M'] = '한여름 불꽃축제', ['N'] = '귀족의 정원', ['P'] = "해적의 날", ['U'] = '새해맞이 전야제', ['V'] = '겨울맞이 축제', ['W'] = "할로윈 축제", ['X'] = '가시덤불 골짜기 낚시왕 선발대회', ['Y'] = "순례자의 감사절", ['Z'] = "한겨울 축제 주간", }

	Grail.professionMapping = { ['A'] = '연금술', ['B'] = '대장기술', ['C'] = '요리', ['E'] = '마법부여', ['F'] = '낚시', ['H'] = '약초채집', ['I'] = '주문각인', ['J'] = '보석세공', ['L'] = '가죽세공', ['M'] = '채광', ['N'] = '기계공학', ['R'] = '탈것 숙련', ['S'] = '무두질', ['T'] = '재봉술', ['X'] = '고고학', ['+'] = '응급치료', }

	Grail.raceNameFemaleMapping = { ['H'] = '인간', ['F'] = '드워프', ['E'] = '나이트 엘프', ['N'] = '노움', ['D'] = '드레나이', ['W'] = '늑대인간', ['O'] = '오크', ['U'] = '언데드', ['T'] = '타우렌', ['L'] = '트롤', ['B'] = '블러드 엘프', ['G'] = '고블린', ['A'] = 'Pandaren', }

	Grail.raceNameMapping = { ['H'] = '인간', ['F'] = '드워프', ['E'] = '나이트 엘프', ['N'] = '노움', ['D'] = '드레나이', ['W'] = '늑대인간', ['O'] = '오크', ['U'] = '언데드', ['T'] = '타우렌', ['L'] = '트롤', ['B'] = '블러드 엘프', ['G'] = '고블린', ['A'] = 'Pandaren', }
elseif locale == "ptBR" then
Grail.holidayMapping = { ['A'] = "O Amor Está No Ar", ['B'] = 'CervaFest', ['C'] = "Semana das Crianças", ['D'] = 'Dia dos Mortos', ['F'] = 'Feira de Negraluna', ['H'] = 'Festival da Colheita', ['K'] = "Campeonato de Pesca dos Kalu'ak", ['L'] = 'Festival da Lua', ['M'] = "Festival do Fogo do Solsticio", ['N'] = 'Jardinova', ['P'] = "Dia dos Piratas", ['U'] = 'New Year', ['V'] = "Festa do Véu de Inverno", ['W'] = "Noturnália", ['X'] = 'Stranglethorn Fishing Extravaganza', ['Y'] = "Festa da Fartura", ['Z'] = "Semana Natalina", }

Grail.professionMapping = {
	['A'] = 'Alquimia',
	['B'] = 'Ferraria',
	['C'] = 'Culinária',
	['E'] = 'Encantamento',
	['F'] = 'Paseca',
	['H'] = 'Herborismo',
	['I'] = 'Escrivania',
	['J'] = 'Joalheria',
	['L'] = 'Couraria',
	['M'] = 'Mineração',
	['N'] = 'Engenharia',
	['R'] = 'Montaria',
	['S'] = 'Esfolamentoa',
	['T'] = 'Alfaiataria',
	['X'] = 'Arqueologia',
	['+'] = 'Primeiros Socorros',
	}

Grail.raceNameFemaleMapping = { ['H'] = 'Humana', ['F'] = 'Anã', ['E'] = 'Elfa Noturna', ['N'] = 'Gnomida', ['D'] = 'Draenaia', ['W'] = 'Worgenin', ['O'] = 'Orquisa', ['U'] = 'Morta-viva', ['T'] = 'Taurena', ['L'] = 'Trolesa', ['B'] = 'Elfa Sangrenta', ['G'] = 'Goblina', ['A'] = 'Pandaren', }

Grail.raceNameMapping = { ['H'] = 'Humano', ['F'] = 'Anão', ['E'] = 'Elfo Noturno', ['N'] = 'Gnomo', ['D'] = 'Draenei', ['W'] = 'Worgen', ['O'] = 'Orc', ['U'] = 'Morto-vivo', ['T'] = 'Tauren', ['L'] = 'Troll', ['B'] = 'Elfo Sangrento', ['G'] = 'Goblin', ['A'] = 'Pandaren', }
elseif locale == "ruRU" then
	Grail.holidayMapping = { ['A'] = 'Любовная лихорадка', ['B'] = 'Хмельной фестиваль', ['C'] = "Детская неделя", ['D'] = 'День мертвых', ['F'] = 'Ярмарка Новолуния', ['H'] = 'Неделя урожая', ['K'] = "Калуакское рыбоборье", ['L'] = 'Лунный фестиваль', ['M'] = 'Огненный солнцеворот', ['N'] = 'Сад чудес', ['P'] = "День пирата", ['U'] = 'Канун Нового Года', ['V'] = 'Зимний Покров', ['W'] = "Тыквовин", ['X'] = 'Рыбная феерия Тернистой долины', ['Y'] = "Пиршество странников", ['Z'] = "Рождественская неделя", }

	Grail.professionMapping = { ['A'] = 'Алхимия', ['B'] = 'Кузнечное дело', ['C'] = 'Кулинария', ['E'] = 'Наложение чар', ['F'] = 'Рыбная ловля', ['H'] = 'Травничество', ['I'] = 'Начертание', ['J'] = 'Ювелирное дело', ['L'] = 'Кожевничество', ['M'] = 'Горное дело', ['N'] = 'Механика', ['R'] = 'Верховая езда', ['S'] = 'Снятие шкур', ['T'] = 'Портняжное дело', ['X'] = 'Археология', ['+'] = 'Первая помощь', }

	Grail.raceNameFemaleMapping = { ['H'] = 'Человек', ['F'] = 'Дворф', ['E'] = 'Ночная эльфийка', ['N'] = 'Гном', ['D'] = 'Дреней', ['W'] = 'Ворген', ['O'] = 'Орк', ['U'] = 'Нежить', ['T'] = 'Таурен', ['L'] = 'Тролль', ['B'] = 'Эльфийка крови', ['G'] = 'Гоблин', ['A'] = 'Pandaren', }

	Grail.raceNameMapping = { ['H'] = 'Человек', ['F'] = 'Дворф', ['E'] = 'Ночной эльф', ['N'] = 'Гном', ['D'] = 'Дреней', ['W'] = 'Ворген', ['O'] = 'Орк', ['U'] = 'Нежить', ['T'] = 'Таурен', ['L'] = 'Тролль', ['B'] = 'Эльф крови', ['G'] = 'Гоблин', ['A'] = 'Pandaren', }
elseif locale == "zhCN" then
	Grail.holidayMapping = { ['A'] = '情人节', ['B'] = '美酒节', ['C'] = "儿童周", ['D'] = '死人节', ['F'] = '暗月马戏团', ['H'] = '收获节', ['K'] = "Tournoi de pêche kalu'ak", ['L'] = '春节', ['M'] = 'Midsummer Fire Festival', ['N'] = '复活节', ['P'] = "Pirates' Day", ['U'] = '除夕夜', ['V'] = '冬幕节', ['W'] = "万圣节", ['X'] = '荆棘谷钓鱼大赛', ['Y'] = "感恩节", ['Z'] = "Christmas Week", }

	Grail.professionMapping = { ['A'] = '炼金术', ['B'] = '锻造', ['C'] = '烹饪', ['E'] = '附魔', ['F'] = '钓鱼', ['H'] = '草药学', ['I'] = '铭文', ['J'] = '珠宝加工', ['L'] = '制皮', ['M'] = '采矿', ['N'] = '工程学', ['R'] = '骑术', ['S'] = '剥皮', ['T'] = '裁缝', ['X'] = 'Archaeology', ['+'] = '急救', }

	Grail.raceNameFemaleMapping = { ['H'] = '人类', ['F'] = '矮人', ['E'] = '暗夜精灵', ['N'] = '侏儒', ['D'] = '德莱尼', ['W'] = '狼人', ['O'] = '兽人', ['U'] = '亡灵', ['T'] = '牛头人', ['L'] = '巨魔', ['B'] = '血精灵', ['G'] = '地精', ['A'] = 'Pandaren', }

	Grail.raceNameMapping = { ['H'] = '人类', ['F'] = '矮人', ['E'] = '暗夜精灵', ['N'] = '侏儒', ['D'] = '德莱尼', ['W'] = '狼人', ['O'] = '兽人', ['U'] = '亡灵', ['T'] = '牛头人', ['L'] = '巨魔', ['B'] = '血精灵', ['G'] = '地精', ['A'] = 'Pandaren', }
elseif locale == "zhTW" then
	Grail.holidayMapping = { ['A'] = '愛就在身邊', ['B'] = '啤酒節', ['C'] = "兒童週", ['D'] = '亡者節', ['F'] = '暗月馬戲團', ['H'] = '收穫節', ['K'] = "卡魯耶克釣魚大賽", ['L'] = '新年慶典', ['M'] = '仲夏火焰節慶', ['N'] = '貴族花園', ['P'] = "海賊日", ['U'] = '除夕夜', ['V'] = '冬幕節', ['W'] = "萬鬼節", ['X'] = '荊棘谷釣魚大賽', ['Y'] = "旅人豐年祭", ['Z'] = "Christmas Week", }

	Grail.professionMapping = { ['A'] = '鍊金術', ['B'] = '鍛造', ['C'] = '烹飪', ['E'] = '附魔', ['F'] = '釣魚', ['H'] = '草藥學', ['I'] = '銘文學', ['J'] = '珠寶設計', ['L'] = '製皮', ['M'] = '採礦', ['N'] = '工程學', ['R'] = '騎術', ['S'] = '剝皮', ['T'] = '裁縫', ['X'] = '考古學', ['+'] = '急救', }

	Grail.raceNameFemaleMapping = { ['H'] = '人類', ['F'] = '矮人', ['E'] = '夜精靈', ['N'] = '地精', ['D'] = '德萊尼', ['W'] = '狼人', ['O'] = '獸人', ['U'] = '不死族', ['T'] = '牛頭人', ['L'] = '食人妖', ['B'] = '血精靈', ['G'] = '哥布林', ['A'] = 'Pandaren', }

	Grail.raceNameMapping = { ['H'] = '人類', ['F'] = '矮人', ['E'] = '夜精靈', ['N'] = '地精', ['D'] = '德萊尼', ['W'] = '狼人', ['O'] = '獸人', ['U'] = '不死族', ['T'] = '牛頭人', ['L'] = '食人妖', ['B'] = '血精靈', ['G'] = '哥布林', ['A'] = 'Pandaren', }
elseif locale == "enUS" or locale == "enGB" then
	-- do nothing as the default values are already in English
else
	print("Grail does not have any knowledge of the localization", locale)
end


--[[
		*** Design ***

		Blizzard provides API that details all the quests that their servers record a player as having completed.  However,
		this information does not show the entire picture, and could be misleading.  Therefore, Grail attempts to provide the
		user with a better picture of reality by adjusting and accounting for the Blizzard results.
		
			* Blizzard can record multiple quests as turned in, when one is turned in.  Sometimes this includes quests
				the player could never have completed (because they are class-specific, or from a different faction).
			* There are a class of quests that Blizzard never records as being completed (like truly repeatable ones).
			* Bliizard sometimes uses a FLAG quest to mark a phase change or completion of an event.
			* There are quests that once abandoned are not able to be gotten again, but an associated quest becomes
				available to take its place.  Sometimes there is no FLAG quest for this, and Grail attempts to handle
				this by using false FLAG quests.

		There are many aspects of the games that influence what quests are available to a player.  Some of these aspects are
		reasonably static, like race and faction, while others are more dynamic like level, and reputation levels.  Therefore,
		Grail monitors events to ensure it is aware of changes that influence the availability of quests.

			* Player class
			* Player race
			* Player faction
			* Player level (both level too low and level too high)
			* Player gender
			* Player profession level
			* Player reputation level (both too low and too high)
			* Player having completed achievements
			* Player having a specific buff
			* Player having or not having a specific item
			* Player having turned in or not turned in specific quests
			* Player having completed the requirements for specific quests
			* Player having abandoned specific quests
			* Player having specific quests in the quest log
			* Quests only available during specific holidays (or other events)

		Quests usually have only one NPC that gives the quest and one to which the quest is turned in.  However, there are
		quests that have more than one, including quests that are accepted or turned in without a direct NPC (meaning an
		automatic quest the player handles).  Most NPCs have a fixed position in the world, but some move in small paths.
		There are also NPCs that change positions depending on the phase the player is in.  Sometimes Blizzard changes the
		NPC ID of the NPC when they phase, and other times the NPC ID remains the same.

		Grail has its quest data configured using strings that are reasonably human-readable.  This fixed information is
		converted to numbers that are interpreted as bitfields upon loading.  This is the fixed data.  Each quest also has
		a status that is computed upon request based on the player-specific information in combination with the fixed data.
		This status is also a number to be interpreted as a bitfield where multiple aspects can be set.  Each of these
		numbers only uses 32 bits, even though the numbers may be able to hold more bits.

		Since the status of a quest may be somewhat expensive in computing because a quest's prerequisites may need to be
		computed, the status of each quest is only computed upon demand.  And once the status of a quest is computed it is
		cached so future requests do not recompute it.  This means Grail needs to understand what influences the status of
		a quest and therefore monitors those influences for changes.  When changes occurs, it invalidates the cached status
		of the quests and posts a notification to its observers indicating a quest status change.  Clients can then update
		their UIs to reflect the changes in quest statuses.

		The Blizzard API that is used to determine quest status varies and covers quite a few different types of API.  Each
		of these API does not return valid results until certain parts of the Blizzard system have been initialized, so
		Grail needs to ensure it does not rely on results from any of these API until certain events have occurred.  In fact,
		some of the information Blizzard returns needs to be queried after delays because Blizzard systems do not update
		immediately after events or expected UI interactions like pressing buttons.  Grail attempts to handle these by
		setting up a delayed system where it processed things after a time period has passed.


]]--
