------------------------------
-- Sphere Container Creator --
------------------------------
local addon, ns = ...
local ss = ns.SS

local CreateFrame, InCombatLockdown = CreateFrame, InCombatLockdown
local GetRuneCooldown, UnitIsDeadOrGhost, pairs = GetRuneCooldown, UnitIsDeadOrGhost, pairs
local STAGGER_RED_TRANSITION, STAGGER_YELLOW_TRANSITION = STAGGER_RED_TRANSITION, STAGGER_YELLOW_TRANSITION
local GetTime, math = GetTime, math

local microSphereWidth = (30 * 1.086)

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

local function ensureVisible(v, visible)
  if not v:GetParent():IsVisible() and visible then
    -- Show the parent.
    ss.Layout:ToggleMicroSpheres()
    ss.Layout:ToggleMicroSpheres("mainResource")
  end
end

--- Updates a display for current stagger.
-- @param display The parent display.
-- @param displayUnit The unit identifier.
--
local updateResourceGroupStagger = function(display, displayUnit)
  local spell, curStagger, maxHealth = displayUnit.getMinMax()
  display.tooltipLabel = ss.T["lbl_stagger"]

  if (curStagger / maxHealth) > STAGGER_RED_TRANSITION then
    display:GetParent():SetAlpha(1)
    display.filling    :SetVertexColor(ss.Utils:GetRGBAValues(ss.ClassResourceData.resourceColor("stagger heavy")))
  elseif (curStagger / maxHealth) > STAGGER_YELLOW_TRANSITION then
    display:GetParent():SetAlpha(1)
    display.filling    :SetVertexColor(ss.Utils:GetRGBAValues(ss.ClassResourceData.resourceColor("stagger medium")))
  elseif curStagger > 0 then
    display:GetParent():SetAlpha(1)
    display.filling    :SetVertexColor(ss.Utils:GetRGBAValues(ss.ClassResourceData.resourceColor("stagger light")))
  else
    display:GetParent():SetAlpha(0)
  end

  if ss.global.show.gradient then
    local r,g,b = ss.Utils:GetRGBAValues(ss.ClassResourceData.resourceColor("stagger light"))
    display.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,0,r,g,b,0)
  end
end

--- Updates a display for current buff details.
-- @param display The parent display.
-- @param group The resource group.
--
local updateResourceGroupBuff = function(display, group)
  group = group or "resource"

  for k,v in pairs(display[group.."Group"]) do
    local index = (group == "resource") and k or (11 - k)

    if display[group.."GroupUnit"].buffGroup[index] then
      if display[group.."GroupUnit"].buffGroup[index].name and display[group.."GroupUnit"].buffGroup[index].name == "Stagger" then
        updateResourceGroupStagger(v, display[group.."GroupUnit"].buffGroup[index])
      elseif display[group.."GroupUnit"].buffGroup[index] then
        v.tooltipLabel = display[group.."GroupUnit"].buffGroup[index].name or nil
        local spell, cur, max, duration, expires = display[group.."GroupUnit"].buffGroup[index].getMinMax(display[group.."GroupUnit"].buffGroup[index][1])

        local rem = expires - GetTime()
        rem       = rem / duration

        if rem > 1 then
          rem = 1
        end

        if cur == 1 and (rem >= 0) and (rem <= 1) then
        
          if ss.global.show.gradient then
            v.filling:UpdateGradient((rem * microSphereWidth), microSphereWidth, display[group.."GroupUnit"].buffGroup[index][2], rem)
          end

          local texCoord = rem - ss.gradientScale
          v.filling    :SetHeight  ((microSphereWidth * rem) - (microSphereWidth * ss.gradientScale))
          v.filling    :SetTexCoord(0,1,math.abs(texCoord - 1),1)
          v.filling    :SetAlpha   (rem)
          v:GetParent():SetAlpha   (1)
        else
          v:GetParent():SetAlpha(0)
        end
      end
    end
  end
end

--- Updates a display for current spell details.
-- @param display The parent display.
-- @param group The resource group.
--
local updateResourceGroupDefault = function(display, group)
  group = group or "resource"

  local spell, curRes, maxRes, start, duration, r = display[group.."GroupUnit"].getMinMax(display[group.."GroupUnitId"])
  local displayMax = (group == "resource") and maxRes or (11 - maxRes)

  if not display[group.."Group"][displayMax] then
    ss.Layout:AddMicroSpheres()
    ss.Layout:AddMicroSpheres("mainResource")
  end

  for k,v in pairs(display[group.."Group"]) do
    v.tooltipLabel = display[group.."GroupUnit"].name or nil
    local curAlpha = v.filling:GetAlpha()
    local validation1, validation2, validation3, validation4, validation5

    if group == "secondaryResource" then
      validation1 = k >= (11 - curRes)
      validation2 = k >= (10 - curRes)
      validation3 = k >= (11 - curRes)
      validation4 = k <  (11 - curRes)
      validation5 = k >=  (11 - maxRes) and not InCombatLockdown()
    else
      validation1 = (k <= curRes)
      validation2 = (k <= curRes + 1)
      validation3 = (k <= curRes)
      validation4 = (k >  curRes)
      validation5 = (k <=  maxRes) and not InCombatLockdown()
    end

    if validation1 and display[group.."GroupUnit"].name == "Runes" then
      -- Handle death knight runes
      local runeCDProg, runeCDTot = GetRuneCooldown(k)
    
      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
        v.filling.start    = runeCDProg
        v.filling.duration = runeCDTot
        runeCDProg         = GetTime()  - runeCDProg
        runeCDProg         = runeCDProg / runeCDTot
      end

      if ss.global.show.gradient then
        v.filling:UpdateGradient((runeCDProg * microSphereWidth), microSphereWidth, display[group.."GroupUnit"][2], runeCDProg)
      end

      local texCoord = runeCDProg - ss.gradientScale
      v.filling:SetHeight  ((microSphereWidth * runeCDProg) - (microSphereWidth * ss.gradientScale))
      v.filling:SetTexCoord(0,1,math.abs(texCoord - 1),1)
      v.filling:SetAlpha   (runeCDProg)
    elseif validation2 and start and duration then
      local rem = (GetTime() - start) / duration

      if group == "secondaryResource" then
        validation1 = (k == (10 - curRes))
      else
        validation1 = (k == curRes + 1)
      end

      if validation1 and  (rem >= 0) and (rem <= 1) then
        if ss.global.show.gradient then
          v.filling:UpdateGradient((rem * microSphereWidth), microSphereWidth, display[group.."GroupUnit"][2], (rem/2))
        end

        local texCoord = rem - ss.gradientScale
        v.filling:SetHeight  ((microSphereWidth * rem) - (microSphereWidth * ss.gradientScale))
        v.filling:SetTexCoord(0,1,math.abs(texCoord - 1),1)
        v.filling:SetAlpha   (rem/2)
      else
        if ss.global.show.gradient then
          v.filling:UpdateGradient((rem * microSphereWidth), microSphereWidth, display[group.."GroupUnit"][2], 1)
        end

        local texCoord = 1 - ss.gradientScale
        v.filling:SetHeight  (microSphereWidth - (microSphereWidth * ss.gradientScale))
        v.filling:SetTexCoord(0,1,math.abs(texCoord - 1),1)
        v.filling:SetAlpha   (curAlpha + 0.08)
      end
  
      ensureVisible(v, validation5)
    elseif validation3 and (curAlpha < 1) then
      if v.resourceAnimation and v.resourceAnimation:IsVisible() then
        v.resourceAnimation:SetAlpha(curAlpha + 0.08)
      end
      
      local texCoord = 1
      v.filling:SetHeight  (microSphereWidth)
      v.filling:SetTexCoord(0,1,math.abs(texCoord - 1),1)
      v.filling:SetAlpha (curAlpha + 0.08)

      if ss.global.show.gradient then
        local r,g,b = ss.Utils:GetRGBAValues(display[group.."GroupUnit"][2])
        v.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,0,r,g,b,0)
      end
  
      ensureVisible(v, validation5)
    elseif validation4 and (curAlpha > 0) then
      if v.resourceAnimation and v.resourceAnimation:IsVisible() then
        v.resourceAnimation:SetAlpha(curAlpha - 0.08)
      end
      
      v.filling:SetAlpha(curAlpha - 0.08)

      if ss.global.show.gradient then
        local r,g,b = ss.Utils:GetRGBAValues(display[group.."GroupUnit"][2])
        v.filling.gradient:SetGradientAlpha("VERTICAL",r,g,b,0,r,g,b,0)
      end
  
      ensureVisible(v, validation5)
    end
  end
end

--- Updates a micro sphere display.
-- @param display The parent display.
-- @param group The resource group.
-- @see updateResourceGroupStagger().
-- @see updateResourceGroupBuff().
-- @see updateResourceGroupDefault().
--
local updateResourceGroup = function(display, group)
  group = group or "resource"

  if display[group.."GroupUnitId"] then
    if display[group.."GroupUnitId"] == "buff" then
      updateResourceGroupBuff(display, group)
    else
      updateResourceGroupDefault(display, group)
    end
  end
end

local mtContainer = {}

--- Creates a new sphere container.
-- @param parent The parent display.
-- @param config The stored config index.
-- @param displayMode The sphere display mode.
-- @return The container frame.
--
function mtContainer:New(parent, config, displayMode)
  local sphere  = CreateFrame("Frame", ss._f..config.."container", parent)
  sphere:SetScale    (1)
  sphere:SetHeight   (ss.global.general.SphereSize / ss.db[config].general.scale)
  sphere:SetWidth    (ss.global.general.SphereSize / ss.db[config].general.scale)
  sphere:SetAllPoints(parent)

  if displayMode == "active" then
    sphere:SetFrameStrata("MEDIUM")

    function sphere:UpdateMicroSpheres(group)
      if group then
        updateResourceGroup(self, group)
      else
        updateResourceGroup(self, "resource")
        updateResourceGroup(self, "secondaryResource")
      end

    end
  else
    sphere:SetFrameStrata("HIGH")
    sphere.displayMode = "preview"
  end

  return sphere
end

--- Creates a new micro sphere container.
-- @param parent The parent display.
-- @param config The stored config index.
-- @param index The display position.
-- @return The container frame.
--
function mtContainer:NewGroupItem(parent, config, index)
  local sphere  = CreateFrame("Frame", ss._f..config.."_microsphere"..index.."_container", parent)
  sphere:SetScale (1)
  sphere:SetHeight(30 / ss.db[config].general.scale)
  sphere:SetWidth (30 / ss.db[config].general.scale)
  sphere:SetAllPoints  (parent)
  sphere:SetFrameStrata(parent:GetFrameStrata() or "HIGH")

  sphere.displayMode = "preview"

  return sphere
end

ss.Utils.addModule("ContainerCreator", mtContainer)
