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

local setmetatable = setmetatable
local math, tonumber = math, tonumber
local pairs, select, table = pairs, select, table
local InCombatLockdown = InCombatLockdown
local GetFramerate, min, max = GetFramerate, min, max

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,
};

function mtSphere:AddResourceGroup(resource, region)
  self.sphere.resourceGroup = self.sphere.resourceGroup 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
      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
  end
end

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

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 = tonumber(resource[1]) and select(3, resource.getMinMax(resource[1])) or 1
    self:AddMicroGroup(resource, resource[1], 10, 11 - max, -1, "secondaryResource", region)
  end
end

function mtSphere:createAssets(parent)
  -- Add sphere base.
  local base = ss.BaseCreator:New(parent, self.dbConfig.general.configName, self.displayMode)
  base:SetScale((self.displayMode == "active") and (ss.global.general.scale / 100) or 1)

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

function mtSphere:createGroupItemAssets(index, resource, region)
  -- base
  local base = ss.BaseCreator:NewGroupItem(self.sphere, self.dbConfig.general.configName, index, region)
  --container
  local sphere = ss.ContainerCreator:NewGroupItem(base, self.dbConfig.general.configName, index)
  --Add background
  sphere.bg = ss.BackgroundCreator:New(base)
  --Add filling
  local fillIndex = region and 2 or 1
  sphere.filling = ss.FillingCreator:New(sphere,30,"microResource",resource[2], ss.FillTextures.get(ss.profile.microResourceFillTextureGroups[fillIndex], ss.profile.microResourceFillTextures[fillIndex]))
  -- Add animation
  sphere.animation = ss.AnimationCreator:NewGroupItem(sphere)
  -- Add gloss
  sphere.glossHolder = ss.Gloss:New(sphere)
  -- Add tooltip
  base.tooltip = ss.GeneralTooltip:New(index, region)
  base:SetScript("OnLeave", function(self) base.tooltip:Hide(); end)
  base:SetScript("OnEnter", function(self)
    self.tooltip:ClearLines()

    if sphere.tooltipLabel and self:GetAlpha() > 0 then
      self.tooltip:SetOwner(self,"CENTER")
      self.tooltip:AddLine (sphere.tooltipLabel)
      self.tooltip:Show()
    end
  end)

  sphere:Show()

  return sphere
end

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: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: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()
  end
end

function mtSphere:getSwirlSpeed()
  return self.sphere.swirl1:GetSpeed()
end

function mtSphere:Get(p)
  return self[p]
end

function mtSphere:getMode()
  return self.mode or self.sphere.mode
end

function mtSphere:getModes(specName)
  return ss.db[self.configName].modes[specName] or ss.db[self.configName].modes[ss.profile.modeInfo[self.configName].modeSpec]
end

function mtSphere:getAnimationStrata()
  return self.sphere.animation:GetFrameLevel()
end

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

function mtSphere:getFillInfo()
  return {
    fillGroup = self.fillGroup,
    fillId    = self.fillId,
  }
end

function mtSphere:getFillTexture()
  return self.fillTexture
end

function mtSphere:getSwirlTextures()
  return {
    self.sphere.swirl1.texture:GetTexture(),
    self.sphere.swirl2.texture:GetTexture(),
    self.sphere.swirl3.texture:GetTexture(),
  }
end

function mtSphere:getSwirlId()
  return self.sphere.swirlId
end

function mtSphere:getSwirlColor(index)
  index = index or 1
  return self.sphere["swirl"..index].texture:GetVertexColor()
end

function mtSphere:getAnimation()
  return self.dbConfig.animation
end

function mtSphere:getAnimationDisplayInfo()
  return self.animGroup, self.animId
end

function mtSphere:getAnimationOffset()
  return self.animPosX, self.animPosY
end

function mtSphere:getAnimationAlpha()
  return self.animAlpha
end

function mtSphere:getAnimationScale()
  return self.animCamDist
end

function mtSphere:getTextColor()
  return self.sphere.font1:GetTextColor()
end

function mtSphere:getTextColor2()
  return self.sphere.font2:GetTextColor()
end

function mtSphere:getText()
  return self.sphere.font1:GetText()
end

function mtSphere:getText2()
  return self.sphere.font2:GetText()
end

function mtSphere:getTextFormat()
  return self.textFormat
end

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:setSwirlSpeed(self.mode.swirl.speed)
  self:showFont     (1, sphereConfig.font[1].show)
  self:showFont     (2, sphereConfig.font[2].show)

  if displayMode == "active" then
    ss:MakeFrameMovable(self.sphere:GetParent(), nil, self.onDrag)

    self.resourceMonitor = ss.ResourceMonitor:New()
    self.sphere:SetScript("OnUpdate",self.resourceMonitor:Watch())
  end
end

function mtSphere.onDrag(self)
  ss.db[self.configName].general.anchor, ss.db[self.configName].general.posX, ss.db[self.configName].general.posY = select(3, self:GetPoint())
end

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

  -- Update the background height.
  if ss.global.general.smoothAnimations == "slow" then
    local limit = 30/GetFramerate()
    prevVal = prevVal + min((currVal-prevVal)/15, max(currVal-prevVal, limit))
  
    if ((prevVal < currVal) and (prevVal / currVal) >= 0.99) or ((prevVal > currVal) and (prevVal / currVal) <= 0.01) then
      -- Ensure animation simply completes itself once steps are small enough
      prevVal = currVal
    end
  elseif ss.global.general.smoothAnimations == "med" then
    local limit = 30/GetFramerate()
    prevVal = prevVal + min((currVal-prevVal)/10, max(currVal-prevVal, limit))
  
    if ((prevVal < currVal) and (prevVal / currVal) >= 0.99) or ((prevVal > currVal) and (prevVal / currVal) <= 0.01) then
      -- Ensure animation simply completes itself once steps are small enough
      prevVal = currVal
    end
  elseif ss.global.general.smoothAnimations == "fast" then
    local limit = 30/GetFramerate()
    prevVal = prevVal + min((currVal-prevVal)/5, max(currVal-prevVal, limit))
  
    if ((prevVal < currVal) and (prevVal / currVal) >= 0.99) or ((prevVal > currVal) and (prevVal / currVal) <= 0.01) 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:valueFormat(currVal, textFormat, false)
      valText:SetText(string)
    end
  end

  return prevVal, newDisplayPercentage
end

function mtSphere:Set(p, v)
  if self[p] then
    self[p] = v
  end
end

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

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

function mtSphere:setAnimationAlpha(alpha)
  self.sphere.animation:SetAlpha(alpha)
  self.animAlpha = alpha
end

function mtSphere:setAnimationScale(val)
  self.sphere.animation:SetCamDistanceScale(val)
  self.animCamDist = val
end

function mtSphere:setAnimationDisplayInfo(groupId, id)
  self.sphere.animation:SetDisplayInfo(ss.Animations.get(groupId, id))
  self.animGroup = groupId
  self.animId    = id
end

function mtSphere:setAnimationStrata(strata)
  self.sphere.animation:SetFrameLevel(strata or 3)
end

function mtSphere:setFillColor(color)
  if color[2] and color[1]then
    self.sphere.fillingLeft :SetVertexColor(ss:GetRGBAValues(color[1]))
    self.sphere.fillingRight:SetVertexColor(ss:GetRGBAValues(color[2]))

    if ss.global.show.gradient then
      local r,g,b,a = ss: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: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:GetRGBAValues(color[2]))

    if ss.global.show.gradient then
      local r,g,b,a = ss: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:GetRGBAValues(color[1]))

    if ss.global.show.gradient then
      local r,g,b,a = ss:GetRGBAValues(color[1])
      self.sphere.filling.gradient:SetVertexColor(ss:GetRGBAValues(color[1]))
      self.sphere.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,a,r,g,b,0)
    end
  end
end

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

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:GetRGBAValues(self.mode.fillColor[1][2]))
      self.sphere.fillingRight:SetVertexColor(ss: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
        self.sphere.fillingLeft :SetTexture(self.mode.fillTexture[1])
        self.sphere.fillingRight:SetTexture(self.mode.fillTexture[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.get("primary","moon") or self.mode.fillTexture[1])
      self.sphere.filling:SetVertexColor(ss: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:GetRGBAValues(absorbColor))

        if ss.global.show.gradient then
          local r,g,b,a = ss: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.get("primary","moon"),
      };
    end

    -- Update sphere
    if ss.global.show.gradient then
      self.sphere.filling.gradient:SetTexture      (self.mode.fillTexture[2] and ss.FillTextures.get("primary","moon") 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:GetRGBAValues(self.mode.fontColor[1]))
  self.sphere.font2:SetTextColor  (ss: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

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():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

function mtSphere:setSwirlColor(color, index)
  if not index then
    self.sphere.swirl1.texture:SetVertexColor(ss:GetRGBAValues(color))
    self.sphere.swirl2.texture:SetVertexColor(ss:GetRGBAValues(color))
    self.sphere.swirl3.texture:SetVertexColor(ss:GetRGBAValues(color))
  else
    self.sphere["swirl"..index].texture:SetVertexColor(ss:GetRGBAValues(color))
  end
end

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

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

function mtSphere:setText(text)
  self.sphere.font1:SetText(text)
end

function mtSphere:setText2(text)
  self.sphere.font2:SetText(text)
end

function mtSphere:setTextColor(color)
  self.sphere.font1:SetTextColor(ss:GetRGBAValues(color))
end

function mtSphere:setTextColor2(color)
  self.sphere.font2:SetTextColor(ss:GetRGBAValues(color))
end

function mtSphere:setTextFormat(textFormat)
  self.textFormat        = textFormat
  self.sphere.textFormat = textFormat
end

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

function mtSphere:Show()
  self.sphere:GetParent():Show()
end

function mtSphere:Hide()
  self.sphere:GetParent():Hide()
end

function mtSphere:show(show)
  if not show then
    self.sphere:GetParent():SetAlpha(0)
    self.sphere:SetAlpha (0)
    self.sphere:SetScript("OnUpdate",nil)
  else
    self.sphere:GetParent():SetAlpha(1)
    self.sphere:SetAlpha (1)
    self.sphere:SetScript("OnUpdate",(self.displayMode == "active") and self.resourceMonitor:Watch() or nil)
  end
end

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

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

function mtSphere:toggleResourceGroup(visible)
  if visible and self.sphere.resourceGroup 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:GetRGBAValues(self.sphere.resourceGroupUnit.buffGroup[k][2]))
          v:GetParent():Show()
        else
          v:GetParent():Hide()
        end
      elseif k <= select(3, self.sphere.resourceGroupUnit.getMinMax(self.sphere.resourceGroupUnitId)) then
        v.filling    :SetVertexColor(ss:GetRGBAValues(self.sphere.resourceGroupUnit[2]))
        v.filling    :SetAlpha      (0)
        v:GetParent():SetAlpha      (1)
        v:GetParent():Show()
      else
        v:GetParent():Hide()
      end
    end
  elseif self.sphere.resourceGroup then
    for k,v in pairs(self.sphere.resourceGroup) do
      v:GetParent():Hide()
    end
  end
end

function mtSphere:toggleSecondaryResourceGroup(visible)
  if visible and self.sphere.secondaryResourceGroup 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:GetRGBAValues(self.sphere.secondaryResourceGroupUnit.buffGroup[index][2]))
          v:GetParent():Show()
        else
          v:GetParent():Hide()
        end
      elseif k >= 11 - select(3, self.sphere.secondaryResourceGroupUnit.getMinMax(self.sphere.secondaryResourceGroupUnitId)) then
        v.filling    :SetVertexColor(ss:GetRGBAValues(self.sphere.secondaryResourceGroupUnit[2]))
        v.filling    :SetAlpha      (0)
        v:GetParent():SetAlpha      (1)
        v:GetParent():Show()
      else
        v:GetParent():Hide()
      end
    end
  elseif self.sphere.secondaryResourceGroup then
    for k,v in pairs(self.sphere.secondaryResourceGroup) do
      v:GetParent():Hide()
    end
  end
end

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

function mtSphere:UpdateScale()
  if not InCombatLockdown() then
    self.sphere:GetParent():SetScale(ss.global.general.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()
        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()
        end
      end
    end
  end
end

ss["Sphere"] = {};

function ss.Sphere:New()
  local instance = {}
  setmetatable(instance, mtSphere)
  mtSphere.__index = mtSphere

  return instance
end
