--[[
--	Libram
--	 	Knowledge is best shared
--
--	By: Alexander Brazie
--	Special Thanks: Anders Kronquist
--
--	This Add-On allows you to register to specific events
--	which affect the quest log.
--
--	$Id: Libram.lua 4318 2006-12-02 20:23:51Z karlkfi $
--	$Rev: 4318 $
--	$LastChangedBy: karlkfi $
--	$Date: 2006-12-02 14:23:51 -0600 (Sat, 02 Dec 2006) $
--]]


--[[
-- Changes on 27 Feb 2006 by zespri.
--
-- LIBRAM_QUEST_TIMER = "TIMER" is no longer fired as a type of event. The constants is
-- still there for backward compatibility. This event wasn't working properly, the prefered
-- method of udating quesr timer is doing this in OnUpdate handler the same way standard
-- Blizzard UI does.
--
-- New debugging constant LIBRAM_COMPATIBILITY_MODE. This will stay true for the time being.
-- All new addons dependant on Libram are encouraged to be tested in LIBRAM_COMPATIBILITY_MODE = false
-- to make sure that they don't depend on obsolete code. If LIBRAM_COMPATIBILITY_MODE = true, all
-- old addons supposed to worked properly as data is returned in old format.
--
-- requestHistory now returns *flat* quest table. i.e there is no longer zone sub-tables in it,
-- all quests are stored on the first level of the returned table. Please see comment to 
-- Sea.wow.questLog.getPlayerQuestTreeFlat for more details
--
-- Scholar callback now called with only two arguments: action and questInfo. questInfo now
-- contains oldQuest, newQuest, oldRecord and newRecord fields which reflect quest information
-- and quest details as returned by requestHistory (quest info) and requestRecord (quest details)
--  ADD event won't have oldQuest and oldRecord fields and REMOVE event won't have newQuest and 
-- newRecord field for obvious reasons.
-- 
-- In compatibility mode (LIBRAM_COMPATIBILITY_MODE = true) requestHistory will return data in old
-- format, but Scholar callback will have both old and new data in there.
--
--]]

-- Embeded code
local VERSION = 1.1
local isBetterInstanceLoaded = ( Libram and Libram.Version and Libram.Version >= Version )

-- Remove the old version
if not isBetterInstanceLoaded and Libram then
	Libram = nil
	LibramData = nil
end

if not isBetterInstanceLoaded then
	-- Constants
	LIBRAM_QUEST_ADD = "ADD"; -- Acquired
	LIBRAM_QUEST_REMOVE = "REMOVE"; -- Abandoned
	LIBRAM_QUEST_CHANGED = "CHANGED"; -- Id is a different quest
	LIBRAM_QUEST_COMPLETED = "COMPLETED"; -- Quest completed
	LIBRAM_QUEST_FAILED = "FAILED"; -- Quest failed
	LIBRAM_QUEST_INIT = "INIT"; -- Initialized
	LIBRAM_QUEST_TIMER = "TIMER"; -- Quest timer updated
	LIBRAM_QUEST_UPDATE = "UPDATE"; -- Quest updated

	-- Debug
	LIBRAM_DEBUG = false;
	LM_DEBUG = "LIBRAM_DEBUG";
	LIBRAM_DEBUG_DETAILED = false;
	LM_DEBUG_D = "LIBRAM_DEBUG_DETAILED";
	LIBRAM_DEBUG_TABLES = false;
	LM_DEBUG_T = "LIBRAM_DEBUG_TABLES";

	LIBRAM_COMPATIBILITY_MODE = true;

	Libram = {
		-- Online switch
		online = false,

		Version = VERSION,
		-- We no longer use Revision, so this is a bogus revision 
		-- higher than the last version released with the revision
		-- based system, so old Librams wont overwrite us.
		Revision = 5000,

		Initialized = false,

		--[[
		--	registerScholar ( {scholar} [, {scholar} ] )
		--		Registers a scholar with the specifed id
		--		who will be notified when the quest log
		--		updates or changes.
		--
		--	args:
		--		scholar - table
		--		{
		--			id - unique ID for the scholar
		--			callback - callback function
		--				callback(action, questInfo, updateInfo)
		--
		--				action - string - the action which has happened:
		--					ADD|REMOVE|CHANGED|COMPLETED|FAILED|UPDATE|INIT
		--
		--				questInfo - table
		--					oldQuest - quest info record before event
		--					newQuest - quest info record after event
		--						see Sea.wow.questLog.getPlayerQuestTreeFlat for the
		--						record format. 
		--					oldRecord - quest details record before event
		--					newRecord - quest details record after event
		--						See Sea.wow.questLog.getPlayerQuestData for the 
		--						details of the additional quest record format.
		--
		--			description - description string
		--
		--		}
		--
		--	returns:
		--		true - scholar enrolled successfully
		--		false - scholar was not valid
		--]]
		registerScholar = function (...)
			if not Libram.Initialized then
				Libram.Initialize()
			end

			local success = true;
			for i=1, select("#", ...) do
				local currArg = select(i, ...);
				if ( Libram.validateScholar(currArg) ) then
					LibramData.scholars[currArg.id] = currArg;
				else
					success = false;
				end
			end
			return success;
		end;

		--[[
		--	validateScholar({scholar})
		--		Ensures a scholar can afford to pay the bills.
		--
		--	args:
		--		see registerScholar
		--
		--	returns:
		--		true - valid scholar
		--		false - invalid scholar
		--]]
		validateScholar = function (scholar)
			if ( not scholar ) then
				Sea.io.error("Scholar is nil: ", this:GetName() );
				return false;
			end
			if ( not scholar.id ) then
				Sea.io.error("Scholar has no id! Sent by ",this:GetName());
				return false;
			end
			if ( type(scholar.id) ~= "string" ) then
				Sea.io.error("Scholar ID is not a string: ",scholar.id, " from ", this:GetName());
				return false;
			end
			if ( LibramData.scholars[scholar.id] ) then
				Sea.io.error("Scholar ID is already in use: ",scholar.id, " from ", this:GetName());
				return false;
			end

			if ( type(scholar.callback) ~= "function" ) then
				Sea.io.error("Scholar callback is not a function: ",scholar.id, " from ", this:GetName());
				return false;
			end

			return true;
		end;

		--[[
		--	unregisterScholar(id)
		--		unregisters the scholar with the specified id
		--
		--	args
		--		id - string id
		--
		--]]
		unregisterScholar = function(id)
			LibramData.scholars[id] = nil;
		end;

		--[[
		--	requestHistory()
		--		Returns a table to the current history.
		--		Don't change it... unless you want to break things.
		--
		--	returns:
		--		table - the current quest log history
		--		nil - no history yet
		--
		--]]
		requestHistory = function ()
			if not Libram.Initialized then
				Libram.Initialize()
			end

			if LIBRAM_COMPATIBILITY_MODE then
				return Sea.wow.questLog.convertPlayerQuestTreeFromFlatToNormal(LibramData.history);
			else
				return LibramData.history;
			end
		end;

		--[[
		--	requestRecord(questID)
		--		This method is obsolete, use 
		--		Libram.requestHistory()[questId].arcana instead
		--]]
		requestRecord = function ( questID )
			if not Libram.Initialized then
				Libram.Initialize()
			end

			return LibramData.arcana[questID];
		end;

		Initialize = function()
			Libram.online = true
			LibramData.updateHistory()
			Libram.Initialized = true
		end;

	};

	LibramData = {
		-- Registrants
		scholars = {};

		-- Quest data
		history = nil;

		-- Quest details
		arcana = nil;

		historyUpdateDue = false;

		updateHistory = function()
			if not Libram.online then
				return
			end
			Libram.online = false

			local discoveries = {}
			local recent = Sea.wow.questLog.getPlayerQuestTreeFlat()
			local recentArcana = {}

			if ( not LibramData.history ) then
				Sea.io.dprint(LM_DEBUG, "Libram: The Researchers decree: Initial DataReceived!");
				for k, quest in pairs(recent) do
					recent[k].crc = LibramData.updateArcana(discoveries, recentArcana, quest)
				end
				table.insert ( discoveries, {type=LIBRAM_QUEST_INIT} )
				LibramData.history = recent
				LibramData.arcana = recentArcana
			elseif ( recent ) then
				for k, quest in pairs(recent) do
					local found = false;
					for k2, oldQuest in pairs(LibramData.history) do
						if ( quest.title == oldQuest.title and quest.level == oldQuest.level ) then
							quest.crc = LibramData.updateArcana(discoveries, recentArcana, quest, oldQuest);
							if quest.crc == oldQuest.crc then
								-- Has the quest ID changed?
								if ( quest.id ~= oldQuest.id ) then
									Sea.io.dprint(LM_DEBUG_D, "Libram: The Researchers decree: Quest ID Changed! ", quest.title );
									if LIBRAM_COMPATIBILITY_MODE then
										table.insert(discoveries, { type = LIBRAM_QUEST_CHANGED; questInfo = {newQuest=quest; oldQuest=oldQuest; newRecord=recentArcana[quest.id]; oldRecord=LibramData.arcana[oldQuest.id]; title=quest.title; level=quest.level; id=quest.id;}; updateInfo={newid=quest.id;oldid=oldQuest.id} } );
									else
										table.insert(discoveries, { type = LIBRAM_QUEST_CHANGED; questInfo = {newQuest=quest; oldQuest=oldQuest; newRecord=recentArcana[quest.id]; oldRecord=LibramData.arcana[oldQuest.id]; }; } );
									end
								end

								-- Quest has been completed
								if ( quest.complete and not oldQuest.complete and not recentArcana[quest.id].failed ) then 
									Sea.io.dprint(LM_DEBUG, "Libram: The Researchers decree: Quest Completed! ", quest.title );
									if LIBRAM_COMPATIBILITY_MODE then
										table.insert(discoveries, { type = LIBRAM_QUEST_COMPLETED; questInfo = {newQuest=quest; oldQuest=oldQuest; newRecord=recentArcana[quest.id]; oldRecord=LibramData.arcana[oldQuest.id]; title=quest.title;level=quest.level;id=quest.id} } );
									else
										table.insert(discoveries, { type = LIBRAM_QUEST_COMPLETED; questInfo = {newQuest=quest; oldQuest=oldQuest; newRecord=recentArcana[quest.id]; oldRecord=LibramData.arcana[oldQuest.id]; } } );
									end
								end

								-- Remove from the old list, we've handled it
								LibramData.history[k2] = nil;
								found = true;
							end
						end
					end

					-- Was not found in the history books, report its discovery
					if ( not found ) then
						quest.crc = LibramData.updateArcana(discoveries, recentArcana, quest);
						Sea.io.dprint(LM_DEBUG, "Libram: The Researchers decree: Quest Added! ", quest.title );
						if LIBRAM_COMPATIBILITY_MODE then
							table.insert(discoveries, { type = LIBRAM_QUEST_ADD; questInfo = {newQuest=quest; newRecord=recentArcana[quest.id]; title=quest.title;level=quest.level;id=quest.id}; updateInfo={zone=zone} } );
						else
							table.insert(discoveries, { type = LIBRAM_QUEST_ADD; questInfo = {newQuest=quest; newRecord=recentArcana[quest.id];} });
						end
					end
				end

				-- If there's anything no longer found, it was lost
				for k,oldQuest in pairs(LibramData.history) do
					Sea.io.dprint(LM_DEBUG, "Libram: The Researchers decree: Quest Removed! ", oldQuest.title );
					if LIBRAM_COMPATIBILITY_MODE then
						table.insert(discoveries, { type = LIBRAM_QUEST_REMOVE; questInfo = {oldQuest=oldQuest; oldRecord=LibramData.arcana[oldQuest.id]; title=oldQuest.title;level=oldQuest.level;id=oldQuest.id}; updateInfo={zone=zone}; } );
					else
						table.insert(discoveries, { type = LIBRAM_QUEST_REMOVE; questInfo = {oldQuest=oldQuest; oldRecord=LibramData.arcana[oldQuest.id];} });
					end;
				end

				-- Store the history
				LibramData.history = recent;
				LibramData.arcana = recentArcana;
			end

			-- Debug!
			if ( getglobal(LM_DEBUG_T) ) then
				Sea.io.printTable(discoveries);
			end

			-- Let the world know
			LibramData.notifyScholars(discoveries);

			Libram.online = true
		end;

		updateArcana = function(discoveries, arcana, quest, oldQuest)
			local crc
			-- Request the tome
			arcana[quest.id] = Sea.wow.questLog.getPlayerQuestData(quest.id);
			-- If we can't do research, just return.
			if ( not arcana[quest.id] ) then 
				return nil
			end
			if ( not arcana[quest.id].description ) then 
				Sea.io.print(quest.id, arcana[quest.id].title)
				return nil
			end
			crc = CRCLib.crc(arcana[quest.id].description)
			arcana[quest.id].crc = crc

			if oldQuest and oldQuest.id and LibramData.arcana[oldQuest.id] then
				Sea.io.dprint(LM_DEBUG_D, "Researchers confirm the existence of ", quest.title, " ", quest.level);

				-- Discover failure
				if ( arcana[quest.id].failed and not LibramData.arcana[oldQuest.id].failed ) then
					Sea.io.dprint(LM_DEBUG, "Libram: The Researchers decree: Quest Failed! ", title );
					if LIBRAM_COMPATIBILITY_MODE then
						arcana[quest.id].newQuest = quest;
						arcana[quest.id].oldQuest = oldQuest;
						arcana[quest.id].newRecord = arcana[quest.id];
						arcana[quest.id].oldRecord = LibramData.arcana[oldQuest.id];
						table.insert(discoveries, { type = LIBRAM_QUEST_FAILED; questInfo = arcana[quest.id];  } );
					else
						table.insert(discoveries, { type = LIBRAM_QUEST_FAILED; questInfo = {newQuest=quest; oldQuest=oldQuest; newRecord = arcana[quest.id]; oldRecord = LibramData.arcana[oldQuest.id]};  } );
					end;
				end

				-- Discover objective updates
				for k,v in pairs(arcana[quest.id].objectives) do
					local updated = false;

					if ( getglobal(LM_DEBUG_T) ) then
						Sea.io.printTable(v.info);
						Sea.io.printTable(LibramData.arcana[oldQuest.id].objectives[k].info);
					end

					if ( v.info ) then
						if ( not  LibramData.arcana[oldQuest.id].objectives ) then
							updated = true;
						elseif ( not  LibramData.arcana[oldQuest.id].objectives[k] ) then
							updated = true;
						elseif ( not  LibramData.arcana[oldQuest.id].objectives[k].info ) then
							updated = true;
						elseif ( v.info.text ~= LibramData.arcana[oldQuest.id].objectives[k].info.text ) then
							updated = true;
						elseif ( v.info.done ~= LibramData.arcana[oldQuest.id].objectives[k].info.done ) then
							updated = true;
						end
					end

					if ( updated ) then
						Sea.io.dprint(LM_DEBUG, "Libram: The Researchers decree: Quest Updated! ", quest.title );
						if LIBRAM_COMPATIBILITY_MODE then
							arcana[quest.id].new = quest;
							arcana[quest.id].old = oldQuest;
							arcana[quest.id].newRecord = arcana[quest.id];
							arcana[quest.id].oldRecord = LibramData.arcana[oldQuest.id];
							table.insert(discoveries, { type = LIBRAM_QUEST_UPDATE; questInfo = arcana[quest.id]; updateInfo=v; } );
						else
							table.insert(discoveries, { type = LIBRAM_QUEST_UPDATE; questInfo = {newQuest=quest; oldQuest=oldQuest; newRecord = arcana[quest.id]; oldRecord = LibramData.arcana[oldQuest.id]}; } );
						end;
					end
				end
			else
				Sea.io.dprint( LM_DEBUG, "Libram: Researchers find ", arcana[quest.id].title, " for the first time." );
			end
			return crc
		end;

		-- Notifies the scholars of discoveries
		notifyScholars = function ( discoveries )
			if ( #(discoveries) > 0 ) then
				Sea.io.dprint(LM_DEBUG, "Libram: The Researchers are notifying the Scholars of ",#(discoveries), " discoveries!");
				for k,discovery in pairs(discoveries) do
					for k2,scholar in pairs(LibramData.scholars) do
						scholar.callback(discovery.type, discovery.questInfo, discovery.updateInfo );
					end
				end
			end
		end;
	};
end
