--========================================================--
--                Scorpio UnitFrame Power API             --
--                                                        --
-- Author      :  kurapica125@outlook.com                 --
-- Create Date :  2021/03/06                              --
--========================================================--

--========================================================--
Scorpio           "Scorpio.Secure.UnitFrame.PowerAPI""1.0.0"
--========================================================--

namespace "Scorpio.Secure.UnitFrame"

import "System.Reactive"
import "System.Toolset"

------------------------------------------------------------
--                     Unit Power API                     --
------------------------------------------------------------
local _PlayerClass              = select(2, UnitClass("player"))
local _ClassPowerType, _ClassPowerToken, _PrevClassPowerType

local _ClassPowerRefresh        = Subject()
local _ClassPowerSubject        = Subject()
local _ClassPowerMaxSubject     = Subject()

local SPEC_DEMONHUNTER_VENGENCE = 2
local SOULFRAGMENT              = 203981
local SOULFRAGMENTNAME

local STAGGER                   = 100 -- DIFF to other class type

local FindAuraByName            = _G.AuraUtil.FindAuraByName
local PowerType                 = _G.Enum.PowerType

__Async__()
OnEnable                        = OnEnable + function ()
    -- Use the custom unit event to provide the API
    if _PlayerClass == "WARRIOR" then

    elseif _PlayerClass == "DEATHKNIGHT" then
        _ClassPowerToken        = "RUNES"
        _ClassPowerType         = PowerType.Runes

        while true do
            local spec          = GetSpecialization()
            Continue(RefreshClassPower)
            NextEvent("PLAYER_SPECIALIZATION_CHANGED")
        end

    elseif _PlayerClass == "PALADIN" and Scorpio.IsRetail then
        _ClassPowerToken        = "HOLY_POWER"
        _ClassPowerType         = PowerType.HolyPower

    elseif _PlayerClass == "MONK" then
        _ClassPowerToken        = "CHI"

        while true do
            local spec          = GetSpecialization()
            _ClassPowerType     = spec == SPEC_MONK_WINDWALKER and PowerType.Chi or spec == SPEC_MONK_BREWMASTER and STAGGER or nil
            Continue(RefreshClassPower)
            NextEvent("PLAYER_SPECIALIZATION_CHANGED")
        end

    elseif _PlayerClass == "PRIEST" and Scorpio.IsRetail then
        _ClassPowerToken        = "MANA"

        while true do
            _ClassPowerType     = GetSpecialization() == SPEC_PRIEST_SHADOW and PowerType.Mana or nil
            Continue(RefreshClassPower)
            NextEvent("PLAYER_SPECIALIZATION_CHANGED")
        end

    elseif _PlayerClass == "SHAMAN" and Scorpio.IsRetail then
        _ClassPowerToken        = "MANA"

        while true do
            _ClassPowerType     = GetSpecialization() ~= SPEC_SHAMAN_RESTORATION and PowerType.Mana or nil
            Continue(RefreshClassPower)
            NextEvent("PLAYER_SPECIALIZATION_CHANGED")
        end

    elseif _PlayerClass == "DRUID" then
        _ClassPowerToken        = "COMBO_POINTS"

        while true do
            _ClassPowerType     = GetShapeshiftFormID() == DRUID_CAT_FORM and PowerType.ComboPoints or nil
            Continue(RefreshClassPower)
            NextEvent("UPDATE_SHAPESHIFT_FORM")
        end

    elseif _PlayerClass == "ROGUE" then
        _ClassPowerToken        = "COMBO_POINTS"
        _ClassPowerType         = PowerType.ComboPoints

    elseif _PlayerClass == "MAGE" and Scorpio.IsRetail then
        _ClassPowerToken        = "ARCANE_CHARGES"

        while true do
            _ClassPowerType     = GetSpecialization() == SPEC_MAGE_ARCANE and PowerType.ArcaneCharges or nil
            Continue(RefreshClassPower)
            NextEvent("PLAYER_SPECIALIZATION_CHANGED")
        end

    elseif _PlayerClass == "WARLOCK" and Scorpio.IsRetail then
        _ClassPowerToken        = "SOUL_SHARDS"
        _ClassPowerType         = PowerType.SoulShards

    elseif _PlayerClass == "HUNTER" then

    elseif _PlayerClass == "DEMONHUNTER" then
        SOULFRAGMENTNAME        = GetSpellInfo(SOULFRAGMENT)
        _ClassPowerToken        = "DEMONHUNTER"

        while true do
            _ClassPowerType     = GetSpecialization() == SPEC_DEMONHUNTER_VENGENCE and SOULFRAGMENT or nil
            Continue(RefreshClassPower)
            NextEvent("PLAYER_SPECIALIZATION_CHANGED")
        end

    elseif _PlayerClass == "EVOKER" then
        _ClassPowerToken        = "ESSENCE"
        _ClassPowerType         = PowerType.Essence
    end

    return RefreshClassPower()
end

function RefreshClassPower()
    if _ClassPowerType then
        if _PrevClassPowerType ~= _ClassPowerType then
            if _PrevClassPowerType then
                _ClassPowerSubject.Subscription = nil
                _ClassPowerMaxSubject.Subscription = nil
            end

            _PrevClassPowerType = _ClassPowerType

            if _ClassPowerType == SOULFRAGMENT then
                -- Use aura to track, keep using Next() for throttling
                Wow.FromEvent("UNIT_AURA"):MatchUnit("player"):Subscribe(_ClassPowerSubject, _ClassPowerSubject.Subscription)
            elseif _ClassPowerType == STAGGER then
                Wow.FromEvent("UNIT_HEALTH"):MatchUnit("player"):Subscribe(_ClassPowerSubject, _ClassPowerSubject.Subscription)
                Wow.FromEvent("UNIT_MAXHEALTH"):MatchUnit("player"):Subscribe(_ClassPowerMaxSubject, _ClassPowerMaxSubject.Subscription)
            elseif _ClassPowerType == PowerType.Runes then
                Wow.FromEvent("RUNE_POWER_UPDATE"):Map("=>'player'"):Subscribe(_ClassPowerSubject, _ClassPowerSubject.Subscription)
            else
                Wow.FromEvent("UNIT_POWER_FREQUENT", "UNIT_POWER_POINT_CHARGE"):MatchUnit("player"):Subscribe(_ClassPowerSubject, _ClassPowerSubject.Subscription)
                Wow.FromEvent("UNIT_MAXPOWER"):MatchUnit("player"):Subscribe(_ClassPowerMaxSubject, _ClassPowerMaxSubject.Subscription)
            end
        end
    else
        _PrevClassPowerType     = false
        _ClassPowerSubject.Subscription = nil
        _ClassPowerMaxSubject.Subscription = nil
    end

    -- Publish the changes
    _ClassPowerRefresh:OnNext("any")        -- For all, but other unit's indicator will be disabled and hide
    _ClassPowerSubject:OnNext("player")     -- For player only
    _ClassPowerMaxSubject:OnNext("player")
end

__Static__() __AutoCache__()
function Wow.ClassPower()
    if _PlayerClass == "DEATHKNIGHT" then
        -- A simple total rune as basic features
        return Wow.FromNextUnitEvent(_ClassPowerSubject):Map(function(unit) local count = 0 for i = 1, 6 do local _, _, ready = GetRuneCooldown(i) if ready then count = count + 1 end end return count end)
    elseif _PlayerClass == "DEMONHUNTER" then
        return Wow.FromNextUnitEvent(_ClassPowerSubject):Map(function(unit) return _ClassPowerType and min((select(3, FindAuraByName(SOULFRAGMENTNAME, "player", "PLAYER|HELPFUL"))) or 0, 5) or 0 end)
    elseif _PlayerClass == "MONK" then
        return Wow.FromNextUnitEvent(_ClassPowerSubject):Map(function(unit) return (_ClassPowerType == STAGGER and UnitStagger(unit) or _ClassPowerType and UnitPower(unit, _ClassPowerType)) or 0 end)
    else
        return Wow.FromNextUnitEvent(_ClassPowerSubject):Map(function(unit) return _ClassPowerType and UnitPower(unit, _ClassPowerType) or 0 end)
    end
end

__Static__() __AutoCache__()
function Wow.ClassPowerMax()
    local minMax                = { min = 0 }
    if _PlayerClass == "DEATHKNIGHT" then
        minMax.max              = 6
        return Wow.FromUnitEvent(_ClassPowerMaxSubject):Map(function(unit)
            return minMax
        end)
    elseif _PlayerClass == "DEMONHUNTER" then
        minMax.max              = 5
        -- Only track max 5 soul fragment
        return Wow.FromUnitEvent(_ClassPowerMaxSubject):Map(function(unit)
            return minMax
        end)
    elseif _PlayerClass == "MONK" then
        return Wow.FromUnitEvent(_ClassPowerMaxSubject):Map(function(unit)
            minMax.max          = _ClassPowerType == STAGGER and UnitHealthMax(unit) or _ClassPowerType and UnitPowerMax(unit, _ClassPowerType) or 100
            return minMax
        end)
    else
        return Wow.FromUnitEvent(_ClassPowerMaxSubject):Map(function(unit)
            minMax.max          = _ClassPowerType and UnitPowerMax(unit, _ClassPowerType) or 100
            return minMax
        end)
    end
end

__Static__() __AutoCache__()
function Wow.ClassPowerColor()
    if _PlayerClass == "MONK" then
        local STAGGER_YELLOW_TRANSITION = _G.STAGGER_YELLOW_TRANSITION
        local STAGGER_RED_TRANSITION    = _G.STAGGER_RED_TRANSITION

        return Wow.FromUnitEvent(_ClassPowerSubject):Map(function(unit)
            if _ClassPowerType and UnitIsUnit(unit, "player") then
                if _ClassPowerType == STAGGER then
                    local curr          = UnitStagger(unit)
                    local maxs          = UnitHealthMax(unit)
                    if curr and maxs then
                        local pct       = curr / max

                        if (pct >= STAGGER_RED_TRANSITION) then
                            return Color.RED
                        elseif (pct >= STAGGER_YELLOW_TRANSITION) then
                            return Color.YELLOW
                        else
                            return Color.GREEN
                        end
                    end
                else
                    return Color[_ClassPowerToken]
                end
            end

            return Color.DISABLED
        end)
    else
        return Wow.FromUnitEvent(_ClassPowerRefresh):Map(function(unit)
            return UnitIsUnit(unit, "player") and _ClassPowerType and Color[_ClassPowerToken] or Color.DISABLED
        end)
    end
end

__Static__() __AutoCache__()
function Wow.ClassPowerUsable()
    return Wow.FromUnitEvent(_ClassPowerRefresh):Map(function(unit) return UnitIsUnit(unit, "player") and _ClassPowerType and true or false end)
end

__Static__() __AutoCache__()
function Wow.UnitPower(frequent)
    return Wow.FromUnitEvent(frequent and "UNIT_POWER_FREQUENT" or "UNIT_POWER_UPDATE", "UNIT_MAXPOWER", "UNIT_DISPLAYPOWER", "UNIT_POWER_BAR_SHOW", "UNIT_POWER_BAR_HIDE")
        :Next():Map(function(unit) return UnitPower(unit, (UnitPowerType(unit))) end)
end

__Static__() __AutoCache__()
function Wow.UnitPowerMax()
    local minMax                = { min = 0 }
    return Wow.FromUnitEvent("UNIT_MAXPOWER", "UNIT_DISPLAYPOWER", "UNIT_POWER_BAR_SHOW", "UNIT_POWER_BAR_HIDE")
        :Map(function(unit) minMax.max =  UnitPowerMax(unit, (UnitPowerType(unit))) return minMax end)
end

__Static__() __AutoCache__()
function Wow.UnitPowerColor()
    local scolor                = Color(1, 1, 1)
    return Wow.FromUnitEvent("UNIT_CONNECTION", "UNIT_DISPLAYPOWER", "UNIT_POWER_BAR_SHOW", "UNIT_POWER_BAR_HIDE")
        :Map(function(unit)
            if not UnitIsConnected(unit) then
                scolor.r        = 0.5
                scolor.g        = 0.5
                scolor.b        = 0.5
            else
                local ptype, ptoken, r, g, b = UnitPowerType(unit)
                local color     = ptoken and Color[ptoken]
                if color then return color end

                if r then
                    scolor.r    = r
                    scolor.g    = g
                    scolor.b    = b
                else
                    return Color.MANA
                end
            end

            return scolor
        end)
end

__Static__() __AutoCache__()
function Wow.UnitMana()
    local MANA                  = _G.Enum.PowerType.Mana
    return Wow.FromUnitEvent("UNIT_POWER_UPDATE", "UNIT_MAXPOWER", "UNIT_DISPLAYPOWER", "UNIT_POWER_BAR_SHOW", "UNIT_POWER_BAR_HIDE")
        :Map(function(unit) return UnitPower(unit, MANA) end)
end

__Static__() __AutoCache__()
function Wow.UnitManaMax()
    local MANA                  = _G.Enum.PowerType.Mana
    local minMax                = { min = 0 }
    return Wow.FromUnitEvent("UNIT_MAXPOWER", "UNIT_DISPLAYPOWER", "UNIT_POWER_BAR_SHOW", "UNIT_POWER_BAR_HIDE")
        :Map(function(unit) minMax.max =  UnitPowerMax(unit, MANA) return minMax end)
end

__Static__() __AutoCache__()
function Wow.UnitManaVisible()
    local MANA                  = _G.Enum.PowerType.Mana
    return Wow.FromUnitEvent("UNIT_MAXPOWER", "UNIT_DISPLAYPOWER", "UNIT_POWER_BAR_SHOW", "UNIT_POWER_BAR_HIDE")
        :Map(function(unit) return UnitPowerType(unit) ~= MANA and (UnitPowerMax(unit, MANA) or 0) > 0 end)
end
