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

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

local classicSpec = 1

local GetSpecializationInfo, GetSpecialization

if ss.retailClient then
  GetSpecialization = _GetSpecialization

  --- Returns the current, non localized, specialization name of the player.
  -- @param id The current specialization identifier.
  -- @return The specialization name.
  -- @see GetSpecialization().
  --
  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
else
  function GetSpecialization()
    return classicSpec
  end
  
  function GetSpecializationInfo()
    return "Classic"
  end
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")))

if ss.curClass == "shaman" then
  classicSpec = 3
elseif ss.curClass == "druid" then
  classicSpec = 4
end

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

--- Event handler for "ADDON_LOADED".
--
function ss:ADDON_LOADED()
  self:RemoveEvent("ADDON_LOADED")
  self:AddEvent("PLAYER_ENTERING_BATTLEGROUND")
  self:AddEvent("PLAYER_ENTERING_WORLD")
  self:AddEvent("PLAYER_LOGIN")
  self:AddEvent("PLAYER_REGEN_DISABLED")
  self:AddEvent("UNIT_MAXPOWER")
  self:AddEvent("UNIT_PET")
  self:AddEvent("UNIT_SPELLCAST_SUCCEEDED")
  self:AddEvent("UNIT_AURA")
  self:AddEvent("VARIABLES_LOADED")
  
  if self.retailClient then
    self:AddEvent("PET_BATTLE_OPENING_START")
    self:AddEvent("PLAYER_SPECIALIZATION_CHANGED")
    self:AddEvent("UNIT_ENTERING_VEHICLE")
    self:AddEvent("UNIT_ENTERED_VEHICLE")
    self:AddEvent("UNIT_EXITED_VEHICLE")
  end
  
  -- Load profile data
  Spheres        = Spheres        or {}
  Seamore        = Seamore        or {}

  -- Depricated
  SeamoreSpheres = nil

  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({}, 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("player")
    end
  end)
end

--- Event handler for "PLAYER_ENTERING_BATTLEGROUND".
--
function ss:PLAYER_ENTERING_BATTLEGROUND()
  refresh()
end

--- Event handler for "PLAYER_ENTERING_WORLD".
--
function ss:PLAYER_ENTERING_WORLD()
  self.Utils:ClearTemp()
  refresh()
end

--- Event handler for "PLAYER_LOGIN".
--
function ss:PLAYER_LOGIN()
  self:RemoveEvent("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)
  
  if self.curClass == "druid" then
    self:AddEvent("UPDATE_SHAPESHIFT_FORM")
  end
end

--- Event handler for "PLAYER_REGEN_ENABLED".
--
function ss:PLAYER_REGEN_ENABLED()
  self:RemoveEvent("PLAYER_REGEN_ENABLED")

  if self.Layout and not InCombatLockdown() then
    self.Layout:EndCombat()
  end
  
  -- Complete any pending tasks
  self.PendingTask.completeAll(true)
  self:AddEvent  ("PLAYER_REGEN_DISABLED")
end

--- Event handler for "PLAYER_REGEN_DISABLED".
--
function ss:PLAYER_REGEN_DISABLED()
  self:RemoveEvent("PLAYER_REGEN_DISABLED")

  if ss.global.general.hideOnCombat then
    self.Config:Close()
  end
  
  if self.Layout then
    self.Layout:StartCombat()
  end
  
  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:RemoveEvent("UNIT_ENTERED_VEHICLE")
      self.Layout:OnPlayerEnteredVehicle()
      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".
-- Some vehicles cause HasPetSpells() to return values without having a pet out,
-- this causes the pet sphere to remain visible.
--
-- UnitGUID("pet") is used to verify that the "pet" unit is not a vehicle
-- since other unit functions do not always return accurate data when this event is called.
--
-- To debug this, look for the world quest "Holding the Spire" (or the original quest "Dawn of Justice")
-- in Argus > Krokuun. (The lightforged warframe one)
-- "Game" quests such as "Make Loh Go" also cause this issue.
--
function ss:UNIT_PET(unit)
  if unit == "player" then
    local unitGUID = UnitGUID("pet") or ""

    self.petAvail = HasPetSpells() and (unitGUID:sub(1, #"Pet") == "Pet") or nil
    self.Layout:TrackPet(self.global.track["pet"] and self.petAvail)
  end
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()
  self:RemoveEvent("VARIABLES_LOADED")

  if self.global.general.MapMarker.show then
    self.MapMarker:Initialize()
  end
end
