--========================================================--
--                Scorpio UnitFrame FrameWork             --
--                                                        --
-- Author      :  kurapica125@outlook.com                 --
-- Create Date :  2020/06/09                              --
--========================================================--

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

do
    ------------------------------------------------------
    --                Hover Spell Helper                --
    ------------------------------------------------------
    local _UnitFrameHoverSpellGroup

    _UpdateGroupQueue           = Queue()

    _ActionType                 = {
        Spell                   = {
            type                = "spell",
            parse               = function (spell)
                if (type(spell) == "string" or type(spell) == "number") then
                    -- Only keep spell id
                    local name, _, _, _, _, _, spellId = GetSpellInfo(spell)
                    if name then return spellId, IsHarmfulSpell(name) end
                end

                error("Invalid spell name|id - "..tostring(spell), 3)
            end,
            content             = "spell",
            tranMacro           = Scorpio.IsRetail and function (spell, with)
                if GetSpellInfo(spell) then
                    return ("/%s %%unit\n/cast %s"):format(with, GetSpellInfo(spell))
                end
            end or function (spell, with)
                if GetSpellInfo(spell) then
                    local sub   = GetSpellSubtext(spell)
                    if sub and sub ~= "" then
                        return ("/%s %%unit\n/cast %s"):format(with, GetSpellInfo(spell).."("..sub..")")
                    else
                        return ("/%s %%unit\n/cast %s"):format(with, GetSpellInfo(spell))
                    end
                end
            end
        },
        Macro                   = {
            type                = "macro",
            parse               = function (macro)
                if type(macro) == "string" then
                    macro       = GetMacroIndexByName(macro)
                end

                if type(macro) == "number" and macro > 0 and GetMacroInfo(macro) then
                    return macro
                end

                error("Invalid macro name|index", 3)
            end,
            content             = "macro"
        },
        MacroText               = {
            type                = "macro",
            parse               = function (macroText)
                if type(macroText) == "string" and strtrim(macroText) ~= "" then
                    return strtrim(macroText)
                end

                error("Invalid macro text", 3)
            end,
            content             = "macrotext"
        },
        Item                    = {
            type                = "item",
            parse               = function (item)
                if (type(item) == "string" or type(item) == "number") and GetItemInfo(item) then
                    return tonumber(select(GetItemInfo(item), 2):match('item:(%d+)'))
                end

                error("Invalid item|id|link", 3)
            end,
            content             = "item",
            tranMacro           = function (item, with)
                if GetItemInfo(item) then
                    return ("/%s %%unit\n/use %s"):format(with, GetItemInfo(item))
                end
            end
        },
        Menu                    = { type = "menu" },
        Target                  = { type = "target" },
        Focus                   = { type = "focus" },
        Assist                  = { type = "assist" },
    }

    _SetupTemplate              = [[self:SetAttribute("%s", %q)]]
    _ClearTemplate              = [[self:SetAttribute("%s", nil)]]

    _EnterTemplate              = [[self:SetBindingClick(true, "%s", self, "%s")]]
    _LeaveTemplate              = [[self:ClearBinding("%s")]]

    _Key2Name                   = setmetatable(
        {
            ["1"]               = "one",
            ["2"]               = "two",
            ["3"]               = "three",
            ["4"]               = "four",
            ["5"]               = "five",
            ["6"]               = "six",
            ["7"]               = "seven",
            ["8"]               = "eight",
            ["9"]               = "nine",
            ["0"]               = "zero",
            [","]               = "comma",
            ["."]               = "period",
            ["/"]               = "slash",
            ["`"]               = "backtick ",
            ["["]               = "leftbracket",
            ["]"]               = "rightbracket",
            ["\\"]              = "backslash",
            ["-"]               = "minus",
            ["="]               = "equals",
            [";"]               = "semicolon",
            ["'"]               = "singlequote",
            ["ESCAPE"]          = false,
            ["PRINTSCREEN"]     = false,
            ["LSHIFT"]          = false,
            ["RSHIFT"]          = false,
            ["LCTRL"]           = false,
            ["RCTRL"]           = false,
            ["LALT"]            = false,
            ["RALT"]            = false,
            ["UNKNOWN"]         = false,
        },{
            __index = function (self, key)
                if type(key) == "string" then
                    return key:lower()
                end
            end
        }
    )

    function parseBindKey(key)
        local ret

        if type(key) == "string" and strtrim(key) ~= "" then
            key                 = strtrim(key):upper()

            -- Check the tail key is "-"
            if key:sub(-1) == "-" then
                ret             = "-"
            else
                ret             = key:match("[^-]+$")
            end

            if not ret or ret == "" or not _Key2Name[ret] then return end

            -- Remap mouse key
            if ret == "LEFTBUTTON" then
                ret             = "BUTTON1"
            elseif ret == "RIGHTBUTTON" then
                ret             = "BUTTON2"
            elseif ret == "MIDDLEBUTTON" then
                ret             = "BUTTON3"
            end

            -- Remap option key
            if key:find("SHIFT-") then
                ret             = "SHIFT-" .. ret
            end

            if key:find("CTRL-") then
                ret             = "CTRL-" .. ret
            end

            if key:find("ALT-") then
                ret             = "ALT-" .. ret
            end
        end

        if not ret then error("Invalid binding key - " .. key, 3) end
        return ret
    end

    function getBindingDB(group, type, content)
        if not (_UnitFrameHoverSpellGroup and _UnitFrameHoverSpellGroup[group]) then return end

        for key, set in pairs(_UnitFrameHoverSpellGroup[group]) do
            if set["HarmAction"] == type and set["HarmContent"] == content then
                return key, set["HarmWith"], true
            elseif set["HelpAction"] == type and set["HelpContent"] == content then
                return key, set["HelpWith"], false
            end
        end
    end

    function getBindingDB4Key(group, key, harmful)
        local db                = _UnitFrameHoverSpellGroup and _UnitFrameHoverSpellGroup[group] and _UnitFrameHoverSpellGroup[group][key]

        if db then
            local prev          = harmful and "Harm" or "Help"
            return db[prev.."Action"], db[prev.."Content"], db[prev.."With"]
        end
    end

    function clearBindingDB(group, type, content)
        local key, with, harmful= getBindingDB(group, type, content)

        if key then
            local db            = _UnitFrameHoverSpellGroup[group][key]

            local prev          = harmful and "Harm" or "Help"

            db[prev.."Action"]  = nil
            db[prev.."Content"] = nil
            db[prev.."With"]    = nil

            if not next(db) then
                _UnitFrameHoverSpellGroup[group][key] = nil
            end

            return queueGroupUpdate(group)
        end
    end

    function clearBindingDB4Key(group, key)
        if not _UnitFrameHoverSpellGroup then return end

        if not _UnitFrameHoverSpellGroup[group] then
            return false
        end

        if not key then
            wipe(_UnitFrameHoverSpellGroup[group])
            return true
        end

        if not _UnitFrameHoverSpellGroup[group][key] then
            return false
        end

        _UnitFrameHoverSpellGroup[group][key] = nil

        return queueGroupUpdate(group)
    end

    function saveBindngDB(group, key, type, content, with, harmful)
        if not _UnitFrameHoverSpellGroup then return end

        if not key then throw("No binding key is set.")  end
        if not type or not _ActionType[type] then throw("No action type is set.") end
        if _ActionType[type].content and not content then throw("No action content is set.") end

        -- one spell in one group only match one key
        clearBindingDB(group, type, content)

        _UnitFrameHoverSpellGroup[group]        = _UnitFrameHoverSpellGroup[group]      or {}
        _UnitFrameHoverSpellGroup[group][key]   = _UnitFrameHoverSpellGroup[group][key] or {}

        local db                = _UnitFrameHoverSpellGroup[group][key]

        local prev              = harmful and "Harm" or "Help"

        db[prev.."Action"]      = type
        db[prev.."Content"]     = content
        db[prev.."With"]        = with

        return queueGroupUpdate(group)
    end

    function mapKey(key)
        key                     = key:lower()

        local isClk             = false
        local prev

        -- Check tail key
        if key:sub(-1) == "-" then
            prev, key           = key:sub(1, -2), key:sub(-1)
        else
            prev, key           = key:match("(.-)([^-]+)$")
        end

        if key:match("button%d+") then
            isClk               = true
            key                 = key:match("button(%d+)")
        else
            key                 = prev:gsub("-", "") .. _Key2Name[key]
            prev                = ""
        end

        return isClk, prev, key
    end

    function mapAction(action, content, with)
        local actionSet         = _ActionType[action]
        if not actionSet then return end

        if with and actionSet.tranMacro then
            return "MacroText", actionSet.tranMacro(content, with)
        else
            return action, content
        end
    end

    function getSnippet(group)
        if not (_UnitFrameHoverSpellGroup and _UnitFrameHoverSpellGroup[group]) then return end

        local setup             = List()
        local clear             = List()
        local enter             = List()
        local leave             = List()

        local isMouseClk, prev, kname, virtualKey, useVirtual, connect
        local actionSet, tranType, tranContent

        for key, set in pairs(_UnitFrameHoverSpellGroup[group]) do
            useVirtual          = false

            isMouseClk, prev, kname = mapKey(key)

            if not isMouseClk then
                enter:Insert(_EnterTemplate:format(key, kname))
                leave:Insert(_LeaveTemplate:format(key))
            end

            connect             = isMouseClk and "" or "-"

            if set.HarmAction and set.HelpAction then
                useVirtual      = true
            end

            if set.HarmAction then
                if useVirtual then
                    virtualKey  = "enemy" .. kname
                    setup:Insert(_SetupTemplate:format(prev .. "harmbutton" .. connect .. kname, virtualKey))
                    clear:Insert(_ClearTemplate:format(prev .. "harmbutton" .. connect .. kname))

                    virtualKey  = "-" .. virtualKey
                else
                    virtualKey  = connect .. kname
                end

                tranType, tranContent = mapAction(set.HarmAction, set.HarmContent, set.HarmWith)

                actionSet       = _ActionType[tranType]

                if actionSet then
                    setup:Insert(_SetupTemplate:format(prev .. "type" .. virtualKey, actionSet.type))
                    clear:Insert(_ClearTemplate:format(prev .. "type" .. virtualKey))

                    if actionSet.content and tranContent then
                        setup:Insert(_SetupTemplate:format(prev .. actionSet.content .. virtualKey, tranContent))
                        clear:Insert(_ClearTemplate:format(prev .. actionSet.content .. virtualKey))
                    end
                end
            end

            if set.HelpAction then
                if useVirtual then
                    virtualKey  = "friend" .. kname
                    setup:Insert(_SetupTemplate:format(prev .. "helpbutton" .. connect .. kname, virtualKey))
                    clear:Insert(_ClearTemplate:format(prev .. "helpbutton" .. connect .. kname))

                    virtualKey  = "-" .. virtualKey
                else
                    virtualKey  = connect .. kname
                end

                tranType, tranContent = mapAction(set.HelpAction, set.HelpContent, set.HelpWith)

                actionSet       = _ActionType[tranType]

                if actionSet then
                    setup:Insert(_SetupTemplate:format(prev .. "type" .. virtualKey, actionSet.type))
                    clear:Insert(_ClearTemplate:format(prev .. "type" .. virtualKey))

                    if actionSet.content and tranContent then
                        setup:Insert(_SetupTemplate:format(prev .. actionSet.content .. virtualKey, tranContent))
                        clear:Insert(_ClearTemplate:format(prev .. actionSet.content .. virtualKey))
                    end
                end
            end
        end
        return setup:Join("\n"), clear:Join("\n"), enter:Join("\n"), leave:Join("\n")
    end

    ------------------------------------------------------
    --               Hover Spell Manager                --
    ------------------------------------------------------
    -- Manager Frame
    _ManagerFrame               = SecureFrame("Scorpio_UnitFrame_HoverSpellManager")
    _ManagerFrame:Hide()

    -- Init manger frame's enviroment
    _ManagerFrame:Execute[[
        Manager                 = self

        _HoverOnUnitFrame       = nil
        _HoverOnUnitFrameLeaveSnippet = nil

        _GroupMap               = newtable()
        _UnitMap                = newtable()

        _SetupSnippet           = newtable()
        _ClearSnippet           = newtable()
        _EnterSnippet           = newtable()
        _LeaveSnippet           = newtable()
    ]]

    -- Global script sinppet, keep all run at global enviroment
    -- Init Snippet
    InitSnippet                 = [[
        local group             = "%s"
        local initFrame         = Manager:GetFrameRef("InitUnitFrame")

        if _HoverOnUnitFrame == initFrame and _HoverOnUnitFrameLeaveSnippet then
            _HoverOnUnitFrame:UnregisterAutoHide()
            Manager:RunFor(_HoverOnUnitFrame, _HoverOnUnitFrameLeaveSnippet)

            _HoverOnUnitFrame   = nil
            _HoverOnUnitFrameLeaveSnippet = nil
        end

        _GroupMap[initFrame]    = group
    ]]

    -- Dispose Snippet
    DisposeSnippet              = [[
        local group             = "%s"
        local disposeFrame      = Manager:GetFrameRef("DisposeUnitFrame")

        _GroupMap[disposeFrame] = nil

        if _HoverOnUnitFrame == disposeFrame then
            if _HoverOnUnitFrameLeaveSnippet then
                _HoverOnUnitFrame:UnregisterAutoHide()
            end
            _HoverOnUnitFrame   = nil
            _HoverOnUnitFrameLeaveSnippet = nil
        end

        if _LeaveSnippet[group] then
            Manager:RunFor(disposeFrame, _LeaveSnippet[group])
        end

        if _ClearSnippet[group] then
            Manager:RunFor(disposeFrame, _ClearSnippet[group])
        end
    ]]

    -- OnEnter Snippet
    -- Also keep clear & leave snippet for update using
    OnEnterSnippet              = [[
        local group             = _GroupMap[self]
        if not group then return end

        local unit              = self:GetAttribute("unit")

        if _HoverOnUnitFrame and _HoverOnUnitFrameLeaveSnippet and _HoverOnUnitFrame ~= self then
            _HoverOnUnitFrame:UnregisterAutoHide()
            Manager:RunFor(_HoverOnUnitFrame, _HoverOnUnitFrameLeaveSnippet)
            if _HoverOnUnitFrame:GetAttribute("unit") then
                _HoverOnUnitFrame:Show()
            end
        end

        if _SetupSnippet[group] and _UnitMap[self] ~= unit then
            _UnitMap[self]      = unit
            Manager:RunFor(self, _SetupSnippet[group]:gsub("%%unit", unit or ""))
        end

        _HoverOnUnitFrame       = self
        _HoverOnUnitFrameLeaveSnippet = _LeaveSnippet[group]

        if _EnterSnippet[group] then
            Manager:RunFor(self, _EnterSnippet[group])
        end

        if _HoverOnUnitFrameLeaveSnippet then
            _HoverOnUnitFrame:RegisterAutoHide(0.25)
        end
    ]]

    -- OnLeave Snippet
    OnLeaveSnippet              = [[
        local group             = _GroupMap[self]
        if not group then return end

        if _HoverOnUnitFrame == self then
            if _HoverOnUnitFrameLeaveSnippet then
                _HoverOnUnitFrame:UnregisterAutoHide()
                if _HoverOnUnitFrame:GetAttribute("unit") then
                    _HoverOnUnitFrame:Show()
                end
            end
            _HoverOnUnitFrame   = nil
            _HoverOnUnitFrameLeaveSnippet = nil
        end

        if _LeaveSnippet[group] then
            Manager:RunFor(self, _LeaveSnippet[group])
        end
    ]]

    -- OnHide Snippet
    OnHideSnippet               = [[
        local group             = _GroupMap[self]
        if not group then return end

        if _HoverOnUnitFrame ~= self then
            return
        end

        if _HoverOnUnitFrameLeaveSnippet then
            self:UnregisterAutoHide()
            if self:GetAttribute("unit") then
                self:Show()
            end
        end

        _HoverOnUnitFrame       = nil
        _HoverOnUnitFrameLeaveSnippet = nil

        if _LeaveSnippet[group] then
            Manager:RunFor(self, _LeaveSnippet[group])
        end
    ]]

    -- Build Snippet for group
    SetupGroupSnippet           = [[
        local group             = "%s"

        if _HoverOnUnitFrame and _GroupMap[_HoverOnUnitFrame] == group then
            if _HoverOnUnitFrameLeaveSnippet then
                _HoverOnUnitFrame:UnregisterAutoHide()
                Manager:RunFor(_HoverOnUnitFrame, _HoverOnUnitFrameLeaveSnippet)
            end
            _HoverOnUnitFrame   = nil
            _HoverOnUnitFrameLeaveSnippet = nil
        end

        for frm, grp in pairs(_GroupMap) do
            if grp == group then
                _UnitMap[frm]   = nil

                if _ClearSnippet[group] then
                    Manager:RunFor(frm, _ClearSnippet[group])
                end
            end
        end

        _SetupSnippet[group]    = %q
        _ClearSnippet[group]    = %q
        _EnterSnippet[group]    = %q
        _LeaveSnippet[group]    = %q

        if _SetupSnippet[group] == "" then
            _SetupSnippet[group]= nil
        end

        if _ClearSnippet[group] == "" then
            _ClearSnippet[group]= nil
        end

        if _EnterSnippet[group] == "" then
            _EnterSnippet[group]= nil
        end

        if _LeaveSnippet[group] == "" then
            _LeaveSnippet[group]= nil
        end
    ]]

    -- Initialize unit frame
    __NoCombat__()
    function initUnitFrame(self, group, old)
        if old then
            _ManagerFrame:SetFrameRef("DisposeUnitFrame", self)
            _ManagerFrame:Execute(DisposeSnippet:format(old:upper()))
        end

        if group then
            _ManagerFrame:SetFrameRef("InitUnitFrame", self)
            _ManagerFrame:Execute(InitSnippet:format(group:upper()))
        end
    end

    -- Dispose unit frame, but since we can't really dispose the frame
    -- Just keep the code, no use
    __NoCombat__()
    function removeUnitFrame(self, group)
        _ManagerFrame:UnwrapScript(self, "OnEnter")
        _ManagerFrame:UnwrapScript(self, "OnLeave")
        _ManagerFrame:UnwrapScript(self, "OnHide")

        _ManagerFrame:SetFrameRef("DisposeUnitFrame", self)
        _ManagerFrame:Execute(DisposeSnippet:format(group))
    end

    function queueGroupUpdate(group)
        if _UpdateGroupQueue[group] then return end
        if _UpdateGroupQueue.Count == 0 then FireSystemEvent("SCORPIO_UNITFRAME_HOVER_GROUP_UPDATE") end

        _UpdateGroupQueue[group]= true
        _UpdateGroupQueue:Enqueue(group)
    end

    __Service__(true)
    function ProcessGroupUpdate()
        while true do
            local group         = _UpdateGroupQueue:Dequeue()

            while group do
                Continue()
                NoCombat()

                if _UpdateGroupQueue[group] then
                    _UpdateGroupQueue[group] = nil

                    Debug("[UnitFrame]Setup the secure snippets for group: %s", group)
                    _ManagerFrame:Execute(SetupGroupSnippet:format(group, getSnippet(group)))
                end

                group           = _UpdateGroupQueue:Dequeue()
            end

            NextEvent("SCORPIO_UNITFRAME_HOVER_GROUP_UPDATE")
        end
    end

    ------------------------------------------------------
    --                   Module Event                   --
    ------------------------------------------------------
    if Scorpio.IsRetail or Scorpio.IsWLK then
        -- Save in Spec
        function OnLoad(self)
            _SVData.Char.Spec:SetDefault {
                UnitFrameHoverSpellGroup= {}
            }
        end

        function OnSpecChanged(self)
            _UnitFrameHoverSpellGroup   = _SVData.Char.Spec.UnitFrameHoverSpellGroup

            for grp in pairs(_UnitFrameHoverSpellGroup) do
                if InCombatLockdown() then
                    queueGroupUpdate(grp)
                else
                    Debug("[UnitFrame]Setup the secure snippets for group: %s", grp)
                    _ManagerFrame:Execute(SetupGroupSnippet:format(grp, getSnippet(grp)))
                end
            end
        end

        function OnQuit(self)
            if _UnitFrameHoverSpellGroup then
                for grp, db in pairs(_UnitFrameHoverSpellGroup) do
                    if not next(db) then
                        _UnitFrameHoverSpellGroup[grp] = nil
                    end
                end
            end
        end
    else
        -- Save in Char
        function OnLoad(self)
            _SVData.Char:SetDefault {
                UnitFrameHoverSpellGroup= {}
            }

            _UnitFrameHoverSpellGroup   = _SVData.Char.UnitFrameHoverSpellGroup

            for grp in pairs(_UnitFrameHoverSpellGroup) do
                if InCombatLockdown() then
                    queueGroupUpdate(grp)
                else
                    Debug("[UnitFrame]Setup the secure snippets for group: %s", grp)
                    _ManagerFrame:Execute(SetupGroupSnippet:format(grp, getSnippet(grp)))
                end
            end
        end

        function OnQuit(self)
            if _UnitFrameHoverSpellGroup then
                for grp, db in pairs(_UnitFrameHoverSpellGroup) do
                    if not next(db) then
                        _UnitFrameHoverSpellGroup[grp] = nil
                    end
                end
            end
        end
    end
end

--- The root unit frame widget class with hover spell casting
-- We can bind short keys to the hover spell group, each unit frame can have a group
--
-- UnitFrame.HoverSpellGroups["Default"].Spell["Holy Light"].With["target"].Key = "ctrl-f"
-- UnitFrame.HoverSpellGroups["Default"].MacroText["/cast Holy Light"].Key = "ctrl-f"
--
__Sealed__() __SecureTemplate__"SecureUnitButtonTemplate, SecureHandlerAttributeTemplate"
class "UnitFrame" (function(_ENV)
    inherit "SecureButton"
    extend "IUnitFrame"

    import "System.Reactive"

    ------------------------------------------------------
    --                      Helper                      --
    ------------------------------------------------------
    -- The secure snippet for the unit attribute changes
    local _onattributechanged   = [[
        if name == "unit" then
            if type(value) == "string" then
                value = strlower(value)
            else
                value = nil
            end

            local nounitwatch = self:GetAttribute("nounitwatch")

            if value == "player" then
                if not nounitwatch then
                    UnregisterUnitWatch(self)
                end
                self:Show()
            elseif value then
                if not nounitwatch then
                    RegisterUnitWatch(self)
                else
                    self:Show()
                end
            else
                if not nounitwatch then
                    UnregisterUnitWatch(self)
                end
                self:Hide()
            end

            self:CallMethod("ProcessUnitChange", value)
        elseif name == "nounitwatch" then
            local unit = self:GetAttribute("unit")

            if unit and unit ~= "player" then
                if value then
                    UnregisterUnitWatch(self)
                else
                    RegisterUnitWatch(self)
                end
            end
        end
    ]]

    local function OnEnter(self)
        return self:UpdateTooltip()
    end

    local function OnLeave(self)
        GameTooltip:Hide()
    end

    ------------------------------------------------------
    --            Hover Spell Group Accessor            --
    ------------------------------------------------------
    __Sealed__() class "HoverSpellGroupAccessor" (function(_ENV)
        local accessor

        ------------------------------------------------------
        --                      method                      --
        ------------------------------------------------------
        function Reset(self)
            return self.Group and clearBindingDB4Key(self.Group)
        end

        ------------------------------------------------------
        --                     Proeprty                     --
        ------------------------------------------------------
        --- The hover spell group
        property "Group"        { type = String }

        --- The action type accessor
        for name, set in pairs(_ActionType) do
            if set.content then
                __Indexer__(String + Number)
                property(name)  {
                    get         = function(self, content)
                        self.Data.Type      = name
                        self.Data.Content   = content
                        self.Data.With      = nil
                        return self
                    end,
                }
            else
                property(name)  {
                    get         = function(self)
                        self.Data.Type      = name
                        self.Data.Content   = nil
                        self.Data.With      = nil
                        return self
                    end
                }
            end
        end

        --- Get the with settings from the binding
        property "With"         {
            get                 = function(self)
                local type, ct  = self.Data.Type, self.Data.Content
                if type then
                    return (select(2, getBindingDB(self.Group, type, ct)))
                end
            end
        }

        --- Whether combine a 'target' action
        property "WithTarget"   {
            get                 = function(self)
                self.Data.With  = "target"
                return self
            end
        }

        --- Whether combine a 'focus' action
        property "WithFocus"    {
            get                 = function(self)
                self.Data.With  = "focus"
                return self
            end
        }

        --- Whether combine a 'assist' action
        property "WithAssist"   {
            get                 = function(self)
                self.Data.With  = "assist"
                return self
            end
        }

        --- Gets/Sets the binding key
        property "Key"          {
            throwable           = true,
            get                 = function(self)
                local type, ct  = self.Data.Type, self.Data.Content
                if type then
                    return getBindingDB(self.Group, type, ct)
                end
            end,
            set                 = function(self, value)
                local type, ct  = self.Data.Type, self.Data.Content
                local harmful

                if not type then
                    throw("Usage: UnitFrame.HoverSpellGroups[group].Action[content].Key = value - the action type not specified")
                end

                local action    = _ActionType[type]

                if action.content and not ct then
                    throw("Usage: UnitFrame.HoverSpellGroups[group].Action[content].Key = value - the action content not specified")
                end

                if action.parse then
                    ct, harmful = action.parse(ct)
                end

                if value == nil then
                    return clearBindingDB(self.Group, type, ct)
                else
                    value       = parseBindKey(value)
                    if not value then
                        throw("Usage: UnitFrame.HoverSpellGroups[group].Action[content].Key = value - the key value not valid")
                    end

                    local otype, oct, owith = getBindingDB4Key(value)
                    if type == otype and ct == oct and self.Data.With == owith then
                        return
                    end

                    return saveBindngDB(self.Group, value, type, ct, self.Data.With, harmful)
                end
            end,
        }

        ------------------------------------------------------
        --                   Constructor                    --
        ------------------------------------------------------
        __Arguments__{ NEString }
        function __ctor(self, group)
            self.Group          = group:upper()
            self.Data           = {}

            accessor            = self
        end

        __Arguments__{ NEString}
        function __exist(cls, group)
            if accessor then
                accessor.Group  = group:upper()
                wipe(accessor.Data)

                return accessor
            end
        end
    end)

    ------------------------------------------------------
    --                 Static Proeprty                  --
    ------------------------------------------------------
    --- The hover spell group accessor
    __Static__() __Indexer__(NEString)
    property "HoverSpellGroups" {
        get                     = function(self, name)
            return HoverSpellGroupAccessor(name)
        end,
    }

    ------------------------------------------------------
    --                     Proeprty                     --
    ------------------------------------------------------
    --- The unit attached to the unit frame
    property "Unit"             {
        type                    = String,
        set                     = function(self, unit)
            if unit ~= self:GetAttribute("unit") then
                self:SetAttribute("unit", unit)
            end
        end,
        get                     = function(self)
            return self:GetAttribute("unit")
        end,
    }

    --- Whether enable the unit watch
    property "UnitWatchEnabled" {
        type                    = Boolean,
        set                     = function(self, enabled)
            if self.UnitWatchEnabled == enabled then return end
            NoCombat(function() self:SetAttribute("nounitwatch", not enabled) end)
        end,
        get                     = function(self)
            return not self:GetAttribute("nounitwatch")
        end,
    }

    --- The refresh interval for special unit like 'targettarget'
    property "Interval"         { type = PositiveNumber, default = 0.5 }

    --- The hover spell group
    property "HoverSpellGroup"  { type = String, handler = initUnitFrame }

    ------------------------------------------------------
    --                      Method                      --
    ------------------------------------------------------
    __SecureMethod__()
    function ProcessUnitChange(self, unit)
        return self:OnUnitRefresh(unit)
    end

    __SecureMethod__()
    function UpdateTooltip(self)
        local unit              = self:GetAttribute("unit")
        if unit then
            GameTooltip_SetDefaultAnchor(GameTooltip, self)
            GameTooltip:SetUnit(unit)
            local r, g, b       = GameTooltip_UnitColor(unit)
            _G.GameTooltipTextLeft1:SetTextColor(r, g, b)
        end
    end

    ------------------------------------------------------
    --                   Constructor                    --
    ------------------------------------------------------
    function __ctor(self, ...)
        -- Use * so those are default actions that can be overridden
        self:SetAttribute("*type1", "target")
        self:SetAttribute("shift-type1", "focus")
        self:SetAttribute("*type2", "togglemenu")

        self:RegisterForClicks("AnyUp")

        self.OnEnter            = self.OnEnter + OnEnter
        self.OnLeave            = self.OnLeave + OnLeave

        _ManagerFrame:WrapScript(self, "OnEnter", OnEnterSnippet)
        _ManagerFrame:WrapScript(self, "OnLeave", OnLeaveSnippet)
        _ManagerFrame:WrapScript(self, "OnHide",  OnHideSnippet)

        -- Prepare for secure handler
        self:SetAttribute("_onattributechanged", _onattributechanged)

        -- The group maybe set by child class
        -- normally should be done by the style system
        return self.HoverSpellGroup and initUnitFrame(self, self.HoverSpellGroup)
    end
end)
