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

local PlayerFrame, PlayerFrame_OnEvent = PlayerFrame, PlayerFrame_OnEvent
local GetShapeshiftFormID = GetShapeshiftFormID
local UnitClass, GetSpecialization = UnitClass, GetSpecialization
local select, math, strlower = select, math, strlower
local GetTime, GetSpellCooldown = GetTime, GetSpellCooldown
local InterfaceOptionsFrame_Show, InCombatLockdown = InterfaceOptionsFrame_Show, InCombatLockdown
local pairs, table = pairs, table
local _GetSpecializationInfo = GetSpecializationInfo

-- Addon properties.

ss.version = "v-0.9.81-beta"

ss.gradientScale = 0.05

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

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

ss._tmp = {}

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

--- Toggles the default PlayerFrame visibility.
-- @param show Visibility flag.
-- @see PlayerFrame.
--
function ss:HandlePlayerFrame(show)
  if show then
    PlayerFrame:SetScript("OnEvent", PlayerFrame_OnEvent)
    PlayerFrame:Show()
  else
    PlayerFrame:SetScript("OnEvent",nil)
    PlayerFrame:Hide()
  end
end

--- Rounds a number by a power of 10.
-- @param num The number to round.
-- @param idp The exponential value.
-- @return The rounded value.
--
function ss:round(num, idp)
  local mult = 10^(idp or 0)
  return math.floor(num * mult + 0.5) / mult
end

--- Provides a string representation for a shapeshift id.
-- @param id The shapeshift id.
-- @return The shapeshift name.
--
function ss.shapeshiftToString(id)
  local forms = {
    [0]  = "default",
    [1]  = "cat",
    [2]  = "tree of life",
    [3]  = "travel",
    [4]  = "aquatic",
    [5]  = "bear",
    [27] = "flight",
    [31] = "moonkin",
  };

  return forms[id] or "default"
end

--- Closes the config panel.
--
function ss:CloseConfig()
  if self.Config:IsVisible() then
    -- Close the interface panel.
    InterfaceOptionsFrame_Show()
    
    if GameMenuFrame:IsShown() then
      -- Close the GameMenuFrame
      ToggleGameMenu()
    end
  end
end

--- Opens or closes the config panel.
--
function ss:ToggleConfig()
  if not InCombatLockdown() and not self.Config:IsVisible() then
    -- Open interface panel to main config.
    PlaySound(SOUNDKIT and SOUNDKIT.IG_SPELLBOOK_OPEN or "SPELLBOOKOPEN", "master")
    InterfaceOptionsFrame_Show()
    InterfaceOptionsFrame_OpenToCategory(AddOnName)
  elseif not InCombatLockdown() then
    self:CloseConfig()
  end
end

--- Updates global cooldown values.
--
function ss.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

--- Updates profile metatables with the proper resource values.
--
function ss:UpdateMetatables()
  ss.ClassResources.initMetaModes()
end

--- Removes temp data once it is no longer used.
--
function ss:ClearTemp()
  local time = GetTime()
  local completed = {};

  for k,v in pairs(self._tmp) do
    if v.created + 1800 < time then
      -- Clear whatever is in here.
      table.insert(completed, k)
    end
  end
  
  for k,v in pairs(completed) do
    self._tmp[k] = nil
  end

  -- Recreate the container if all objects have been removed.
  self._tmp = self._tmp or {}
end

--- Storea temporary values.
-- @param name
-- @param temp
--
function ss:NewTemp(name, temp)
  self:ClearTemp()
  self._tmp[name] = {
    temp = temp,
    created = GetTime(),
  }
end

--- Retrieves a temporary value.
-- @param name The table name. (index)
-- @return The stored value.
-- @see ss:NewTemp()
--
function ss:GetTemp(name)
  return self._tmp[name] and self._tmp[name].temp or nil
end

------------------------------
-- Liftcycle Event Handlers --
------------------------------

--- Event handler for "ADDON_LOADED".
--
function ss:OnInitialized()
  ss.Updater.update()
end

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

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

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

--- Event handler for "UNIT_ENTERED_VEHICLE".
--
function ss:OnPlayerEnteredVehicle()
  ss.Layout:OnPlayerEnteredVehicle()
end

--- Event handler for "UNIT_EXITED_VEHICLE".
--
function ss:OnPlayerExitedVehicle()
  ss.Layout:OnPlayerExitedVehicle()
end

--- Event handler for "PLAYER_LOGIN".
--
function ss:OnPlayerLoggedIn()
  self.curSpec     = GetSpecialization()
  self.curSpecName = self.curSpec and GetSpecializationInfo(self.curSpec) or "None"
  self.curSpec     = strlower(self.curSpec) -- convert to string value.
  self:UpdateMetatables()
  
  self.profile.modeInfo.mainResource.modeSpec       = ss.db.mainResource.modes[ss.curSpec]       and ss.curSpec or "defaultSpec"
  self.profile.modeInfo.additionalResource.modeSpec = ss.db.additionalResource.modes[ss.curSpec] and ss.curSpec or "defaultSpec"
  self.profile.modeInfo.health.modeSpec             = ss.db.health.modes[ss.curSpec]             and ss.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

  -- Complete any pending tasks
  self.PendingTask.completeAll(true)

  if self.curClass == "druid" then
    self:AddEvent("UPDATE_SHAPESHIFT_FORM")
  end
end

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

--- Event handler for "PLAYER_SPECIALIZATION_CHANGED".
--
function ss:OnPlayerSpecializationChanged()
  self.curSpec = GetSpecialization()
  self.curSpecName = self.curSpec and GetSpecializationInfo(self.curSpec) or "None"
  self.curSpec = strlower(self.curSpec) -- convert to string value.

  self:CloseConfig()
  ss.Config.shouldReload = true
  self.Layout:OnSpecializationChanged()
end

--- Event handler for "PLAYER_REGEN_ENABLED".
--
function ss:OnRegenEnabled()
  if self.Layout and not InCombatLockdown() then
    self.Layout:EndCombat()
  end

  -- Complete any pending tasks
  self.PendingTask.completeAll(true)
end

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

  if self.Layout then
    self.Layout:StartCombat()
  end
end

--- Event handler for "UNIT_SPELLCAST_SUCCEEDED".
--
function ss:OnSpellCastSuccess()
  self.updateGCD()
end

--- Event handler for "UNIT_MAXPOWER".
--
function ss:OnUnitMaxPowerChanged()
  ss.Layout:OnUnitMaxPowerChanged()
end

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