-- Copyright (C) 2010 Tyson Brown
-- 
-- This file is part QuestHelper2, an AddOn for World of Warcraft.
-- 
-- QuestHelper2 is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- 
-- QuestHelper2 is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with QuestHelper2. If not, see <http://www.gnu.org/licenses/>.

if LibDebug then LibDebug() end
local shared = select(2, ...)

-- This file creates objectives out of any watched achievements.

-- TODO: Assign locations and what not to the created objectives;
-- as it stands, these do little more than look pretty.

-- Maps an achievement_id to a list of objectives.
local achievement_objective_lists = {}

local criteria_types_to_objective_types = {
  [62] = "MONEY", -- Money from quest rewards.
  [67] = "MONEY"  -- Money from loot.
}

local function achievementCallback(achievement_id)
  return function ()
    if not _G.AchievementFrame then
      AchievementFrame_LoadUI()
    end
    
    if WorldMapFrame:IsVisible() then
      ToggleFrame(WorldMapFrame)
    end
    
    if IsModifiedClick("CHATLINK") and ChatEdit_GetActiveWindow() then
      local link = GetAchievementLink(achievement_id)
      if link then ChatEdit_InsertLink(link) end
    else
      AchievementFrame:Show()
      AchievementFrame_SelectAchievement(achievement_id)
    end
  end
end

-- TODO: This is a temporary hack for the elders achievements; it will be removed later.
local overrides = {}

local function addElderAchievement(id, locations)
  local list = achievement_objective_lists[id]
  
  if list then
    for i = 1,#locations do
      local cur, req = shared.getAchievementCriteriaProgress(id, i)
      list[i].complete = cur == req
    end
    
    return
  end
  
  list = {}
  
  local basename = "achievement-"..id
  local icon = select(10, GetAchievementInfo(id))
  local callback = achievementCallback(id)
  list[0] = shared.createObjective(basename)
  list[0].text = select(2, GetAchievementInfo(id))
  list[0].root = true
  list[0].icon = icon
  list[0]:setCallback(callback)
  
  local criteria = GetAchievementNumCriteria(id)
  
  assert(criteria == #locations)
  
  for i = 1,criteria do
    local cur, req, criteria_type = shared.getAchievementCriteriaProgress(id, i)
    list[i] = shared.createObjective(basename.."/"..i)
    list[i]:setLocation(unpack(locations[i]))
    list[i].text = GetAchievementCriteriaInfo(id, i)
    list[i].icon = icon
    list[i]:setCallback(callback)
    list[i].complete = cur == req
    list[i].enabled = true
  end
  
  list[0]:addDependencies(unpack(list))
  list[0].enabled = true
  
  achievement_objective_lists[id] = list
end

overrides[915] = function() -- Elders of the Alliance
  addElderAchievement(915, {
    {381, 0, .39, .32}, -- Elder Bladeswift in Darnassus
    {341, 0, .29, .16}, -- Elder Bronzebeard in Ironforge
    {30, 0, .34, .5}  -- Elder Hammershout in Stormwind
  })
end

overrides[914] = function() -- Elders of the Horde
  addElderAchievement(914, {
    {321, 1, .52, .60}, -- Elder Darkhorn in Orgrimmar
    {362, 0, .72, .23}, -- Elder Wheathoof in Thunder Bluff
    {382, 0, .666, .381, .661, .33}  -- Elder Darkcore in Undercity
  })
end

overrides[911] = function() -- Elders of Kalimdor
  addElderAchievement(911, {
    {4, 0, .53, .44}, -- Elder Runetotem in Razor Hill
    {181, 0, .65, .79}, -- Elder Skygleam in Azshara
    {11, 0, .485, .592}, -- Elder Moonwarden in The Crossroads
    {607, 0, .415, .475}, -- Elder High Mountain in Camp Taurajo
    {11, 0, .68, .67}, -- Elder Windtotem in Ratchet
    {41, 0, .57, .53}, -- Elder Bladeleaf in Dolanaar
    {42, 0, .495, .19}, -- Elder Starweave in Auberdine
    {9, 0, .48, .53}, -- Elder Bloodhoof in Bloodhoof Village
    {43, 0, .36, .49}, -- Elder Riversong in Astranaar
    {121, 0, .76, .37}, -- Elder Grimtotem in Feralas
    {121, 0, .62, .31}, -- Elder Mistwalker in Dire Maul
    {182, 0, .38, .52}, -- Elder Nightwind in Felwood
    {61, 0, .46, .51}, -- Elder Skyseer in Freewind Post
    {61, 0, .77, .765}, -- Elder Morningdew in Mirage Raceway
    {161, 0, .37, .79}, -- Elder Ragetotem in Tanaris
    {161, 0, .51, .28}, -- Elder Dreamseer in Gadgetzan
    {201, 0, .5, .76}, -- Elder Thunderhorn in Un'Goro
    {281, 0, .53, .567}, -- Elder Brightspear in Winterspring
    {281, 0, .6, .5}, -- Elder Stonespire in Everlook
    {261, 0, .307, .133}, -- Elder Primestone in Silithus
    {261, 0, .53, .35} -- Elder Bladesing in Cenarion Hold
  })
end

overrides[912] = function() -- Elders of Eastern Kingdoms
  addElderAchievement(912, {
    {27, 0, .53, .49}, -- Elder Goldwell in Kharanos
    {19, 0, .54, .49}, -- Elder Bellowrage in Blasted Lands
    {30, 0, .4, .64}, -- Elder Stormbrow in Goldshire
    {22, 0, .635, .361, .645, .485}, -- Elder Meadowrun in Western Plaguelands
    {37, 0, .71, .34}, -- Elder Starglade in Zul'Grub
    {673, 0, .40, .73}, -- Elder Winterhoof in Booty Bay
    {35, 0, .44, .46}, -- Elder Silvervein in Thelsamar
    {39, 0, .57, .47}, -- Elder Skychaser in Sentinel Hill
    {29, 0, .70, .45}, -- Elder Rumblerock in Burning Steppes
    {29, 0, .53, .24}, -- Elder Dawnstriker in Flame Crest
    {26, 0, .50, .48}, -- Elder Highpeak in Hinterlands
    {28, 0, .21, .79}, -- Elder Ironband in Searing Gorge
    {20, 0, .61, .53}, -- Elder Graveborn in Brill
    {21, 0, .45, .41}, -- Elder Obsidian in The Sepulcher
    {23, 0, .35, .68}, -- Elder Windrun in Eastern Plaguelands
    {23, 0, .75, .54}, -- Elder Snowcrown in Light's Hope Chapel
    {22, 0, .69, .73} -- Elder Moonstrike in Scholomace
  })
end

overrides[1396] = function() -- Elders of Northrend
  addElderAchievement(1396, {
    {486, 0, .59, .66}, -- Elder Sardis in Valiance Keep
    {490, 0, .61, .28}, -- Elder Beldak in Westfall Brigade
    {488, 0, .30, .56}, -- Elder Morthie in Star's Rest
    {495, 0, .29, .74}, -- Elder Fargal in Frosthold
    {486, 0, .57, .44}, -- Elder Arp in D.E.H.T.A.
    {486, 0, .34, .34}, -- Elder Northal in Transitus Shield
    {493, 0, .50, .64}, -- Elder Sandrene in Lakeside Landing
    {493, 0, .64, .49}, -- Elder Wanikaya in Rainspeaker Rapids
    {490, 0, .81, .37}, -- Elder Lunaro in Ruins of Tethys
    {501, 0, .49, .14}, -- Elder Bluewolf in Wintergrasp
    {496, 0, .59, .56}, -- Elder Tauros in Zom'Torga
    {488, 0, .49, .78}, -- Elder Thoim in Moa'ki Harbor
    {495, 0, .41, .85}, -- Elder Greymane in K3
    {495, 0, .313, .376, .307, .371}, -- Elder Stonebeard in Bouldercrag's Refuge
    {486, 0, .43, .50}, -- Elder Pamuya in Warsong Hold
    {490, 0, .64, .47}, -- Elder Whurain in Camp Oneqwah
    {488, 0, .35, .48}, -- Elder Skywarden in Agmar's Hammer
    {495, 0, .65, .51} -- Elder Muraco in Camp Tunka'lo
  })
end

overrides[910] = function() -- Elders of the Dungeons
  addElderAchievement(910, {
    {686, 0, .35, .39}, -- Elder Wildmane in Zul'Farrak
    {687, 1, .63, .34}, -- Elder Starsong in the Sunken Temple
    {750, 2, .51, .94}, -- Elder Splitrock in Maraudon
    {721, 3, .62, .4}, -- Elder Stonefort in Blackrock Spire
    {704, 1, .51, .63}, -- Elder Morndeep in Blackrock Depths
    {765, 2, .5, .69}, -- Elder Farwhisper in Stratholm
    {523, 1, .47, .7}, -- Elder Jarten in Utgarde Keep
    {520, 1, .55, .65}, -- Elder Igasho in The Nexus
    {533, 1, .22, .44}, -- Elder Nurgen in Azjol-Nerub
    {534, 0, .57, .85}, -- Elder Kilias in Drak'Tharon Keep
    {530, 1, .46, .62}, -- Elder Ohanzee in Gundrak
    {526, 1, .29, .64}, -- Elder Yurauk in the Halls of Stone
    {524, 1, .49, .23}  -- Elder Chohan'gada un Utgarde Pinnacle
  })
end

  --[=[
  local id = 910
  
  local list = {}
  list[0] = shared.createObjective("achievement-"..id)
  list[1] = list[0]
  achievement_objective_lists[id] = list
  
  for i = 1,GetAchievementNumCriteria(id) do
    print(id.."/"..i.." = "..GetAchievementCriteriaInfo(id, i))
  end
  --]=]

local achievement_db

local visited_achievements = {}

local function visitAchievements(achievement_id, ...)
  if achievement_id then
    -- TODO: Temporary hack.
    if overrides[achievement_id] and not visited_achievements[achievement_id] then
      visited_achievements[achievement_id] = true
      overrides[achievement_id]()
    end
    
    if not visited_achievements[achievement_id] then
      visited_achievements[achievement_id] = true
      
      local list = achievement_objective_lists[achievement_id]
      if list then
        -- Update the criteria progress.
        for i, obj in ipairs(list) do
          local cur, req, criteria_type = shared.getAchievementCriteriaProgress(achievement_id, i)
          obj:setProgress(cur, req, criteria_types_to_objective_types[criteria_type])
          
          if criteria_type == 8 and cur ~= req then
            -- This objective depends on another achievement, visit it as well to prevent it from being removed.
            visitAchievements((select(8, GetAchievementCriteriaInfo(achievement_id, i))))
          end
        end
      else
        -- This seems to be newly tracked; let's create the objectives for it.
        local callback = achievementCallback(achievement_id)
        
        list = {}
        achievement_objective_lists[achievement_id] = list
        
        local basename = "achievement-"..achievement_id
        local icon = select(10, GetAchievementInfo(achievement_id))
        
        -- Element 0 is the achievement itself; elements 1 through whatever are its criteria.
        list[0] = shared.createObjective(basename)
        list[0].text = select(2, GetAchievementInfo(achievement_id))
        list[0].root = true
        list[0].icon = icon
        list[0]:setProgress(0,1)
        list[0].obtainable = true
        list[0]:setCallback(callback)
        
        for i = 1,GetAchievementNumCriteria(achievement_id) do
          list[i] = shared.createObjective(("%s/%d"):format(basename, i))
          list[i]:setCallback(callback)
          list[i].text = GetAchievementCriteriaInfo(achievement_id, i)
          list[i].icon = icon
          
          if list[i].text == "" then
            -- Some criteria don't seem to have a description; in this case, we'll use the description of the achievement itself.
            list[i].text = select(8, GetAchievementInfo(achievement_id))
          end
          
          
          local cur, req, criteria_type = shared.getAchievementCriteriaProgress(achievement_id, i)
          list[i]:setProgress(cur, req, criteria_types_to_objective_types[criteria_type])
          list[i].obtainable = true
          list[i].enabled = true
          
          if criteria_type == 8 and cur ~= req then
            -- This criteria depends on another achievement.
            local dep_achievement_id = select(8, GetAchievementCriteriaInfo(achievement_id, i))
            visitAchievements(dep_achievement_id)
            local dep_list = assert(achievement_objective_lists[dep_achievement_id])
            
            if dep_list then
              -- To make things more tidy, we'll disable the actual achivement we depend on, and
              -- instead depend on their criteria.
              dep_list[0].enabled = false
              
              if dep_list[1] then
                -- Achievement had criteria, lets hyjack them four out own purposes.
                list[i]:addDependencies(unpack(dep_list))
              else
                -- Achievement didn't have any criteria, depend on the fake critria we created for them.
                list[i]:addDependencies(dep_list[-1])
              end
            end
          end
          
          if criteria_type == 27 and cur ~= req then
            -- This criteria depends on a quest.
            local quest_id = select(8, GetAchievementCriteriaInfo(achievement_id, i))
            
            -- TODO: Add quest dependency.
          end
        end
        
        if list[1] then
          list[0]:addDependencies(unpack(list))
        else
          -- This achievement doesn't have any criteria; create a fake one just to look pretty.
          list[-1] = shared.createObjective(basename.."/desc")
          list[-1]:setCallback(callback)
          list[-1].text = select(8, GetAchievementInfo(achievement_id))
          list[-1].icon = icon
          list[-1]:setProgress(0, 1)
          list[-1].obtainable = true
          list[-1].enabled = true
          list[0]:addDependencies(list[-1])
        end
        
        list[0].enabled = true
        
        local achievement_data = shared.createAchievementData()
        
        if not achievement_db then
          if shared.settings.database_use then
            local db_name = "QuestHelper2_AchievementDB"
            
            local loaded, error_reason = LoadAddOn(db_name)
            
            if loaded then
              achievement_db = assert(_G[db_name])
            else
              shared.onDelay(5, _G.print, "QuestHelper2 failed to load "..db_name..": "..tostring(error_reason))
              achievement_db = {}
            end
          else
            achievement_db = {}
          end
        end
        
        local function achievementDataChanged()
          list[0]:setLocationList(achievement_data.finish)
          
          if list[1] then
            for i, criteria_data in pairs(achievement_data.criteria) do
              local obj = list[i]
              if obj then
                obj:setLocationList(criteria_data.location)
              end
            end
          else
            list[-1]:setLocationList(achievement_data.finish)
          end
        end
        
        shared.getAchievementData(achievement_id, function (collected_data)
          achievement_data:merge(collected_data)
          achievement_data:reduceToEssentials()
          achievementDataChanged()
        end)
        
        if achievement_db[achievement_id] then
          shared.onUnserialize(achievement_db[achievement_id], function (static_data)
            achievement_data:merge(static_data)
            achievement_data:reduceToEssentials()
            achievementDataChanged()
          end)
        end
      end
    end
    
    visitAchievements(...)
  end
end

local function recheckAchievementWatches()
  --[===[@debug@
  print("Updating achevement watches...")
  --@end-debug@]===]
  
  visitAchievements(GetTrackedAchievements())
  
  -- Check if we have any objectives for achievements that aren't tracked.
  for achievement_id, list in pairs(achievement_objective_lists) do
    if not visited_achievements[achievement_id] then
      -- Check if this achievement hyjacked the criteria of another, and re-enable that criteria's real parent.
      for i, obj in pairs(list) do
        local cur, req, criteria_type = shared.getAchievementCriteriaProgress(achievement_id, i)
        if criteria_type == 8 then
          local dep_list = achievement_objective_lists[(select(8, GetAchievementCriteriaInfo(achievement_id, i)))]
          if dep_list then
            -- There's probably a bug here if multiple achievements are depending on the same watched achievement.
            -- All well, I don't care. It's your fault for doing such a silly thing.
            dep_list[0].enabled = true
          end
        end
      end
      
      -- Disable their objectives.
      for _, obj in pairs(list) do obj.enabled = false end
      
      -- And remove the list.
      achievement_objective_lists[achievement_id] = nil
    end
  end
  
  -- Keep the set of visited achievements empty for the next time this function is called.
  table.wipe(visited_achievements)
end

local function queueRecheckAchievementWatches()
  shared.callOnce(recheckAchievementWatches, .25)
end

shared.onLoad(function ()
  shared.onEvent("TRACKED_ACHIEVEMENT_UPDATE", queueRecheckAchievementWatches)
  shared.onAchievementProgress(queueRecheckAchievementWatches)
  shared.onAchievementComplete(queueRecheckAchievementWatches)
  
  queueRecheckAchievementWatches()
end)
