--========================================================--
--             Scorpio Secure UI Framework                --
--                                                        --
-- Author      :  kurapica125@outlook.com                 --
-- Create Date :  2020/06/04                              --
--========================================================--

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

__Sealed__() __Final__() interface "Scorpio.Secure" {}

Environment.RegisterGlobalNamespace("Scorpio.Secure")

namespace "Scorpio.Secure"

import "Scorpio.UI"

-- We can't directly change the secure widgets to the Scorpio UI widget,
-- we can only use the wrapper/origin model, so to define the functions that
-- can be called in the secure snippets, we should use __SecureMethod__ attribute
-- to bind the class object method or the obect method to the secure ui objects
-- so the system can keep those functions defined in the wrapper/origin at the same time
_SecureCallMap                          = {}

--- The interface that provide the basic features for secure widgets
__Sealed__()__ObjFuncAttr__{ Inheritable= true }
interface "ISecureHandler" (function(_ENV)

    local GetRawUI                      = Scorpio.UI.GetRawUI
    local GetProxyUI                    = Scorpio.UI.GetProxyUI
    local GetSuperClass                 = Class.GetSuperClass

    local _RegisterAttributeDriver      = RegisterAttributeDriver
    local _UnregisterAttributeDriver    = UnregisterAttributeDriver
    local _RegisterStateDriver          = RegisterStateDriver
    local _UnregisterStateDriver        = UnregisterStateDriver
    local _RegisterUnitWatch            = RegisterUnitWatch
    local _UnregisterUnitWatch          = UnregisterUnitWatch
    local _UnitWatchRegistered          = UnitWatchRegistered

    ------------------------------------------------------
    -- Method
    ------------------------------------------------------
    --- Execute a snippet against a header frame
    __Arguments__{ String }
    function Execute(self, body)
        return SecureHandlerExecute(GetRawUI(self), body)
    end

    --- Wrap the script on a frame to invoke snippets against a header
    __Arguments__{ UI, ScriptsType, String/nil, String/nil }
    function WrapScript(self, frame, script, preBody, postBody)
        return SecureHandlerWrapScript(GetRawUI(frame), script, GetRawUI(self), preBody, postBody)
    end

    --- Remove previously applied wrapping, returning its details
    __Arguments__{ UI, ScriptsType }
    function UnwrapScript(self, frame, script)
        return SecureHandlerUnwrapScript(GetRawUI(frame), script)
    end

    --- Create a frame handle reference and store it against a frame
    __Arguments__{ String, UI }
    function SetFrameRef(self, label, refFrame)
        return SecureHandlerSetFrameRef(GetRawUI(self), label, GetRawUI(refFrame))
    end

    --- Register a frame attribute to be set automatically with changes in game state
    __Arguments__{ String, Any }
    function RegisterAttributeDriver(self, attribute, values)
        return _RegisterAttributeDriver(GetRawUI(self), attribute, values)
    end

    --- Unregister a frame from the state driver manager
    __Arguments__{ String }
    function UnregisterAttributeDriver(self, attribute)
        return _UnregisterAttributeDriver(GetRawUI(self), attribute)
    end

    --- Register a frame state to be set automatically with changes in game state
    __Arguments__{ String, Any }
    function RegisterStateDriver(self, state, values)
        return _RegisterStateDriver(GetRawUI(self), state, values)
    end

    --- Unregister a frame from the state driver manager
    __Arguments__{ String }
    function UnregisterStateDriver(self, state)
        return _UnregisterStateDriver(GetRawUI(self), state)
    end

    --- Register a frame to be notified when a unit's existence changes
    __Arguments__{ Boolean/nil }
    function RegisterUnitWatch(self, asState)
        return _RegisterUnitWatch(GetRawUI(self), asState)
    end

    --- Unregister a frame from the unit existence monitor
    function UnregisterUnitWatch(self)
        return _UnregisterUnitWatch(GetRawUI(self))
    end

    --- Check to see if a frame is registered
    function IsUnitWatchRegistered(self)
        return _UnitWatchRegistered(GetRawUI(self))
    end

    __Arguments__{ (String + struct { String })/nil } __NoCombat__()
    function SetAutoHide(self, conds)
        if not conds then
            self:UnregisterStateDriver("visibility")
            self:Show()
        elseif type(conds) == "string" then
            conds               = conds:match("%b[]")
            if conds then
                self:RegisterStateDriver("visibility", (conds .. "hide;show"))
            else
                self:UnregisterStateDriver("visibility")
                self:Show()
            end
        else
            local str           = ""
            for _, cond in ipairs(conds) do
                cond            = cond:match("%b[]")
                if cond then
                    str         = str .. cond .. "hide;"
                end
            end
            if str == "" then
                self:UnregisterStateDriver("visibility")
                self:Show()
            else
                self:RegisterStateDriver("visibility", str .. "show")
            end
        end
    end

    ------------------------------------------------------
    -- Initializer
    ------------------------------------------------------
    function __init(self)
        local cls               = getmetatable(self)
        self                    = GetRawUI(self)
        while cls do
            local map           = _SecureCallMap[cls]
            if map then

                for name, func in pairs(map) do
                    self[name]  = self[name] or func
                end
            end
            cls                 = GetSuperClass(cls)
        end
    end
end)

--- The attribute used to bind functions that be be called by the secure environment
__Sealed__() __Final__()
class "__SecureMethod__" (function(_ENV)
    extend "IAttachAttribute"

    local GetRawUI              = Scorpio.UI.GetRawUI
    local GetProxyUI            = Scorpio.UI.GetProxyUI

    -----------------------------------------------------------
    --                        method                         --
    -----------------------------------------------------------
    function AttachAttribute(self, target, targettype, owner, name, stack)
        if targettype == AttributeTargets.Method then
            if Class.IsSubType(owner, ISecureHandler) then
                local map               = _SecureCallMap[owner] or {}
                _SecureCallMap[owner]   = map

                map[name]               = function(self, ...)
                    return target(GetProxyUI(self), ...)
                end
            end
        else
            if Class.IsObjectType(owner, ISecureHandler) then
                GetRawUI(owner)[name]   = function(self, ...)
                    return target(owner, ...)
                end
            end
        end
    end

    -----------------------------------------------------------
    --                       property                        --
    -----------------------------------------------------------
    --- the attribute target
    property "AttributeTarget"  { type = AttributeTargets,  default = AttributeTargets.Method + AttributeTargets.Function }

    --- the attribute's priority
    property "Priority"         { type = AttributePriority, default = AttributePriority.Lowest }
end)

--- The attribute used to bind secure template for the class objects
__Sealed__() __Final__()
class "__SecureTemplate__" (function(_ENV)
    extend "IAttachAttribute"

    local _SecureTemplateMap    = {}
    local _TempList             = List()

    function _TempList:Distinct()
        self:Sort()

        for i = #self, 1, -1 do
            if self[i] == self[i - 1] then
                self:RemoveByIndex(i)
            end
        end

        return self
    end

    -----------------------------------------------------------
    --                        method                         --
    -----------------------------------------------------------
    function AttachAttribute(self, target, targettype, owner, name, stack)
        if Class.IsSubType(target, ISecureHandler) then
            _SecureTemplateMap[target] = self.template
        end
    end

    __Static__() function GetTemplate(cls, template)
        local default           = _SecureTemplateMap[cls]
        while cls and default == nil do
            cls                 = Class.GetSuperClass(cls)
            default             = cls and _SecureTemplateMap[cls]
        end

        if default then
            _TempList:Clear()

            _TempList:Splice(#_TempList + 1, 0, default:gmatch("[%w_]+"))
            if type(template) == "string" then
                _TempList:Splice(#_TempList + 1, 0, template:gmatch("[%w_]+"))
            end

            return _TempList:Distinct():Join(",")
        elseif type(template) == "string" then
            return template
        end
    end

    -----------------------------------------------------------
    --                       property                        --
    -----------------------------------------------------------
    --- the attribute target
    property "AttributeTarget"  { type = AttributeTargets,  default = AttributeTargets.Class }

    -----------------------------------------------------------
    --                      constructor                      --
    -----------------------------------------------------------
    __Arguments__{ String/nil }
    function __ctor(self, template)
        self.template           = template or false
    end
end)

--- SecureFrame is a root widget class for secure frames
__Sealed__() __SecureTemplate__"SecureFrameTemplate"
class "SecureFrame" {
    Frame, ISecureHandler,

    __new                       = function(cls, name, parent, inherits)
        local ui                = CreateFrame("Frame", name, parent, __SecureTemplate__.GetTemplate(cls, inherits))
        local self              = { [0] = ui[0] }
        UI.RegisterProxyUI(self)
        UI.RegisterRawUI(ui)
        return self
    end
}

--- SecureButton is used as the root widget class for secure buttons
__Sealed__() __SecureTemplate__"SecureActionButtonTemplate"
class "SecureButton"  {
    Button, ISecureHandler,

    __new                       = function(cls, name, parent, inherits)
        local ui                = CreateFrame("Button", name, parent, __SecureTemplate__.GetTemplate(cls, inherits))
        local self              = { [0] = ui[0] }
        UI.RegisterProxyUI(self)
        UI.RegisterRawUI(ui)
        return self
    end
}

--- SecureCheckButton is used as the root widget class for secure check buttons
__Sealed__()  __SecureTemplate__"SecureActionButtonTemplate"
class "SecureCheckButton" {
    CheckButton, ISecureHandler,

    __new                       = function(cls, name, parent, inherits)
        local ui                = CreateFrame("CheckButton", name, parent, __SecureTemplate__.GetTemplate(cls, inherits))
        local self              = { [0] = ui[0] }
        UI.RegisterProxyUI(self)
        UI.RegisterRawUI(ui)
        return self
    end
}

-----------------------------------------------------------
--                    Style Property                     --
-----------------------------------------------------------
UI.Property                     {
    name                        = "AutoHide",
    type                        = String + struct { String },
    require                     = { SecureFrame, SecureButton, SecureCheckButton },
    set                         = function(self, val) self:SetAutoHide(val) end,
    clear                       = function(self) self:SetAutoHide(nil) end,
}