--- 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 GetSpecializationInfo, GetSpecialization

ss.curClass = strlower(select(2, UnitClass("player")))

local classicSpec = 1

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

if ss.retailClient then
  GetSpecialization = function(...)
    local specId = _GetSpecialization(...)
    return specId ~= 5 and specId or classicSpec
  end

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

  GetSpecializationInfo = function()
    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.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("VARIABLES_LOADED")

  if self.retailClient then
    self:AddEvent("PET_BATTLE_OPENING_START")
    self:AddEvent("PLAYER_SPECIALIZATION_CHANGED")
    self:AddEvent("PLAYER_TALENT_UPDATE")
    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 {}

  self.global = self.Utils:SetMetatables(Seamore, self.dVars.global)
  self.profile = self.Utils:SetMetatables(Spheres, self.dVars.spheres)

  -- Deprecated
  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()
  self.shouldRefresh = true
  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 self.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

    if self.shouldRefresh or specName ~= self.curSpecName then
      self.curSpec = spec
      self.curSpecName = specName
      self.curSpec = strlower(self.curSpec) -- convert to string value.

      self.Config:Close()
      self.Config.shouldReload = true
      self.shouldRefresh = nil
      self.Layout:Refresh()
    end
  end
end

--- Event handler for "PLAYER_TALENT_UPDATE".
function ss:PLAYER_TALENT_UPDATE()
  self.shouldRefresh = true
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 "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
