--- Core Addon Framework
local AddOnName, nsVars = ...
local ss = nsVars.SS

local GetShapeshiftFormID = GetShapeshiftFormID
local UnitClass, GetSpecialization = UnitClass, GetSpecialization
local select, strlower = select, strlower
local GetTime, GetSpellCooldown = GetTime, GetSpellCooldown
local InCombatLockdown = InCombatLockdown
local _GetSpecializationInfo = GetSpecializationInfo
local UnitName, HasPetSpells = UnitName, HasPetSpells

--- Returns the current, non localized, specialization name of the player.
-- @param id The current specialization identifier.
-- @return The specialization name.
-- @see GetSpecialization().
--
local function GetSpecializationInfo(id)
  id = id or GetSpecialization()
  local gid = id and _GetSpecializationInfo(id) or ""
  
  local specNames = {
    [62]  = { "Mage",         "Arcane", },
    [63]  = { "Mage",         "Fire", },
    [64]  = { "Mage",         "Frost", },
    [65]  = { "Paladin",      "Holy", },
    [66]  = { "Paladin",      "Protection", },
    [70]  = { "Paladin",      "Retribution", },
    [71]  = { "Warrior",      "Arms", },
    [72]  = { "Warrior",      "Fury", },
    [73]  = { "Warrior",      "Protection", },
    [102] = { "Druid",        "Balance", },
    [103] = { "Druid",        "Feral", },
    [104] = { "Druid",        "Guardian", },
    [105] = { "Druid",        "Restoration", },
    [250] = { "Death Knight", "Blood", },
    [251] = { "Death Knight", "Frost", },
    [252] = { "Death Knight", "Unholy", },
    [253] = { "Hunter",       "Beast Mastery", },
    [254] = { "Hunter",       "Marksmanship", },
    [255] = { "Hunter",       "Survival", },
    [256] = { "Priest",       "Discipline", },
    [257] = { "Priest",       "Holy", },
    [258] = { "Priest",       "Shadow", },
    [259] = { "Rogue",        "Assassination", },
    [260] = { "Rogue",        "Outlaw", },
    [261] = { "Rogue",        "Subtlety", },
    [262] = { "Shaman",       "Elemental", },
    [263] = { "Shaman",       "Enhancement", },
    [264] = { "Shaman",       "Restoration", },
    [265] = { "Warlock",      "Affliction", },
    [266] = { "Warlock",      "Demonology", },
    [267] = { "Warlock",      "Destruction", },
    [268] = { "Monk",         "Brewmaster", },
    [269] = { "Monk",         "Windwalker", },
    [270] = { "Monk",         "Mistweaver", },
    [577] = { "Demon Hunter", "Havoc", },
    [581] = { "Demon Hunter", "Vengeance", },
  }
  
  return specNames[gid] and specNames[gid][2] or nil
end

--- Updates global cooldown values.
--
local function updateGCD()
  -- Get the GCD duration (id = 61304)
  local _, tempGCD = GetSpellCooldown(61304)
  
  if tempGCD > 0 then
    ss.gcdWatch.max = tempGCD
    
    if not ss.gcdWatch.start then
      ss.gcdWatch.start = GetTime()
    end
    
    ss.gcdWatch.cur = 0
  end
end

ss.gradientScale = 0.05
ss.playerBuffs = {}
ss.curClass = strlower(select(2, UnitClass("player")))

ss.gcdWatch = {
  max = 1,
  cur = 1,
}

--- Event handler for "ADDON_LOADED".
--
function ss:ADDON_LOADED()
  self:AddEvent("PET_BATTLE_OPENING_START")
  self:AddEvent("PLAYER_ENTERING_BATTLEGROUND")
  self:AddEvent("PLAYER_ENTERING_WORLD")
  self:AddEvent("PLAYER_LOGIN")
  self:AddEvent("PLAYER_REGEN_DISABLED")
  self:AddEvent("PLAYER_SPECIALIZATION_CHANGED")
  self:AddEvent("UNIT_ENTERED_VEHICLE")
  self:AddEvent("UNIT_EXITED_VEHICLE")
  self:AddEvent("UNIT_MAXPOWER")
  self:AddEvent("UNIT_PET")
  self:AddEvent("UNIT_SPELLCAST_SUCCEEDED")
  self:AddEvent("UNIT_AURA")
  self:AddEvent("VARIABLES_LOADED")
  self:RemoveEvent("ADDON_LOADED")
  
  -- Load profile data
  Spheres        = Spheres        or {}
  Seamore        = Seamore        or {}
  SeamoreSpheres = SeamoreSpheres or {}
  
  self.global  = self.Utils:SetMetatables(Seamore, self.dVars.global)
  self.profile = self.Utils:SetMetatables(Spheres, self.dVars.spheres)
  
  -- Depricated
  self.global["SSProfile_Default"] = self.global["SSProfile_Default"] or {}
  
  local player = UnitName("player") .. "-" .. GetRealmName()
  
  if not self.global["SSProfile_" .. player] and not self.global.characters[player] then
    self.global["SSProfile_" .. player] = {}
    self.global.characters[player] = true
    self.profile.profileName = "SSProfile_" .. player
  end
  
  self.profile.profileName = self.profile.profileName or "SSProfile_" .. player
  self.MicroSphereData.load()
  
  if not self.global[self.profile.profileName] then
    self.profile.modeInfo = nil
  end
  
  self.global[self.profile.profileName] = self.global[self.profile.profileName] or {}
  self.db      = self.Utils:SetMetatables(self.global[self.profile.profileName], self.pVars.spheres["SSProfile_Default"])
  self.private = self.Utils:SetMetatables(SeamoreSpheres, self.pVars.spheres)
  
  self.Updater.update()
end

--- Event handler for "PET_BATTLE_CLOSE".
--
function ss:PET_BATTLE_CLOSE()
  self:RemoveEvent("PET_BATTLE_CLOSE")
  self:AddEvent  ("PET_BATTLE_OPENING_START")
  self.Layout:OnPetBattleEnd()
  self.isInPetBattle = nil
end

--- Event handler for "PET_BATTLE_OPENING_START".
--
function ss:PET_BATTLE_OPENING_START()
  self:RemoveEvent("PET_BATTLE_OPENING_START")
  self:AddEvent  ("PET_BATTLE_CLOSE")
  self.isInPetBattle = true
  self.Layout:OnPetBattleStart()
end

local function refresh()
  -- Delay the refresh to make sure talents have been updated.
  ss.PendingTask.delay(1, function()
    -- Ensure the proper layout is active.
    ss:PLAYER_SPECIALIZATION_CHANGED("player")

    if (ss.curClass == "hunter") or (ss.curClass == "warlock") or (ss.curClass == "mage") or (ss.curClass == "deathknight") then
      -- Check for pet information.
      ss:UNIT_PET()
    end
  end)
end

--- Event handler for "PLAYER_ENTERING_BATTLEGROUND".
--
function ss:PLAYER_ENTERING_BATTLEGROUND()
  if not self.shouldRefresh then
    self.shouldRefresh = true
    refresh()
  end
end

--- Event handler for "PLAYER_ENTERING_WORLD".
--
function ss:PLAYER_ENTERING_WORLD()
  self.Utils:ClearTemp()
  
  if self.shouldRefresh then
    self.shouldRefresh = nil
    refresh()
  end
end

--- Event handler for "PLAYER_LOGIN".
--
function ss:PLAYER_LOGIN()
  self.curSpec     = GetSpecialization()
  self.curSpecName = self.curSpec and GetSpecializationInfo(self.curSpec) or "None"
  self.curSpec     = strlower(self.curSpec) -- convert to string value.
  self.ClassResources.initMetaModes()
  
  self.profile.modeInfo.mainResource.modeSpec       = self.db.mainResource.modes[self.curSpec]       and self.curSpec or "defaultSpec"
  self.profile.modeInfo.additionalResource.modeSpec = self.db.additionalResource.modes[self.curSpec] and self.curSpec or "defaultSpec"
  self.profile.modeInfo.health.modeSpec             = self.db.health.modes[self.curSpec]             and self.curSpec or "defaultSpec"
  self.profile.modeInfo.health.mode                 = "default"
  self.profile.modeInfo.mainResource.mode           = "default"
  self.profile.modeInfo.additionalResource.mode     = "default"
  self.profile.modeInfo.vehicle.mode                = "default"
  self.profile.modeInfo.vehicleResource.mode        = "default"
  self.Layout:Initialize()
  self.playerLoggedIn = true
  self.shouldRefresh = true
  
  -- Complete any pending tasks
  self.PendingTask.completeAll(true)
  self:RemoveEvent("PLAYER_LOGIN")
  
  if self.curClass == "druid" then
    self:AddEvent("UPDATE_SHAPESHIFT_FORM")
  end
end

--- Event handler for "PLAYER_REGEN_ENABLED".
--
function ss:PLAYER_REGEN_ENABLED()
  if self.Layout and not InCombatLockdown() then
    self.Layout:EndCombat()
  end
  
  -- Complete any pending tasks
  self.PendingTask.completeAll(true)
  self:RemoveEvent("PLAYER_REGEN_ENABLED")
  self:AddEvent  ("PLAYER_REGEN_DISABLED")
end

--- Event handler for "PLAYER_REGEN_DISABLED".
--
function ss:PLAYER_REGEN_DISABLED()
  if ss.global.general.hideOnCombat then
    self.Config:Close()
  end
  
  if self.Layout then
    self.Layout:StartCombat()
  end
  
  self:RemoveEvent("PLAYER_REGEN_DISABLED")
  self:AddEvent  ("PLAYER_REGEN_ENABLED")
end

--- Event handler for "PLAYER_SPECIALIZATION_CHANGED".
--
function ss:PLAYER_SPECIALIZATION_CHANGED(unit)
  if unit == "player" then
    local spec = GetSpecialization()
    local specName = spec and GetSpecializationInfo(spec) or "None"
  
    if not spec or specName == "None" then
      return
    end
    
    self.curSpec = spec
    self.curSpecName = specName
    self.curSpec = strlower(self.curSpec) -- convert to string value.
  
    self.Config:Close()
    self.Config.shouldReload = true
    self.Layout:Refresh()
  end
end

--- Event handler for "UNIT_ENTERED_VEHICLE".
--
function ss:UNIT_ENTERED_VEHICLE(unit, ...)
  if unit == "player" then
    local args = {...}
    
    if args[1] == true then
      self.Layout:OnPlayerEnteredVehicle()
      self:RemoveEvent("UNIT_ENTERED_VEHICLE")
      self:AddEvent("UNIT_EXITED_VEHICLE")
    end
  end
end

--- Event handler for "UNIT_EXITED_VEHICLE".
--
function ss:UNIT_EXITED_VEHICLE(unit)
  if unit == "player" then
    self:RemoveEvent("UNIT_EXITED_VEHICLE")
    self:AddEvent("UNIT_ENTERED_VEHICLE")
    self.Layout:OnPlayerExitedVehicle()
  end
end

--- Event handler for "UNIT_MAXPOWER".
--
function ss:UNIT_MAXPOWER(unit)
  if unit == "player" then
    self.Layout:OnUnitMaxPowerChanged()
  end
end

--- Event handler for "UNIT_PET".
-- @param name The pet's name.
-- @param hasPetSpells The number of spells available for the pet.
--
function ss:UNIT_PET()
  local name, hasPetSpells = UnitName("pet"), (HasPetSpells() or 0)
  self.petAvail = ((hasPetSpells > 10) and name) and true or nil
  
  self.Layout:TrackPet(self.global.track["pet"] and self.petAvail)
end

--- Event handler for "UNIT_SPELLCAST_SUCCEEDED".
--
function ss:UNIT_SPELLCAST_SUCCEEDED(unit)
  if unit == "player" then
    updateGCD()
  end
end

--- Event handler for "UNIT_AURA".
--
function ss:UNIT_AURA(unit)
  if unit == "player" then
    -- Refresh stored player auras.
    self.playerBuffs = self.APIUtils.GetPlayerBuffs()
  end
end

--- Event handler for "UPDATE_SHAPESHIFT_FORM".
--
function ss:UPDATE_SHAPESHIFT_FORM()
  if self.curSpec and self.curClass == "druid" then
    self.Layout:Shapeshift(self.APIUtils.shapeshiftToString(GetShapeshiftFormID() or 0))
  end
end

--- Event handler for "VARIABLES_LOADED".
--
function ss:VARIABLES_LOADED()
  if self.global.general.MapMarker.show then
    self.MapMarker:Initialize()
  end
  
  self:RemoveEvent("VARIABLES_LOADED")
end
