--[[
This library contains work of Hendrick "nevcairiel" Leppkes
https://www.wowace.com/projects/libbuttonglow-1-0
]]

-- luacheck: globals CreateFromMixins ObjectPoolMixin CreateTexturePool CreateFramePool

local MAJOR_VERSION = "LibCustomGlow-1.0"
local MINOR_VERSION = 21
if not LibStub then error(MAJOR_VERSION .. " requires LibStub.") end
local lib, oldversion = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
if not lib then return end

local pairs, ipairs = pairs, ipairs
local ceil, floor, min, mod = math.ceil, math.floor, math.min, mod
local tinsert, tremove = table.insert, table.remove

-- ===============================================================================
-- !!! IMPORTANT: CHANGE `texturePath` BELOW TO POINT TO YOUR ADDON'S FILE LOCATION !!!
-- DON'T FORGET THE BACKSLASH AT THE END (\)!
-- Example: local texturePath = [[Interface\AddOns\YourAddon\Libs\]]
-- ===============================================================================
-- This is the path to the texture files used by this library.
local texturePath = [[Interface\AddOns\WeakAuras\Libs\]]

local textureList = {
  ["empty"] = texturePath .. [[LibCustomGlow-1.0\AM_29]],
  ["white"] = [[Interface\BUTTONS\WHITE8X8]],
  ["shine"] = texturePath .. [[LibCustomGlow-1.0\Artifacts]]
}

function lib.RegisterTextures(texture, id)
  textureList[id] = texture
end

lib.glowList = {}
lib.startList = {}
lib.stopList = {}

local GlowParent = UIParent

local TexPoolResetter = function(pool, tex)
  tex:Hide()
  tex:ClearAllPoints()
end
local GlowTexPool = CreateTexturePool(GlowParent, "ARTWORK", nil, TexPoolResetter)
lib.GlowTexPool = GlowTexPool

local FramePoolResetter = function(framePool, frame)
  frame:SetScript("OnSizeChanged", nil)
  frame:SetScript("OnUpdate", nil)

  local parent = frame:GetParent()
  if parent[frame.name] then
    parent[frame.name] = nil
  end

  if frame.textures then
    for _, texture in ipairs(frame.textures) do
      GlowTexPool:Release(texture)
    end
  end

  frame.textures = {}
  frame.info = {}
  frame.name = nil
  frame.timer = nil
  frame:Hide()
  frame:ClearAllPoints()
end

local GlowFramePool = CreateFramePool("Frame", GlowParent, "", FramePoolResetter)
lib.GlowFramePool = GlowFramePool

local function addFrameAndTex(r, color, name, key, N, xOffset, yOffset, texture, texCoord, desaturated, frameLevel)
  key = key or ""
  frameLevel = frameLevel or 8

  if not r[name..key] then
    r[name..key] = GlowFramePool:Acquire()
    r[name..key]:SetParent(r)
    r[name..key].name = name..key
  end

  local f = r[name..key]
  f:SetFrameLevel(r:GetFrameLevel() + frameLevel)
  f:SetPoint("TOPLEFT", r, "TOPLEFT", -xOffset, yOffset)
  f:SetPoint("BOTTOMRIGHT", r, "BOTTOMRIGHT", xOffset, -yOffset)
  f:Show()

  if not f.textures then
    f.textures = {}
  end

  for i = 1, N do
    if not f.textures[i] then
      f.textures[i] = GlowTexPool:Acquire()
      f.textures[i]:SetTexture(texture)
      f.textures[i]:SetTexCoord(texCoord[1], texCoord[2], texCoord[3], texCoord[4])
      f.textures[i]:SetParent(f)
    end

    f.textures[i]:SetVertexColor(color[1], color[2], color[3], color[4])
    f.textures[i]:Show()
  end

  while #f.textures > N do
    GlowTexPool:Release(f.textures[#f.textures])
    tremove(f.textures)
  end
end


--Pixel Glow Functions--
local pPoint = {
  ["BOTTOMLEFT"] = "BOTTOMRIGHT",
  ["BOTTOMRIGHT"] = "TOPRIGHT",
  ["TOPRIGHT"] = "TOPLEFT",
  ["TOPLEFT"] = "BOTTOMLEFT",
}

local function pWidth(position, width, length, thickness, line1, line2, point)
  line1:ClearAllPoints()
  line1:SetPoint(pPoint[point], point == "BOTTOMLEFT" and -position or position, 0)
  position = width - position
  if position > length then
    line1:SetSize(length, thickness)
    line2:Hide()
  else
    line2:ClearAllPoints()
    line2:SetPoint(point)
    line2:Show()

    line1:SetSize(position, thickness)
    line2:SetSize(thickness, length - position)
  end
end

local function pHeight(position, height, length, thickness, line1, line2, point)
  line1:ClearAllPoints()
  line1:SetPoint(pPoint[point], 0, point == "BOTTOMRIGHT" and -position or position)
  position = height - position
  if position > length then
    line1:SetSize(thickness, length)
    line2:Hide()
  else
    line2:ClearAllPoints()
    line2:SetPoint(point)
    line2:Show()

    line1:SetSize(thickness, position)
    line2:SetSize(length - position, thickness)
  end
end

--[[
local function pSize(position, size, length, thickness, line1, line2, point, left)
  line1:ClearAllPoints()
  line1:SetPoint(pPoint[point], not left and (point == "BOTTOMLEFT" and -position or position) or 0, left and (point == "BOTTOMRIGHT" and -position or position) or 0)
  position = size - position
  if position > length then
    line1:SetSize(left and thickness or length, left and length or thickness)
    line2:Hide()
  else
    line2:ClearAllPoints()
    line2:SetPoint(point)
    line2:Show()

    line1:SetSize(left and thickness or position, left and position or thickness)
    line2:SetSize(left and (length - position) or thickness, not left and (length - position) or thickness)
  end
end
]]

local function pSizeChanged(self, width, height)
  if not (width or height) then
    width, height = self:GetSize()
  end

  if width ~= self.info.width or height ~= self.info.height then
    self.info.width = width
    self.info.height = height
    self.info.perimeter = 2 * (width + height)
    self.info.bottomlim = height * 2 + width
    self.info.rightlim = height + width
    self.info.space = self.info.perimeter / self.info.N
  end
end

local function pUpdate(self, elapsed)
  self.timer = self.timer + elapsed / self.info.period
  if self.timer > 1 or self.timer < -1 then
    self.timer = self.timer % 1
  end

  local info = self.info
  for i = 1, info.N do
    local position = (info.space * i + info.perimeter * self.timer) % info.perimeter
    if position > info.bottomlim then -- BOTTOM
      pWidth(position - info.bottomlim, info.width, info.length, info.th, self.textures[i], self.textures[info.N + i], "BOTTOMLEFT")
    elseif position > info.rightlim then -- RIGHT
      pHeight(position - info.rightlim, info.height, info.length, info.th, self.textures[i], self.textures[info.N + i], "BOTTOMRIGHT")
    elseif position > info.height then -- TOP
      pWidth(position - info.height, info.width, info.length, info.th, self.textures[i], self.textures[info.N + i], "TOPRIGHT")
    else -- LEFT
      pHeight(position, info.height, info.length, info.th, self.textures[i], self.textures[info.N + i], "TOPLEFT")
    end
  end
end

function lib.PixelGlow_Start(r, color, N, frequency, length, th, xOffset, yOffset, border, key, frameLevel)
  if not r then return end
  if not N or N <= 0 then N = 8 end

  local width, height = r:GetSize()
  length = length or floor((width + height) * (2 / N - 0.1))
  length = min(length, min(width, height))
  key = key or ""

  addFrameAndTex(r, color or {.95, .95, .32, 1}, "_PixelGlow", key, N * 2, xOffset or 0, yOffset or 0, textureList.white, {0, 1, 0, 1}, nil, frameLevel)

  local f = r["_PixelGlow"..key]

  f.timer = f.timer or 0
  f.info = f.info or {}
  f.info.N = N
  f.info.period = (not frequency or frequency == 0) and 4 or (1 / frequency)
  f.info.th = th or 1

  if f.info.length ~= length then
    f.info.width = nil
    f.info.length = length
  end

  pSizeChanged(f)
  f:SetScript("OnSizeChanged", pSizeChanged)
  pUpdate(f, 0)
  f:SetScript("OnUpdate", pUpdate)
end

function lib.PixelGlow_Stop(r, key)
  if not r then return end
  key = key or ""
  if not r["_PixelGlow"..key] then
    return false
  else
    GlowFramePool:Release(r["_PixelGlow"..key])
  end
end

tinsert(lib.glowList, "Pixel Glow")
lib.startList["Pixel Glow"] = lib.PixelGlow_Start
lib.stopList["Pixel Glow"] = lib.PixelGlow_Stop


--Autocast Glow Funcitons--
local acSizes = {7, 6, 5, 4}

local function acSizeChanged(self, width, height)
  if not (width or height) then
    width, height = self:GetSize()
  end

  if width ~= self.info.width or height ~= self.info.height then
    if width*height == 0 then return end
    self.info.width = width
    self.info.height = height
    self.info.perimeter = 2 * (width + height)
    self.info.bottomlim = height * 2 + width
    self.info.rightlim = height + width
    self.info.space = self.info.perimeter / self.info.N
  end
end

local function acUpdate(self, elapsed)
  local texIndex, info = 0, self.info
  for k = 1, 4 do
    self.timer[k] = self.timer[k] + elapsed / (info.period * k)
    if self.timer[k] > 1 or self.timer[k] < -1 then
      self.timer[k] = self.timer[k] % 1
    end
    for i = 1, info.N do
      texIndex = texIndex + 1
      local position = (info.space * i + info.perimeter * self.timer[k]) % info.perimeter
      if position > info.bottomlim then
        self.textures[texIndex]:SetPoint("CENTER", self, "BOTTOMRIGHT", -position + info.bottomlim, 0)
      elseif position > info.rightlim then
        self.textures[texIndex]:SetPoint("CENTER", self, "TOPRIGHT", 0, -position + info.rightlim)
      elseif position > info.height then
        self.textures[texIndex]:SetPoint("CENTER", self, "TOPLEFT", position - info.height, 0)
      else
        self.textures[texIndex]:SetPoint("CENTER", self, "BOTTOMLEFT", 0, position)
      end
    end
  end
end

function lib.AutoCastGlow_Start(r, color, N, frequency, scale, xOffset, yOffset, key, frameLevel)
  if not r then return end
  if not (N and N > 0) then N = 4 end

  scale = scale or 1
  xOffset = xOffset or 0
  yOffset = yOffset or 0
  key = key or ""

  addFrameAndTex(r, color or {.95, .95, .32, 1}, "_AutoCastGlow", key, N * 4, xOffset, yOffset, textureList.shine, {0.8115234375, 0.9169921875, 0.8798828125, 0.9853515625}, true, frameLevel)
  local f = r["_AutoCastGlow"..key]
  for k, size in pairs(acSizes) do
    for i = 1, N do
      f.textures[i + N * (k - 1)]:SetSize(size * scale, size * scale)
    end
  end

  f.timer = f.timer or {0, 0, 0, 0}
  f.info = f.info or {}
  f.info.N = N
  f.info.period = (not frequency or frequency == 0) and 4 or (1 / frequency)

  acSizeChanged(f)
  f:SetScript("OnSizeChanged", acSizeChanged)
  f:SetScript("OnUpdate", acUpdate)
  acUpdate(f, 0)
end

function lib.AutoCastGlow_Stop(r, key)
  if not r then return end

  key = key or ""
  if not r["_AutoCastGlow"..key] then
    return false
  else
    GlowFramePool:Release(r["_AutoCastGlow"..key])
  end
end

tinsert(lib.glowList, "Autocast Shine")
lib.startList["Autocast Shine"] = lib.AutoCastGlow_Start
lib.stopList["Autocast Shine"] = lib.AutoCastGlow_Stop


-- Animation Functions
local function InitAlphaAnimation(self)
  self.target = self.target or self:GetRegionParent()
  self.change = self.change or 0

  self.frameAlpha = self.target:GetAlpha()
  self.alphaFactor = self.frameAlpha + self.change - self.frameAlpha
end

local function TidyAlphaAnimation(self)
  self.alphaFactor = nil
  self.frameAlpha = nil
end

local function AlphaAnimation_OnUpdate(self, elapsed)
  local progress = self:GetSmoothProgress()
  if progress ~= 0 then
    if not self.played then
      InitAlphaAnimation(self)
      self.played = 1
    end

    if self.frameAlpha then
      self.target:SetAlpha(self.frameAlpha + self.alphaFactor * progress)

      if progress == 1 then
        TidyAlphaAnimation(self)
      end
    end
  end
end

local function AlphaAnimation_OnStop(self)
  if self.frameAlpha then
    TidyAlphaAnimation(self)
  end

  self.played = nil
end

local function CreateAlphaAnim(group, target, order, duration, change, delay, onPlay, onFinished)
  local alpha = group:CreateAnimation()

  if target then
    alpha.target = alpha:GetRegionParent()[target]
  end

  if order then
    alpha:SetOrder(order)
  end

  alpha:SetDuration(duration)
  alpha.change = change

  if delay then
    alpha:SetStartDelay(delay)
  end

  if onPlay then
    alpha:SetScript("OnPlay", onPlay)
  end

  alpha:SetScript("OnUpdate", AlphaAnimation_OnUpdate)
  alpha:SetScript("OnStop", AlphaAnimation_OnStop)
  alpha:SetScript("OnFinished", onFinished or AlphaAnimation_OnStop)
end

local function InitScaleAnimation(self)
  self.target = self.target or self:GetRegionParent()
  self.scaleX = self.scaleX or 0
  self.scaleY = self.scaleY or 0

  local _, _, width, height = self.target:GetRect()
  if not width then return end

  self.frameWidth = width
  self.frameHeight = height

  self.widthFactor = width * self.scaleX - width
  self.heightFactor = height * self.scaleY - height

  local setCenter
  local parent = self.target:GetParent()
  local numPoints = self.target:GetNumPoints()

  if numPoints > 0 then
    local point, relativeTo, relativePoint, xOffset, yOffset = self.target:GetPoint(1)

    if numPoints == 1 and point == "CENTER" then
      setCenter = false
    else
      local i = 1
      while true do
        if relativeTo ~= parent and yOffset then
          local j = #self + 1
          self[j], self[j + 1], self[j + 2], self[j + 3], self[j + 4] = point, relativeTo, relativePoint, xOffset, yOffset
        end

        i = i + 1
        if numPoints >= i then
          point, relativeTo, relativePoint, xOffset, yOffset = self.target:GetPoint(i)
        else
          break
        end
      end

      setCenter = true
    end
  else
    setCenter = true
  end

  if setCenter then
    local x, y = self.target:GetCenter()
    local parentX, parentY = parent:GetCenter()

    self.target:ClearAllPoints()
    self.target:SetPoint("CENTER", x - parentX, y - parentY)
  end

  return 1
end

local function TidyScaleAnimation(self)
  local target = self.target

  if #self ~= 0 then
    target:ClearAllPoints()

    for i = 1, #self, 5 do
      target:SetPoint(self[i], self[i + 1], self[i + 2], self[i + 3], self[i + 4])
      self[i] = nil
      self[i + 1] = nil
      self[i + 2] = nil
      self[i + 3] = nil
      self[i + 4] = nil
    end
  end

  self.widthFactor = nil
  self.heightFactor = nil

  self.frameWidth = nil
  self.frameHeight = nil
end

local function ScaleAnimation_OnUpdate(self, elapsed)
  local progress = self:GetSmoothProgress()
  if progress ~= 0 then
    if not self.played then
      if InitScaleAnimation(self) then
        self.played = 1
      end
    end

    if self.frameWidth then
      self.target:SetSize(self.frameWidth + self.widthFactor * progress, self.frameHeight + self.heightFactor * progress)

      if progress == 1 then
        TidyScaleAnimation(self)
      end
    end
  end
end

local function ScaleAnimation_OnStop(self)
  if self.frameWidth then
    TidyScaleAnimation(self)
  end

  self.played = nil
end

local function CreateScaleAnim(group, target, order, duration, x, y, delay, smoothing, onPlay)
  local scale = group:CreateAnimation()

  if target then
    scale.target = scale:GetRegionParent()[target]
  end

  scale:SetOrder(order)
  scale:SetDuration(duration)
  scale.scaleX, scale.scaleY = x, y

  if delay then
    scale:SetStartDelay(delay)
  end

  if smoothing then
    scale:SetSmoothing(smoothing)
  end

  if onPlay then
    scale:SetScript("OnPlay", onPlay)
  end

  scale:SetScript("OnUpdate", ScaleAnimation_OnUpdate)
  scale:SetScript("OnStop", ScaleAnimation_OnStop)
  scale:SetScript("OnFinished", ScaleAnimation_OnStop)
end

local function AnimateTexCoords(texture, textureWidth, textureHeight, frameWidth, frameHeight, numFrames, elapsed, throttle)
  if not texture.frame then
    texture.frame = 1
    texture.throttle = throttle
    texture.numColumns = floor(textureWidth / frameWidth)
    texture.numRows = floor(textureHeight / frameHeight)
    texture.columnWidth = frameWidth / textureWidth
    texture.rowHeight = frameHeight / textureHeight
  end

  if not texture.throttle or texture.throttle > throttle then
    local frame = texture.frame
    local framesToAdvance = floor(texture.throttle / throttle)
    while frame + framesToAdvance > numFrames do
      frame = frame - numFrames
    end

    frame = frame + framesToAdvance
    texture.throttle = 0

    local left = mod(frame - 1, texture.numColumns) * texture.columnWidth
    local right = left + texture.columnWidth
    local bottom = ceil(frame / texture.numColumns) * texture.rowHeight
    local top = bottom - texture.rowHeight
    texture:SetTexCoord(left, right, top, bottom)

    texture.frame = frame
  else
    texture.throttle = texture.throttle + elapsed
  end
end

--Action Button Glow--
local function ButtonGlowResetter(framePool,frame)
--	frame:SetScript("OnUpdate",nil)
  local parent = frame:GetParent()
  if parent._ButtonGlow then
    parent._ButtonGlow = nil
  end
  frame:Hide()
  frame:ClearAllPoints()
end
local ButtonGlowPool = CreateFramePool("Frame", GlowParent, "", ButtonGlowResetter)
lib.ButtonGlowPool = ButtonGlowPool

local function AnimIn_OnPlay(anim)
  local frame = anim:GetRegionParent()
  local frameWidth, frameHeight = frame:GetSize()
  frame.spark:SetSize(frameWidth, frameHeight)
  frame.spark:SetAlpha(not (frame.color) and 1 or 0.3 * frame.color[4])
  frame.innerGlow:SetSize(frameWidth / 2, frameHeight / 2)
  frame.innerGlow:SetAlpha(not (frame.color) and 1 or frame.color[4])
  frame.innerGlowOver:SetAlpha(not (frame.color) and 1 or frame.color[4])
  frame.outerGlow:SetSize(frameWidth * 2, frameHeight * 2)
  frame.outerGlow:SetAlpha(not (frame.color) and 1 or frame.color[4])
  frame.outerGlowOver:SetAlpha(not (frame.color) and 1 or frame.color[4])
  frame.ants:SetSize(frameWidth * 0.85, frameHeight * 0.85)
  frame.ants:SetAlpha(0)
  frame:Show()
end

local function AnimIn_OnFinished(group)
  local frame = group:GetParent()
  local frameWidth, frameHeight = frame:GetSize()
  frame.spark:SetAlpha(0)
  frame.innerGlow:SetAlpha(0)
  frame.innerGlow:SetSize(frameWidth, frameHeight)
  frame.innerGlowOver:SetAlpha(0)
  frame.outerGlow:SetSize(frameWidth, frameHeight)
  frame.outerGlowOver:SetAlpha(0)
  frame.outerGlowOver:SetSize(frameWidth, frameHeight)
  frame.ants:SetAlpha(not (frame.color) and 1 or frame.color[4])
end

local function AnimIn_OnStop(group)
  local frame = group:GetParent()
  frame.spark:SetAlpha(0)
  frame.innerGlow:SetAlpha(0)
  frame.innerGlowOver:SetAlpha(0)
  frame.outerGlowOver:SetAlpha(0)
end

local function bgHide(self)
  if self.animOut:IsPlaying() then
    self.animOut:Stop()
    ButtonGlowPool:Release(self)
  end
end

local function bgUpdate(self, elapsed)
  AnimateTexCoords(self.ants, 256, 256, 48, 48, 22, elapsed, self.throttle)
end

local function IsAnimPlaying(self)
  return self.isPlaying
end

local function configureButtonGlow(f, alpha)
  f.spark = f:CreateTexture(nil, "BACKGROUND")
  f.spark:SetPoint("CENTER")
  f.spark:SetAlpha(0)
  f.spark:SetTexture(texturePath .. [[LibCustomGlow-1.0\IconAlert]])
  f.spark:SetTexCoord(0.00781250, 0.61718750, 0.00390625, 0.26953125)

  -- inner glow
  f.innerGlow = f:CreateTexture()
  f.innerGlow:SetPoint("CENTER")
  f.innerGlow:SetAlpha(0)
  f.innerGlow:SetTexture(texturePath .. [[LibCustomGlow-1.0\IconAlert]])
  f.innerGlow:SetTexCoord(0.00781250, 0.50781250, 0.27734375, 0.52734375)
  f.innerGlow:Show()

  -- inner glow over
  f.innerGlowOver = f:CreateTexture()
  f.innerGlowOver:SetPoint("TOPLEFT", f.innerGlow, "TOPLEFT")
  f.innerGlowOver:SetPoint("BOTTOMRIGHT", f.innerGlow, "BOTTOMRIGHT")
  f.innerGlowOver:SetAlpha(0)
  f.innerGlowOver:SetTexture(texturePath .. [[LibCustomGlow-1.0\IconAlert]])
  f.innerGlowOver:SetTexCoord(0.00781250, 0.50781250, 0.53515625, 0.78515625)

  -- outer glow
  f.outerGlow = f:CreateTexture()
  f.outerGlow:SetPoint("CENTER")
  f.outerGlow:SetAlpha(0)
  f.outerGlow:SetTexture(texturePath .. [[LibCustomGlow-1.0\IconAlert]])
  f.outerGlow:SetTexCoord(0.00781250, 0.50781250, 0.27734375, 0.52734375)

  -- outer glow over
  f.outerGlowOver = f:CreateTexture()
  f.outerGlowOver:SetPoint("TOPLEFT", f.outerGlow, "TOPLEFT")
  f.outerGlowOver:SetPoint("BOTTOMRIGHT", f.outerGlow, "BOTTOMRIGHT")
  f.outerGlowOver:SetAlpha(0)
  f.outerGlowOver:SetTexture(texturePath .. [[LibCustomGlow-1.0\IconAlert]])
  f.outerGlowOver:SetTexCoord(0.00781250, 0.50781250, 0.53515625, 0.78515625)

  -- ants
  f.ants = f:CreateTexture(nil, "OVERLAY")
  f.ants:SetPoint("CENTER")
  f.ants:SetAlpha(0)
  f.ants:SetTexture(texturePath .. [[LibCustomGlow-1.0\IconAlertAnts]])

  f.animIn = f:CreateAnimationGroup()
  f.animIn.appear = {}
  f.animIn.fade = {}
  CreateScaleAnim(f.animIn, "spark",			1, 0.2, 1.5, 1.5, nil, nil, AnimIn_OnPlay)
  CreateAlphaAnim(f.animIn, "spark",			1, 0.2, alpha, nil, nil, nil, true)
  CreateScaleAnim(f.animIn, "innerGlow",		1, 0.3, 2, 2)
  CreateScaleAnim(f.animIn, "innerGlowOver",	1, 0.3, 2, 2)
  CreateAlphaAnim(f.animIn, "innerGlowOver",	1, 0.3, alpha, nil, nil, nil, false)
  CreateScaleAnim(f.animIn, "outerGlow",		1, 0.3, 0.5, 0.5)
  CreateScaleAnim(f.animIn, "outerGlowOver",	1, 0.3, 0.5, 0.5)
  CreateAlphaAnim(f.animIn, "outerGlowOver",	1, 0.3, alpha, nil, nil, nil, false)
  CreateScaleAnim(f.animIn, "spark",			1, 0.2, 0.666666, 0.666666, 0.2)
  CreateAlphaAnim(f.animIn, "spark",			1, 0.2, alpha, 0.2, nil, nil, false)
  CreateAlphaAnim(f.animIn, "innerGlow",		1, 0.2, alpha, 0.3, nil, nil, false)
  CreateAlphaAnim(f.animIn, "ants",			1, 0.2, alpha, 0.3, nil, nil, true)
  f.animIn:SetScript("OnStop", AnimIn_OnStop)
  f.animIn:SetScript("OnFinished", AnimIn_OnFinished)

  f.animOut = f:CreateAnimationGroup()
  f.animOut.appear = {}
  f.animOut.fade = {}
  CreateAlphaAnim(f.animOut, "outerGlowOver",	1, 0.2, alpha, nil, nil, nil, true)
  CreateAlphaAnim(f.animOut, "ants",			1, 0.2, alpha, nil, nil, nil, false)
  CreateAlphaAnim(f.animOut, "outerGlowOver",	2, 0.2, alpha, nil, nil, nil, false)
  CreateAlphaAnim(f.animOut, "outerGlow",		2, 0.2, alpha, nil, nil, nil, false)
  f.animOut:SetScript("OnPlay", function(self) self.isPlaying = true; end)
  f.animOut:SetScript("OnStop", function(self) self.isPlaying = false; end)
  f.animOut:SetScript("OnFinished", function(self) self.isPlaying = false; ButtonGlowPool:Release(f) end)

  f.animOut.isPlaying = false
  f.animOut.IsPlaying = IsAnimPlaying

  f:SetScript("OnHide", bgHide)
end

local function updateAlphaAnim(f,alpha)
  for _,anim in pairs(f.animIn.appear) do
    anim.change = alpha
  end
  for _,anim in pairs(f.animIn.fade) do
    anim.change = -alpha
  end
  for _,anim in pairs(f.animOut.appear) do
    anim.change = alpha
  end
  for _,anim in pairs(f.animOut.fade) do
    anim.change = -alpha
  end
end

local ButtonGlowTextures = {["spark"] = true, ["innerGlow"] = true, ["innerGlowOver"] = true, ["outerGlow"] = true, ["outerGlowOver"] = true, ["ants"] = true}

local function noZero(num)
    if num == 0 then
        return 0.001
    else
        return num
    end
end

function lib.ButtonGlow_Start(r, color, frequency, frameLevel)
  if not r then return end
  frameLevel = frameLevel or 8;

  local throttle = frequency and frequency > 0 and 0.25 / frequency * 0.01 or 0.01

  if r._ButtonGlow then
    local f = r._ButtonGlow
    local width, height = r:GetSize()
    f:SetFrameLevel(r:GetFrameLevel() + frameLevel)
    f:SetSize(width * 1.4 , height * 1.4)
    f:SetPoint("TOPLEFT", r, "TOPLEFT", -width * 0.2, height * 0.2)
    f:SetPoint("BOTTOMRIGHT", r, "BOTTOMRIGHT", width * 0.2, -height * 0.2)
    f.ants:SetSize(width * 1.4 * 0.85, height * 1.4 * 0.85)
    AnimIn_OnFinished(f.animIn)
    if f.animOut:IsPlaying() then
      f.animOut:Stop()
      f.animIn:Play()
    end

    if not color then
      for texture in pairs(ButtonGlowTextures) do
        f[texture]:SetVertexColor(1, 1, 1)
        local alpha = math.min(f[texture]:GetAlpha()/noZero(f.color and f.color[4] or 1), 1)
                f[texture]:SetAlpha(alpha)
        updateAlphaAnim(f, 1)
      end
      f.color = false
    else
      for texture in pairs(ButtonGlowTextures) do
        f[texture]:SetVertexColor(color[1], color[2], color[3])
                local alpha = math.min(f[texture]:GetAlpha()/noZero(f.color and f.color[4] or 1)*color[4], 1)
                f[texture]:SetAlpha(alpha)
        updateAlphaAnim(f,color and color[4] or 1)
      end
      f.color = color
    end
    f.throttle = throttle
  else
    local f, new = ButtonGlowPool:Acquire()
    if new then
      configureButtonGlow(f,color and color[4] or 1)
    else
      updateAlphaAnim(f,color and color[4] or 1)
    end
    r._ButtonGlow = f
    local width, height = r:GetSize()
    f:SetParent(r)
    f:SetFrameLevel(r:GetFrameLevel() + frameLevel)
    f:SetSize(width * 1.4, height * 1.4)
    f:SetPoint("TOPLEFT", r, "TOPLEFT", -width * 0.2, height * 0.2)
    f:SetPoint("BOTTOMRIGHT", r, "BOTTOMRIGHT", width * 0.2, -height * 0.2)
    if not color then
      f.color = false
      for texture in pairs(ButtonGlowTextures) do
        f[texture]:SetVertexColor(1, 1, 1)
      end
    else
      f.color = color
      for texture in pairs(ButtonGlowTextures) do
        f[texture]:SetVertexColor(color[1], color[2], color[3])
      end
    end
    f.throttle = throttle
    f:SetScript("OnUpdate", bgUpdate)

    f.animIn:Play()
  end
end

function lib.ButtonGlow_Stop(r)
  if r._ButtonGlow then
    if r._ButtonGlow.animOut:IsPlaying() then
      -- Do nothing the animOut finishing will release
    elseif r._ButtonGlow.animIn:IsPlaying() then
      r._ButtonGlow.animIn:Stop()
      ButtonGlowPool:Release(r._ButtonGlow)
    elseif r:IsVisible() then
      r._ButtonGlow.animOut:Play()
    else
      ButtonGlowPool:Release(r._ButtonGlow)
    end
  end
end

tinsert(lib.glowList, "Action Button Glow")
lib.startList["Action Button Glow"] = lib.ButtonGlow_Start
lib.stopList["Action Button Glow"] = lib.ButtonGlow_Stop


-- ProcGlow

local BaseTexCoord = {
  ["Loop"] = {0.412598, 0.575195, 0.000976562, 0.391602},
  ["Start"] = {0.000488281, 0.411621, 0.000976562, 0.987305},
}

local function SetTile(texture, frame, rows, columns, frameScaleW, frameScaleH, key)
  frame = frame - 1
  local row = math.floor(frame / columns)
  local column = frame % columns

  local leftStart, rightEnd, topStart, bottomEnd = BaseTexCoord[key][1], BaseTexCoord[key][2], BaseTexCoord[key][3], BaseTexCoord[key][4]

  local fullWidth = rightEnd - leftStart
  local fullHeight = bottomEnd - topStart

  local baseDeltaX = fullWidth / columns
  local baseDeltaY = fullHeight / rows

  local deltaX = baseDeltaX * frameScaleW
  local deltaY = baseDeltaY * frameScaleH

  local left = leftStart + baseDeltaX * column + (baseDeltaX - deltaX) / 2
  local right = left + deltaX

  local top = topStart + baseDeltaY * row + (baseDeltaY - deltaY) / 2
  local bottom = top + deltaY

  pcall(function()
    texture:SetTexCoord(left, right, top, bottom)
  end)
end

local StartFlipbook
local FlipbookAnimation_OnUpdate

FlipbookAnimation_OnUpdate = function(self, elapsed)
  local data = self.flipbookData
  if not data then return end

  if data.animElapsed then
    data.animElapsed = data.animElapsed + elapsed
    if data.animElapsed >= 0.7 then
      if self:IsShown() then
        StartFlipbook(self, self.ProcLoop, 6, 5, 30, ((data.animOptions and (30 / data.animOptions)) or 30), nil, nil, "Loop")
      end
      data.animElapsed = nil
      data.animOptions = nil
    end
  end
  data.elapsedTime = data.elapsedTime + elapsed
  local frameDuration = 1 / data.frameRate

  if data.elapsedTime >= frameDuration then
    data.elapsedTime = data.elapsedTime - frameDuration
    data.currentFrame = data.currentFrame + 1
    if data.currentFrame > data.totalFrames then
      data.currentFrame = 1
    end
    SetTile(data.texture, data.currentFrame, data.rows, data.columns, 1, 1, data.key)
  end
end

local function StopFlipbook(f)
  f:SetScript("OnUpdate", nil)
  if f.flipbookData and f.flipbookData.texture then
    f.flipbookData.texture:Hide()
  end
  f.flipbookData = nil
end

StartFlipbook = function(f, texture, rows, columns, totalFrames, frameRate, startAnim, startOptionsDur, key)
  StopFlipbook(f)
  f.flipbookData = {
    key = key,
    texture = texture,
    rows = rows,
    columns = columns,
    totalFrames = totalFrames,
    frameRate = frameRate,
    currentFrame = 1,
    elapsedTime = 0,
    animElapsed = startAnim,
    animOptions = startOptionsDur,
  }
  texture:Show()
  f:SetScript("OnUpdate", FlipbookAnimation_OnUpdate)
end

local function ProcGlowResetter(framePool, frame)
  frame:Hide()
  frame:ClearAllPoints()
  frame:SetScript("OnShow", nil)
  frame:SetScript("OnHide", nil)
  frame:SetScript("OnUpdate", nil)
  local parent = frame:GetParent()
  if frame.name and parent[frame.name] then
    parent[frame.name] = nil
  end
end

local ProcGlowPool = CreateFramePool("Frame", GlowParent, "", ProcGlowResetter)
lib.ProcGlowPool = ProcGlowPool

local function InitProcGlow(f)
  -- Start-Flipbook
  f.ProcStart = f:CreateTexture(nil, "ARTWORK")
  f.ProcStart:SetBlendMode("ADD")
  f.ProcStart:SetTexture(texturePath .. [[LibCustomGlow-1.0\UIActionBarFX]])
  f.ProcStart:SetTexCoord(0.0827148248, 0.1649413686, 0.000976562, 0.165364635) -- First Frame
  f.ProcStart:SetAlpha(1)
  f.ProcStart:SetSize(150, 150)
  f.ProcStart:SetPoint("CENTER")
  f.ProcStart:Hide()

  -- Loop-Flipbook
  f.ProcLoop = f:CreateTexture(nil, "ARTWORK")
  f.ProcLoop:SetTexture(texturePath .. [[LibCustomGlow-1.0\UIActionBarFX]])
  f.ProcLoop:SetTexCoord(0.412598, 0.4451174, 0.000976562, 0.066080801666667) -- First Frame
  f.ProcLoop:SetAlpha(1)
  f.ProcLoop:SetAllPoints()
  f.ProcLoop:Hide()
end

local function SetupProcGlow(f, options)
  f.name = "_ProcGlow" .. options.key

  f:SetScript("OnHide", function(self)
    StopFlipbook(self)
  end)

  f:SetScript("OnShow", function(self)
    StopFlipbook(self)
    if self.startAnim then
      local width, height = self:GetSize()
      self.ProcStart:SetSize((width / 42 * 150) / 1.4, (height / 42 * 150) / 1.4)
      StartFlipbook(self, self.ProcStart, 6, 5, 30, 30, 0, options.duration, "Start")
    else
      StartFlipbook(self, self.ProcLoop , 6, 5, 30, (30 / options.duration), nil, nil, "Loop")
    end
  end)

  local color = options.color or {1, 1, 1, 1}
  f.ProcStart:SetVertexColor(unpack(color))
  f.ProcLoop:SetVertexColor(unpack(color))

  f.startAnim = options.startAnim
end

local ProcGlowDefaults = {
  frameLevel = 8,
  color = nil,
  startAnim = true,
  xOffset = 0,
  yOffset = 0,
  duration = 1,
  key = ""
}

function lib.ProcGlow_Start(r, options)
  if not r then return end
  options = options or {}
  setmetatable(options, { __index = ProcGlowDefaults })
  local key = "_ProcGlow" .. options.key
  local f, new
  if r[key] then
    f = r[key]
  else
    f, new = ProcGlowPool:Acquire()
    if new then
      InitProcGlow(f)
    end
    r[key] = f
  end

  f:SetParent(r)
  f:SetFrameLevel(r:GetFrameLevel() + options.frameLevel)

  local width, height = r:GetSize()
  local xOffset = options.xOffset + width * 0.2
  local yOffset = options.yOffset + height * 0.2
  f:SetPoint("TOPLEFT", r, "TOPLEFT", -xOffset, yOffset)
  f:SetPoint("BOTTOMRIGHT", r, "BOTTOMRIGHT", xOffset, -yOffset)

  SetupProcGlow(f, options)
  f:Show()
end

function lib.ProcGlow_Stop(r, key)
  key = key or ""
  local f = r["_ProcGlow" .. key]
  if f then
    ProcGlowPool:Release(f)
  end
end

table.insert(lib.glowList, "Proc Glow")
lib.startList["Proc Glow"] = lib.ProcGlow_Start
lib.stopList["Proc Glow"] = lib.ProcGlow_Stop
