--- SeamoreSpheresUnitFrame
--
local SeamoreSpheresUnitFrame = {}
local AddOnName, nsVars = ...
local ss = nsVars.SS

local tonumber, string, strlower = tonumber, string, strlower
local pairs, table = pairs, table
local InCombatLockdown = InCombatLockdown
local CreateFrame = ss.CreateFrame
local GetScreenWidth, GetScreenHeight = GetScreenWidth, GetScreenHeight
local type, select, tostring = type, select, tostring
local ErrorInvalidArgs = ss.Utils.Errors.ErrorInvalidArgs
local throw = ss.Utils.throw

local sphereIndex = 0

local _frameIndex = {
  active = 0,
  preview = 0,
}

local function onDrag(seamoreSpheresUnitFrame)
  local configName = (seamoreSpheresUnitFrame.configName == "vehicle") and "health"
    or ((seamoreSpheresUnitFrame.configName == "vehicleResource") and "mainResource" or seamoreSpheresUnitFrame.configName)

  ss.db[configName].general.anchor, ss.db[configName].general.posX, ss.db[configName].general.posY = select(3, seamoreSpheresUnitFrame:GetPoint())

  if configName == "health" then
    ss.db["vehicle"].general.anchor, ss.db["vehicle"].general.posX, ss.db["vehicle"].general.posY = select(3, seamoreSpheresUnitFrame:GetPoint())
  elseif configName == "mainResource" then
    ss.db["vehicleResource"].general.anchor, ss.db["vehicleResource"].general.posX, ss.db["vehicleResource"].general.posY = select(3, seamoreSpheresUnitFrame:GetPoint())
  end

  if ss.Layout.spheres[configName] then
    -- Display the change using the actual sphere.
    ss.Layout.spheres[configName]:RefreshPos()
  end
end

local function initOnce(seamoreSpheresUnitFrame, generalInfo)
  if not seamoreSpheresUnitFrame._initialized then
    seamoreSpheresUnitFrame:SetHeight(ss.global.general.SphereSize / generalInfo.scale)
    seamoreSpheresUnitFrame:SetWidth (ss.global.general.SphereSize / generalInfo.scale)
  
    if seamoreSpheresUnitFrame.displayMode == "active" then
      seamoreSpheresUnitFrame:RegisterForClicks("AnyUp")
      seamoreSpheresUnitFrame:SetAttribute     ("type1","target")
      seamoreSpheresUnitFrame:SetAttribute     ("target","unit")
      seamoreSpheresUnitFrame:SetAttribute     ("type2","togglemenu")
      seamoreSpheresUnitFrame:SetAttribute     ("togglemenu", generalInfo.unit)
      seamoreSpheresUnitFrame:SetAttribute     ("unit",generalInfo.unit)
    
      ss.Extentions:MakeMovable(seamoreSpheresUnitFrame, nil, onDrag)
    
      -- Add the tooltip.
      ss.Utils:AddUnitTooltip(seamoreSpheresUnitFrame, generalInfo.unit)
    else
      seamoreSpheresUnitFrame:ClearAllPoints()
      seamoreSpheresUnitFrame:EnableMouse(false)
      seamoreSpheresUnitFrame:SetFrameStrata("HIGH")
      seamoreSpheresUnitFrame:SetPoint("CENTER", 0, 40)
    end
  
    seamoreSpheresUnitFrame:SetScale((seamoreSpheresUnitFrame.displayMode == "active") and (generalInfo.globalScale / 100) or 1)
  
    seamoreSpheresUnitFrame._initialized = true
  end
end

--- Converts a region-relative (ie: "CENTER", "TOPRIGHT"...) point to a "LEFT" or "BOTTOM" one.
-- @param val The position value.
-- @param scale The sphere scale.
-- @param pad The padding (region dimension) to use.
-- @return The adjusted value.
--
local function adjustMidForRaw(val, scale, pad)
  local adjust = ((ss.global.general.SphereSize * scale) / 2) - (ss.global.general.SphereSize / 2)
  local _cur = (val * scale) - (pad * scale)
  local _max = ((pad * scale) * 3) - ss.global.general.SphereSize - (pad * scale)
  local _per = _cur / _max
  _cur = _cur + ((adjust * 2) * _per) - adjust
  _per = _cur / _max
  adjust = ((adjust * 2) * _per) - adjust
  
  return (val + (adjust * (1/scale)))
end

--- Converts a "LEFT" or "BOTTOM" point to a region-relative one (ie: "CENTER", "TOPRIGHT"...).
-- @param val The raw value.
-- @param scale The sphere scale.
-- @param pad The padding (region dimension) to use.
-- @return The adjusted value.
--
local function adjustMidFromRaw(val, scale, pad)
  local adjust = ((ss.global.general.SphereSize * scale) / 2) - (ss.global.general.SphereSize / 2)
  local _max = (pad * 3) - ss.global.general.SphereSize - pad
  local _per = (val - pad) / _max
  adjust = ((adjust * 2) * _per) - adjust
  
  return (val + (ss.global.general.SphereSize / 2) - (pad * 2) - adjust)
end

function SeamoreSpheresUnitFrame:Init(configName, displayMode)
  if type(configName) == "string" and type(displayMode) == "string" then
    -- Initialize the properties.
    self.configName = configName
    self.displayMode = displayMode
    local generalInfo = ss.db[configName].general
    
    if displayMode == "active" then
      if InCombatLockdown() then
        ss.PendingTask.add("SeamoreSpheresUnitFrame"..tostring(self._sphereIndex).."_Init", function()
          self:ClearAllPoints()
          self:SetHeight(ss.global.general.SphereSize / generalInfo.scale)
          self:SetWidth (ss.global.general.SphereSize / generalInfo.scale)
          self:SetPoint(generalInfo.anchor, generalInfo.posX, generalInfo.posY)
          self:SetScale(generalInfo.globalScale / 100)
        end)
      else
        self:ClearAllPoints()
        self:SetHeight(ss.global.general.SphereSize / generalInfo.scale)
        self:SetWidth (ss.global.general.SphereSize / generalInfo.scale)
        self:SetPoint(generalInfo.anchor, generalInfo.posX, generalInfo.posY)
        self:SetScale(generalInfo.globalScale / 100)
      end
    end
    
    initOnce(self, generalInfo)
  
    if displayMode == "active" then
      -- TODO: Find a way to set unit while in combat. (maybe need to use multiple frames?)
      ss.PendingTask.add("SeamoreSpheresUnitFrame"..tostring(self._sphereIndex).."_UpdateUnit", function()
        self:SetAttribute     ("togglemenu", generalInfo.unit)
        self:SetAttribute     ("unit",generalInfo.unit)
  
        -- Update the tooltip.
        ss.Utils:AddUnitTooltip(self, generalInfo.unit)
      end)
    end
    
    -- Initialize the container.
    self.sphere:Init()
  else
    throw(ErrorInvalidArgs, "SeamoreSpheresUnitFrame", "Init",
      {"configName", "string", configName},
      {"displayMode", "string", displayMode}
    )
  end
  
  return self.sphere.config
end

function SeamoreSpheresUnitFrame:ShowSphere(show)
  show = (show ~= false) and true
  
  if InCombatLockdown() then
    ss.PendingTask.add("SeamoreSpheresUnitFrame"..tostring(self._sphereIndex).. (show and "_ShowSphere" or "_HideSphere"), function()
      -- Run outside of combat.
      self:EnableMouse(show)
    end)
  else
    self:EnableMouse(show)
  end
  
  if not show then
    self.sphere.ResourceUpdate = nil
    self:Fade(0, function(display)
      display.sphere:SetAlpha (0)
    end)
  else
    self.sphere:SetAlpha (1)
    self.sphere.ResourceUpdate = (self.displayMode == "active") and self.sphere.resourceMonitor:Watch() or nil
    self:Fade(1)
  end
end

function SeamoreSpheresUnitFrame:HideSphere()
  self:ShowSphere(false)
end

function SeamoreSpheresUnitFrame:SetResource(configName)
  if type(configName) ~= "string" then
    throw(ErrorInvalidArgs, "SeamoreSpheresUnitFrame", "SetResource",
      {"configName", "string", configName}
    )
  elseif not ss.db[configName] or not ss.db[configName].modes[ss.profile.modeInfo[ss.db[configName].general.configName].modeSpec] then
    throw("Invalid configName passed to SeamoreSpheresUnitFrame" .. tostring(self._sphereIndex) .. ":SetResource()")
  else
    self.configName = configName
    
    if self.displayMode == "preview" then
      -- Initialize properties.
      self.sphere.textFormat = ss.db[configName].textFormat
      self.sphere.configName = configName
      return self.sphere:SetMode(ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][ss.profile.modeInfo[configName].mode])
    else
      return self:Init(self.configName, self.displayMode)
    end
  end
  
  return self.sphere.config
end

--- Creates a resource group.
-- @param resource The resource data.
-- @param region The sphere region.
--
function SeamoreSpheresUnitFrame:AddResourceGroup(resource, region)
  self.sphere.resourceGroup = self.sphere.resourceGroup or {}
  resource = resource or {}
  
  if resource.buffGroup then
    self:AddMicroGroup(resource, "buff", 1, table.getn(resource.buffGroup), 1, nil, region)
  elseif table.getn(resource) > 0 then
    if not resource.getMinMax then
      self:AddResourceGroup         (resource[1], region)
      self:AddSecondaryResourceGroup(resource[2], region)
    else
      resource.getMinMax(resource[1])
      local max = tonumber(resource[1]) and select(3, resource.getMinMax(resource[1])) or 1
      self:AddMicroGroup(resource, resource[1], 1, max, 1, nil, region)
    end
  elseif self.displayMode == "active" then
    for k,v in pairs(self.sphere.resourceGroup or {}) do
      v:GetParent():SetMicroResource(nil)
    end
  
    -- Add power tracking
    self.sphere.resourceGroupUnit   = nil
    self.sphere.resourceGroupUnitId = nil
    self:AddSecondaryResourceGroup()
  end
end

--- Adds a group of micro spheres to the display.
-- @param resource The resource data.
-- @param resourceId The resource identifier.
-- @param start The start position.
-- @param limit The itterator limit.
-- @param step The itterator value.
-- @param group The group identifier.
-- @param region The sphere region.
--
function SeamoreSpheresUnitFrame:AddMicroGroup(resource, resourceId, start, limit, step, group, region)
  group = group or "resource"

  for i = start, limit, step do
    self.sphere[group.."Group"][i] = self.sphere[group.."Group"][i] or self.sphere:InitMicroSphere(i, (resourceId == "buff") and resource.buffGroup[i] or resource, region, group)
    
    if resourceId ~= "buff" and ss.ClassResourceData.resourceAnimation(resource.name) then
      -- Add or update resource animation.
      self.sphere[group.."Group"][i].resourceAnimation:UpdateAnimation(ss.ClassResourceData.resourceAnimation(resource.name))
      self.sphere[group.."Group"][i].resourceAnimation:SetAlpha(0)
    elseif self.sphere[group.."Group"][i].resourceAnimation then
      self.sphere[group.."Group"][i].resourceAnimation:Hide()
    end
  end
  
  if self.displayMode == "active" then
  
    -- Add power tracking
    self.sphere[group.."GroupUnit"]   = resource
    self.sphere[group.."GroupUnitId"] = resourceId
  
    for k,v in pairs(self.sphere[group.."Group"]) do
      v:GetParent():SetMicroResource(resourceId, resource)
    end
  end
end

--- Creates a secondary resource group.
-- @param resource The resource data.
-- @param region The sphere region.
--
function SeamoreSpheresUnitFrame:AddSecondaryResourceGroup(resource, region)
  self.sphere.secondaryResourceGroup = self.sphere.secondaryResourceGroup or {}
  
  if resource and resource.buffGroup then
    self:AddMicroGroup(resource, "buff", 10, 11 - table.getn(resource.buffGroup), -1, "secondaryResource", region)
  elseif resource and (table.getn(resource) > 0) then
    local max = resource[1] and select(3, resource.getMinMax(resource[1])) or 1
    self:AddMicroGroup(resource, resource[1], 10, 11 - max, -1, "secondaryResource", region)
  else
    for k,v in pairs(self.sphere.secondaryResourceGroup or {}) do
      v:GetParent():SetMicroResource(nil)
    end
    
    self.sphere.secondaryResourceGroupUnit   = nil
    self.sphere.secondaryResourceGroupUnitId = nil
  end
end

--- Updates the storage values.
--
function SeamoreSpheresUnitFrame:dbUpdate()
  if self.displayMode == "preview" then
    local configName = self.configName
    
    -- Update the animations.
    ss.db[configName].animation.posX,    ss.db[configName].animation.posY = self:getAnimationOffset()
    ss.db[configName].animation.groupId, ss.db[configName].animation.id   = self:getAnimationDisplayInfo()
    
    ss.db[configName].animation.camDistance = self:getAnimationScale()
    ss.db[configName].animation.alpha       = self:getAnimationAlpha()
    ss.db[configName].animation.frameLevel  = self:getAnimationStrata() - ss.private.layout.config.frameLevelAdjust
    
    -- Update the swirl.
    ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].swirl.speed = self:getSwirlSpeed()
    ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].swirl.id    = self:getSwirlId()
    
    -- Update the modes.
    ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].fillTexture = self:getFillTexture()
    ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].fillInfo    = self:getFillInfo()
    
    -- Update the fil colors.
    local color   = self:getFillColor()
    local r,g,b,a = ss.Utils:GetRGBAValues(color[1])
    ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].fillColor[1][2] = {r=r,g=g,b=b,a=a}
    
    if color[2] then
      r,g,b,a = ss.Utils:GetRGBAValues(color[2])
      ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].fillColor[2][2] = {r=r,g=g,b=b,a=a}
    end
    
    -- Update the swirl colors.
    r,g,b,a = self:getSwirlColor(1)
    ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].swirlColor[1] = {r=r,g=g,b=b,a=a}
    r,g,b,a = self:getSwirlColor(2)
    ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].swirlColor[2] = {r=r,g=g,b=b,a=a}
    r,g,b,a = self:getSwirlColor(3)
    ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].swirlColor[3] = {r=r,g=g,b=b,a=a}
    
    -- Update the fonts.
    r,g,b,a = self:getTextColor()
    ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].fontColor[1]  = {r=r,g=g,b=b,a=a}
    r,g,b,a = self:getTextColor2()
    ss.db[configName].modes[ss.profile.modeInfo[configName].modeSpec][self.modeId].fontColor[2]  = {r=r,g=g,b=b,a=a}
    
    ss.db[configName].textFormat   = self:getTextFormat()
    ss.db[configName].font[1].show = self.sphere.font1:IsVisible()
    ss.db[configName].font[2].show = self.sphere.font2:IsVisible()
    
    -- Update the values ignoring vehicle resources..
    configName = (configName == "vehicle") and "health"
      or ((configName == "vehicleResource") and "mainResource" or configName)
    
    if configName == "health" then
      ss.db["vehicle"].general.globalScale = self:getScale()
      ss.db["vehicle"].general.anchor, ss.db["vehicle"].general.posX, ss.db["vehicle"].general.posY = self:getPos()
    elseif configName == "mainResource" then
      ss.db["vehicleResource"].general.globalScale = self:getScale()
      ss.db["vehicleResource"].general.anchor, ss.db["vehicleResource"].general.posX, ss.db["vehicleResource"].general.posY = self:getPos()
    end
    
    ss.db[configName].general.globalScale = self:getScale()
    ss.db[configName].general.anchor, ss.db[configName].general.posX, ss.db[configName].general.posY = self:getPos()
  end
end

--- Provides a generic "getter"
-- @param p The property to return.
-- @return The property value.
--
function SeamoreSpheresUnitFrame:Get(p)
  return self[p]
end

--- Returns animation details.
-- @return The animation data.
--
function SeamoreSpheresUnitFrame:getAnimation()
  return self.dbConfig.animation
end

--- Retrieves the animation identifiers.
-- @return The animation indexs.
--
function SeamoreSpheresUnitFrame:getAnimationDisplayInfo()
  return self.animGroup, self.animId
end

--- Retrieves the animation alpha.
-- @return The alpha value.
--
function SeamoreSpheresUnitFrame:getAnimationAlpha()
  return self.animAlpha
end

--- Retrieves the animation position.
-- @return The x/y positions.
--
function SeamoreSpheresUnitFrame:getAnimationOffset()
  return self.animPosX, self.animPosY
end

--- Retrieves the animation scale.
-- The scale value.
--
function SeamoreSpheresUnitFrame:getAnimationScale()
  return self.animCamDist
end

--- Retrieves the animation strata.
-- @return The strata value.
--
function SeamoreSpheresUnitFrame:getAnimationStrata()
  return self.sphere.animation:GetFrameLevel()
end

--- Retrieves the fill texture color.
-- @return The color value.
--
function SeamoreSpheresUnitFrame:getFillColor()
  if self.mode.fillColor[2] and self.sphere.fillingLeft and self.sphere.fillingRight then
    local r,g,b,a     = self.sphere.fillingLeft :GetVertexColor()
    local r2,g2,b2,a2 = self.sphere.fillingRight:GetVertexColor()
    
    return {
      [1] = {r=r,g=g,b=b,a=a},
      [2] = {r=r2,g=g2,b=b2,a=a2},
    };
  elseif self.sphere.filling then
    local r,g,b,a = self.sphere.filling:GetVertexColor()
    
    return {
      [1] = {r=r,g=g,b=b,a=a},
    };
  else
    return nil
  end
end

--- Retrieves the fill texture details.
-- @return The fill information.
--
function SeamoreSpheresUnitFrame:getFillInfo()
  return {
    fillGroup = self.fillGroup,
    fillId    = self.fillId,
  }
end

--- Retrieves the fill texture.
-- @return The texture value.
--
function SeamoreSpheresUnitFrame:getFillTexture()
  return self.fillTexture
end

--- Retrieves the current mode.
-- @return The mode data.
--
function SeamoreSpheresUnitFrame:getMode()
  return self.mode or self.sphere.mode
end

--- Retrieves the modes available for a specialization.
-- @param specName The specialization identifier.
-- @return The availailable modes.
--
function SeamoreSpheresUnitFrame:getModes(specName)
  return ss.db[self.configName].modes[specName] or ss.db[self.configName].modes[ss.profile.modeInfo[self.configName].modeSpec]
end

--- Returnrs the sphere position.
-- @param raw Flag to return raw values.
-- @return the position values.
-- @see adjustMidForRaw().
--
function SeamoreSpheresUnitFrame:getPos(raw)
  if raw then
    local scale, bScale = self:getRawScales()
    local sphereSize    = bScale * ss.global.general.SphereSize
    local xPad = (bScale * GetScreenWidth()) / 4
    local yPad = (bScale * GetScreenHeight()) / 4
    
    local x = self._posX
    local y = self._posY
    
    if string.match(strlower(self._anchor), "right") then
      -- Right adjust.
      x = x + (xPad * 4) - sphereSize
    elseif not string.match(strlower(self._anchor), "left") then
      -- Middle adjust.
      x = x - (sphereSize/2) + (xPad * 2)
      x = adjustMidForRaw(x, scale, xPad)
    end
    
    if string.match(strlower(self._anchor), "top") then
      -- Top adjust.
      y = y + (yPad * 4) - sphereSize
    elseif not string.match(strlower(self._anchor), "bottom") then
      -- Middle adjust.
      y = y - (sphereSize/2) + (yPad * 2)
      y = adjustMidForRaw(y, scale, yPad)
    end
    
    return self._anchor, x * scale, y * scale
  else
    return self._anchor, self._posX, self._posY
  end
end

--- Retrieves the actual sphere scales.
-- These are used as conversion values.
-- @return the scale values.
--
function SeamoreSpheresUnitFrame:getRawScales()
  local scale = self:getScale() / 100
  return scale, (1/scale)
end

--- Retrieves the sphere scale.
-- @return the scale value.
--
function SeamoreSpheresUnitFrame:getScale()
  return self._globalScale
end

--- Retrieves the swirl textures.
-- @return The texture value.
--
function SeamoreSpheresUnitFrame:getSwirlTextures()
  return {
    self.sphere.swirl1.texture:GetTexture(),
    self.sphere.swirl2.texture:GetTexture(),
    self.sphere.swirl3.texture:GetTexture(),
  }
end

--- Retrieves the swirl color.
-- @param the swirl index.
-- @return The color value.
--
function SeamoreSpheresUnitFrame:getSwirlColor(index)
  index = index or 1
  return self.sphere["swirl"..index].texture:GetVertexColor()
end

--- Retrieves the swirl identifier.
-- @return The texture id.
--
function SeamoreSpheresUnitFrame:getSwirlId()
  return self.sphere.swirlId
end

--- Retrieves the swirl speed.
-- @return The speed value.
--
function SeamoreSpheresUnitFrame:getSwirlSpeed()
  return self.sphere.swirl1:GetSpeed()
end

--- Retrieves the text color of font 1.
-- @return The color value.
--
function SeamoreSpheresUnitFrame:getTextColor()
  return self.sphere.font1:GetTextColor()
end

--- Retrieves the text color of font 2.
-- @return The color value.
--
function SeamoreSpheresUnitFrame:getTextColor2()
  return self.sphere.font2:GetTextColor()
end

--- Retrieves the text value of font 1
-- @return The text value.
--
function SeamoreSpheresUnitFrame:getText()
  return self.sphere.font1:GetText()
end

--- Retrieves the text value of font 2.
-- @return The text value.
--
function SeamoreSpheresUnitFrame:getText2()
  return self.sphere.font2:GetText()
end

--- Retrieves the text format.
-- @return The format value.
--
function SeamoreSpheresUnitFrame:getTextFormat()
  return self.textFormat
end

--- Hides the sphere display.
--
function SeamoreSpheresUnitFrame:Hide()
  self:ShowSphere(false)
end

--- Hides the gradients.
--
function SeamoreSpheresUnitFrame:hideGradients()
  if self.sphere.fillingLeft and self.sphere.fillingLeft.gradient then
    self.sphere.fillingLeft.gradient:SetAlpha(0)
    self.sphere.fillingLeft.gradient:Hide()
  end
  
  if self.sphere.fillingRight and self.sphere.fillingRight.gradient then
    self.sphere.fillingRight.gradient:SetAlpha(0)
    self.sphere.fillingRight.gradient:Hide()
  end
  
  if self.sphere.filling and self.sphere.filling.gradient then
    self.sphere.filling.gradient:SetAlpha(0)
    self.sphere.filling.gradient:Hide()
  end
  
  if self.sphere.resourceGroup then
    for k,v in pairs(self.sphere.resourceGroup) do
      if v.filling.gradient then
        v.filling.gradient:SetAlpha(0)
        v.filling.gradient:Hide()
      end
    end
  end
  
  if self.sphere.secondaryResourceGroup then
    for k,v in pairs(self.sphere.secondaryResourceGroup) do
      if v.filling.gradient then
        v.filling.gradient:SetAlpha(0)
        v.filling.gradient:Hide()
      end
    end
  end
end

--- Refreshes sphere position from storage.
--
function SeamoreSpheresUnitFrame:RefreshPos()
  local configName = (self.configName == "vehicle") and "health"
    or ((self.configName == "vehicleResource") and "mainResource" or self.configName)
  
  -- Set the pos using the proper config.
  if self._anchor ~= ss.db[configName].general.anchor or self._posX ~= ss.db[configName].general.posX or self._posY ~= ss.db[configName].general.posY then
    self:setPos(ss.db[configName].general.anchor, ss.db[configName].general.posX, ss.db[configName].general.posY)
  end
end

--- Provides a generic "setter"
-- @param p The property to update.
-- @param v The value to use.
--
function SeamoreSpheresUnitFrame:Set(p, v)
  if self[p] then
    self[p] = v
  end
end

--- Sets the sphere animation.
-- @param anim The animation details.
--
function SeamoreSpheresUnitFrame:setAnimation(anim)
  if not self.sphere.animation then
    self.sphere.animation = ss.AnimationCreator:New(self, anim)
  end
  
  self:setAnimationAlpha      (anim.alpha)
  self:setAnimationOffset     (anim.posX, anim.posY)
  self:setAnimationScale      (anim.camDistance)
  self:setAnimationDisplayInfo(anim.groupId, anim.id)
  self:setAnimationStrata     (anim.frameLevel or 3)
end

--- Sets the animation alpha.
-- @param alpha The alpha value.
--
function SeamoreSpheresUnitFrame:setAnimationAlpha(alpha)
  self.sphere.animation:SetAlpha(alpha)
  self.animAlpha = alpha
end

--- Sets the animation positions.
-- To set one value, pass nil to the other.
-- @param x The x value. (optional)
-- @param y The y value. (optional)
--
function SeamoreSpheresUnitFrame:setAnimationOffset(x, y)
  x, y = x or select(2, self.sphere.animation:GetPosition()), y or select(3, self.sphere.animation:GetPosition())
  self.sphere.animation:SetPosition(0,x,y)
  self.animPosX = x
  self.animPosY = y
end

--- Sets the animation scale.
-- @param val The scale value.
--
function SeamoreSpheresUnitFrame:setAnimationScale(val)
  self.sphere.animation:SetCamDistanceScale(val)
  self.animCamDist = val
end

--- Sets the animation display info.
-- @param groupId The animation group identifier.
-- @param id The animation identifier.
-- @see Animation:SetDisplayInfo().
--
function SeamoreSpheresUnitFrame:setAnimationDisplayInfo(groupId, id)
  self.sphere.animation:SetDisplayInfo(ss.Animations.get(groupId, id))
  self.animGroup = groupId
  self.animId    = id
end

--- Sets the animation strata.
-- @param strata The strata value.
--
function SeamoreSpheresUnitFrame:setAnimationStrata(strata)
  self.sphere.animation:SetFrameLevel(strata or 3)
end

--- Sets the fill texture color.
-- @param color The color value.
--
function SeamoreSpheresUnitFrame:setFillColor(color)
  if color[2] and color[1]then
    self.sphere.fillingLeft :SetVertColor(color[1])
    self.sphere.fillingRight:SetVertColor(color[2])
    
    if ss.global.show.gradient then
      local r,g,b,a = ss.Utils:GetRGBAValues(color[1])
      self.sphere.fillingLeft.gradient:SetVertexColor(r,g,b,a)
      self.sphere.fillingLeft.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
      
      r,g,b,a = ss.Utils:GetRGBAValues(color[2])
      self.sphere.fillingRight.gradient:SetVertexColor(r,g,b,a)
      self.sphere.fillingRight.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
    end
  elseif color[2] then
    self.sphere.fillingRight:SetVertColor(color[2])
    
    if ss.global.show.gradient then
      local r,g,b,a = ss.Utils:GetRGBAValues(color[2])
      self.sphere.fillingRight.gradient:SetVertexColor(r,g,b,a)
      self.sphere.fillingRight.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
    end
  else
    self.sphere.filling:SetVertColor(color[1])
    
    if ss.global.show.gradient then
      local r,g,b,a = ss.Utils:GetRGBAValues(color[1])
      self.sphere.filling.gradient:SetVertexColor(r,g,b,a)
      self.sphere.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
    end
  end
end

--- Sets the fill texture.
-- @param group The texture group.
-- @param id The texture identifier.
--
function SeamoreSpheresUnitFrame:setFillTexture(group, id)
  local texture = ss.FillTextures.get(group, id)
  self.fillGroup = group
  self.fillId    = id
  
  if texture[2] then
    self.sphere.fillingLeft :SetTexture(texture[1])
    self.sphere.fillingRight:SetTexture(texture[2])
    
    if ss.global.show.gradient then
      self.sphere.fillingLeft.gradient  :SetTexture(texture[1])
      self.sphere.fillingRight.gradient:SetTexture(texture[2])
    end
    self.fillTexture = texture
  else
    self.sphere.filling:SetTexture(texture)
    
    if ss.global.show.gradient then
      self.sphere.filling.gradient:SetTexture(texture)
    end
    self.fillTexture = { [1] = texture, }
  end
end

--- Sets the resource mode.
-- @param mode The target mode.
-- @param modeName The mode name.
--
function SeamoreSpheresUnitFrame:setMode(mode, modeName)
  if not mode then return; end
  
  if self.displayMode == "active" then
    ss.profile.modeInfo[self.configName].mode = modeName or ss.profile.modeInfo[self.configName].mode
  end
  
  self.prevMode    = self.mode
  self.mode        = mode
  self.sphere.mode = mode
  self.fillId      = mode.fillInfo.fillId
  self.fillGroup   = mode.fillInfo.fillGroup
  
  self.monitorConfig = self.sphere:SetMode(mode, modeName)
  
  self.fillTexture = self.mode.fillTexture
end

--- Updates the sphere position.
-- @param raw Flag to use raw values.
-- @see adjustMidFromRaw().
--
function SeamoreSpheresUnitFrame:setPos(anchor, x, y, raw)
  if raw then
    local scale, bScale = self:getRawScales()
    local xPad = GetScreenWidth() / 4
    local yPad = GetScreenHeight() / 4
    local anchorAdjust = ""
    
    if not x then
      x = self._posX * scale
      if string.match(strlower(self._anchor), "left") then
        anchorAdjust = "LEFT"
      elseif string.match(strlower(self._anchor), "right") then
        anchorAdjust = "RIGHT"
      end
    elseif x + ss.global.general.SphereSize > (xPad * 3) then
      anchorAdjust = "RIGHT"
      x = x + ss.global.general.SphereSize - (xPad * 4)
    elseif x <= xPad then
      anchorAdjust = "LEFT"
    elseif x > xPad then
      x = adjustMidFromRaw(x, scale, xPad)
    end
    
    if not y then
      y = self._posY * scale
      if string.match(strlower(self._anchor), "top") then
        anchorAdjust = "TOP" .. anchorAdjust
      elseif string.match(strlower(self._anchor), "bottom") then
        anchorAdjust = "BOTTOM" .. anchorAdjust
      end
    elseif y + ss.global.general.SphereSize > (yPad * 3) then
      anchorAdjust = "TOP" .. anchorAdjust
      y = y + ss.global.general.SphereSize - (yPad * 4)
    elseif y <= yPad then
      anchorAdjust = "BOTTOM" .. anchorAdjust
    elseif y > yPad then
      y = adjustMidFromRaw(y, scale, yPad)
    end
    
    anchor = (anchorAdjust == "") and "CENTER" or anchorAdjust
    x = x * bScale
    y = y * bScale
  end
  
  self._anchor = anchor or self._anchor
  self._posX   = x or self._posX
  self._posY   = y or self._posY
  
  if self.displayMode ~= "preview" then
    ss.PendingTask.remove("setPos"..tostring(self._sphereIndex))
    ss.PendingTask.add   ("setPos"..tostring(self._sphereIndex), function()
      -- Update the pos and all contained animations.
      self:ClearAllPoints()
      self:SetPoint(self._anchor, self._posX, self._posY)
    end)
  else
    local configName = (self.configName == "vehicle") and "health"
      or ((self.configName == "vehicleResource") and "mainResource" or self.configName)
    
    if ss.Layout.spheres[configName] then
      -- Display the change using the actual sphere.
      ss.Layout.spheres[configName]:setPos(anchor, x, y)
    end
  end
end

--- Sets the sphere resource.
-- @param resource The target resource.
--
function SeamoreSpheresUnitFrame:setResource(resource)
  if not resource and not resource.modes[ss.profile.modeInfo[resource.general.configName].modeSpec] then
    return;
  end
  
  self.sphere.textFormat = resource.textFormat
  self.dbConfig    = resource
  self.textFormat  = resource.textFormat
  self.modeId      = ss.profile.modeInfo[resource.general.configName].mode
  self.configName  = resource.general.configName
  self.animPosX    = resource.animation.posX
  self.animPosY    = resource.animation.posY
  self.animGroup   = resource.animation.groupId
  self.animId      = resource.animation.id
  self.animCamDist = resource.animation.camDistance
  self.animAlpha   = resource.animation.alpha
  
  local mode = resource.modes[ss.profile.modeInfo[resource.general.configName].modeSpec][ss.profile.modeInfo[resource.general.configName].mode]
  self.speed = mode.swirl.speed or 5
  
  self.monitorConfig = self.sphere:SetResource(resource.general.configName)
end

--- Sets the sphere scale.
-- @param scale The scale value.
--
function SeamoreSpheresUnitFrame:setScale(scale)
  self._globalScale = scale
  
  if self.displayMode ~= "preview" then
    ss.PendingTask.remove("setScale"..tostring(self._sphereIndex))
    ss.PendingTask.add   ("setScale"..tostring(self._sphereIndex), function()
      -- Update the scale and all contained animations.
      self:SetScale(scale / 100)
      self:setAnimation(self:getAnimation())
      
      if self.sphere.resourceGroup then
        for k,v in pairs(self.sphere.resourceGroup) do
          v.animation:UpdateAnimation()
          if v.resourceAnimation then
            v.resourceAnimation:UpdateAnimation(nil, true)
          end
        end
      end
      
      if self.sphere.secondaryResourceGroup then
        for k,v in pairs(self.sphere.secondaryResourceGroup) do
          v.animation:UpdateAnimation()
          if v.resourceAnimation then
            v.resourceAnimation:UpdateAnimation(nil, true)
          end
        end
      end
    end)
  else
    local configName = (self.configName == "vehicle") and "health"
      or ((self.configName == "vehicleResource") and "mainResource" or self.configName)
    
    if ss.Layout.spheres[configName] then
      -- Display the change using the actual sphere.
      ss.Layout.spheres[configName]:setScale(scale)
    end
  end
end

--- Sets the swirl color.
-- @param color The color value.
-- @param index The swirl index. (optional)
--
function SeamoreSpheresUnitFrame:setSwirlColor(color, index)
  if not index then
    self.sphere.swirl1.texture:SetVertColor(color)
    self.sphere.swirl2.texture:SetVertColor(color)
    self.sphere.swirl3.texture:SetVertColor(color)
  else
    self.sphere["swirl"..index].texture:SetVertColor(color)
  end
end

--- Sets the swirl speed.
-- @param speed The speed value.
--
function SeamoreSpheresUnitFrame:setSwirlSpeed(speed)
  self.sphere.swirl1:SetSpeed(speed, 2, 4)
  self.sphere.swirl2:SetSpeed(speed, 3, 5)
  self.sphere.swirl3:SetSpeed(speed, 2, 3)
  
  self.speed = speed or 5
end

--- Sets the swirl texture.
-- @param id The texture identifier.
--
function SeamoreSpheresUnitFrame:setSwirlTexture(id)
  self.sphere.swirlId = id
  local textures      = ss.FillTextures.get("swirl", id)
  
  self.sphere.swirl1.texture:SetTexture(textures[1])
  self.sphere.swirl2.texture:SetTexture(textures[2])
  self.sphere.swirl3.texture:SetTexture(textures[3])
  
  local size = (self.displayMode == "active") and ss.db[self.configName].general.size or (ss.db[self.configName].general.size * ss.db[self.configName].general.scale)
  size       = ss.FillTextures.getSwirlSize(id, size)
  
  self.sphere.swirl1:SetWidth (size * 0.9)
  self.sphere.swirl1:SetHeight(size * 0.9)
  self.sphere.swirl2:SetWidth (size * 0.9)
  self.sphere.swirl2:SetHeight(size * 0.9)
  self.sphere.swirl3:SetWidth (size * 0.9)
  self.sphere.swirl3:SetHeight(size * 0.9)
end

--- Sets the text value for font 1.
-- @param text The string to use.
--
function SeamoreSpheresUnitFrame:setText(text)
  self.sphere.font1:SetText(text)
end

--- Sets the text value for font 2.
-- @param text The string to use.
--
function SeamoreSpheresUnitFrame:setText2(text)
  self.sphere.font2:SetText(text)
end

--- Sets the color for font 1.
-- @param color The color value.
--
function SeamoreSpheresUnitFrame:setTextColor(color)
  self.sphere.font1:SetTextColor(ss.Utils:GetRGBAValues(color))
end

--- Sets the color for font 2.
-- @param color The color value.
--
function SeamoreSpheresUnitFrame:setTextColor2(color)
  self.sphere.font2:SetTextColor(ss.Utils:GetRGBAValues(color))
end

--- Sets the text format.
-- @param textFormat The format value.
--
function SeamoreSpheresUnitFrame:setTextFormat(textFormat)
  self.textFormat        = textFormat
  self.sphere.textFormat = textFormat
end

--- Toggles sphere visibility. (safe)
-- @param show Flag to display the frames.
--
function SeamoreSpheresUnitFrame:show(show)
  self:ShowSphere(show)
end

--- Shows the sphere display.
--
function SeamoreSpheresUnitFrame:Show()
  self:ShowSphere(true)
end

--- Toggles font visibility.
-- @param font The target font.
-- @param show Flag to display the frame.
--
function SeamoreSpheresUnitFrame:showFont(font, show)
  if font == 1 and show == false then
    self.sphere.font1:Hide()
  elseif font == 2 and show == false then
    self.sphere.font2:Hide()
  elseif not font and show == false then
    self.sphere.font1:Hide()
    self.sphere.font2:Hide()
  elseif font == 1 then
    self.sphere.font1:Show()
  elseif font == 2 then
    self.sphere.font2:Show()
  else
    self.sphere.font1:Show()
    self.sphere.font2:Show()
  end
end

--- Displays the gradients.
--
function SeamoreSpheresUnitFrame:showGradients()
  if not InCombatLockdown() then
    self.sphere.fillingLeft.gradient:SetAlpha(1)
    self.sphere.fillingLeft.gradient:Show()
    self.sphere.fillingRight.gradient:SetAlpha(1)
    self.sphere.fillingRight.gradient:Show()
    
    self.sphere.filling.gradient:SetAlpha(1)
    self.sphere.filling.gradient:Show()
    
    if self.configName == "health" and self.displayMode == "active" then
      self.sphere.fillingAbsorb.gradient:SetAlpha(1)
      self.sphere.fillingAbsorb.gradient:Show()
    end
  end
end

--- Updates resource group textures.
-- @param index The group index.
-- @param textureGroup The texture group identifier.
-- @param texture The texture identifier.
--
function SeamoreSpheresUnitFrame:updateResourceGroupTextures(index, textureGroup, texture)
  local group = (index and index == 1) and "resourceGroup" or "secondaryResourceGroup"
  
  if self.sphere[group] then
    for k,v in pairs(self.sphere[group]) do
      v.filling:SetTexture(ss.FillTextures.get(textureGroup, texture))
      
      if ss.global.show.gradient then
        v.filling.gradient:SetTexture(ss.FillTextures.get(textureGroup, texture))
      end
    end
  end
end

--- Refreshes the sphere scale from storage.
--
function SeamoreSpheresUnitFrame:UpdateScale()
  local configName = (self.configName == "vehicle") and "health"
    or ((self.configName == "vehicleResource") and "mainResource" or self.configName)
  
  -- Set the scale using the proper config.
  if not self._globalScale or self._globalScale ~= ss.db[configName].general.globalScale then
    self:setScale(ss.db[configName].general.globalScale)
  end
end

rawset(_G, "SeamoreSpheresUnitFrame_OnLoad", function(self)
  sphereIndex = sphereIndex + 1
  self._sphereIndex = sphereIndex

  for k,v in pairs(SeamoreSpheresUnitFrame) do
    self[k] = v
  end
  
  ss.Extentions:MakeFadable(self)
end)

rawset(_G, "SeamoreSpheresUnitFrame", {
  DISPLAY_TYPE_ACTIVE = "active",
  DISPLAY_TYPE_PREVIEW = "preview",
  New = function(self, sphereConfig, parent, displayMode)
    displayMode = displayMode or self.DISPLAY_TYPE_ACTIVE
    _frameIndex[displayMode] = _frameIndex[displayMode] + 1
  
    local template = (displayMode == self.DISPLAY_TYPE_PREVIEW) and "SeamoreSpheresPreviewFrameTemplate" or "SeamoreSpheresUnitFrameTemplate"
    local name = ss._f .. sphereConfig.general.configName .. "_" .. string.gsub(template, "Template", tostring(_frameIndex[displayMode]))
    local base = CreateFrame("Button", name, parent, template)
  
    base.displayMode = displayMode
    base.dbConfig    = sphereConfig
    base.configName  = sphereConfig.general.configName
    base.name        = sphereConfig.general.name
    base.mode        = sphereConfig.modes[ss.profile.modeInfo[base.configName].modeSpec][ss.profile.modeInfo[base.configName].mode]
    base.textFormat  = sphereConfig.textFormat
    base.animPosX    = sphereConfig.animation.posX
    base.animPosY    = sphereConfig.animation.posY
    base.animGroup   = sphereConfig.animation.groupId
    base.animId      = sphereConfig.animation.id
    base.animCamDist = sphereConfig.animation.camDistance
    base.animAlpha   = sphereConfig.animation.alpha
    base.fillTexture = base.mode.fillTexture
    base.fillId      = base.mode.fillInfo.fillId
    base.fillGroup   = base.mode.fillInfo.fillGroup
    base.modeId      = ss.profile.modeInfo[base.configName].mode
  
    base.monitorConfig = base:Init(base.dbConfig.general.configName, base.displayMode)
  
    base:setPos(sphereConfig.general.anchor, sphereConfig.general.posX, sphereConfig.general.posY)
    base:setSwirlSpeed(base.mode.swirl.speed)
    base:showFont     (1, sphereConfig.font[1].show)
    base:showFont     (2, sphereConfig.font[2].show)
  
    return base
  end,
})
