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

local IsShiftKeyDown = IsShiftKeyDown
local PlayerFrame, PlayerFrame_OnEvent = PlayerFrame, PlayerFrame_OnEvent
local GetShapeshiftFormID = GetShapeshiftFormID
local UnitClass, GetSpecialization, GetSpecializationInfo = UnitClass, GetSpecialization, GetSpecializationInfo
local select, math, strlower = select, math, strlower
local UnitAffectingCombat, GetTime, GetSpellCooldown = UnitAffectingCombat, GetTime, GetSpellCooldown
local floor, tostring = floor, tostring
local InterfaceOptionsFrame_Show, InCombatLockdown = InterfaceOptionsFrame_Show, InCombatLockdown
local pairs, table = pairs, table

-- Addon properties.

ss.version = "v-0.9.76-alpha"

ss.gradientScale = 0.05

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

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

ss._tmp = {}

-- Addon functions.

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

---
-- Creates or extracts values from color tables.
-- If RGB values are greater than 1, a 255 is used.
--
-- @param colorTable
--   A table containing RGBA values, or the red color value when creating the color table.
--
-- @param g
--   Green color value.
--
-- @param b
--   Blue color value.
--
-- @param a
--   Alpha value.
--
function ss:GetRGBAValues(colorTable, g, b, a)
  if not colorTable then return 1,1,1,1; end

  if g and b and a then
    -- Return the colors in a table.
    return { r=colorTable, g=g, b=b, a=a }
  elseif (colorTable.r and colorTable.r > 1) or (colorTable[1] and colorTable[1] > 1) then
    -- Return 255 based RGBA values.
    return (colorTable.r or colorTable[1]) / 255, (colorTable.g or colorTable[2]) / 255, (colorTable.b or colorTable[3]) / 255, (100 - (colorTable.a or colorTable[4])) / 100
  else
    -- Return RGBA values.
    return colorTable.r or colorTable[1], colorTable.g or colorTable[2], colorTable.b or colorTable[3], colorTable.a or colorTable[4]
  end
end

function ss:MakeFrameMovable(frame,button,listener)
  frame:SetClampedToScreen(true)
  frame:SetMovable(true)
  frame:EnableMouse(true)
  frame:RegisterForDrag(button or "LeftButton")
  frame:SetScript("OnDragStart", function(self)
    if IsShiftKeyDown() then
      self:StartMoving()
    end
  end)
  frame:SetScript("OnDragStop", function(self)
    self:StopMovingOrSizing()
    if listener then
      listener(self)
    end
  end)
end

function ss:round(num, idp)
  local mult = 10^(idp or 0)
  return math.floor(num * mult + 0.5) / mult
end

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

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

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

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

function ss:valueFormat(val, formatting, petOverride)
  local newVal = val
  if petOverride == true then
    if val > 10000 then
      newVal = (floor((val/1000)*10)/10).."k"
    end
    return newVal;
  elseif formatting == "truncated" then
    if val > 1000000 then
      newVal = (floor((val/1000000)*10)/10) .."m"
    elseif val > 999 then
      newVal = (floor((val/1000)*10)/10) .."k"
    end
    return newVal;
  elseif formatting == "commaSeparated" then
    newVal = tostring(val):reverse():gsub("%d%d%d", "%1,"):reverse():gsub("^,", "");
    return newVal;
  else
    return newVal;
  end
end

function ss:UpdateMetatables()
  ss.ClassResources.initMetaModes()
end

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

function ss:NewTemp(name, temp)
  self:ClearTemp()
  self._tmp[name] = {
    temp = temp,
    created = GetTime(),
  }
end

function ss:GetTemp(name)
  return self._tmp[name] and self._tmp[name].temp or nil
end

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

function ss:OnInitialized()
  ss.Updater.update()
end

function ss:OnPetBattleStart()
  self.isInPetBattle = true
  ss.Layout:OnPetBattleStart()
end

function ss:OnPetBattleEnd()
  ss.Layout:OnPetBattleEnd()
  self.isInPetBattle = nil
end

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

function ss:OnPlayerEnteredVehicle()
  ss.Layout:OnPlayerEnteredVehicle()
end

function ss:OnPlayerExitedVehicle()
  ss.Layout:OnPlayerExitedVehicle()
end

function ss:OnPlayerLoggedIn()
  self.curSpec = GetSpecialization()
  self.curSpecName = self.curSpec and select(2, GetSpecializationInfo(self.curSpec)) or "None"
  self.curSpec = strlower(self.curSpec)
  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

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

function ss:OnPlayerSpecializationChanged()
  self.curSpec = GetSpecialization()
  self.curSpecName = self.curSpec and select(2, GetSpecializationInfo(self.curSpec)) or "None"
  self.curSpec = strlower(self.curSpec)

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

function ss:OnRegenEnabled()
  if self.Layout and not UnitAffectingCombat("player") then
    self.Layout:EndCombat()
  end

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

function ss:OnRegenDisabled()
  self:CloseConfig()

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

function ss:OnSpellCastSuccess(event, ...)
  self.updateGCD()
end

function ss:OnUnitMaxPowerChanged()
  ss.Layout:OnUnitMaxPowerChanged()
end

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