local _, Cell = ...
local L = Cell.L
local F = Cell.funcs
local I = Cell.iFuncs
local P = Cell.pixelPerfectFuncs

local LCG = LibStub("LibCustomGlow-1.0")

CELL_BORDER_SIZE = 1
CELL_BORDER_COLOR = {0, 0, 0, 1}
CELL_COOLDOWN_STYLE = "VERTICAL"

-------------------------------------------------
-- SetFont
-------------------------------------------------
local function JustifyText(text, point)
    if strfind(point, "LEFT$") then
        text:SetJustifyH("LEFT")
    elseif strfind(point, "RIGHT$") then
        text:SetJustifyH("RIGHT")
    else
        text:SetJustifyH("CENTER")
    end

    if strfind(point, "^TOP") then
        text:SetJustifyV("TOP")
    elseif strfind(point, "^BOTTOM") then
        text:SetJustifyV("BOTTOM")
    else
        text:SetJustifyV("MIDDLE")
    end
end

function I.SetFont(fs, anchorTo, font, size, outline, shadow, anchor, xOffset, yOffset, color)
    font = F:GetFont(font)

    local flags
    if outline == "None" then
        flags = ""
    elseif outline == "Outline" then
        flags = "OUTLINE"
    else
        flags = "OUTLINE,MONOCHROME"
    end

    fs:SetFont(font, size, flags)

    if shadow then
        fs:SetShadowOffset(1, -1)
        fs:SetShadowColor(0, 0, 0, 1)
    else
        fs:SetShadowOffset(0, 0)
        fs:SetShadowColor(0, 0, 0, 0)
    end

    P:ClearPoints(fs)
    P:Point(fs, anchor, anchorTo, anchor, xOffset, yOffset)
    JustifyText(fs, anchor)

    if color then
        fs.r = color[1]
        fs.g = color[2]
        fs.b = color[3]
        fs:SetTextColor(fs.r, fs.g, fs.b)
    else
        fs.r, fs.g, fs.b = 1, 1, 1
    end
end

-------------------------------------------------
-- Shared
-------------------------------------------------
local function Shared_SetFont(frame, font1, font2)
    I.SetFont(frame.stack, frame, unpack(font1))
    I.SetFont(frame.duration, frame, unpack(font2))
end

local function Shared_ShowStack(frame, show)
    frame.stack:SetShown(show)
end

local function Shared_ShowDuration(frame, show)
    frame.showDuration = show
    frame.duration:SetShown(show)
end

-------------------------------------------------
-- VerticalCooldown
-------------------------------------------------
local function ReCalcTexCoord(self, width, height)
    local texCoord = F:GetTexCoord(width, height)
    self.icon:SetTexCoord(unpack(texCoord))
    if self.cooldown.icon then
        self.cooldown.icon:SetTexCoord(unpack(texCoord))
    end
end

local function VerticalCooldown_OnUpdate(self, elapsed)
    self.elapsed = self.elapsed + elapsed
    if self.elapsed >= 0.1 then
        self:SetValue(self:GetValue() + self.elapsed)
        self.elapsed = 0
    end
end

-- for LCG.ButtonGlow_Start
local function VerticalCooldown_GetCooldownDuration()
    return 0
end

local function VerticalCooldown_ShowCooldown(self, start, duration, _, icon, debuffType)
    if debuffType then
        self.spark:SetColorTexture(I.GetDebuffTypeColor(debuffType))
    else
        self.spark:SetColorTexture(0.5, 0.5, 0.5)
    end

    if self.icon then
        self.icon:SetTexture(icon)
    end

    self.elapsed = 0.1 -- update immediately
    self:SetMinMaxValues(0, duration)
    self:SetValue(GetTime() - start)
    self:Show()
end

local function Shared_CreateCooldown_Vertical(frame)
    local cooldown = CreateFrame("StatusBar", nil, frame)
    frame.cooldown = cooldown
    cooldown:Hide()

    cooldown.GetCooldownDuration = VerticalCooldown_GetCooldownDuration
    cooldown.ShowCooldown = VerticalCooldown_ShowCooldown
    cooldown:SetScript("OnUpdate", VerticalCooldown_OnUpdate)

    P:Point(cooldown, "TOPLEFT", frame.icon)
    P:Point(cooldown, "BOTTOMRIGHT", frame.icon, "BOTTOMRIGHT", 0, CELL_BORDER_SIZE)
    cooldown:SetOrientation("VERTICAL")
    cooldown:SetReverseFill(true)
    cooldown:SetStatusBarTexture(Cell.vars.whiteTexture)

    local texture = cooldown:GetStatusBarTexture()
    texture:SetAlpha(0)

    local spark = cooldown:CreateTexture(nil, "BORDER")
    cooldown.spark = spark
    P:Height(spark, 1)
    spark:SetBlendMode("ADD")
    spark:SetPoint("TOPLEFT", texture, "BOTTOMLEFT")
    spark:SetPoint("TOPRIGHT", texture, "BOTTOMRIGHT")

    local mask = cooldown:CreateMaskTexture()
    mask:SetTexture(Cell.vars.whiteTexture, "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    mask:SetPoint("TOPLEFT")
    mask:SetPoint("BOTTOMRIGHT", texture)

    local icon = cooldown:CreateTexture(nil, "ARTWORK")
    cooldown.icon = icon
    -- icon:SetTexCoord(0.12, 0.88, 0.12, 0.88)
    icon:SetDesaturated(true)
    icon:SetAllPoints(frame.icon)
    icon:SetVertexColor(0.5, 0.5, 0.5, 1)
    icon:AddMaskTexture(mask)
end

local function Shared_CreateCooldown_Vertical_NoIcon(frame)
    local cooldown = CreateFrame("StatusBar", nil, frame)
    frame.cooldown = cooldown
    cooldown:Hide()

    cooldown.GetCooldownDuration = VerticalCooldown_GetCooldownDuration
    cooldown.ShowCooldown = VerticalCooldown_ShowCooldown
    cooldown:SetScript("OnUpdate", VerticalCooldown_OnUpdate)

    P:Point(cooldown, "TOPLEFT", frame, CELL_BORDER_SIZE, -CELL_BORDER_SIZE)
    P:Point(cooldown, "BOTTOMRIGHT", frame, -CELL_BORDER_SIZE, CELL_BORDER_SIZE + CELL_BORDER_SIZE)
    cooldown:SetOrientation("VERTICAL")
    cooldown:SetReverseFill(true)
    cooldown:SetStatusBarTexture(Cell.vars.whiteTexture)

    local texture = cooldown:GetStatusBarTexture()
    texture:SetVertexColor(0, 0, 0, 0.8)

    local spark = cooldown:CreateTexture(nil, "BORDER")
    cooldown.spark = spark
    P:Height(spark, 1)
    spark:SetBlendMode("ADD")
    spark:SetPoint("TOPLEFT", texture, "BOTTOMLEFT")
    spark:SetPoint("TOPRIGHT", texture, "BOTTOMRIGHT")
end

-------------------------------------------------
-- ClockCooldown
-------------------------------------------------
local function Shared_CreateCooldown_Clock(frame)
    local cooldown = CreateFrame("Cooldown", nil, frame)
    frame.cooldown = cooldown
    cooldown:Hide()

    P:Point(cooldown, "TOPLEFT", frame, CELL_BORDER_SIZE, -CELL_BORDER_SIZE)
    P:Point(cooldown, "BOTTOMRIGHT", frame, -CELL_BORDER_SIZE, CELL_BORDER_SIZE)
    cooldown:SetReverse(true)
    cooldown:SetDrawEdge(false)
    cooldown:SetSwipeTexture(Cell.vars.whiteTexture)
    cooldown:SetSwipeColor(0, 0, 0, 0.8)
    -- cooldown:SetEdgeTexture([[Interface\Cooldown\UI-HUD-ActionBar-SecondaryCooldown]])

    -- cooldown text
    cooldown:SetHideCountdownNumbers(true)
    -- disable omnicc
    cooldown.noCooldownCount = true
    -- prevent some dirty addons from adding cooldown text
    cooldown.ShowCooldown = cooldown.SetCooldown
    cooldown.SetCooldown = nil
end

-------------------------------------------------
-- SetCooldownStyle
-------------------------------------------------
local function Shared_SetCooldownStyle(frame, style, noIcon)
    if frame.style == style then return end

    if frame.cooldown then
        frame.cooldown:SetParent(nil)
        frame.cooldown:Hide()
    end

    frame.style = style

    if style == "CLOCK" then
        Shared_CreateCooldown_Clock(frame)
    else
        if noIcon then
            Shared_CreateCooldown_Vertical_NoIcon(frame)
        else
            Shared_CreateCooldown_Vertical(frame)
        end
    end
end

-------------------------------------------------
-- Icon_OnUpdate
-------------------------------------------------
local function Icon_OnUpdate(frame, elapsed)
    frame._remain = frame._duration - (GetTime() - frame._start)
    if frame._remain < 0 then frame._remain = 0 end

    if frame._remain > frame._threshold then
        frame.duration:SetText("")
        return
    end

    frame._elapsed = frame._elapsed + elapsed
    if frame._elapsed >= 0.1 then
        frame._elapsed = 0
        -- color
        if Cell.vars.iconDurationColors then
            if frame._remain < Cell.vars.iconDurationColors[3][4] then
                frame.duration:SetTextColor(Cell.vars.iconDurationColors[3][1], Cell.vars.iconDurationColors[3][2], Cell.vars.iconDurationColors[3][3])
            elseif frame._remain < (Cell.vars.iconDurationColors[2][4] * frame._duration) then
                frame.duration:SetTextColor(Cell.vars.iconDurationColors[2][1], Cell.vars.iconDurationColors[2][2], Cell.vars.iconDurationColors[2][3])
            else
                frame.duration:SetTextColor(Cell.vars.iconDurationColors[1][1], Cell.vars.iconDurationColors[1][2], Cell.vars.iconDurationColors[1][3])
            end
        else
            frame.duration:SetTextColor(frame.duration.r, frame.duration.g, frame.duration.b)
        end
    end

    -- format
    if frame._remain > 60 then
        frame.duration:SetFormattedText("%dm", frame._remain / 60)
    else
        if Cell.vars.iconDurationRoundUp then
            frame.duration:SetFormattedText("%d", ceil(frame._remain))
        else
            if frame._remain < Cell.vars.iconDurationDecimal then
                frame.duration:SetFormattedText("%.1f", frame._remain)
            else
                frame.duration:SetFormattedText("%d", frame._remain)
            end
        end
    end
end

local function Icon_OnUpdate_ElapsedTime(frame, elapsed)
    frame._remain = frame._duration - (GetTime() - frame._start)
    if frame._remain < 0 then frame._remain = 0 end

    if frame._remain > frame._threshold then
        frame.duration:SetText("")
        return
    end

    frame._elapsed = frame._elapsed + elapsed
    if frame._elapsed >= 0.1 then
        frame._elapsed = 0
        -- color
        if Cell.vars.iconDurationColors then
            if frame._remain < Cell.vars.iconDurationColors[3][4] then
                frame.duration:SetTextColor(Cell.vars.iconDurationColors[3][1], Cell.vars.iconDurationColors[3][2], Cell.vars.iconDurationColors[3][3])
            elseif frame._remain < (Cell.vars.iconDurationColors[2][4] * frame._duration) then
                frame.duration:SetTextColor(Cell.vars.iconDurationColors[2][1], Cell.vars.iconDurationColors[2][2], Cell.vars.iconDurationColors[2][3])
            else
                frame.duration:SetTextColor(Cell.vars.iconDurationColors[1][1], Cell.vars.iconDurationColors[1][2], Cell.vars.iconDurationColors[1][3])
            end
        else
            frame.duration:SetTextColor(frame.duration.r, frame.duration.g, frame.duration.b)
        end
    end

    -- format
    frame._elapsedTime = GetTime() - frame._start
    if frame._elapsedTime > frame._duration then frame._elapsedTime = frame._duration end

    if frame._elapsedTime > 60 then
        frame.duration:SetFormattedText("%dm", frame._elapsedTime / 60)
    else
        frame.duration:SetFormattedText("%d", frame._elapsedTime)
    end
end

-------------------------------------------------
-- CreateAura_BorderIcon
-------------------------------------------------
local function BorderIcon_SetCooldown(frame, start, duration, debuffType, texture, count, refreshing, useElapsedTime)
    local r, g, b
    if debuffType then
        r, g, b = I.GetDebuffTypeColor(debuffType)
    else
        r, g, b = 0, 0, 0
    end

    if duration == 0 then
        frame.border:Show()
        frame.border:SetColorTexture(r, g, b)
        frame.cooldown:Hide()
        frame.duration:Hide()
        frame:SetScript("OnUpdate", nil)
        frame._start = nil
        frame._duration = nil
        frame._remain = nil
        frame._elapsed = nil
        frame._threshold = nil
        frame._elapsedTime = nil
    else
        frame.border:Hide()
        frame.cooldown:Show()
        frame.cooldown:SetSwipeColor(r, g, b)
        frame.cooldown:_SetCooldown(start, duration)

        if not frame.showDuration then
            frame.duration:Hide()
        else
            if frame.showDuration == true then
                frame._threshold = duration
            elseif frame.showDuration >= 1 then
                frame._threshold = frame.showDuration
            else -- < 1
                frame._threshold = frame.showDuration * duration
            end
            frame.duration:Show()
        end

        if frame.showDuration then
            frame._start = start
            frame._duration = duration
            frame._elapsed = 0.1 -- update immediately
            frame:SetScript("OnUpdate", useElapsedTime and Icon_OnUpdate_ElapsedTime or Icon_OnUpdate)
        end
    end

    frame.icon:SetTexture(texture)
    frame.stack:SetText((count == 0 or count == 1) and "" or count)
    frame:Show()

    if refreshing then
        frame.ag:Play()
    end
end

local function BorderIcon_SetBorder(frame, thickness)
    P:ClearPoints(frame.iconFrame)
    P:Point(frame.iconFrame, "TOPLEFT", frame, "TOPLEFT", thickness, -thickness)
    P:Point(frame.iconFrame, "BOTTOMRIGHT", frame, "BOTTOMRIGHT", -thickness, thickness)
end

local function BorderIcon_ShowDuration(frame, show)
    frame.showDuration = show
    if show then
        frame.duration:Show()
    else
        frame.duration:Hide()
    end
end

local function BorderIcon_UpdatePixelPerfect(frame)
    P:Resize(frame)
    P:Repoint(frame)
    P:Repoint(frame.iconFrame)
    P:Repoint(frame.stack)
    P:Repoint(frame.duration)
end

function I.CreateAura_BorderIcon(name, parent, borderSize)
    local frame = CreateFrame("Frame", name, parent, "BackdropTemplate")
    frame:Hide()
    -- frame:SetSize(11, 11)
    frame:SetBackdrop({bgFile = Cell.vars.whiteTexture})
    frame:SetBackdropColor(0, 0, 0, 0.85)

    local border = frame:CreateTexture(name.."Border", "BORDER")
    frame.border = border
    border:SetAllPoints(frame)
    border:Hide()

    local cooldown = CreateFrame("Cooldown", name.."Cooldown", frame)
    frame.cooldown = cooldown
    cooldown:SetAllPoints(frame)
    cooldown:SetSwipeTexture(Cell.vars.whiteTexture)
    cooldown:SetSwipeColor(1, 1, 1)
    cooldown:SetHideCountdownNumbers(true)
    -- disable omnicc
    cooldown.noCooldownCount = true
    -- prevent some addons from adding cooldown text
    cooldown._SetCooldown = cooldown.SetCooldown
    cooldown.SetCooldown = nil

    local iconFrame = CreateFrame("Frame", name.."IconFrame", frame)
    frame.iconFrame = iconFrame
    P:Point(iconFrame, "TOPLEFT", frame, "TOPLEFT", borderSize, -borderSize)
    P:Point(iconFrame, "BOTTOMRIGHT", frame, "BOTTOMRIGHT", -borderSize, borderSize)
    iconFrame:SetFrameLevel(cooldown:GetFrameLevel()+1)

    local icon = iconFrame:CreateTexture(name.."Icon", "ARTWORK")
    frame.icon = icon
    icon:SetTexCoord(0.12, 0.88, 0.12, 0.88)
    icon:SetAllPoints(iconFrame)

    frame.stack = iconFrame:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")
    frame.duration = iconFrame:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")

    local ag = frame:CreateAnimationGroup()
    frame.ag = ag
    local t1 = ag:CreateAnimation("Translation")
    t1:SetOffset(0, 5)
    t1:SetDuration(0.1)
    t1:SetOrder(1)
    t1:SetSmoothing("OUT")
    local t2 = ag:CreateAnimation("Translation")
    t2:SetOffset(0, -5)
    t2:SetDuration(0.1)
    t2:SetOrder(2)
    t2:SetSmoothing("IN")

    frame.SetFont = Shared_SetFont
    frame.SetBorder = BorderIcon_SetBorder
    frame.SetCooldown = BorderIcon_SetCooldown
    frame.ShowDuration = BorderIcon_ShowDuration
    frame.UpdatePixelPerfect = BorderIcon_UpdatePixelPerfect

    return frame
end

-------------------------------------------------
-- CreateAura_BarIcon
-------------------------------------------------
local function BarIcon_SetCooldown(frame, start, duration, debuffType, texture, count, refreshing)
    if duration == 0 then
        frame.cooldown:Hide()
        frame.duration:Hide()
        frame.stack:SetParent(frame)
        frame:SetScript("OnUpdate", nil)
        frame._start = nil
        frame._duration = nil
        frame._threshold = nil
        frame._remain = nil
        frame._elapsed = nil
    else
        if frame.showAnimation then
            frame.cooldown:ShowCooldown(start, duration, nil, texture, debuffType)
            frame.duration:SetParent(frame.cooldown)
            frame.stack:SetParent(frame.cooldown)
        else
            frame.cooldown:Hide()
            frame.duration:SetParent(frame)
            frame.stack:SetParent(frame)
        end

        if not frame.showDuration then
            frame.duration:Hide()
        else
            if frame.showDuration == true then
                frame._threshold = duration
            elseif frame.showDuration >= 1 then
                frame._threshold = frame.showDuration
            else -- < 1
                frame._threshold = frame.showDuration * duration
            end
            frame.duration:Show()
        end

        if frame.showDuration then
            frame._start = start
            frame._duration = duration
            frame._elapsed = 0.1 -- update immediately
            frame:SetScript("OnUpdate", Icon_OnUpdate)
        end
    end

    if debuffType then
        frame:SetBackdropColor(I.GetDebuffTypeColor(debuffType))
    else
        frame:SetBackdropColor(0, 0, 0)
    end

    frame.icon:SetTexture(texture)
    frame.stack:SetText((count == 0 or count == 1) and "" or count)
    frame:Show()

    if refreshing then
        frame.ag:Play()
    end
end

local function BarIcon_ShowAnimation(frame, show)
    frame.showAnimation = show
    if show then
        frame.cooldown:Show()
    else
        frame.cooldown:Hide()
    end
end

local function BarIcon_UpdatePixelPerfect(frame)
    P:Resize(frame)
    P:Repoint(frame)
    P:Repoint(frame.icon)
    P:Repoint(frame.stack)
    P:Repoint(frame.duration)
    P:Repoint(frame.cooldown)
    if frame.cooldown.spark then
        P:Resize(frame.cooldown.spark)
    end
end

function I.CreateAura_BarIcon(name, parent)
    local frame = CreateFrame("Frame", name, parent, "BackdropTemplate")
    frame:Hide()
    -- frame:SetSize(11, 11)
    frame:SetBackdrop({bgFile = Cell.vars.whiteTexture})
    frame:SetBackdropColor(0, 0, 0, 1)

    local icon = frame:CreateTexture(name.."Icon", "ARTWORK")
    frame.icon = icon
    -- icon:SetTexCoord(0.12, 0.88, 0.12, 0.88)
    P:Point(icon, "TOPLEFT", frame, "TOPLEFT", CELL_BORDER_SIZE, -CELL_BORDER_SIZE)
    P:Point(icon, "BOTTOMRIGHT", frame, "BOTTOMRIGHT", -CELL_BORDER_SIZE, CELL_BORDER_SIZE)
    -- icon:SetDrawLayer("ARTWORK", 1)

    frame.stack = frame:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")
    frame.duration = frame:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")

    local ag = frame:CreateAnimationGroup()
    frame.ag = ag
    local t1 = ag:CreateAnimation("Translation")
    t1:SetOffset(0, 5)
    t1:SetDuration(0.1)
    t1:SetOrder(1)
    t1:SetSmoothing("OUT")
    local t2 = ag:CreateAnimation("Translation")
    t2:SetOffset(0, -5)
    t2:SetDuration(0.1)
    t2:SetOrder(2)
    t2:SetSmoothing("IN")

    frame.SetFont = Shared_SetFont
    frame.SetCooldown = BarIcon_SetCooldown
    frame.ShowDuration = Shared_ShowDuration
    frame.ShowStack = Shared_ShowStack
    frame.ShowAnimation = BarIcon_ShowAnimation
    frame.UpdatePixelPerfect = BarIcon_UpdatePixelPerfect

    Shared_SetCooldownStyle(frame, CELL_COOLDOWN_STYLE)

    frame:SetScript("OnSizeChanged", ReCalcTexCoord)

    -- frame:SetScript("OnEnter", function()
        -- local f = frame
        -- repeat
        --     f = f:GetParent()
        -- until f:IsObjectType("button")
        -- f:GetScript("OnEnter")(f)
    -- end)

    return frame
end

-------------------------------------------------
-- CreateAura_Text
-------------------------------------------------
local function Text_SetFont(frame, font, size, outline, shadow)
    font = F:GetFont(font)

    local flags
    if outline == "None" then
        flags = ""
    elseif outline == "Outline" then
        flags = "OUTLINE"
    else
        flags = "OUTLINE,MONOCHROME"
    end

    frame.text:SetFont(font, size, flags)

    if shadow then
        frame.text:SetShadowOffset(1, -1)
        frame.text:SetShadowColor(0, 0, 0, 1)
    else
        frame.text:SetShadowOffset(0, 0)
        frame.text:SetShadowColor(0, 0, 0, 0)
    end

    frame:SetSize(size, size)
end

local function Text_SetPoint(frame, point, relativeTo, relativePoint, x, y)
    frame.text:ClearAllPoints()
    frame.text:SetPoint(point)
    frame:_SetPoint(point, relativeTo, relativePoint, x, y)
    JustifyText(frame.text, point)
end

local function Text_SetDuration(frame, durationTbl)
    frame.durationTbl = durationTbl
end

local function Text_SetCircledStackNums(frame, circled)
    frame.circledStackNums = circled
end

local function Text_SetColors(frame, colors)
    frame.state = nil
    frame.colors = colors
end

local function Text_OnUpdateColor(frame)
    if frame.colors[3][1] and frame._remain <= frame.colors[3][2] then
        if frame.state ~= 3 then
            frame.state = 3
            frame.text:SetTextColor(frame.colors[3][3][1], frame.colors[3][3][2], frame.colors[3][3][3], frame.colors[3][3][4])
        end
    elseif frame.colors[2][1] and frame._remain <= frame._duration * frame.colors[2][2] then
        if frame.state ~= 2 then
            frame.state = 2
            frame.text:SetTextColor(frame.colors[2][3][1], frame.colors[2][3][2], frame.colors[2][3][3], frame.colors[2][3][4])
        end
    elseif frame.state ~= 1 then
        frame.state = 1
        frame.text:SetTextColor(frame.colors[1][1], frame.colors[1][2], frame.colors[1][3], frame.colors[1][4])
    end
end

local function Text_OnUpdateDuration(frame, elapsed)
    frame._remain = frame._duration - (GetTime() - frame._start)
    if frame._remain < 0 then frame._remain = 0 end

    frame._elapsed = frame._elapsed + elapsed
    if frame._elapsed >= 0.1 then
        frame._elapsed = 0
        -- color
        Text_OnUpdateColor(frame)
    end

    -- format
    if frame._remain > 60 then
        frame.text:SetFormattedText(frame._count.."%dm", frame._remain/60)
    else
        if frame.durationTbl[2] then
            frame.text:SetFormattedText(frame._count.."%d", ceil(frame._remain))
        else
            if frame._remain < frame.durationTbl[3] then
                frame.text:SetFormattedText(frame._count.."%.1f", frame._remain)
            else
                frame.text:SetFormattedText(frame._count.."%d", frame._remain)
            end
        end
    end
end

local function Text_OnUpdate(frame, elapsed)
    frame._elapsed = frame._elapsed + elapsed
    if frame._elapsed >= 0.1 then
        frame._elapsed = 0

        frame._remain = frame._duration - (GetTime() - frame._start)
        -- update color
        Text_OnUpdateColor(frame)
    end
end

local circled = {"①","②","③","④","⑤","⑥","⑦","⑧","⑨","⑩","⑪","⑫","⑬","⑭","⑮","⑯","⑰","⑱","⑲","⑳","㉑","㉒","㉓","㉔","㉕","㉖","㉗","㉘","㉙","㉚","㉛","㉜","㉝","㉞","㉟","㊱","㊲","㊳","㊴","㊵","㊶","㊷","㊸","㊹","㊺","㊻","㊼","㊽","㊾","㊿"}
local function Text_SetCooldown(frame, start, duration, debuffType, texture, count)
    if duration == 0 then
        count = count == 0 and 1 or count
        count = frame.circledStackNums and circled[count] or count
        frame.text:SetText(count)
        frame:SetScript("OnUpdate", nil)
        frame._count = nil
        frame._start = nil
        frame._duration = nil
        frame._remain = nil
        frame._elapsed = nil
    else
        frame._start = start
        frame._duration = duration

        if frame.durationTbl[1] then
            if count == 0 then
                frame._count = ""
            elseif frame.circledStackNums then
                frame._count = circled[count].." "
            else
                frame._count = count.." "
            end

            frame._elapsed = 0.1 -- update immediately
            frame:SetScript("OnUpdate", Text_OnUpdateDuration)
        else
            count = count == 0 and 1 or count
            if frame.circledStackNums then
                frame.text:SetText(circled[count])
            else
                frame.text:SetText(count)
            end

            frame._elapsed = 0.1 -- update immediately
            frame:SetScript("OnUpdate", Text_OnUpdate)
        end
    end

    frame:Show()
end

function I.CreateAura_Text(name, parent)
    local frame = CreateFrame("Frame", name, parent)
    frame:Hide()
    frame.indicatorType = "text"

    local text = frame:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")
    frame.text = text
    text:SetPoint("CENTER", 1, 0)

    frame.SetFont = Text_SetFont
    frame._SetPoint = frame.SetPoint
    frame.SetPoint = Text_SetPoint
    frame.SetCooldown = Text_SetCooldown
    frame.SetDuration = Text_SetDuration
    frame.SetCircledStackNums = Text_SetCircledStackNums
    frame.SetColors = Text_SetColors

    return frame
end

-------------------------------------------------
-- CreateAura_Rect
-------------------------------------------------
local function Rect_SetFont(frame, font1, font2)
    I.SetFont(frame.stack, frame, unpack(font1))
    I.SetFont(frame.duration, frame, unpack(font2))
end

local function Rect_OnUpdateColor(frame)
    if frame.colors[3][1] and frame._remain <= frame.colors[3][2] then
        if frame.state ~= 3 then
            frame.state = 3
            frame.tex:SetColorTexture(frame.colors[3][3][1], frame.colors[3][3][2], frame.colors[3][3][3], frame.colors[3][3][4])
        end
    elseif frame.colors[2][1] and frame._remain <= frame._duration * frame.colors[2][2] then
        if frame.state ~= 2 then
            frame.state = 2
            frame.tex:SetColorTexture(frame.colors[2][3][1], frame.colors[2][3][2], frame.colors[2][3][3], frame.colors[2][3][4])
        end
    elseif frame.state ~= 1 then
        frame.state = 1
        frame.tex:SetColorTexture(frame.colors[1][1], frame.colors[1][2], frame.colors[1][3], frame.colors[1][4])
    end
end

local function Rect_OnUpdate(frame, elapsed)
    frame._remain = frame._duration - (GetTime() - frame._start)
    if frame._remain < 0 then frame._remain = 0 end

    frame._elapsed = frame._elapsed + elapsed
    if frame._elapsed >= 0.1 then
        frame._elapsed = 0
        -- update color
        Rect_OnUpdateColor(frame)
    end

    if frame._remain > frame._threshold then
        frame.duration:SetText("")
        return
    end

    -- format
    if frame._remain > 60 then
        frame.duration:SetFormattedText("%dm", frame._remain / 60)
    else
        if Cell.vars.iconDurationRoundUp then
            frame.duration:SetFormattedText("%d", ceil(frame._remain))
        else
            if frame._remain < Cell.vars.iconDurationDecimal then
                frame.duration:SetFormattedText("%.1f", frame._remain)
            else
                frame.duration:SetFormattedText("%d", frame._remain)
            end
        end
    end
end

local function Rect_SetCooldown(frame, start, duration, debuffType, texture, count)
    if duration == 0 then
        frame.tex:SetColorTexture(unpack(frame.colors[1]))
        frame:SetScript("OnUpdate", nil)
        frame.duration:Hide()
        frame._start = nil
        frame._duration = nil
        frame._remain = nil
        frame._elapsed = nil
        frame._threshold = nil
    else
        if not frame.showDuration then
            frame._threshold = -1
            frame.duration:Hide()
        else
            if frame.showDuration == true then
                frame._threshold = duration
            elseif frame.showDuration >= 1 then
                frame._threshold = frame.showDuration
            else -- < 1
                frame._threshold = frame.showDuration * duration
            end
            frame.duration:Show()
        end

        frame._start = start
        frame._duration = duration
        frame._elapsed = 0.1 -- update immediately
        frame:SetScript("OnUpdate", Rect_OnUpdate)
    end

    frame.stack:SetText((count == 0 or count == 1) and "" or count)
    frame:Show()
end

local function Rect_SetColors(frame, colors)
    frame.state = nil
    frame.colors = colors
    frame:SetBackdropBorderColor(colors[4][1], colors[4][2], colors[4][3], colors[4][4])
end

local function Rect_UpdatePixelPerfect(frame)
    P:Resize(frame)
    P:Reborder(frame)
    P:Repoint(frame)
end

function I.CreateAura_Rect(name, parent)
    local frame = CreateFrame("Frame", name, parent, "BackdropTemplate")
    frame:Hide()
    frame.indicatorType = "rect"
    frame:SetBackdrop({edgeFile=Cell.vars.whiteTexture, edgeSize=P:Scale(1)})
    frame:SetBackdropBorderColor(0, 0, 0, 1)

    local tex = frame:CreateTexture(nil, "BORDER", nil, -7)
    frame.tex = tex
    tex:SetAllPoints()

    frame.stack = frame:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")
    frame.duration = frame:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")

    frame.SetFont = Rect_SetFont
    frame.SetCooldown = Rect_SetCooldown
    frame.SetColors = Rect_SetColors
    frame.ShowStack = Shared_ShowStack
    frame.ShowDuration = Shared_ShowDuration
    frame.UpdatePixelPerfect = Rect_UpdatePixelPerfect

    return frame
end

-------------------------------------------------
-- CreateAura_Bar
-------------------------------------------------
local function Bar_SetFont(bar, font1, font2)
    I.SetFont(bar.stack, bar, unpack(font1))
    I.SetFont(bar.duration, bar, unpack(font2))
end

local function Bar_OnUpdate(bar, elapsed)
    bar._remain = bar._duration - (GetTime() - bar._start)
    if bar._remain < 0 then bar._remain = 0 end
    bar:SetValue(bar._remain)

    bar._elapsed = bar._elapsed + elapsed
    if bar._elapsed >= 0.1 then
        bar._elapsed = 0
        -- update color
        if bar.colors[3][1] and bar._remain <= bar.colors[3][2] then
            if bar.state ~= 3 then
                bar.state = 3
                bar:SetStatusBarColor(bar.colors[3][3][1], bar.colors[3][3][2], bar.colors[3][3][3], bar.colors[3][3][4])
            end
        elseif bar.colors[2][1] and bar._remain <= bar._duration * bar.colors[2][2] then
            if bar.state ~= 2 then
                bar.state = 2
                bar:SetStatusBarColor(bar.colors[2][3][1], bar.colors[2][3][2], bar.colors[2][3][3], bar.colors[2][3][4])
            end
        elseif bar.state ~= 1 then
            bar.state = 1
            bar:SetStatusBarColor(bar.colors[1][1], bar.colors[1][2], bar.colors[1][3], bar.colors[1][4])
        end
    end

    if bar._remain > bar._threshold then
        bar.duration:SetText("")
        return
    end

    -- format
    if bar._remain > 60 then
        bar.duration:SetFormattedText("%dm", bar._remain / 60)
    else
        if Cell.vars.iconDurationRoundUp then
            bar.duration:SetFormattedText("%d", ceil(bar._remain))
        else
            if bar._remain < Cell.vars.iconDurationDecimal then
                bar.duration:SetFormattedText("%.1f", bar._remain)
            else
                bar.duration:SetFormattedText("%d", bar._remain)
            end
        end
    end
end

local function Bar_SetCooldown(bar, start, duration, debuffType, texture, count)
    if duration == 0 then
        bar:SetScript("OnUpdate", nil)
        bar.duration:Hide()
        bar:SetMinMaxValues(0, 1)
        bar:SetValue(1)
        bar._start = nil
        bar._duration = nil
        bar._threshold = nil
        bar._remain = nil
        bar._elapsed = nil
    else
        if not bar.showDuration then
            bar._threshold = -1
            bar.duration:Hide()
        else
            if bar.showDuration == true then
                bar._threshold = duration
            elseif bar.showDuration >= 1 then
                bar._threshold = bar.showDuration
            else -- < 1
                bar._threshold = bar.showDuration * duration
            end
            bar.duration:Show()
        end

        bar:SetMinMaxValues(0, duration)
        bar._start = start
        bar._duration = duration
        bar._elapsed = 0.1 -- update immediately
        bar:SetScript("OnUpdate", Bar_OnUpdate)
    end

    bar.stack:SetText((count == 0 or count == 1) and "" or count)
    bar:Show()
end

local function Bar_SetColors(bar, colors)
    bar:SetBackdropBorderColor(colors[4][1], colors[4][2], colors[4][3], colors[4][4])
    bar:SetBackdropColor(colors[5][1], colors[5][2], colors[5][3], colors[5][4])
    bar.state = nil
    bar.colors = colors
end

function I.CreateAura_Bar(name, parent)
    local bar = Cell:CreateStatusBar(name, parent, 18, 4, 100)
    bar:Hide()
    bar.indicatorType = "bar"

    bar.stack = bar:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")
    bar.duration = bar:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")

    bar.SetFont = Bar_SetFont
    bar.SetCooldown = Bar_SetCooldown
    bar.ShowStack = Shared_ShowStack
    bar.ShowDuration = Shared_ShowDuration
    bar.SetColors = Bar_SetColors

    return bar
end

-------------------------------------------------
-- CreateAura_Color
-------------------------------------------------
local function Color_OnUpdate(color, elapsed)
    color._elapsed = color._elapsed + elapsed
    if color._elapsed >= 0.1 then
        color._elapsed = 0

        color._remain = color._duration - (GetTime() - color._start)
        -- update color
        if color._remain <= color.colors[6][1] then
            if color.state ~= 3 then
                color.state = 3
                color.solidTex:SetVertexColor(color.colors[6][2][1], color.colors[6][2][2], color.colors[6][2][3], color.colors[6][2][4])
            end
        elseif color._remain <= color._duration * color.colors[5][1] then
            if color.state ~= 2 then
                color.state = 2
                color.solidTex:SetVertexColor(color.colors[5][2][1], color.colors[5][2][2], color.colors[5][2][3], color.colors[5][2][4])
            end
        elseif color.state ~= 1 then
            color.state = 1
            color.solidTex:SetVertexColor(color.colors[4][1], color.colors[4][2], color.colors[4][3], color.colors[4][4])
        end
    end
end

local function Color_SetCooldown(color, start, duration, debuffType)
    if color.type == "change-over-time" then
        if duration == 0 then
            color.solidTex:SetVertexColor(unpack(color.colors[4]))
            color:SetScript("OnUpdate", nil)
            color._start = nil
            color._duration = nil
            color._remain = nil
            color._elapsed = nil
        else
            color._start = start
            color._duration = duration
            color._elapsed = 0.1 -- update immediately
            color:SetScript("OnUpdate", Color_OnUpdate)
        end
    elseif color.type == "class-color" then
        color.solidTex:SetVertexColor(F:GetClassColor(color.parent.states.class))
    elseif color.type == "debuff-type" and debuffType then
        color.solidTex:SetVertexColor(CellDB["debuffTypeColor"][debuffType]["r"], CellDB["debuffTypeColor"][debuffType]["g"], CellDB["debuffTypeColor"][debuffType]["b"], 1)
    end
    color:Show()
end

-- +6 ~ +55
local function Color_SetFrameLevel(color, frameLevel)
    color:_SetFrameLevel(frameLevel + 5)
end

local function Color_SetAnchor(color, anchorTo)
    color:ClearAllPoints()
    if anchorTo == "healthbar-current" then
        -- current hp texture
        color:SetAllPoints(color.parent.widgets.healthBar:GetStatusBarTexture())
    elseif anchorTo == "healthbar-entire" then
        -- entire hp bar
        color:SetAllPoints(color.parent.widgets.healthBar)
    else -- unitbutton
        P:Point(color, "TOPLEFT", color.parent, "TOPLEFT", CELL_BORDER_SIZE, -CELL_BORDER_SIZE)
        P:Point(color, "BOTTOMRIGHT", color.parent, "BOTTOMRIGHT", -CELL_BORDER_SIZE, CELL_BORDER_SIZE)
    end

    -- color:SetFrameLevel(color:GetParent():GetFrameLevel() + color.configs.frameLevel)
end

local function Color_SetColors(self, colors)
    self.state = nil
    self.type = colors[1]
    self.colors = colors

    if colors[1] == "solid" then
        self:SetScript("OnUpdate", nil)
        self.solidTex:SetVertexColor(colors[2][1], colors[2][2], colors[2][3], colors[2][4])
        self.solidTex:Show()
        self.gradientTex:Hide()
    elseif colors[1] == "gradient-vertical" then
        self:SetScript("OnUpdate", nil)
        self.gradientTex:SetGradient("VERTICAL", CreateColor(colors[2][1], colors[2][2], colors[2][3], colors[2][4]), CreateColor(colors[3][1], colors[3][2], colors[3][3], colors[3][4]))
        self.gradientTex:Show()
        self.solidTex:Hide()
    elseif colors[1] == "gradient-horizontal" then
        self:SetScript("OnUpdate", nil)
        self.gradientTex:SetGradient("HORIZONTAL", CreateColor(colors[2][1], colors[2][2], colors[2][3], colors[2][4]), CreateColor(colors[3][1], colors[3][2], colors[3][3], colors[3][4]))
        self.gradientTex:Show()
        self.solidTex:Hide()
    elseif colors[1] == "debuff-type" then
        self:SetScript("OnUpdate", nil)
        self.solidTex:SetVertexColor(colors[2][1], colors[2][2], colors[2][3], colors[2][4])
        self.solidTex:Show()
        self.gradientTex:Hide()
    elseif colors[1] == "change-over-time" then
        self.solidTex:SetVertexColor(colors[4][1], colors[4][2], colors[4][3], colors[4][4])
        self.solidTex:Show()
        self.gradientTex:Hide()
    elseif colors[1] == "class-color" then
        self:SetScript("OnUpdate", nil)
        self.solidTex:Show()
        self.gradientTex:Hide()
    end
end

function I.CreateAura_Color(name, parent)
    local color = CreateFrame("Frame", name, parent)
    color:Hide()
    color.indicatorType = "color"
    color.parent = parent

    local solidTex = color:CreateTexture(nil, "ARTWORK")
    color.solidTex = solidTex
    solidTex:SetTexture(Cell.vars.texture)
    solidTex:SetAllPoints(color)
    solidTex:Hide()

    solidTex:SetScript("OnShow", function()
        -- update texture
        solidTex:SetTexture(Cell.vars.texture)
    end)

    local gradientTex = color:CreateTexture(nil, "ARTWORK")
    color.gradientTex = gradientTex
    gradientTex:SetTexture(Cell.vars.whiteTexture)
    gradientTex:SetAllPoints(color)
    gradientTex:Hide()

    color.SetCooldown = Color_SetCooldown
    color._SetFrameLevel = color.SetFrameLevel
    color.SetFrameLevel = Color_SetFrameLevel
    color.SetAnchor = Color_SetAnchor
    color.SetColors = Color_SetColors

    return color
end

-------------------------------------------------
-- CreateAura_Texture
-------------------------------------------------
local function Texture_OnUpdate(texture, elapsed)
    texture._elapsed = texture._elapsed + elapsed
    if texture._elapsed >= 0.1 then
        texture._elapsed = 0

        texture._remain = texture._duration - (GetTime() - texture._start)
        if texture._remain < 0 then texture._remain = 0 end
        texture.tex:SetAlpha(texture._remain / texture._duration * 0.9 + 0.1)
    end
end

function I.CreateAura_Texture(name, parent)
    local texture = CreateFrame("Frame", name, parent)
    texture:Hide()
    texture.indicatorType = "texture"

    local tex = texture:CreateTexture(name, "OVERLAY")
    texture.tex = tex
    tex:SetAllPoints(texture)

    function texture:SetCooldown(start, duration)
        if texture.fadeOut then
            texture._start = start
            texture._duration = duration
            texture._elapsed = 0.1 -- update immediately
            texture:SetScript("OnUpdate", Texture_OnUpdate)
        else
            texture:SetScript("OnUpdate", nil)
            tex:SetAlpha(texture.colorAlpha)
            texture._start = nil
            texture._duration = nil
            texture._remain = nil
            texture._elapsed = nil
            tex:SetAlpha(1)
        end
        texture:Show()
    end

    function texture:SetFadeOut(fadeOut)
        texture.fadeOut = fadeOut
    end

    function texture:SetTexture(texTbl) -- texture, rotation, color
        if strfind(strlower(texTbl[1]), "^interface") then
            tex:SetTexture(texTbl[1])
        else
            tex:SetAtlas(texTbl[1])
        end
        tex:SetRotation(texTbl[2] * math.pi / 180)
        tex:SetVertexColor(unpack(texTbl[3]))
        texture.colorAlpha = texTbl[3][4]
    end

    return texture
end

-------------------------------------------------
-- CreateAura_Icons
-------------------------------------------------
local function Icons_UpdateSize(icons, numAuras)
    if not (icons.width and icons.orientation) then return end -- not init

    if numAuras then -- call from I.CheckCustomIndicators or preview
        for i = numAuras + 1, icons.maxNum do
            icons[i]:Hide()
        end
    else
        numAuras = 0
        for i = 1, icons.maxNum do
            if icons[i]:IsShown() then
                numAuras = i
            else
                break
            end
        end
    end

    -- set size
    local lines = ceil(numAuras / icons.numPerLine)
    numAuras = min(numAuras, icons.numPerLine)

    if icons.isHorizontal then
        P:SetGridSize(icons, icons.width, icons.height, icons.spacingX, icons.spacingY, numAuras, lines)
    else
        P:SetGridSize(icons, icons.width, icons.height, icons.spacingX, icons.spacingY, lines, numAuras)
    end
end

local function Icons_SetNumPerLine(icons, numPerLine)
    icons.numPerLine = min(numPerLine, icons.maxNum)


    if icons.orientation then
        icons:SetOrientation(icons.orientation)
    -- else
    --     icons:UpdateSize()
    end
end

local function Icons_SetOrientation(icons, orientation)
    icons.orientation = orientation

    local anchor = icons:GetPoint()
    assert(anchor, "[indicator] SetPoint must be called before SetOrientation")

    icons.isHorizontal = not strfind(orientation, "top")

    local point1, point2, x, y
    local newLinePoint2, newLineX, newLineY

    if orientation == "left-to-right" then
        if strfind(anchor, "^BOTTOM") then
            point1 = "BOTTOMLEFT"
            point2 = "BOTTOMRIGHT"
            newLinePoint2 = "TOPLEFT"
            y = 0
            newLineY = icons.spacingY
        else
            point1 = "TOPLEFT"
            point2 = "TOPRIGHT"
            newLinePoint2 = "BOTTOMLEFT"
            y = 0
            newLineY = -icons.spacingY
        end
        x = icons.spacingX
        newLineX = 0

    elseif orientation == "right-to-left" then
        if strfind(anchor, "^BOTTOM") then
            point1 = "BOTTOMRIGHT"
            point2 = "BOTTOMLEFT"
            newLinePoint2 = "TOPRIGHT"
            y = 0
            newLineY = icons.spacingY
        else
            point1 = "TOPRIGHT"
            point2 = "TOPLEFT"
            newLinePoint2 = "BOTTOMRIGHT"
            y = 0
            newLineY = -icons.spacingY
        end
        x = -icons.spacingX
        newLineX = 0

    elseif orientation == "top-to-bottom" then
        if strfind(anchor, "RIGHT$") then
            point1 = "TOPRIGHT"
            point2 = "BOTTOMRIGHT"
            newLinePoint2 = "TOPLEFT"
            x = 0
            newLineX = -icons.spacingX
        else
            point1 = "TOPLEFT"
            point2 = "BOTTOMLEFT"
            newLinePoint2 = "TOPRIGHT"
            x = 0
            newLineX = icons.spacingX
        end
        y = -icons.spacingY
        newLineY = 0

    elseif orientation == "bottom-to-top" then
        if strfind(anchor, "RIGHT$") then
            point1 = "BOTTOMRIGHT"
            point2 = "TOPRIGHT"
            newLinePoint2 = "BOTTOMLEFT"
            x = 0
            newLineX = -icons.spacingX
        else
            point1 = "BOTTOMLEFT"
            point2 = "TOPLEFT"
            newLinePoint2 = "BOTTOMRIGHT"
            x = 0
            newLineX = icons.spacingX
        end
        y = icons.spacingY
        newLineY = 0
    end

    for i = 1, icons.maxNum do
        P:ClearPoints(icons[i])
        if i == 1 then
            P:Point(icons[i], point1)
        elseif i % icons.numPerLine == 1 then
            P:Point(icons[i], point1, icons[i-icons.numPerLine], newLinePoint2, newLineX, newLineY)
        else
            P:Point(icons[i], point1, icons[i-1], point2, x, y)
        end
    end

    icons:UpdateSize()
end

local function Icons_SetSize(icons, width, height)
    icons.width = width
    icons.height = height

    for i = 1, icons.maxNum do
        icons[i]:SetSize(width, height)
    end

    icons:UpdateSize()
end

local function Icons_SetSpacing(icons, spacing)
    icons.spacingX = spacing[1]
    icons.spacingY = spacing[2]

    if icons.orientation then
        icons:SetOrientation(icons.orientation)
    end
end

local function Icons_Hide(icons, hideAll)
    icons:_Hide()
    if hideAll then
        for i = 1, icons.maxNum do
            icons[i]:Hide()
        end
    end
end

local function Icons_SetFont(icons, ...)
    for i = 1, icons.maxNum do
        icons[i]:SetFont(...)
    end
end

local function Icons_ShowDuration(icons, show)
    for i = 1, icons.maxNum do
        icons[i]:ShowDuration(show)
    end
end

local function Icons_ShowStack(icons, show)
    for i = 1, icons.maxNum do
        icons[i]:ShowStack(show)
    end
end

local function Icons_ShowAnimation(icons, show)
    for i = 1, icons.maxNum do
        icons[i]:ShowAnimation(show)
    end
end

local function Icons_UpdatePixelPerfect(icons)
    P:Repoint(icons)
    for i = 1, icons.maxNum do
        icons[i]:UpdatePixelPerfect()
    end
end

function I.CreateAura_Icons(name, parent, num)
    local icons = CreateFrame("Frame", name, parent)
    icons:Hide()

    icons.indicatorType = "icons"
    icons.maxNum = num
    icons.numPerLine = num
    icons.spacingX = 0
    icons.spacingY = 0

    icons._SetSize = icons.SetSize
    icons.SetSize = Icons_SetSize
    icons._Hide = icons.Hide
    icons.Hide = Icons_Hide
    icons.SetFont = Icons_SetFont
    icons.UpdateSize = Icons_UpdateSize
    icons.SetOrientation = Icons_SetOrientation
    icons.SetSpacing = Icons_SetSpacing
    icons.SetNumPerLine = Icons_SetNumPerLine
    icons.ShowDuration = Icons_ShowDuration
    icons.ShowStack = Icons_ShowStack
    icons.ShowAnimation = Icons_ShowAnimation
    icons.UpdatePixelPerfect = Icons_UpdatePixelPerfect

    for i = 1, num do
        local name = name.."Icon"..i
        local frame = I.CreateAura_BarIcon(name, icons)
        icons[i] = frame
    end

    return icons
end

-------------------------------------------------
-- CreateAura_Glow
-------------------------------------------------
local function Glow_OnUpdate(glow, elapsed)
    glow._elapsed = glow._elapsed + elapsed
    if glow._elapsed >= 0.1 then
        glow._elapsed = 0

        glow._remain = glow._duration - (GetTime() - glow._start)
        if glow._remain < 0 then glow._remain = 0 end
        glow:SetAlpha(glow._remain / glow._duration * 0.9 + 0.1)
    end
end

local function Glow_SetCooldown(glow, start, duration)
    if glow.fadeOut then
        glow._start = start
        glow._duration = duration
        glow._elapsed = 0.1 -- update immediately
        glow:SetScript("OnUpdate", Glow_OnUpdate)
    else
        glow:SetScript("OnUpdate", nil)
        glow:SetAlpha(1)
        glow._start = nil
        glow._duration = nil
        glow._remain = nil
        glow._elapsed = nil
    end

    glow:Show()

    local glowOptions = glow.glowOptions
    local glowType = glowOptions[1]

    if glowType == "Normal" then
        LCG.PixelGlow_Stop(glow)
        LCG.AutoCastGlow_Stop(glow)
        LCG.ProcGlow_Stop(glow)
        LCG.ButtonGlow_Start(glow, glowOptions[2])
    elseif glowType == "Pixel" then
        LCG.ButtonGlow_Stop(glow)
        LCG.AutoCastGlow_Stop(glow)
        LCG.ProcGlow_Stop(glow)
        -- color, N, frequency, length, thickness
        LCG.PixelGlow_Start(glow, glowOptions[2], glowOptions[3], glowOptions[4], glowOptions[5], glowOptions[6])
    elseif glowType == "Shine" then
        LCG.ButtonGlow_Stop(glow)
        LCG.PixelGlow_Stop(glow)
        LCG.ProcGlow_Stop(glow)
        -- color, N, frequency, scale
        LCG.AutoCastGlow_Start(glow, glowOptions[2], glowOptions[3], glowOptions[4], glowOptions[5])
    elseif glowType == "Proc" then
        LCG.ButtonGlow_Stop(glow)
        LCG.PixelGlow_Stop(glow)
        LCG.AutoCastGlow_Stop(glow)
        -- color, duration
        LCG.ProcGlow_Start(glow, {color=glowOptions[2], duration=glowOptions[3], startAnim=false})
    else
        LCG.ButtonGlow_Stop(glow)
        LCG.PixelGlow_Stop(glow)
        LCG.AutoCastGlow_Stop(glow)
        LCG.ProcGlow_Stop(glow)
        glow:Hide()
    end
end

function I.CreateAura_Glow(name, parent)
    local glow = CreateFrame("Frame", name, parent)
    glow:SetAllPoints(parent)
    glow:Hide()
    glow.indicatorType = "glow"

    glow.SetCooldown = Glow_SetCooldown

    function glow:SetFadeOut(fadeOut)
        glow.fadeOut = fadeOut
    end

    function glow:UpdateGlowOptions(options)
        glow.glowOptions = options
    end

    -- glow:SetScript("OnHide", function()
    --     LCG.ButtonGlow_Stop(glow)
    --     LCG.PixelGlow_Stop(glow)
    --     LCG.AutoCastGlow_Stop(glow)
    --     LCG.ProcGlow_Stop(glow)
    -- end)

    return glow
end

-------------------------------------------------
-- CreateAura_Bars
-------------------------------------------------
local function Bars_OnUpdate(bar, elapsed)
    bar._remain = bar._duration - (GetTime() - bar._start)
    if bar._remain < 0 then bar._remain = 0 end
    bar:SetValue(bar._remain)
end

local function Bars_SetCooldown(bar, start, duration, color)
    if duration == 0 then
        bar:SetScript("OnUpdate", nil)
        bar:SetMinMaxValues(0, 1)
        bar:SetValue(1)
        bar._start = nil
        bar._duration = nil
        bar._remain = nil
    else
        bar._start = start
        bar._duration = duration
        bar:SetMinMaxValues(0, duration)
        bar:SetScript("OnUpdate", Bars_OnUpdate)
    end

    bar:SetStatusBarColor(color[1], color[2], color[3], 1)
    bar:Show()
end

function I.CreateAura_Bars(name, parent, num)
    local bars = CreateFrame("Frame", name, parent)
    bars:Hide()
    bars.indicatorType = "bars"

    bars._SetSize = bars.SetSize

    function bars:UpdateSize(barsShown)
        if not (bars.width and bars.height) then return end -- not init
        if barsShown then -- call from I.CheckCustomIndicators or preview
            for i = barsShown + 1, num do
                bars[i]:Hide()
            end
            if barsShown ~= 0 then
                bars:_SetSize(bars.width, bars.height*barsShown-P:Scale(1)*(barsShown-1))
            end
        else
            for i = 1, num do
                if bars[i]:IsShown() then
                    bars:_SetSize(bars.width, bars.height*i-P:Scale(1)*(i-1))
                else
                    break
                end
            end
        end
    end

    function bars:SetSize(width, height)
        bars.width = width
        bars.height = height

        for i = 1, num do
            bars[i]:SetSize(width, height)
        end

        bars:UpdateSize()
    end

    function bars:SetOrientation(orientation)
        local point1, point2, offset
        if orientation == "top-to-bottom" then
            point1 = "TOPLEFT"
            point2 = "BOTTOMLEFT"
            offset = 1
        elseif orientation == "bottom-to-top" then
            point1 = "BOTTOMLEFT"
            point2 = "TOPLEFT"
            offset = -1
        end

        for i = 1, num do
            P:ClearPoints(bars[i])
            if i == 1 then
                P:Point(bars[i], point1)
            else
                P:Point(bars[i], point1, bars[i-1], point2, 0, offset)
            end
        end

        bars:UpdateSize()
    end

    for i = 1, num do
        local name = name.."Bar"..i
        local bar = I.CreateAura_Bar(name, bars)
        bars[i] = bar

        bar.stack:Hide()
        bar.duration:Hide()
        bar.SetCooldown = Bars_SetCooldown
    end

    bars._Hide = bars.Hide
    function bars:Hide(hideAll)
        bars:_Hide()
        if hideAll then
            for i = 1, num do
                bars[i]:Hide()
            end
        end
    end

    function bars:UpdatePixelPerfect()
        -- P:Resize(bars)
        P:Repoint(bars)
        for i = 1, num do
            bars[i]:UpdatePixelPerfect()
        end
    end

    return bars
end

-------------------------------------------------
-- CreateAura_Overlay
-------------------------------------------------
local function Overlay_OnUpdate(overlay, elapsed)
    overlay._remain = overlay._duration - (GetTime() - overlay._start)
    if overlay._remain < 0 then overlay._remain = 0 end
    overlay:_SetValue(overlay._remain)

    overlay._elapsed = overlay._elapsed + elapsed
    if overlay._elapsed >= 0.1 then
        overlay._elapsed = 0
        -- update color
        if overlay.colors[3][1] and overlay._remain <= overlay.colors[3][2] then
            if overlay.state ~= 3 then
                overlay.state = 3
                overlay:SetStatusBarColor(overlay.colors[3][3][1], overlay.colors[3][3][2], overlay.colors[3][3][3], overlay.colors[3][3][4])
            end
        elseif overlay.colors[2][1] and overlay._remain <= overlay._duration * overlay.colors[2][2] then
            if overlay.state ~= 2 then
                overlay.state = 2
                overlay:SetStatusBarColor(overlay.colors[2][3][1], overlay.colors[2][3][2], overlay.colors[2][3][3], overlay.colors[2][3][4])
            end
        elseif overlay.state ~= 1 then
            overlay.state = 1
            overlay:SetStatusBarColor(overlay.colors[1][1], overlay.colors[1][2], overlay.colors[1][3], overlay.colors[1][4])
        end
    end
end

local function Overlay_SetCooldown(overlay, start, duration, debuffType, texture, count)
    if duration == 0 then
        overlay:SetScript("OnUpdate", nil)
        overlay:_SetMinMaxValues(0, 1)
        overlay:_SetValue(1)
        overlay:SetStatusBarColor(unpack(overlay.colors[1]))
        overlay._start = nil
        overlay._duration = nil
        overlay._remain = nil
        overlay._elapsed = nil
    else
        overlay:_SetMinMaxValues(0, duration)
        overlay._start = start
        overlay._duration = duration
        overlay._elapsed = 0.1 -- update immediately
        overlay:SetScript("OnUpdate", Overlay_OnUpdate)
    end

    overlay:Show()
end

local function Overlay_EnableSmooth(overlay, smooth)
    if smooth then
        overlay._SetMinMaxValues = overlay.SetMinMaxSmoothedValue
        overlay._SetValue = overlay.SetSmoothedValue
    else
        overlay._SetMinMaxValues = overlay.SetMinMaxValues
        overlay._SetValue = overlay.SetValue
    end
end

local function Overlay_SetColors(overlay, colors)
    overlay.state = nil
    overlay.colors = colors
end

-- +56 ~ +110
local function Overlay_SetFrameLevel(overlay, frameLevel)
    overlay:_SetFrameLevel(frameLevel + 55)
end

function I.CreateAura_Overlay(name, parent)
    local overlay = CreateFrame("StatusBar", name, parent.widgets.healthBar)
    overlay:SetStatusBarTexture(Cell.vars.whiteTexture)
    overlay:Hide()
    overlay.indicatorType = "overlay"

    Mixin(overlay, SmoothStatusBarMixin)
    overlay:SetAllPoints()
    -- overlay:SetBackdropColor(0, 0, 0, 0)

    overlay.SetCooldown = Overlay_SetCooldown
    overlay._SetMinMaxValues = overlay.SetMinMaxValues
    overlay._SetValue = overlay.SetValue
    overlay._SetFrameLevel = overlay.SetFrameLevel
    overlay.SetFrameLevel = Overlay_SetFrameLevel
    overlay.EnableSmooth = Overlay_EnableSmooth
    overlay.SetColors = Overlay_SetColors

    return overlay
end

-------------------------------------------------
-- CreateAura_Block
-------------------------------------------------
local function Block_OnUpdate_Duration(frame, elapsed)
    frame._remain = frame._duration - (GetTime() - frame._start)
    if frame._remain < 0 then frame._remain = 0 end

    frame._elapsed = frame._elapsed + elapsed
    if frame._elapsed >= 0.1 then
        frame._elapsed = 0
        -- update color
        if frame.colors[4][1] and frame._remain <= frame.colors[4][2] then
            if frame.state ~= 3 then
                frame.state = 3
                frame:SetBackdropColor(frame.colors[4][3][1], frame.colors[4][3][2], frame.colors[4][3][3], frame.colors[4][3][4])
            end
        elseif frame.colors[3][1] and frame._remain <= frame._duration * frame.colors[3][2] then
            if frame.state ~= 2 then
                frame.state = 2
                frame:SetBackdropColor(frame.colors[3][3][1], frame.colors[3][3][2], frame.colors[3][3][3], frame.colors[3][3][4])
            end
        elseif frame.state ~= 1 then
            frame.state = 1
            frame:SetBackdropColor(frame.colors[2][1], frame.colors[2][2], frame.colors[2][3], frame.colors[2][4])
        end
    end

    if frame._remain > frame._threshold then
        frame.duration:SetText("")
        return
    end

    -- format
    if frame._remain > 60 then
        frame.duration:SetFormattedText("%dm", frame._remain / 60)
    else
        if Cell.vars.iconDurationRoundUp then
            frame.duration:SetFormattedText("%d", ceil(frame._remain))
        else
            if frame._remain < Cell.vars.iconDurationDecimal then
                frame.duration:SetFormattedText("%.1f", frame._remain)
            else
                frame.duration:SetFormattedText("%d", frame._remain)
            end
        end
    end
end

local function Block_SetCooldown_Duration(frame, start, duration, debuffType, texture, count, refreshing)
    -- local r, g, b
    -- if debuffType then
    --     r, g, b = I.GetDebuffTypeColor(debuffType)
    -- else
    --     r, g, b = 0, 0, 0
    -- end

    if duration == 0 then
        frame.cooldown:Hide()
        frame.duration:Hide()
        frame:SetScript("OnUpdate", nil)
        frame._start = nil
        frame._duration = nil
        frame._remain = nil
        frame._elapsed = nil
        frame._threshold = nil
    else
        -- frame.cooldown:SetSwipeColor(r, g, b)
        frame.cooldown:ShowCooldown(start, duration)

        if not frame.showDuration then
            frame._threshold = -1
            frame.duration:Hide()
        else
            if frame.showDuration == true then
                frame._threshold = duration
            elseif frame.showDuration >= 1 then
                frame._threshold = frame.showDuration
            else -- < 1
                frame._threshold = frame.showDuration * duration
            end
            frame.duration:Show()
        end

        frame._start = start
        frame._duration = duration
        frame._elapsed = 0.1 -- update immediately
        frame:SetScript("OnUpdate", Block_OnUpdate_Duration)
    end

    frame.stack:SetText((count == 0 or count == 1) and "" or count)
    frame:Show()

    if refreshing then
        frame.ag:Play()
    end
end

local function Block_OnUpdate_Stack(frame, elapsed)
    frame._remain = frame._duration - (GetTime() - frame._start)
    if frame._remain < 0 then frame._remain = 0 end

    if frame._remain > frame._threshold then
        frame.duration:SetText("")
        return
    end

    -- format
    if frame._remain > 60 then
        frame.duration:SetFormattedText("%dm", frame._remain / 60)
    else
        if Cell.vars.iconDurationRoundUp then
            frame.duration:SetFormattedText("%d", ceil(frame._remain))
        else
            if frame._remain < Cell.vars.iconDurationDecimal then
                frame.duration:SetFormattedText("%.1f", frame._remain)
            else
                frame.duration:SetFormattedText("%d", frame._remain)
            end
        end
    end
end

local function Block_SetCooldown_Stack(frame, start, duration, debuffType, texture, count, refreshing)
    if duration == 0 then
        frame.cooldown:Hide()
        frame.duration:Hide()
        frame:SetScript("OnUpdate", nil)
        frame._start = nil
        frame._duration = nil
        frame._remain = nil
        frame._threshold = nil
    else
        -- frame.cooldown:SetSwipeColor(r, g, b)
        frame.cooldown:ShowCooldown(start, duration)

        if not frame.showDuration then
            frame._threshold = -1
            frame.duration:Hide()
        else
            if frame.showDuration == true then
                frame._threshold = duration
            elseif frame.showDuration >= 1 then
                frame._threshold = frame.showDuration
            else -- < 1
                frame._threshold = frame.showDuration * duration
            end
            frame.duration:Show()
        end

        frame._start = start
        frame._duration = duration
        frame:SetScript("OnUpdate", Block_OnUpdate_Stack)
    end

    -- update color
    if frame.colors[4][1] and count >= frame.colors[4][2] then
        frame:SetBackdropColor(frame.colors[4][3][1], frame.colors[4][3][2], frame.colors[4][3][3], frame.colors[4][3][4])
    elseif frame.colors[3][1] and count >= frame.colors[3][2] then
        frame:SetBackdropColor(frame.colors[3][3][1], frame.colors[3][3][2], frame.colors[3][3][3], frame.colors[3][3][4])
    else
        frame:SetBackdropColor(frame.colors[2][1], frame.colors[2][2], frame.colors[2][3], frame.colors[2][4])
    end

    frame.stack:SetText((count == 0 or count == 1) and "" or count)
    frame:Show()

    if refreshing then
        frame.ag:Play()
    end
end

local function Block_SetColors(frame, colors)
    if colors[1] == "duration" then
        frame.SetCooldown = Block_SetCooldown_Duration
    else
        frame.SetCooldown = Block_SetCooldown_Stack
    end
    frame:SetBackdropBorderColor(colors[5][1], colors[5][2], colors[5][3], colors[5][4])
    frame.state = nil
    frame.colors = colors
end

local function Block_UpdatePixelPerfect(frame)
    P:Resize(frame)
    P:Repoint(frame)
    P:Repoint(frame.stack)
    P:Repoint(frame.duration)
    P:Repoint(frame.cooldown)
    if frame.cooldown.spark then
        P:Resize(frame.cooldown.spark)
    end
end

function I.CreateAura_Block(name, parent)
    local frame = CreateFrame("Frame", name, parent, "BackdropTemplate")
    frame:Hide()
    frame.indicatorType = "block"

    frame:SetBackdrop({bgFile = Cell.vars.whiteTexture, edgeFile = Cell.vars.whiteTexture, edgeSize = P:Scale(CELL_BORDER_SIZE)})

    Shared_SetCooldownStyle(frame, CELL_COOLDOWN_STYLE, true)

    frame.stack = frame.cooldown:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")
    frame.duration = frame.cooldown:CreateFontString(nil, "OVERLAY", "CELL_FONT_STATUS")

    frame.SetFont = Shared_SetFont
    frame.SetColors = Block_SetColors
    frame.ShowStack = Shared_ShowStack
    frame.ShowDuration = Shared_ShowDuration
    frame.SetCooldown = Block_SetCooldown_Duration
    frame.UpdatePixelPerfect = Block_UpdatePixelPerfect

    local ag = frame:CreateAnimationGroup()
    frame.ag = ag
    local t1 = ag:CreateAnimation("Translation")
    t1:SetOffset(0, 5)
    t1:SetDuration(0.1)
    t1:SetOrder(1)
    t1:SetSmoothing("OUT")
    local t2 = ag:CreateAnimation("Translation")
    t2:SetOffset(0, -5)
    t2:SetDuration(0.1)
    t2:SetOrder(2)
    t2:SetSmoothing("IN")

    return frame
end

-------------------------------------------------
-- CreateAura_Blocks
-------------------------------------------------
local function Blocks_OnUpdate(frame, elapsed)
    frame._remain = frame._duration - (GetTime() - frame._start)
    if frame._remain < 0 then frame._remain = 0 end

    if frame._remain > frame._threshold then
        frame.duration:SetText("")
        return
    end

    -- format
    if frame._remain > 60 then
        frame.duration:SetFormattedText("%dm", frame._remain / 60)
    else
        if Cell.vars.iconDurationRoundUp then
            frame.duration:SetFormattedText("%d", ceil(frame._remain))
        else
            if frame._remain < Cell.vars.iconDurationDecimal then
                frame.duration:SetFormattedText("%.1f", frame._remain)
            else
                frame.duration:SetFormattedText("%d", frame._remain)
            end
        end
    end
end

local function Blocks_SetCooldown(frame, start, duration, debuffType, texture, count, refreshing, color)
    if duration == 0 then
        frame.cooldown:Hide()
        frame.duration:Hide()
        frame:SetScript("OnUpdate", nil)
        frame._start = nil
        frame._duration = nil
        frame._remain = nil
        frame._threshold = nil
    else
        frame.cooldown:ShowCooldown(start, duration)

        if not frame.showDuration then
            frame._threshold = -1
            frame.duration:Hide()
        else
            if frame.showDuration == true then
                frame._threshold = duration
            elseif frame.showDuration >= 1 then
                frame._threshold = frame.showDuration
            else -- < 1
                frame._threshold = frame.showDuration * duration
            end
            frame.duration:Show()
        end

        frame._start = start
        frame._duration = duration
        frame:SetScript("OnUpdate", Blocks_OnUpdate)
    end

    frame:SetBackdropColor(color[1], color[2], color[3], color[4])
    frame.stack:SetText((count == 0 or count == 1) and "" or count)
    frame:Show()

    if refreshing then
        frame.ag:Play()
    end
end

function I.CreateAura_Blocks(name, parent, num)
    local blocks = CreateFrame("Frame", name, parent)
    blocks:Hide()

    blocks.indicatorType = "blocks"
    blocks.maxNum = num
    blocks.numPerLine = num

    blocks._SetSize = blocks.SetSize
    blocks.SetSize = Icons_SetSize
    blocks._Hide = blocks.Hide
    blocks.Hide = Icons_Hide
    blocks.SetFont = Icons_SetFont
    blocks.UpdateSize = Icons_UpdateSize
    blocks.SetOrientation = Icons_SetOrientation
    blocks.SetSpacing = Icons_SetSpacing
    blocks.SetNumPerLine = Icons_SetNumPerLine
    blocks.ShowDuration = Icons_ShowDuration
    blocks.ShowStack = Icons_ShowStack
    blocks.UpdatePixelPerfect = Icons_UpdatePixelPerfect

    for i = 1, num do
        local name = name.."Icons"..i
        local frame = I.CreateAura_Block(name, blocks)
        blocks[i] = frame
        frame.SetCooldown = Blocks_SetCooldown
        frame:SetBackdropBorderColor(0, 0, 0, 1)
    end

    return blocks
end

-------------------------------------------------
-- CreateAura_Border
-------------------------------------------------
local function Border_OnUpdate(border, elapsed)
    border._elapsed = border._elapsed + elapsed
    if border._elapsed >= 0.1 then
        border._elapsed = 0

        border._remain = border._duration - (GetTime() - border._start)
        if border._remain < 0 then border._remain = 0 end
        border:SetAlpha(border._remain / border._duration * 0.9 + 0.1)
    end
end

local function Border_SetFadeOut(border, fadeOut)
    border.fadeOut = fadeOut
end

local function Border_SetCooldown(border, start, duration, _, _, _, _, color)
    if border.fadeOut then
        border._start = start
        border._duration = duration
        border._elapsed = 0.1 -- update immediately
        border:SetScript("OnUpdate", Border_OnUpdate)
    else
        border:SetScript("OnUpdate", nil)
        border._start = nil
        border._duration = nil
        border._remain = nil
        border._elapsed = nil
        border:SetAlpha(1)
    end
    border.tex:SetVertexColor(color[1], color[2], color[3], color[4])
    border:Show()
end

local function Border_UpdatePixelPerfect(border)
    P:Repoint(border)
    P:Repoint(border.mask)
    P:Repoint(border.mask2)
end

local function Border_SetThickness(border, thickness)
    P:ClearPoints(border.mask)
    P:Point(border.mask, "TOPLEFT", thickness, -thickness)
    P:Point(border.mask, "BOTTOMRIGHT", -thickness, thickness)
    P:ClearPoints(border.mask2)
    P:Point(border.mask2, "TOPLEFT", thickness+CELL_BORDER_SIZE, -thickness-CELL_BORDER_SIZE)
    P:Point(border.mask2, "BOTTOMRIGHT", -thickness-CELL_BORDER_SIZE, thickness+CELL_BORDER_SIZE)
end

function I.CreateAura_Border(name, parent)
    local border = CreateFrame("Frame", name, parent)
    border:Hide()
    border.indicatorType = "border"

    P:Point(border, "TOPLEFT", CELL_BORDER_SIZE, -CELL_BORDER_SIZE)
    P:Point(border, "BOTTOMRIGHT", -CELL_BORDER_SIZE, CELL_BORDER_SIZE)

    local mask = border:CreateMaskTexture()
    border.mask = mask
    mask:SetTexture(Cell.vars.emptyTexture, "CLAMPTOWHITE","CLAMPTOWHITE")

    local tex = border:CreateTexture(nil, "ARTWORK")
    border.tex = tex
    tex:SetAllPoints()
    tex:SetTexture(Cell.vars.whiteTexture)
    tex:AddMaskTexture(mask)

    local mask2 = border:CreateMaskTexture()
    border.mask2 = mask2
    mask2:SetTexture(Cell.vars.emptyTexture, "CLAMPTOWHITE","CLAMPTOWHITE")

    local tex2 = border:CreateTexture(nil, "ARTWORK", nil, -1)
    tex2:SetAllPoints()
    tex2:SetColorTexture(0, 0, 0)
    tex2:AddMaskTexture(mask2)

    border.SetCooldown = Border_SetCooldown
    border.SetFadeOut = Border_SetFadeOut
    border.SetThickness = Border_SetThickness
    border.UpdatePixelPerfect = Border_UpdatePixelPerfect

    return border
end