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)
local microSphereWidth = (30 * 1.05)
--local microSphereWidth = 30

-- 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 _lbl, cur, _, duration, expires = displayUnit.getMinMax(displayUnit[1])

  if not ss.retailClient and ss.curClass == "shaman" and displayUnit.name and displayUnit.name:match("^%a+%sTotem$") then
    seamoreSpheresMicroSphereFrame.sphere.tooltipLabel = _lbl
  end

  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(ss.global.general.fadeMicroSpheres and curAlpha + 0.08 or 1)
    end
  elseif validation3 and (curAlpha < 1) then
    if seamoreSpheresMicroSphereFrame.sphere.resourceAnimation and seamoreSpheresMicroSphereFrame.sphere.resourceAnimation:IsVisible() then
      seamoreSpheresMicroSphereFrame.sphere.resourceAnimation:SetAlpha(ss.global.general.fadeMicroSpheres and curAlpha + 0.08 or 1)
    end

    local texCoord = 1
    seamoreSpheresMicroSphereFrame.sphere.filling:SetHeight(microSphereWidth)
    seamoreSpheresMicroSphereFrame.sphere.filling:SetTexCoord(0, 1, math.abs(texCoord - 1), 1)
    seamoreSpheresMicroSphereFrame.sphere.filling:SetAlpha(ss.global.general.fadeMicroSpheres and curAlpha + 0.08 or 1)

    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(ss.global.general.fadeMicroSpheres and curAlpha - 0.08 or 0)
    end

    seamoreSpheresMicroSphereFrame.sphere.filling:SetAlpha(ss.global.general.fadeMicroSpheres and curAlpha - 0.08 or 0)

    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 = 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 = 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 = 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.Extensions:MakeFadable(self)
end)
