--[[	*** DataStore_Achievements ***
Written by : Thaoky, EU-Marécages de Zangar
June 21st, 2009
--]]
if not DataStore then return end

local addonName = "DataStore_Achievements"

_G[addonName] = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0")

local addon = _G[addonName]

local THIS_ACCOUNT = "Default"

local AddonDB_Defaults = {
	global = {
		Characters = {
			['*'] = {					-- ["Account.Realm.Name"] 
				lastUpdate = nil,
				numAchievements = 0,
				numCompletedAchievements = 0,
				numAchievementPoints = 0,
				Achievements = {},
			}
		}
	}
}

-- *** Scanning functions ***
local CriteriaCache = {}

local function ScanSingleAchievement(id, isCompleted)
	addon.ThisCharacter.lastUpdate = time()
	local Achievements = addon.ThisCharacter.Achievements
	
	-- a given entry can have 3 types of values:
	--	nil	= the achievement has not been started, not even a single criteria
	--	true = the achievement has been completed, so implicitly, all criterias have been completed too
	--	string	= the achievement is partially complete, so a string of values describes the state of completion
	
	if isCompleted then
		Achievements[id] = true		-- true when completed, all criterias are completed thus
		return
	end

	wipe(CriteriaCache)
	for j = 1, GetAchievementNumCriteria(id) do
		-- ** calling GetAchievementCriteriaInfo in this loop is what costs the most in terms of cpu time **
		local _, _, critCompleted, quantity, reqQuantity = GetAchievementCriteriaInfo(id, j);
	   local currentCrit
	   
	   if critCompleted then 
	      table.insert(CriteriaCache, tostring(j))
	   else                  
	      if reqQuantity > 1 then
	         table.insert(CriteriaCache, j .. ":" .. quantity)
	      end
	   end
	end
	
	if #CriteriaCache > 0 then		-- if at least one criteria completed, save the entry, do nothing otherwise
		Achievements[id] = table.concat(CriteriaCache, ",")
	end
end

local function ScanAllAchievements()
	wipe(addon.ThisCharacter.Achievements)
	
	local cats = GetCategoryList()
	local achievementID, achCompleted
	local prevID
	
	for _, categoryID in ipairs(cats) do
		for i = 1, GetCategoryNumAchievements(categoryID) do
			achievementID, _, _, achCompleted = GetAchievementInfo(categoryID, i);
			ScanSingleAchievement(achievementID, achCompleted)
			
			-- track previous steps of a progressive achievements
			prevID = GetPreviousAchievement(achievementID)
			
			while type(prevID) ~= "nil" do
				achievementID, _, _, achCompleted = GetAchievementInfo(prevID);
				ScanSingleAchievement(achievementID, achCompleted)
				prevID = GetPreviousAchievement(achievementID)
			end
		end
	end	
end

local function ScanProgress()
	local char = addon.ThisCharacter
	local total, completed = GetNumCompletedAchievements()
	
	char.numAchievements = total
	char.numCompletedAchievements = completed
	char.numAchievementPoints = GetTotalAchievementPoints()
end


-- ** Mixins **
local function _GetAchievementInfo(character, achievementID)
	local achievement = character.Achievements[achievementID]
	
	if achievement then						-- if there's a value ..
		local isStarted = true				-- .. the achievement has been worked on
		local isComplete
		if achievement == true then		-- ..and might even have been completed
			isComplete = true
		end
		return isStarted, isComplete
	end
	-- implicit return of nil, nil otherwise
end
	
local function _GetCriteriaInfo(character, achievementID, criteriaIndex)
	local achievement = character.Achievements[achievementID]
	
	if type(achievement) == "string" then	-- only return criteria info if the DB value is a string

		for v in achievement:gmatch("([^,]+)") do
			local index, qty = strsplit(":", v)
			
			index = tonumber(index)
			qty = tonumber(qty)
			
			if criteriaIndex == index then
				local isStarted = true				-- .. the criteria has been worked on
				local isComplete
				if not qty then						-- ..and might even have been completed (no qty means complete)
					isComplete = true
				end
				
				-- this will return :
					-- true, true, nil		if the criteria is 100% completed
					-- true, nil, value		if the criteria is partially complete
				return isStarted, isComplete, qty
			end
		end
	end
	-- implicit return of nil, nil , nil 	(not started, not complete)
end
	
local function _GetNumAchievements(character)
	return character.numAchievements
end
	
local function _GetNumCompletedAchievements(character)
	return character.numCompletedAchievements
end
	
local function _GetNumAchievementPoints(character)
	return character.numAchievementPoints
end


local PublicMethods = {
	GetAchievementInfo = _GetAchievementInfo,
	GetCriteriaInfo = _GetCriteriaInfo,
	GetNumAchievements = _GetNumAchievements,
	GetNumCompletedAchievements = _GetNumCompletedAchievements,
	GetNumAchievementPoints = _GetNumAchievementPoints,
}

function addon:OnInitialize()
	addon.db = LibStub("AceDB-3.0"):New(addonName .. "DB", AddonDB_Defaults)
	
	DataStore:RegisterModule(addonName, addon, PublicMethods)
	DataStore:SetCharacterBasedMethod("GetAchievementInfo")
	DataStore:SetCharacterBasedMethod("GetCriteriaInfo")
	DataStore:SetCharacterBasedMethod("GetNumAchievements")
	DataStore:SetCharacterBasedMethod("GetNumCompletedAchievements")
	DataStore:SetCharacterBasedMethod("GetNumAchievementPoints")
end

function addon:OnEnable()
	
	addon:RegisterEvent("PLAYER_ALIVE")
	addon:RegisterEvent("ACHIEVEMENT_EARNED")
end

function addon:OnDisable()
	addon:UnregisterEvent("PLAYER_ALIVE")
	addon:UnregisterEvent("ACHIEVEMENT_EARNED")
end


-- *** EVENT HANDLERS ***
function addon:PLAYER_ALIVE()
	ScanAllAchievements()
	ScanProgress()
end

function addon:ACHIEVEMENT_EARNED(self, id)
	if id then
		ScanSingleAchievement(id, true)
		ScanProgress()
	end
end
