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

local math = math

--- Updates a fill texture and it's gradient data.
-- @param self A reference to the fill texture.
-- @param targetHeight The target height
-- @param color The target color.
-- @param maxAlpha The alpha limit.
--
local function updateGradientFilling(self, targetHeight, color, maxAlpha)
  if self and ss.global.show.gradient then
    if color and maxAlpha and self.gradient then
      local r, g, b = ss.Utils:GetRGBAValues(color)
      self.gradient:SetGradientAlpha("VERTICAL", r, g, b, maxAlpha, r, g, b, 0)
    end

    self._gradT = targetHeight / self._maxH
    self._gradT = self._gradT > 1 and 1 or self._gradT -- ensure value isnt greater than 1 when resource changes

    local textCoordTop = (self._gradT - ss.gradientScale)
    local newHeight = targetHeight - (self._maxH * ss.gradientScale)
    textCoordTop = textCoordTop < 0 and 0 or textCoordTop

    if self._gradT < 0 then
      self._gradT = 0
    end

    newHeight = newHeight < 1 and 1 or newHeight

    self:SetHeight(newHeight)
    self:SetTexCoord(0, 1, math.abs(textCoordTop - 1), 1)

    self._gradB = 1 - (self._gradT - ss.gradientScale)
    self._gradH = self._maxH * ss.gradientScale

    self._gradB = self._gradB < 0 and 0 or self._gradB
    self._gradB = self._gradB > 1 and 1 or self._gradB

    self._newY = (self._maxH * (1 - self._gradB)) - self._gradH

    if self._updateSplitValues then
      self:_updateSplitValues(textCoordTop, newHeight)
    end
  end
end

--- Updates split fill textures and their gradient data.
-- @param self A reference to the texture.
-- @param textCoordTop The target top.
-- @param newHeight The target height.
--
local function handleSplitValues(self, textCoordTop, newHeight)
  if self then
    self._gradB = self._gradB - ss.gradientScale
    self._gradT = self._gradT + ss.gradientScale

    newHeight = newHeight + (ss.gradientScale * self._maxH)
    self._newY = self._newY + (ss.gradientScale * self._maxH)
    textCoordTop = textCoordTop + ss.gradientScale

    if self._gradT > 1 then
      local diff = self._gradT - 1
      self._gradH = self._gradH - (diff * self._maxH)
      self._gradT = 1
    elseif (self._gradT - ss.gradientScale) < ss.gradientScale then
      local diff = ss.gradientScale - (self._gradT - ss.gradientScale)

      newHeight = newHeight - (diff * self._maxH)
      self._newY = self._newY - (diff * self._maxH)
      textCoordTop = textCoordTop - diff
      self._gradB = 1
    end

    self:SetHeight(newHeight)
    self:SetTexCoord(0, 1, math.abs(textCoordTop - 1), 1)

    self._newY = self._newY + (self._maxH * 0.075)
  end
end

--- Updates a texture gradient.
-- @param self The texture reference.
--
local function updateGradient(self)
  if self and ss.global.show.gradient then
    self.gradient:SetHeight(self._gradH)
    self.gradient:SetTexCoord(0, 1, math.abs(self._gradT - 1), math.abs(self._gradB))
    self.gradient:SetPoint("BOTTOM", self._newX, self._newY)

    -- Clear temp values.
    self._maxH = nil
    self._newY = nil
    self._newX = nil
    self._gradH = nil
    self._gradT = nil
    self._gradB = nil
  end
end

local function initSplitFillGradientTextures(container, size)
  -- Initialize the left gradient.
  container.fillingLeftGradient:ClearAllPoints()
  container.fillingLeftGradient:SetPoint("BOTTOM", (-size / 4 + 0.70), (size * 0.025))
  container.fillingLeftGradient:SetWidth((size * 0.95) / 2)
  container.fillingLeftGradient:SetHeight(size * 0.95)

  -- Initialize the right gradient.
  container.fillingRightGradient:ClearAllPoints()
  container.fillingRightGradient:SetPoint("BOTTOM", (size / 4 - 0.70), (size * 0.025))
  container.fillingRightGradient:SetWidth((size * 0.95) / 2)
  container.fillingRightGradient:SetHeight(size * 0.95)
end

local function setSplitFillTextures(container, unitId, secondaryUnitId)
  if container.mode.fillColor[2] and secondaryUnitId then
    local texture = container.mode.fillTexture or ss.FillTextures.get("split", 1)

    container.fillingLeft:SetTexture(texture[1])
    container.fillingLeft:SetAlpha(1)
    container.fillingLeft:SetVertColor(container.mode.fillColor[1][2])

    container.fillingRight:SetTexture(texture[2])
    container.fillingRight:SetAlpha(1)
    container.fillingRight:SetVertColor(container.mode.fillColor[2][2])

    if ss.global.show.gradient then
      -- Set gradient color.
      local r, g, b, a = container.fillingLeft:GetVertexColor()
      container.fillingLeftGradient:SetTexture(texture[1])
      container.fillingLeftGradient:SetAlpha(1)
      container.fillingLeftGradient:SetGradientAlpha("VERTICAL", r, g, b, a, r, g, b, 0)

      r, g, b, a = container.fillingRight:GetVertexColor()
      container.fillingRightGradient:SetTexture(texture[2])
      container.fillingRightGradient:SetAlpha(1)
      container.fillingRightGradient:SetGradientAlpha("VERTICAL", r, g, b, a, r, g, b, 0)
    else
      container.fillingRightGradient:SetAlpha(0)
      container.fillingLeftGradient:SetAlpha(0)
      container.fillingLeftGradient:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0, 0, 0, 0)
      container.fillingRightGradient:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0, 0, 0, 0)
    end

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

      -- Add power tracking
      container.config = {
        ["name"] = ss.db[container.configName].general.configName,
        ["unit"] = ss.db[container.configName].general.unit,
        ["unitId"] = unitId,
        ["realHeight"] = ss.db[container.configName].general.size * 0.95,
        ["textFormat"] = ss.db[container.configName].textFormat,
        ["previousValue"] = prevVal,
        ["secondaryUnitId"] = secondaryUnitId,
        ["previousSecondaryValue"] = prevVal2,
      }
    end

    container.divider:SetAlpha(1)
  else
    container.divider:SetAlpha(0)
    container.fillingLeft:SetAlpha(0)
    container.fillingLeftGradient:SetAlpha(0)
    container.fillingLeftGradient:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0, 0, 0, 0)
    container.fillingRight:SetAlpha(0)
    container.fillingRightGradient:SetAlpha(0)
    container.fillingRightGradient:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0, 0, 0, 0)
  end
end

local function initSplitFillTextures(container)
  local size = (container.displayMode and container.displayMode == "preview") and 150 or ss.db[container.configName].general.size

  -- Initialize the left texture.
  container.fillingLeft:ClearAllPoints()
  container.fillingLeft:SetPoint("BOTTOM", -size / 4 + 0.70, size * 0.025)
  container.fillingLeft:SetWidth((size * 0.95) / 2)
  container.fillingLeft:SetHeight(size * 0.95)

  container.fillingLeft._updateValues = updateGradientFilling
  container.fillingLeft._updateSplitValues = handleSplitValues
  container.fillingLeft._updateGradient = updateGradient

  function container.fillingLeft:UpdateGradient(targetHeight, maxHeight)
    self._maxH = maxHeight
    self._newX = (-self._maxH / 4 - 1)
    self:_updateValues(targetHeight)
    self:_updateGradient()
  end

  -- Initialize the right texture.
  container.fillingRight:ClearAllPoints()
  container.fillingRight:SetPoint("BOTTOM", size / 4 - 0.70, size * 0.025)
  container.fillingRight:SetWidth((size * 0.95) / 2)
  container.fillingRight:SetHeight(size * 0.95)

  container.fillingRight._updateValues = updateGradientFilling
  container.fillingRight._updateSplitValues = handleSplitValues
  container.fillingRight._updateGradient = updateGradient

  function container.fillingRight:UpdateGradient(targetHeight, maxHeight)
    self._maxH = maxHeight
    self._newX = (self._maxH / 4 + 1)
    self:_updateValues(targetHeight)
    self:_updateGradient()
  end

  initSplitFillGradientTextures(container, size)

  -- Initialize the divider.
  container.divider:SetPoint("CENTER", 100, 100)
end

local function initFillGradientTexture(container, size)
  -- Initialize the gradient.
  container.fillingGradient:ClearAllPoints()
  container.fillingGradient:SetPoint("BOTTOM", 0, (size * -0.05))
  container.fillingGradient:SetWidth(size * 1.086)
  container.fillingGradient:SetHeight(size * 1.086)

  if container.displayMode == "active" and container.configName == "health" then
    -- Initialize the absorb gradient.
    container.fillingAbsorbGradient:ClearAllPoints()
    container.fillingAbsorbGradient:SetPoint("BOTTOM", 0, (size * -0.05))
    container.fillingAbsorbGradient:SetWidth(size * 1.086)
    container.fillingAbsorbGradient:SetHeight(size * 1.086)
  end
end

local function setFillTextures(container, unitId, secondaryUnitId)
  if container.mode.fillColor[2] and secondaryUnitId then
    container.filling:SetAlpha(0)
    container.fillingGradient:SetAlpha(0)
    container.fillingGradient:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0, 0, 0, 0)
    container.fillingAbsorb:SetAlpha(0)
    container.fillingAbsorbGradient:SetAlpha(0)
    container.fillingAbsorbGradient:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0, 0, 0, 0)
  else
    local texture = container.mode.fillTexture[2] and ss.FillTextures.defaultTexture or container.mode.fillTexture[1]
    local color = container.mode.fillColor[1][2]

    container.filling:SetTexture(texture)
    container.filling:SetAlpha(1)
    container.filling:SetVertColor(color)

    if container.configName ~= "health" or container.displayMode ~= "active" then
      -- Hide the absorb textures for all but active health.
      container.fillingAbsorb:SetAlpha(0)
      container.fillingAbsorbGradient:SetAlpha(0)
      container.fillingAbsorbGradient:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0, 0, 0, 0)
    end

    -- Update gradient textures.
    if ss.global.show.gradient then
      local r, g, b, a = container.filling:GetVertexColor()
      container.fillingGradient:SetTexture(texture)
      container.fillingGradient:SetAlpha(1)
      container.fillingGradient:SetGradientAlpha("VERTICAL", r, g, b, a, r, g, b, 0)
    else
      container.fillingGradient:SetAlpha(0)
      container.fillingAbsorbGradient:SetAlpha(0)
      container.fillingGradient:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0, 0, 0, 0)
      container.fillingAbsorbGradient:SetGradientAlpha("VERTICAL", 0, 0, 0, 0, 0, 0, 0, 0)
    end

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

      -- Update the power tracking config.
      container.config = {
        ["name"] = ss.db[container.configName].general.configName,
        ["unit"] = ss.db[container.configName].general.unit,
        ["unitId"] = unitId,
        ["realHeight"] = ss.db[container.configName].general.size * 1.086,
        ["textFormat"] = ss.db[container.configName].textFormat,
        ["previousValue"] = prevVal,
      }

      if container.configName == "health" then
        local absorbColor

        if (color.r > 0.7) and (color.g > 0.7) and (color.b > 0.7) then
          -- Ensure the absorbs are displayed on white/light grey values.
          absorbColor = ss.private.general.resources.Absorb
        end

        -- Update absorb texture.
        container.fillingAbsorb:SetTexture(texture)
        container.fillingAbsorb:SetAlpha(1)
        container.fillingAbsorb:SetVertColor(absorbColor)

        -- Add absorb values to the config.
        container.fillingAbsorb.name = "absorb"
        container.config.absorbUnitId = "absorb"
        container.config.previousAbsorbValue = prevAbsorbVal or 0

        if ss.global.show.gradient then
          -- Update absorb gradient texture.
          local r, g, b, a = container.fillingAbsorb:GetVertexColor()
          container.fillingAbsorbGradient:SetTexture(texture)
          container.fillingAbsorbGradient:SetAlpha(1)
          container.fillingAbsorbGradient:SetGradientAlpha("VERTICAL", r, g, b, a, r, g, b, 0)
        end
      end
    end
  end
end

local function initFillTexture(container)
  local size = (container.displayMode and container.displayMode == "preview") and 150 or ss.db[container.configName].general.size

  -- Initialize the texture.
  container.filling:ClearAllPoints()
  container.filling:SetPoint("BOTTOM", 0, size * -0.05)
  container.filling:SetWidth(size * 1.086)
  container.filling:SetHeight(size * 1.086)

  container.filling._updateValues = updateGradientFilling
  container.filling._updateGradient = updateGradient

  function container.filling:UpdateGradient(targetHeight, maxHeight, color, maxAlpha)
    self._maxH = maxHeight
    self._newX = 0
    self:_updateValues(targetHeight, color, maxAlpha)
    self:_updateGradient()
  end

  if container.displayMode == "active" and container.configName == "health" then
    -- Initialize the absorb texture.
    container.fillingAbsorb:ClearAllPoints()
    container.fillingAbsorb:SetPoint("BOTTOM", 0, size * -0.05)
    container.fillingAbsorb:SetWidth(size * 1.086)
    container.fillingAbsorb:SetHeight(size * 1.086)

    container.fillingAbsorb._updateValues = updateGradientFilling
    container.fillingAbsorb._updateGradient = updateGradient

    function container.fillingAbsorb:UpdateGradient(targetHeight, maxHeight, color, maxAlpha)
      self._maxH = maxHeight
      self._newX = 0
      self:_updateValues(targetHeight, color, maxAlpha)
      self:_updateGradient()
    end
  end

  initFillGradientTexture(container, size)
end

function SeamoreSpheresContainerLayers:Init()
  local unitId, secondaryUnitId = ss.ClassResources.modeResourceUnitId(self.configName)

  -- Initialize Fill Textures.
  initSplitFillTextures(self, unitId, secondaryUnitId)
  initFillTexture(self, unitId, secondaryUnitId)
  self.config = self.config or {}
end

function SeamoreSpheresContainerLayers:Refresh()
  local unitId, secondaryUnitId = ss.ClassResources.modeResourceUnitId(self.configName,
      (self.displayMode == "preview") and self.modeName)

  -- Initialize fill textures.
  setSplitFillTextures(self, unitId, secondaryUnitId)
  setFillTextures(self, unitId, secondaryUnitId)
end

rawset(_G, "SeamoreSpheresContainerLayers_OnLoad", function(container)
  container.layers = {}
  for k, v in pairs(SeamoreSpheresContainerLayers) do
    container.layers[k] = function()
      v(container)
    end
  end

  -- Add texture envents.
  ss.Extensions:DelegateVertexColor(
      container.fillingLeft,
      container.fillingLeftGradient,
      container.fillingRight,
      container.fillingRightGradient,
      container.filling,
      container.fillingGradient,
      container.fillingAbsorb,
      container.fillingAbsorbGradient)

  -- TODO: Remove this. references for old code to work
  container.fillingLeft.gradient = container.fillingLeftGradient
  container.fillingRight.gradient = container.fillingRightGradient
  container.filling.gradient = container.fillingGradient
  container.fillingAbsorb.gradient = container.fillingAbsorbGradient
  container.font1 = container.fontHolder.font1
  container.font2 = container.fontHolder.font2
end)
