-------------------------------------------------
-- Unit frame builder for an individual sphere --
-------------------------------------------------
local addon, ns = ...
local ss = ns.SS

local setmetatable = setmetatable
local math, tonumber, string, strlower = math, tonumber, string, strlower
local pairs, select, table, tostring = pairs, select, table, tostring
local InCombatLockdown = InCombatLockdown
local GetFramerate, min, max = GetFramerate, min, max
local GetScreenWidth, GetScreenHeight = GetScreenWidth, GetScreenHeight

--- 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 adjustMidForRaw = function(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 adjustMidFromRaw = function(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

local mtSphere = {
  displayMode     = "active",
  dbConfig        = {},
  sphere          = {},
  configName      = "",
  name            = "",
  modeId          = "",
  mode            = {},
  prevMode        = {},
  monitorConfig   = {},
  textFormat      = {},
  resourceMonitor = {},
  animPosX        = 0,
  animPosY        = 0,
  animId          = 0,
  animGroup       = 0,
  animCamDist     = 1,
  animAlpha       = 1,
  speed           = 5,
  fillTexture     = {},
  template        = {},
  anchor          = "",
  offsetX         = 0,
  offsetY         = 0,
};

--- Creates a resource group.
-- @param resource The resource data.
-- @param region The sphere region.
--
function mtSphere: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
    -- Add power tracking
    self.sphere["resourceGroupUnit"]   = nil
    self.sphere["resourceGroupUnitId"] = nil
    self.sphere["secondaryResourceGroupUnit"]   = nil
    self.sphere["secondaryResourceGroupUnitId"] = nil
  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 mtSphere: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:createGroupItemAssets(i, (resourceId == "buff") and resource.buffGroup[i] or resource, region)

    if resourceId ~= "buff" and ss.ClassResourceData.resourceAnimation(resource.name) then
      -- Add or update resource animation.
      if self.sphere[group.."Group"][i].resourceAnimation then
        self.sphere[group.."Group"][i].resourceAnimation:UpdateAnimation(ss.ClassResourceData.resourceAnimation(resource.name))
      else
        self.sphere[group.."Group"][i].resourceAnimation = ss.AnimationCreator:New(self.sphere[group.."Group"][i], ss.ClassResourceData.resourceAnimation(resource.name))
      end

      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
  end
end

--- Creates a secondary resource group.
-- @param resource The resource data.
-- @param region The sphere region.
--
function mtSphere: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)
  end
end

--- Creates the child frames.
-- @param parent The parent display.
-- @return The configured sphere.
--
function mtSphere:createAssets(parent)
  -- Add sphere base.
  local base = ss.BaseCreator:New(parent, self.dbConfig.general.configName, self.displayMode)

  -- Add sphere container
  local sphere = ss.ContainerCreator:New(base, self.dbConfig.general.configName, self.displayMode)

  -- Add sphere background
  sphere.bg = ss.BackgroundCreator:New(base)
  
  -- Add extra properties
  sphere.textFormat             = self.dbConfig.textFormat
  sphere.configName             = self.dbConfig.general.configName
  base.configName               = sphere.configName
  sphere.mode                   = self.dbConfig.modes[ss.profile.modeInfo[base.configName].modeSpec][ss.profile.modeInfo[base.configName].mode]
  local unitId, secondaryUnitId = ss.ClassResources.modeResourceUnitId(sphere.configName)

  if sphere.mode.fillColor[2] and secondaryUnitId then
    sphere.fillingLeft, sphere.fillingRight = ss.FillingCreator:New(
      sphere,
      self.dbConfig.general.size,
      self.displayMode,
      sphere.mode.fillColor[1][2],
      sphere.mode.fillTexture[1],
      sphere.mode.fillTexture[2],
      sphere.mode.fillColor[2][2])

    if self.displayMode == "active" then
      -- Add power tracking
      sphere.config = {
        ["name"]                     = self.dbConfig.general.configName,
        ["unit"]                     = self.dbConfig.general.unit,
        ["unitId"]                   = unitId,
        ["realHeight"]               = self.dbConfig.general.size * 0.95,
        ["textFormat"]               = self.dbConfig.textFormat,
        ["previousValue"]            = 0,
        ["secondaryUnitId"]          = secondaryUnitId,
        ["previousSecondaryValue"]   = 0,
        ["onMonitorUpdate"]          = self.resourceUpdate,
        ["onMonitorUpdateSecondary"] = self.resourceUpdate,
      };

      self.monitorConfig = sphere.config
    end
  
    if ss.global.show.gradient then
      -- Set gradient color.
      local r,g,b,a = sphere.fillingLeft:GetVertexColor()
      sphere.fillingLeft.gradient:SetTexture      (sphere.mode.fillTexture[1])
      sphere.fillingLeft.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
    
      r,g,b,a = sphere.fillingRight:GetVertexColor()
      sphere.fillingRight.gradient:SetTexture      (sphere.mode.fillTexture[2])
      sphere.fillingRight.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
    end

    -- Add divider
    sphere.divider = ss.DividerCreator:New(sphere)
  else
    sphere.filling = ss.FillingCreator:New(sphere, self.dbConfig.general.size, self.displayMode, sphere.mode.fillColor[1][2], sphere.mode.fillTexture[1], sphere.mode.fillTexture[2])
  
    if ss.global.show.gradient then
      -- Set gradient color.
      local r,g,b,a = sphere.filling:GetVertexColor()
      sphere.filling.gradient:SetTexture      (sphere.mode.fillTexture[1])
      sphere.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
    end
    
    if self.displayMode == "active" then
      -- Add power tracking
      sphere.config = {
        ["name"]            = self.dbConfig.general.configName,
        ["unit"]            = self.dbConfig.general.unit,
        ["unitId"]          = unitId,
        ["realHeight"]      = self.dbConfig.general.size * 1.086,
        ["textFormat"]      = self.dbConfig.textFormat,
        ["previousValue"]   = 0,
        ["onMonitorUpdate"] = self.resourceUpdate,
      };

     -- Add absorbs texture for health spheres
      if sphere.configName == "health" then
        local absorbColor = {r=1,g=1,b=1,a=0.5 }
        if (sphere.mode.fillColor[1][2].r > 0.7) and (sphere.mode.fillColor[1][2].g > 0.7) and (sphere.mode.fillColor[1][2].b > 0.7) then
          -- Ensure the absorbs are displayed on white/light grey values
          absorbColor = ss.private.general.resources.Absorb
        end

        sphere.fillingAbsorb                 = ss.FillingCreator:New(sphere, self.dbConfig.general.size, self.displayMode, absorbColor, sphere.mode.fillTexture[1], sphere.mode.fillTexture[2])
        sphere.fillingAbsorb.name            = "absorb"
        sphere.config.absorbUnitId           = "absorb"
        sphere.config.previousAbsorbValue    = 0

        if ss.global.show.gradient then
          -- Set gradient color.
          local r,g,b,a = sphere.fillingAbsorb:GetVertexColor()
          sphere.fillingAbsorb.gradient:SetTexture      (sphere.mode.fillTexture[1])
          sphere.fillingAbsorb.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
        end
      end

      self.monitorConfig = sphere.config
    end
  end

  -- Add swirl
  sphere.swirl1, sphere.swirl2, sphere.swirl3 = ss.Swirl:NewGroup(sphere, sphere.configName, sphere.mode.swirlColor)
  -- Add animation
  sphere.animation = ss.AnimationCreator:New(sphere, self.dbConfig.animation)
  -- Add gloss
  sphere.glossHolder = ss.Gloss:New(sphere)
  -- Add text
  sphere.fontHolder, sphere.font1, sphere.font2 = ss.FontCreator:New(sphere, self.displayMode, sphere.mode.fontColor[1], sphere.mode.fontColor[2], (sphere.mode.fillColor[2] and secondaryUnitId))

  sphere:Show()

  return sphere
end

--- Creates the micro sphere frames.
-- @param index The position to create.
-- @param resource The resource data.
-- @param region The sphere region.
-- @return The configured sphere.
--
function mtSphere:createGroupItemAssets(index, resource, region)
  -- base
  local base = ss.BaseCreator:NewGroupItem(self.sphere, self.dbConfig.general.configName, index, region)
  --container
  base._sphere = ss.ContainerCreator:NewGroupItem(base, self.dbConfig.general.configName, index)
  --Add background
  base._sphere.bg = ss.BackgroundCreator:New(base)
  --Add filling
  local fillIndex = region and 2 or 1
  base._sphere.filling = ss.FillingCreator:New(base._sphere,30,"microResource",resource[2], ss.FillTextures.get(ss.profile.microResourceFillTextureGroups[fillIndex], ss.profile.microResourceFillTextures[fillIndex]))
  -- Add animation
  base._sphere.animation = ss.AnimationCreator:NewGroupItem(base._sphere)
  -- Add gloss
  base._sphere.glossHolder = ss.Gloss:New(base._sphere)

  base._sphere.getTooltipLabel = function(self)
    return self.tooltipLabel
  end

  -- Add tooltip
  ss.Utils:AddTooltip(base, "ANCHOR_NONE", { ss.Utils:GetAnchors(base) }, nil, function(self)
    if self._sphere:getTooltipLabel() and self:GetAlpha() > 0 then
      -- Update the tooltip's display value.
      self.tLines = {
        self._sphere:getTooltipLabel(),
      }
    else
      return false
    end
  end)
  base._sphere:Show()

  return base._sphere
end

--- Updates the storage values.
--
function mtSphere: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)
  
    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 mtSphere:Get(p)
  return self[p]
end

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

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

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

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

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

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

--- Retrieves the fill texture color.
-- @return The color value.
--
function mtSphere: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 mtSphere:getFillInfo()
  return {
    fillGroup = self.fillGroup,
    fillId    = self.fillId,
  }
end

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

--- Retrieves the current mode.
-- @return The mode data.
--
function mtSphere: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 mtSphere:getModes(specName)
  return ss.db[self.configName].modes[specName] or ss.db[self.configName].modes[ss.profile.modeInfo[self.configName].modeSpec]
end

--- Retrieves the base frame's height.
-- @return The height value.
--
function mtSphere:GetHeight()
  return self.sphere:GetParent():GetHeight()
end

--- Returnrs the sphere position.
-- @param raw Flag to return raw values.
-- @return the position values.
-- @see adjustMidForRaw().
--
function mtSphere: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 mtSphere:getRawScales()
  local scale = self:getScale() / 100
  return scale, (1/scale)
end

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

--- Retrieves the swirl textures.
-- @return The texture value.
--
function mtSphere: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 mtSphere:getSwirlColor(index)
  index = index or 1
  return self.sphere["swirl"..index].texture:GetVertexColor()
end

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

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

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

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

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

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

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

--- Retrieves the base frame's with.
-- @return The width value.
--
function mtSphere:GetWidth()
  return self.sphere:GetParent():GetWidth()
end

--- Hides the sphere display.
--
function mtSphere:Hide()
  self:show(false)
end

--- Hides the gradients.
--
function mtSphere: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

--- Initializes a sphere display.
-- @param sphereConfig The stored configurate,
-- @param parent The parent container.
-- @param displayMode The sphere display mode.
--
function mtSphere:initialize(sphereConfig,parent,displayMode)
  self.displayMode = displayMode or "active"
  self.dbConfig    = sphereConfig
  self.configName  = sphereConfig.general.configName
  self.name        = sphereConfig.general.name
  self.mode        = sphereConfig.modes[ss.profile.modeInfo[self.configName].modeSpec][ss.profile.modeInfo[self.configName].mode]
  self.textFormat  = sphereConfig.textFormat
  self.animPosX    = sphereConfig.animation.posX
  self.animPosY    = sphereConfig.animation.posY
  self.animGroup   = sphereConfig.animation.groupId
  self.animId      = sphereConfig.animation.id
  self.animCamDist = sphereConfig.animation.camDistance
  self.animAlpha   = sphereConfig.animation.alpha
  self.animAlpha   = sphereConfig.animation.alpha
  self.fillTexture = self.mode.fillTexture
  self.fillId      = self.mode.fillInfo.fillId
  self.fillGroup   = self.mode.fillInfo.fillGroup
  self.modeId      = ss.profile.modeInfo[self.configName].mode
  self.sphere      = self:createAssets(parent)
  
  self:setPos(sphereConfig.general.anchor, sphereConfig.general.posX, sphereConfig.general.posY)
  self:setSwirlSpeed(self.mode.swirl.speed)
  self:showFont     (1, sphereConfig.font[1].show)
  self:showFont     (2, sphereConfig.font[2].show)

  if displayMode == "active" then
    ss.Utils:MakeFrameMovable(self.sphere:GetParent(), nil, self.onDrag)
  
    self.resourceMonitor = ss.ResourceMonitor:New()
    self.sphere.ResourceUpdate = self.resourceMonitor:Watch()
    self.sphere:HookScript("OnUpdate", function(self)
      if self.ResourceUpdate then
        self:ResourceUpdate()
      end
    end)
  end
end

--- Drag event handler.
-- Stores the new position.
-- @param frm A reference to the dragged frame.
--
function mtSphere.onDrag(frm)
  local configName = (frm.configName == "vehicle") and "health"
                       or ((frm.configName == "vehicleResource") and "mainResource" or frm.configName)

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

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

--- Refreshes sphere position from storage.
--
function mtSphere: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

--- Callback event for the resource monitor.
-- @param currVal The current resource value.
-- @param maxVal The max resource value.
-- @param height The texture height.
-- @param text The font 1 display. (optional)
-- @param filling The fill texture being updated.
-- @param prevVal The resource value being animated. (From prevVal to 0currVal)
-- @param valText The font 2 object. (optional)
-- @param textFormat The text format to use. (optional)
-- @param passed Flag to skip processing. (internal)
-- @return The animated value and display percentage. (used to continue progress)
--
function mtSphere.resourceUpdate(currVal, maxVal, height, text, filling, prevVal, valText, textFormat, passed)
  if (not tonumber(maxVal)) or (maxVal == 0) then maxVal = 1 end
  if (not tonumber(currVal)) then currVal = 1 end

  if passed and (currVal == prevVal) then
    -- dont process the resource if it isn't changing
    return currVal
  end

  -- If the previous value is higher than the maximum reset to max
  prevVal = (prevVal <= maxVal) and prevVal or maxVal

  -- Determine animation speed if enabled.
  local speed

  if ss.global.general.smoothAnimations == "slow" then
    speed = 15
  elseif ss.global.general.smoothAnimations == "med" then
    speed = 10
  elseif ss.global.general.smoothAnimations == "fast" then
    speed = 5
  end
  
  -- Update the background height.
  if speed then
    local limit = 30/GetFramerate()
    prevVal = prevVal + min((currVal-prevVal)/speed, max(currVal-prevVal, limit))
  
    if ((prevVal < currVal) and (prevVal / currVal) >= 0.99) or ((prevVal > currVal) and (currVal / prevVal) >= 0.99) then
      -- Ensure animation simply completes itself once steps are small enough
      prevVal = currVal
    end
  else
    prevVal = currVal
  end
  
  local targetTexHeight = (prevVal/maxVal * height)
  if targetTexHeight < 1 then targetTexHeight = 1 end
  local newDisplayPercentage = (prevVal/maxVal)

  if newDisplayPercentage > 1 then
    newDisplayPercentage = 1
    targetTexHeight      = height
  end

  if ss.global.show.gradient then
    filling:UpdateGradient(targetTexHeight, height)
  else
    filling:SetHeight  (targetTexHeight)
    filling:SetTexCoord(0, 1, math.abs(newDisplayPercentage - 1), 1)
  end
  
  if text or valText then
    local string = math.floor(newDisplayPercentage * 100)
    if tonumber(string) == nil then string = 0 end
    
    if text then
      text:SetText(string)
    end

    if valText then
      string = ss.Utils:valueFormat(currVal, textFormat, false)
      valText:SetText(string)
    end
  end

  return prevVal, newDisplayPercentage
end

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

--- Sets the sphere animation.
-- @param anim The animation details.
--
function mtSphere: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 mtSphere: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 mtSphere: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 mtSphere: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 mtSphere: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 mtSphere:setAnimationStrata(strata)
  self.sphere.animation:SetFrameLevel(strata or 3)
end

--- Sets the fill texture color.
-- @param color The color value.
--
function mtSphere:setFillColor(color)
  if color[2] and color[1]then
    self.sphere.fillingLeft :SetVertexColor(ss.Utils:GetRGBAValues(color[1]))
    self.sphere.fillingRight:SetVertexColor(ss.Utils:GetRGBAValues(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:SetVertexColor(ss.Utils:GetRGBAValues(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:SetVertexColor(ss.Utils:GetRGBAValues(color[1]))

    if ss.global.show.gradient then
      local r,g,b,a = ss.Utils:GetRGBAValues(color[1])
      self.sphere.filling.gradient:SetVertexColor(ss.Utils:GetRGBAValues(color[1]))
      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 mtSphere: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 mtSphere:setMode(mode, modeName)
  if not mode then return; end

  ss.profile.modeInfo[self.configName].mode = modeName or ss.profile.modeInfo[self.configName].mode
  self.prevMode    = self.mode
  self.mode        = mode
  self.sphere.mode = mode
  self.fillId      = mode.fillInfo.fillId
  self.fillGroup   = mode.fillInfo.fillGroup

  -- adjust sphere colors
  local unitId, secondaryUnitId = ss.ClassResources.modeResourceUnitId(self.configName)

  if self.mode.fillColor and self.mode.fillColor[2] and secondaryUnitId then
    if self.sphere.filling then
      self.sphere.filling:SetAlpha(0)

      if ss.global.show.gradient then
        self.sphere.filling.gradient:SetGradientAlpha("VERTICAL",0,0,0,0,0,0,0,0)
      end
    end

    -- display the proper fill texture frames are displayed
    if not self.sphere.fillingLeft then
      self.sphere.fillingLeft, self.sphere.fillingRight = ss.FillingCreator:New(
        self.sphere,
        self.dbConfig.general.size,
        self.displayMode,
        self.sphere.mode.fillColor[1][2],
        self.sphere.mode.fillTexture[1],
        self.sphere.mode.fillTexture[2],
        self.sphere.mode.fillColor[2][2],
        self.displayMode)

      self.sphere.divider = ss.DividerCreator:New(self.sphere)
    else
      self.sphere.fillingLeft :SetVertexColor(ss.Utils:GetRGBAValues(self.mode.fillColor[1][2]))
      self.sphere.fillingRight:SetVertexColor(ss.Utils:GetRGBAValues(self.mode.fillColor[2][2] or self.mode.fillColor[1][2]))

      if not self.mode.fillTexture[2] then
        local t = ss.FillTextures.get("split", 1)
        self.sphere.fillingLeft :SetTexture(t[1])
        self.sphere.fillingRight:SetTexture(t[2])

        if ss.global.show.gradient then
          self.sphere.fillingLeft.gradient  :SetTexture(t[1])
          self.sphere.fillingRight.gradient:SetTexture(t[2])
        end
      else
        local t = ss.FillTextures.get("split", 1)
        self.sphere.fillingLeft :SetTexture(t[1])
        self.sphere.fillingRight:SetTexture(t[2])

        if ss.global.show.gradient then
          self.sphere.fillingLeft.gradient  :SetTexture(self.mode.fillTexture[1])
          self.sphere.fillingRight.gradient:SetTexture(self.mode.fillTexture[2])
        end
      end
    end

    ss.FontCreator:SetFonts(self.sphere.font1, self.sphere.font2, true)

    if self.displayMode == "active" then
      -- keep previous val if set to avoid "restarting" the sphere visually.
      local prevVal  = self.monitorConfig.previousValue          or 0
      local prevVal2 = self.monitorConfig.previousSecondaryValue or 0

      self.monitorConfig = {
        ["name"]                     = self.configName,
        ["unit"]                     = self.dbConfig.general.unit,
        ["unitId"]                   = unitId,
        ["realHeight"]               = self.dbConfig.general.size * 0.95,
        ["textFormat"]               = self.dbConfig.textFormat,
        ["previousValue"]            = prevVal,
        ["secondaryUnitId"]          = secondaryUnitId,
        ["previousSecondaryValue"]   = prevVal2,
        ["onMonitorUpdate"]          = self.resourceUpdate,
        ["onMonitorUpdateSecondary"] = self.resourceUpdate,
      };

      self.sphere.config = self.monitorConfig;
    elseif not self.mode.fillTexture[2] then
      self.sphere.mode.fillTexture  = ss.FillTextures.get("split", 1)
      self.sphere.mode.fillColor[2] = self.sphere.mode.fillColor[1]
    end

    -- Update sphere colors
    self.sphere.fillingLeft :SetAlpha(1)
    self.sphere.fillingRight:SetAlpha(1)
    self.sphere.divider     :SetAlpha(1)

    if ss.global.show.gradient then
      local r,g,b,a = self.sphere.fillingLeft:GetVertexColor()
      self.sphere.fillingLeft.gradient   :SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)

      r,g,b,a = self.sphere.fillingRight    :GetVertexColor()
      self.sphere.fillingRight.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
    end
  else
    if self.sphere.fillingLeft then
      -- clear power tracking and hide previous textures
      self.sphere.fillingLeft :SetAlpha(0)
      self.sphere.fillingRight:SetAlpha(0)
      self.sphere.divider     :SetAlpha(0)

      if ss.global.show.gradient then
        self.sphere.fillingLeft.gradient  :SetGradientAlpha("VERTICAL",0,0,0,0,0,0,0,0)
        self.sphere.fillingRight.gradient:SetGradientAlpha("VERTICAL",0,0,0,0,0,0,0,0)
      end
    end

    -- display the proper fill texture frames are displayed
    if not self.sphere.filling then
      self.sphere.filling = ss.FillingCreator:New(
        self.sphere,
        self.dbConfig.general.size,
        self.displayMode,
        self.sphere.mode.fillColor[1][2],
        self.sphere.mode.fillTexture[1],
        self.sphere.mode.fillTexture[2])
    else
      self.sphere.filling:SetTexture    (self.mode.fillTexture[2] and ss.FillTextures.defaultTexture or self.mode.fillTexture[1])
      self.sphere.filling:SetVertexColor(ss.Utils:GetRGBAValues(self.mode.fillColor[1][2]))
    end

    ss.FontCreator:SetFonts(self.sphere.font1, self.sphere.font2, false)

    if self.displayMode == "active" then
      -- keep previous val if set to avoid "restarting" the sphere visually.
      local prevVal       = self.monitorConfig.previousValue       or 0
      local prevAbsorbVal = self.monitorConfig.previousAbsorbValue or 0

      -- replace power tracking
      self.monitorConfig = {
        ["name"]            = self.configName,
        ["unit"]            = self.dbConfig.general.unit,
        ["unitId"]          = unitId,
        ["realHeight"]      = self.dbConfig.general.size * 1.086,
        ["textFormat"]      = self.dbConfig.textFormat,
        ["previousValue"]   = prevVal,
        ["onMonitorUpdate"] = self.resourceUpdate,
      };

      -- Update Absorb texture if required
      if self.sphere.configName == "health" or self.sphere.configName == "vehicle" then
        local absorbColor = {r=1,g=1,b=1,a=0.5}
        if (self.mode.fillColor[1][2].r > 0.7) and (self.mode.fillColor[1][2].g > 0.7) and (self.mode.fillColor[1][2].b > 0.7) then
          -- Ensure the absorbs are displayed on white/light grey values
          absorbColor = ss.private.general.resources.Absorb
        end
        self.monitorConfig.absorbUnitId        = "absorb"
        self.monitorConfig.previousAbsorbValue = prevAbsorbVal
        self.sphere.fillingAbsorb:SetVertexColor(ss.Utils:GetRGBAValues(absorbColor))

        if ss.global.show.gradient then
          local r,g,b,a = ss.Utils:GetRGBAValues(absorbColor)
          self.sphere.fillingAbsorb.gradient:SetVertexColor  (r,g,b,a)
          self.sphere.fillingAbsorb.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
        end
      end

      self.sphere.config = self.monitorConfig;
    elseif self.sphere.mode.fillTexture[2] then
      self.sphere.mode.fillTexture = {
        [1] = ss.FillTextures.defaultTexture,
      };
    end

    -- Update sphere
    if ss.global.show.gradient then
      self.sphere.filling.gradient:SetTexture      (self.mode.fillTexture[2] and ss.FillTextures.defaultTexture or self.mode.fillTexture[1])
      local r,g,b,a = self.sphere.filling:GetVertexColor()
      self.sphere.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
    end
  end

  self.sphere.font1:SetTextColor  (ss.Utils:GetRGBAValues(self.mode.fontColor[1]))
  self.sphere.font2:SetTextColor  (ss.Utils:GetRGBAValues(self.mode.fontColor[2]))
  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)
  ss.Swirl         :UpdateGroup   (self.sphere, self.sphere.mode.swirl, self.sphere.mode.swirlColor, size)
  self.fillTexture = self.mode.fillTexture
end

--- Updates the sphere position.
-- @param raw Flag to use raw values.
-- @see adjustMidFromRaw().
--
function mtSphere: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.sphere:GetParent():ClearAllPoints()
      self.sphere:GetParent():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 mtSphere:setResource(resource)
  if not resource and not resource.modes[ss.profile.modeInfo[resource.general.configName].modeSpec] then
    return;
  end

  if self.displayMode == "active" and not InCombatLockdown() and resource.general.configName ~= "vehicle" and resource.general.configName ~= "vehicleResource" then
    self:show(true);
    self.sphere:GetParent():SetHeight(ss.global.general.SphereSize / resource.general.scale)
    self.sphere:GetParent():SetWidth (ss.global.general.SphereSize / resource.general.scale)
    self.sphere:GetParent():ClearAllPoints()
    self.sphere:GetParent():SetPoint (resource.general.anchor, resource.general.posX, resource.general.posY)
  end

  self.dbConfig                      = resource
  self.sphere.textFormat             = resource.textFormat
  self.textFormat                    = resource.textFormat
  self.modeId                        = ss.profile.modeInfo[resource.general.configName].mode
  local mode                         = resource.modes[ss.profile.modeInfo[resource.general.configName].modeSpec][ss.profile.modeInfo[resource.general.configName].mode]
  self.sphere.configName             = resource.general.configName
  self.sphere:GetParent().configName = resource.general.configName
  self.configName                    = resource.general.configName
  self:setSwirlSpeed  (mode.swirl.speed)
  self:setSwirlTexture(mode.swirl.id)
  self:setAnimation   (resource.animation)

  if self.displayMode == "preview" then
    self:setAnimationStrata(resource.animation.frameLevel + ss.private.layout.config.frameLevelAdjust)
  end

  self:showFont(1, resource.font[1].show)
  self:showFont(2, resource.font[2].show)
  self:setMode (mode, ss.profile.modeInfo[resource.general.configName].mode)
end

--- Sets the sphere scale.
-- @param scale The scale value.
--
function mtSphere: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.sphere:GetParent():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 mtSphere:setSwirlColor(color, index)
  if not index then
    self.sphere.swirl1.texture:SetVertexColor(ss.Utils:GetRGBAValues(color))
    self.sphere.swirl2.texture:SetVertexColor(ss.Utils:GetRGBAValues(color))
    self.sphere.swirl3.texture:SetVertexColor(ss.Utils:GetRGBAValues(color))
  else
    self.sphere["swirl"..index].texture:SetVertexColor(ss.Utils:GetRGBAValues(color))
  end
end

--- Sets the swirl speed.
-- @param speed The speed value.
--
function mtSphere: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 mtSphere: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 mtSphere:setText(text)
  self.sphere.font1:SetText(text)
end

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

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

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

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

--- Toggles sphere visibility. (safe)
-- @param show Flag to display the frames.
--
function mtSphere:show(show)
  if InCombatLockdown() then
      ss.PendingTask.add   ("sphere:show"..tostring(self._sphereIndex), function()
        -- Run outside of combat.
        self.sphere:GetParent():EnableMouse(show)
      end)
  else
    self.sphere:GetParent():EnableMouse(show)
  end
  
  if not show then
    self.sphere:GetParent():SetAlpha(0)
    self.sphere:SetAlpha (0)
    self.sphere.ResourceUpdate = nil
  else
    self.sphere:GetParent():SetAlpha(1)
    self.sphere:SetAlpha (1)
    self.sphere.ResourceUpdate = (self.displayMode == "active") and self.resourceMonitor:Watch() or nil
  end
end

--- Shows the sphere display.
--
function mtSphere:Show()
  self:show(true)
end

--- Toggles font visibility.
-- @param font The target font.
-- @param show Flag to display the frame.
--
function mtSphere: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 mtSphere:showGradients()
  if not InCombatLockdown() then
    if self.sphere.fillingLeft and self.sphere.fillingRight and not self.sphere.fillingLeft.gradient and not self.sphere.fillingRight.gradient then
      self.sphere.fillingLeft.gradient, self.sphere.fillingRight.gradient = ss.FillingCreator.createSplitGradients(
        self.sphere,
        self.sphere.fillingLeft,
        self.sphere.fillingRight,
        self.dbConfig.general.size)
    elseif self.sphere.fillingLeft and self.sphere.fillingRight then
      self.sphere.fillingLeft.gradient:SetAlpha(1)
      self.sphere.fillingLeft.gradient:Show()
      self.sphere.fillingRight.gradient:SetAlpha(1)
      self.sphere.fillingRight.gradient:Show()
    end

    if self.sphere.filling and not self.sphere.filling.gradient then
      self.sphere.filling.gradient = ss.FillingCreator.createFillGradient(self.sphere, self.sphere.filling, self.dbConfig.general.size)
    elseif self.sphere.filling then
      self.sphere.filling.gradient:SetAlpha(1)
      self.sphere.filling.gradient:Show()
    end

    if self.sphere.fillingAbsorb and not self.sphere.fillingAbsorb.gradient then
      self.sphere.fillingAbsorb.gradient = ss.FillingCreator.createFillGradient(self.sphere, self.sphere.fillingAbsorb, self.dbConfig.general.size)
    elseif self.sphere.fillingAbsorb then
      self.sphere.fillingAbsorb.gradient:SetAlpha(1)
      self.sphere.fillingAbsorb.gradient:Show()
    end

    if self.sphere.resourceGroup then
      for k,v in pairs(self.sphere.resourceGroup) do
        v.filling.gradient = v.filling.gradient or ss.FillingCreator.createFillGradient(v, v.filling, (30 * 1.086))
      end
    end

    if self.sphere.secondaryResourceGroup then
      for k,v in pairs(self.sphere.secondaryResourceGroup) do
        v.filling.gradient = v.filling.gradient or ss.FillingCreator.createFillGradient(v, v.filling, (30 * 1.086))
      end
    end
  end
end

--- Toggles the resource group visibility.
-- @param visible Flag to display the frames.
--
function mtSphere:toggleResourceGroup(visible)
  if visible and self.sphere.resourceGroup and self.sphere.resourceGroupUnit then
    for k,v in pairs(self.sphere.resourceGroup) do
      if self.sphere.resourceGroupUnitId == "buff" then
        if k <= table.getn(self.sphere.resourceGroupUnit.buffGroup) then
          v.filling    :SetAlpha      (1)
          v.filling    :SetVertexColor(ss.Utils:GetRGBAValues(self.sphere.resourceGroupUnit.buffGroup[k][2]))
          v:GetParent():SetAlpha(1)
        else
          v:GetParent():SetAlpha(0)
        end
      elseif k <= select(3, self.sphere.resourceGroupUnit.getMinMax(self.sphere.resourceGroupUnitId)) then
        v.filling    :SetVertexColor(ss.Utils:GetRGBAValues(self.sphere.resourceGroupUnit[2]))
        v.filling    :SetAlpha      (0)
        v:GetParent():SetAlpha      (1)
      else
        v:GetParent():SetAlpha(0)
      end
    end
  elseif self.sphere.resourceGroup then
    for k,v in pairs(self.sphere.resourceGroup) do
      v:GetParent():SetAlpha(0)
    end
  end
end

--- Toggles the secondary resource group visibility.
-- @param visible Flag to display the frames.
--
function mtSphere:toggleSecondaryResourceGroup(visible)
  if visible and self.sphere.secondaryResourceGroup and self.sphere.secondaryResourceGroupUnit then
    for k,v in pairs(self.sphere.secondaryResourceGroup) do
      if self.sphere.secondaryResourceGroupUnitId == "buff" then
        local index = 11 - k
        if index <= table.getn(self.sphere.secondaryResourceGroupUnit.buffGroup) then
          v.filling    :SetAlpha      (1)
          v.filling    :SetVertexColor(ss.Utils:GetRGBAValues(self.sphere.secondaryResourceGroupUnit.buffGroup[index][2]))
          v:GetParent():SetAlpha(1)
        else
          v:GetParent():SetAlpha(0)
        end
      elseif k >= 11 - select(3, self.sphere.secondaryResourceGroupUnit.getMinMax(self.sphere.secondaryResourceGroupUnitId)) then
        v.filling    :SetVertexColor(ss.Utils:GetRGBAValues(self.sphere.secondaryResourceGroupUnit[2]))
        v.filling    :SetAlpha      (0)
        v:GetParent():SetAlpha      (1)
      else
        v:GetParent():SetAlpha(0)
      end
    end
  elseif self.sphere.secondaryResourceGroup then
    for k,v in pairs(self.sphere.secondaryResourceGroup) do
      v:GetParent():SetAlpha(0)
    end
  end
end

--- Updates resource group textures.
-- @param index The group index.
-- @param textureGroup The texture group identifier.
-- @param texture The texture identifier.
--
function mtSphere: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 mtSphere: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

local sphereIndex = 0

ss["Sphere"] = {};

--- Creates a new sphere display class.
-- @return the class instance.
--
function ss.Sphere:New()
  sphereIndex = sphereIndex + 1
  local instance = {
    _sphereIndex = sphereIndex
  }
  
  setmetatable(instance, mtSphere)
  mtSphere.__index = mtSphere

  return instance
end
