--[[
Name: SpellCache-1.0
Revision: $Rev: 15969 $
Author(s): Nightdew (denzsolnightdew@gmail.com)
Website: http://www.wowace.com/index.php/SpellCache-1.0
Documentation: http://www.wowace.com/index.php/SpellCache-1.0
SVN: http://svn.wowace.com/root/trunk/SpellCache/SpellCache-1.0
Description: Library that caches spells to speed up look ups
Dependencies: AceLibrary, AceDebug-2.0, AceEvent-2.0, Deformat-2.0
]]

--, Gratuity-2.0

local MAJOR_VERSION = "SpellCache-1.0"
local MINOR_VERSION = "$Revision: 15969 $"

if (not AceLibrary) then 
	error(MAJOR_VERSION .. " requires AceLibrary.") 
elseif (not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION)) then 
	return 
end

local function CheckDependencies(dependencies)
	for index, value in ipairs(dependencies) do
		if (not AceLibrary:HasInstance(value)) then 
			error(format("%s requires %s to function properly", MAJOR_VERSION, value))
		end
	end
end

local dependencyLibraries = {
	"AceDebug-2.0", 
	"AceEvent-2.0", 
	"Deformat-2.0",
	--"Gratuity-2.0"
}

CheckDependencies(dependencyLibraries)

local aceDebug = AceLibrary("AceDebug-2.0")
local aceEvent = AceLibrary("AceEvent-2.0")
local deformat = AceLibrary("Deformat-2.0")

--Create Library Object
local SpellCache = {}

--Embed all needed mixins into the Library Object SpellCache
aceDebug:embed(SpellCache)
aceEvent:embed(SpellCache)

--
-- Initialization methods.
--

local function GetSpellRankPattern(self, spellRank)
	local rankPattern, changeCount = string.gsub(spellRank, "%d+", "%%d")
	if (changeCount > 0) then
		self.RankPattern = rankPattern;
		self.FullPattern = format("%s(%s)", "%s", rankPattern)
		self:LevelDebug(2, "DetectRankPattern", spellRank, self.RankPattern, self.FullPattern)
		return rankPattern
	end
end

local function GetSpellRankNumber(self, spellRank)
	self:LevelDebug(3, "GetSpellRankNumber1", spellRank)
	if (spellRank and (spellRank ~= "")) then
		local rankPattern = self.RankPattern or GetSpellRankPattern(self, spellRank)
		if (rankPattern) then
			self:LevelDebug(3, "GetSpellRankNumber2", rankPattern, spellRank)
			local spellRankNumber = self:GetRankNumber(spellRank)
			return spellRankNumber
		end
	end
end

local function ResetVariables(self)
	if (not self:IsInitialized()) then
		return
	end

	--clear hash
	setmetatable(self.hash, nil) 
	for k in pairs(self.hash) do 
		self.hash[k] = nil
	end
	table.setn(self.hash, 0);

    local spellTabCount = GetNumSpellTabs()
    local tabName, tabTexture, tabStart, tabOffset = GetSpellTabInfo(spellTabCount)
    local spellCount = tabStart + tabOffset

	self:LevelDebug(3, "ResetVariables", spellTabCount, tabName, tabTexture, tabStart, tabOffset, spellCount)
    
    local spellData, spellName, spellRank, spellNamePrevious, spellNameNew = nil;
    for i = 1, spellCount do
        spellName, spellRank = GetSpellName(i, BOOKTYPE_SPELL)

		spellNameNew = spellNamePrevious ~= spellName
		spellNamePrevious = spellName
	
		spellData = self.data[spellName] or {}

		if (spellNameNew) then
			self.hash[i] = spellName
			self.data[spellName] = spellData
			spellData.IdStart = i
		else
			self.hash[i] = spellData.IdStart
		end

		spellData.IdStop = i
		
		spellData.Rank = GetSpellRankNumber(self, spellRank);
		
		self:LevelDebug(3, 
			spellData.IdStart,
			spellData.IdStop,
			spellData.Rank 
		)
    end
   	self:TriggerEvent("SpellCache_Updated")
end

local function InitializeVariables(self)
	--will collect spells as they are being cast.
	--SpellName -> Data
	self.data = {}
	--SpellId -> SpellName or SpellId
	self.hash = {}
	
	ResetVariables(self)
end

--
-- Event Registration
--

local function InitializeEventRegisters(self)
	self:RegisterEvent("LEARNED_SPELL_IN_TAB")
	self:RegisterEvent("PLAYER_ENTERING_WORLD", "LEARNED_SPELL_IN_TAB")
	--Supposedly not needed, and will spam too much
	--self:RegisterEvent("SPELLS_CHANGED", "LEARNED_SPELL_IN_TAB")
end

--update whatever spells are there!
function SpellCache:LEARNED_SPELL_IN_TAB()
	self.isInitialized = true
	ResetVariables(self)
end

function SpellCache:IsInitialized()
	return self.isInitialized == true
end

--
-- Activate method
--

local function activate(self, oldLib, oldDeactivate)
	--self:SetDebugLevel(3);
	if (oldLib) then
		oldLib:UnregisterAllEvents()
	end

	--Default code to clean up the oldlib
	if (oldDeactivate) then
		oldDeactivate(oldLib)
	end

	InitializeVariables(self)
	InitializeEventRegisters(self)	
end

--
-- Util Methods
--

--
-- External Methods
--

function SpellCache:GetRankNumber(spellRankText)
	self:LevelDebug(3, "SpellCache:GetRankNumber1", spellRankText)
	self:argCheck(spellRankText, 2, "string")
	local spellRankNumber = deformat:Deformat(spellRankText, self.RankPattern)
	self:LevelDebug(3, "SpellCache:GetRankNumber2", spellRankText, self.RankPattern, spellRankNumber)
	return spellRankNumber
end

function SpellCache:GetRankText(spellRankNumber)
	self:argCheck(spellRankNumber, 2, "number")
	local spellRankText = format(self.RankPattern, spellRankNumber)
	self:LevelDebug(2, "GetRankText", spellRankNumber, self.RankPattern, spellRankText)
	return spellRankText
end

function SpellCache:GetRanklessSpellName(spellName)
	self:argCheck(spellName, 2, "string")

	--self:LevelDebug(2, "GetRanklessSpellName1", spellName)
	--For those that like to cast spells with SpellName() lets take out the () 
	spellName = string.gsub(spellName, "%(%)$", "")
	--self:LevelDebug(2, "GetRanklessSpellName2", spellName)

	if (self.FullPattern) then
		local sName, sRank = deformat:Deformat(spellName, self.FullPattern)
		self:LevelDebug(2, "GetRanklessSpellName", spellName, sName, sRank)
		if (sName and sRank) then
			return sName, sRank
		end
	end
	return spellName
end

function SpellCache:GetSpellNameText(spellName, spellRankNumber)
	self:argCheck(spellName, 2, "string")
	self:argCheck(spellRankNumber, 3, "number", "nil")

	local fullSpellName
	if (not spellRankNumber) then
		fullSpellName = spellName
	else
		fullSpellName = format(self.FullPattern, spellName, spellRankNumber)
	end
	self:LevelDebug(2, "GetSpellNameText", spellName, spellRankNumber, self.FullPattern, fullSpellName)
	return fullSpellName
end

-- 24, ignore all
-- Arcane Intellect, nil
-- Arcane Intellect, 3
-- Arcane Intellect, Rank 3
-- Arcane Intellect(Rank 2), nil
-- Arcane Intellect(Rank 2), 3
-- Arcane Intellect(Rank 2), Rank 3
function SpellCache:GetSpellData(spellNameOrId, spellRankNumberOrText)
	self:argCheck(spellNameOrId, 2, "string", "number")
	self:argCheck(spellRank, 3, "string", "number", "nil")
	self:LevelDebug(2, ">GetSpellData", spellNameOrId, spellRankNumberOrText)

	local sId, sName, sRank
	if (type(spellNameOrId) == "number") then
		sId = tonumber(spellNameOrId)
		sName = self.hash[sId]
		--Doesnt exist
		if (not sName) then
			return
		end
		--sId > IdStart 
		if (type(sName) == "number") then
			sName = self.hash[sName]
		end
		self:LevelDebug(2, "GetSpellData1", sId, sName)
	else
		sName, sRank = self:GetRanklessSpellName(spellNameOrId)

		if (not sRank) then
			sRank = spellRankNumberOrText;
			if (type(sRank) == "string") then
				self:LevelDebug(2, "GetSpellData2", sRank)
				sRank = GetSpellRankNumber(self, sRank);
				self:LevelDebug(2, "GetSpellData3", sRank)
			end
		end
	end
	
	--if we cant find any data
	local spellData = self.data[sName]
	if (not spellData) then
		self:LevelDebug(1, "<GetSpellData", spellNameOrId)
		return;
	end;
		
	if (sId) then
		sRank = spellData.Rank and sId - spellData.IdStart + 1 or nil
	else
		if (not spellData.Rank) then
			sRank = nil
		elseif (not sRank) then
			sRank = spellData.Rank
		elseif ((sRank) and (sRank > spellData.Rank)) then
			sRank = spellData.Rank
		elseif ((sRank) and (sRank < 1)) then
			sRank = 1
		end
		sId = sRank and spellData.IdStart + sRank -1 or spellData.IdStart
	end

	local sRankText, sFullText
	if (sRank) then
		sRankText = self:GetRankText(sRank)
	end
	sFullText = self:GetSpellNameText(sName, sRank)
		
	self:LevelDebug(1, "<GetSpellData", sName, sRankText, sId, sFullText, sRank, spellData.IdStart, spellData.IdStop, spellData.Rank)
	return sName, sRankText, sId, sFullText, sRank, spellData.IdStart, spellData.IdStop, spellData.Rank
end

--
-- Final Registration of the library.
--
AceLibrary:Register(SpellCache, MAJOR_VERSION, MINOR_VERSION, activate)
SpellCache = AceLibrary(MAJOR_VERSION)
