local SeamoreSpheresMicroSphere = {}
local AddOnName, nsVars = ...
local ss = nsVars.SS

local STAGGER_RED_TRANSITION, STAGGER_YELLOW_TRANSITION = STAGGER_RED_TRANSITION, STAGGER_YELLOW_TRANSITION
local GetTime, UnitIsDeadOrGhost = GetTime, UnitIsDeadOrGhost
local GetRuneCooldown, UIParent = GetRuneCooldown, UIParent
local math, cos, sin = math, cos, sin

local microSphereIndex = 0
local microSphereWidth = (30 * 1.086)

-- Micro sphere locations.
local resourceGroupPoints = {
  [1] = {
    [1] = -471.22714233398,
    [2] = 133.99948120117,
  },
  [2] = {
    [1] = -447.04940795898,
    [2] = 159.79916381836,
  },
  [3] = {
    [1] = -417.14456176758,
    [2] = 174.73603820801,
  },
  [4] = {
    [1] = -382.66906738281,
    [2] = 178.61676025391,
  },
  [5] = {
    [1] = -348.99588012695,
    [2] = 170.17042541504,
  },
  [6] = {
    [1] = -319.02352905273,
    [2] = 149.04713439941,
  },
  [7] = {
    [1] = -299.69085693359,
    [2] = 120.04951477051,
  },
  [8] = {
    [1] = -291.75930786133,
    [2] = 85.393577575684,
  },
  [9] = {
    [1] = -296.63027954102,
    [2] = 50.645896911621,
  },
  [10] = {
    [1] = -312.75939941406,
    [2] = 20.778709411621,
  },
}

--- Micro Sphere Update Functions.
-------------------------------------

local function checkVisibility(seamoreSpheresMicroSphereFrame, visible)
  if visible and seamoreSpheresMicroSphereFrame:GetAlpha() == 0 then
    seamoreSpheresMicroSphereFrame:Fade(1)
  elseif not visible and seamoreSpheresMicroSphereFrame:GetAlpha() ~= 0 then
    seamoreSpheresMicroSphereFrame:Fade(0)
  end
end

--- Updates a display for current stagger.
-- @param display The parent display.
-- @param displayUnit The unit identifier.
--
local function updateResourceStagger(seamoreSpheresMicroSphereFrame, displayUnit)
  local _, curStagger, maxHealth = displayUnit.getMinMax()
  seamoreSpheresMicroSphereFrame.sphere.tooltipLabel = ss.T["lbl_stagger"]
  
  if (curStagger / maxHealth) > STAGGER_RED_TRANSITION then
    seamoreSpheresMicroSphereFrame:Fade(1)
    seamoreSpheresMicroSphereFrame.sphere.filling:SetVertColor(ss.ClassResourceData.resourceColor("stagger heavy"))
  elseif (curStagger / maxHealth) > STAGGER_YELLOW_TRANSITION then
    seamoreSpheresMicroSphereFrame:Fade(1)
    seamoreSpheresMicroSphereFrame.sphere.filling:SetVertColor(ss.ClassResourceData.resourceColor("stagger medium"))
  elseif curStagger > 0 then
    seamoreSpheresMicroSphereFrame:Fade(1)
    seamoreSpheresMicroSphereFrame.sphere.filling:SetVertColor(ss.ClassResourceData.resourceColor("stagger light"))
  else
    seamoreSpheresMicroSphereFrame:Fade(0)
  end
  
  if ss.global.show.gradient then
    local r,g,b = ss.Utils:GetRGBAValues(ss.ClassResourceData.resourceColor("stagger light"))
    seamoreSpheresMicroSphereFrame.sphere.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,0,r,g,b,0)
  end
end

local function updateResourceBuff(seamoreSpheresMicroSphereFrame, displayUnit)
  local _, cur, _, duration, expires = displayUnit.getMinMax(displayUnit[1])
  
  local rem = expires - GetTime()
  rem = rem / duration
  
  if rem > 1 then
    rem = 1
  end
  
  local show = cur == 1 and (rem >= 0) and (rem <= 1)

  checkVisibility(seamoreSpheresMicroSphereFrame, show)

  if show then
    if ss.global.show.gradient then
      seamoreSpheresMicroSphereFrame.sphere.filling:UpdateGradient((rem * microSphereWidth), microSphereWidth, displayUnit[2], rem)
    end
    
    local texCoord = rem - ss.gradientScale
    seamoreSpheresMicroSphereFrame.sphere.filling:SetHeight  ((microSphereWidth * rem) - (microSphereWidth * ss.gradientScale))
    seamoreSpheresMicroSphereFrame.sphere.filling:SetTexCoord(0,1,math.abs(texCoord - 1),1)
    seamoreSpheresMicroSphereFrame.sphere.filling:SetAlpha   (rem)
  end
end

local function updateResource(seamoreSpheresMicroSphereFrame, displayUnit, curRes, maxRes, start, duration)
  local curAlpha = seamoreSpheresMicroSphereFrame.sphere.filling:GetAlpha()
  local validation1, validation2, validation3, validation4, validation5
  
  validation1 = (seamoreSpheresMicroSphereFrame._index <= curRes)
  validation2 = (seamoreSpheresMicroSphereFrame._index <= curRes + 1)
  validation3 = (seamoreSpheresMicroSphereFrame._index <= curRes)
  validation4 = (seamoreSpheresMicroSphereFrame._index >  curRes)
  validation5 = (seamoreSpheresMicroSphereFrame._index <=  maxRes)

  checkVisibility(seamoreSpheresMicroSphereFrame, validation5)

  if validation1 and displayUnit.name == "Runes" then
    -- Handle death knight runes
    local runeCDProg, runeCDTot = GetRuneCooldown(seamoreSpheresMicroSphereFrame._index)
    
    if runeCDProg == 0 or UnitIsDeadOrGhost("player") or ((GetTime()  - runeCDProg) > runeCDTot) then
      -- ((GetTime()  - runeCDProg) > runeCDTot) is to check for lag, since the value can potentially just keep going up.
      runeCDProg = 1
    else
      seamoreSpheresMicroSphereFrame.sphere.filling.start    = runeCDProg
      seamoreSpheresMicroSphereFrame.sphere.filling.duration = runeCDTot
      runeCDProg = GetTime()  - runeCDProg
      runeCDProg = runeCDProg / runeCDTot
    end
    
    if ss.global.show.gradient then
      seamoreSpheresMicroSphereFrame.sphere.filling:UpdateGradient((runeCDProg * microSphereWidth), microSphereWidth, displayUnit[2], runeCDProg)
    end
    
    local texCoord = runeCDProg - ss.gradientScale
    seamoreSpheresMicroSphereFrame.sphere.filling:SetHeight  ((microSphereWidth * runeCDProg) - (microSphereWidth * ss.gradientScale))
    seamoreSpheresMicroSphereFrame.sphere.filling:SetTexCoord(0,1,math.abs(texCoord - 1),1)
    seamoreSpheresMicroSphereFrame.sphere.filling:SetAlpha   (runeCDProg)
  elseif validation2 and start and duration then
    local rem = (GetTime() - start) / duration
    
    validation1 = (seamoreSpheresMicroSphereFrame._index == curRes + 1)
    
    if validation1 and  (rem >= 0) and (rem <= 1) then
      if ss.global.show.gradient then
        seamoreSpheresMicroSphereFrame.sphere.filling:UpdateGradient((rem * microSphereWidth), microSphereWidth, displayUnit[2], (rem/2))
      end
      
      local texCoord = rem - ss.gradientScale
      seamoreSpheresMicroSphereFrame.sphere.filling:SetHeight  ((microSphereWidth * rem) - (microSphereWidth * ss.gradientScale))
      seamoreSpheresMicroSphereFrame.sphere.filling:SetTexCoord(0,1,math.abs(texCoord - 1),1)
      seamoreSpheresMicroSphereFrame.sphere.filling:SetAlpha   (rem/2)
    else
      if ss.global.show.gradient then
        seamoreSpheresMicroSphereFrame.sphere.filling:UpdateGradient((rem * microSphereWidth), microSphereWidth, displayUnit[2], 1)
      end
      
      local texCoord = 1 - ss.gradientScale
      seamoreSpheresMicroSphereFrame.sphere.filling:SetHeight  (microSphereWidth - (microSphereWidth * ss.gradientScale))
      seamoreSpheresMicroSphereFrame.sphere.filling:SetTexCoord(0,1,math.abs(texCoord - 1),1)
      seamoreSpheresMicroSphereFrame.sphere.filling:SetAlpha   (curAlpha + 0.08)
    end
  elseif validation3 and (curAlpha < 1) then
    if seamoreSpheresMicroSphereFrame.sphere.resourceAnimation and seamoreSpheresMicroSphereFrame.sphere.resourceAnimation:IsVisible() then
      seamoreSpheresMicroSphereFrame.sphere.resourceAnimation:SetAlpha(curAlpha + 0.08)
    end
    
    local texCoord = 1
    seamoreSpheresMicroSphereFrame.sphere.filling:SetHeight  (microSphereWidth)
    seamoreSpheresMicroSphereFrame.sphere.filling:SetTexCoord(0,1,math.abs(texCoord - 1),1)
    seamoreSpheresMicroSphereFrame.sphere.filling:SetAlpha (curAlpha + 0.08)
    
    if ss.global.show.gradient then
      local r,g,b = ss.Utils:GetRGBAValues(displayUnit[2])
      seamoreSpheresMicroSphereFrame.sphere.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,0,r,g,b,0)
    end
  elseif validation4 and (curAlpha > 0) then
    if seamoreSpheresMicroSphereFrame.sphere.resourceAnimation and seamoreSpheresMicroSphereFrame.sphere.resourceAnimation:IsVisible() then
      seamoreSpheresMicroSphereFrame.sphere.resourceAnimation:SetAlpha(curAlpha - 0.08)
    end
    
    seamoreSpheresMicroSphereFrame.sphere.filling:SetAlpha(curAlpha - 0.08)
    
    if ss.global.show.gradient then
      local r,g,b = ss.Utils:GetRGBAValues(displayUnit[2])
      seamoreSpheresMicroSphereFrame.sphere.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,0,r,g,b,0)
    end
  end
end

local function updateResourceGroupAura(seamoreSpheresMicroSphereFrame, displayUnit)
  if displayUnit.buffGroup[seamoreSpheresMicroSphereFrame._index] then
    if displayUnit.buffGroup[seamoreSpheresMicroSphereFrame._index].name and displayUnit.buffGroup[seamoreSpheresMicroSphereFrame._index].name == "Stagger" then
      updateResourceStagger(seamoreSpheresMicroSphereFrame, displayUnit.buffGroup[seamoreSpheresMicroSphereFrame._index])
    else
      updateResourceBuff(seamoreSpheresMicroSphereFrame, displayUnit.buffGroup[seamoreSpheresMicroSphereFrame._index])
    end
  else
    seamoreSpheresMicroSphereFrame:Fade(0)
  end
end

--- Sets a value to a circular boundary.
-- @param val The value to update.
-- @param min The min value.
-- @param max The max value.
-- @return The updated value.
--
local function adjustToBoundary(val, min, max)
  local newval = val
  if newval < min then
    newval = adjustToBoundary(min + (min - newval), min, max)
  elseif newval > max then
    newval = adjustToBoundary(max - (newval - max), min, max)
  end
  return newval
end

function SeamoreSpheresMicroSphere:Init(config, index, resource, region, group)
  if not self._initialized then
    self._index = (group == "secondaryResource") and 11 - index or index
    self.configName = config
    local generalInfo = ss.db[config].general
    
    self:SetHeight(30 / generalInfo.scale)
    self:SetWidth (30 / generalInfo.scale)
    
    if resourceGroupPoints[index] then
      self:ClearAllPoints()
      
      if region and region == "secondary" then
        self:SetPoint("BOTTOM", -(resourceGroupPoints[index][1] + 389), resourceGroupPoints[index][2] - 22)
      else
        self:SetPoint("BOTTOM", resourceGroupPoints[index][1] + 389,    resourceGroupPoints[index][2] - 22)
      end
    else
      local adjust = index - 1
      
      if adjust < 0 then
        adjust = -adjust
      end
      
      local yAdjustment = ((adjust*15))
      local xAdjustment = index - 1
      local ypos        = 285 + yAdjustment
      local xpos        = 360 + (xAdjustment * 20)
      xpos              = adjustToBoundary(xpos, 310, 480)
      ypos              = adjustToBoundary(ypos, 110, 290)
      
      local parent = self:GetParent()
      
      local xmin, ymin = parent:GetLeft()-45, parent:GetBottom()+45
      xpos             = xmin-xpos/UIParent:GetScale() + (parent:GetWidth()/2)
      ypos             = ypos/UIParent:GetScale() - ymin - (parent:GetWidth()/2)
      
      local locationAngle = math.deg(math.atan2(ypos, xpos))
      local x = 60 - (((parent:GetWidth() + 45)/2) * cos(locationAngle))
      local y = (((parent:GetWidth() + 45)/2) * sin(locationAngle)) - 60
      
      self:SetPoint("TOPLEFT",parent,"TOPLEFT", x, y)
    end
    
    -- Add tooltip
    ss.Utils:AddTooltip(self, "ANCHOR_NONE", { ss.Utils:GetAnchors(self) }, nil, function(self)
      if self.sphere:GetTooltipLabel() and self:GetAlpha() > 0 then
        -- Update the tooltip's display value.
        self.tLines = {
          self.sphere:GetTooltipLabel(),
        }
        return true
      else
        return false
      end
    end)
    
    -- Initialize the container.
    self.sphere:Init(resource, (region and 2 or 1))
    self._initialized = true
  end
end

function SeamoreSpheresMicroSphere:SetMicroResource(unitId, unit)
  self.UpdateResource = unitId == "buff" and updateResourceGroupAura or updateResource

  if unitId == "buff" and unit.buffGroup[self._index] then
    self.sphere.tooltipLabel = unit.buffGroup[self._index].name or nil
    self.sphere.filling:SetVertColor(unit.buffGroup[self._index][2])
    self.sphere.filling:SetAlpha(0)
    
    if ss.global.show.gradient then
      local r,g,b,a = self.sphere.filling:GetVertexColor()
      self.sphere.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,0,r,g,b,0)
    end
  elseif unitId ~= "buff" and unit then
    self.sphere.tooltipLabel = unit.name or nil
    self.sphere.filling:SetVertColor(unit[2])
    self.sphere.filling:SetAlpha(0)
    
    if ss.global.show.gradient then
      local r,g,b,a = self.sphere.filling:GetVertexColor()
      self.sphere.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,0,r,g,b,0)
    end
  else
    self.sphere.tooltipLabel = nil
    self.sphere.filling:SetAlpha(0)
    
    if ss.global.show.gradient then
      local r,g,b,a = self.sphere.filling:GetVertexColor()
      self.sphere.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,0,r,g,b,0)
    end
    self:Fade(0)
  end
end

rawset(_G, "SeamoreSpheresMicroSphere_OnLoad", function(self)
  microSphereIndex = microSphereIndex + 1
  self._sphereIndex = microSphereIndex
  
  for k,v in pairs(SeamoreSpheresMicroSphere) do
    self[k] = v
  end
  
  ss.Extentions:MakeFadable(self)
end)
