--========================================================--
--                Scorpio Core System                     --
--                                                        --
-- Author      :  kurapica125@outlook.com                 --
-- Create Date :  2020/07/13                              --
-- Update Date :  2021/06/24                              --
--========================================================--

--========================================================--
Scorpio           "Scorpio.UI.Core"                  "1.1.0"
--========================================================--

import "System.Reactive"

--- Clear the property value of the target object
local NIL                       = Namespace.SaveNamespace("Scorpio.UI.NIL",   prototype { __tostring = function() return "nil"   end })

--- Clear the property value of the settings(so other settings may be used for the property)
local CLEAR                     = Namespace.SaveNamespace("Scorpio.UI.CLEAR", prototype { __tostring = function() return "clear" end })

local isClassType               = Class.Validate
local isObjectType              = Class.IsObjectType
local isTypeValidDisabled       = System.Platform.TYPE_VALIDATION_DISABLED
local getSuperClass             = Class.GetSuperClass
local isUIObject                = UI.IsUIObject
local isUIObjectType            = UI.IsUIObjectType
local isSubType                 = Class.IsSubType
local clone                     = Toolset.clone
local yield                     = coroutine.yield
local tinsert                   = table.insert
local tremove                   = table.remove
local strlower                  = strlower
local tsort                     = table.sort
local isObservable              = function(val) return type(val) == "table" and isObjectType(val, IObservable) end
local gettable                  = function(self, key) local val = self[key] if not val or val == NIL or val == CLEAR then val = {} self[key] = val end return val end
local isStylableType            = function(k) return isUIObjectType(k) or Interface.Validate(k) and Interface.GetRequireClass(k) and isUIObjectType(Interface.GetRequireClass(k)) end

local CHILD_SETTING             = 0   -- For children
local CHILD_CLS_SETTING         = 1   -- For child classes
local INSTANT_STYLE_UI_CLASS    = {}

----------------------------------------------
--            Helper - Property             --
----------------------------------------------
local _Property                 = {}

local _RecycleHolder            = CreateFrame("Frame") _RecycleHolder:Hide()
local _PropertyChildName        = setmetatable({}, META_WEAKKEY)
local _PropertyChildMap         = setmetatable({}, { __index = function(self, prop) local val = setmetatable({}, META_WEAKALL) rawset(self, prop, val) return val end })
local _PropertyChildRecycle     = setmetatable({}, {
    __index                     = function(self, type)
        if isSubType(type, AnimationGroup) or isSubType(type, ControlPoint) or isSubType(type, Animation) then
            -- No recycle for the animation type, since they can't change their parent
            rawset(self, type, false)
            return false
        else
            local recycle       = Recycle(type, "__" .. Namespace.GetNamespaceName(type):gsub("%.", "_") .. "%d", _RecycleHolder)
            rawset(self, type, recycle)
            return recycle
        end
    end,
    __call                      = function(self, obj)
        if obj.Disposed then return true end

        local cls               = getmetatable(obj)
        local recycle           = cls and rawget(self, cls)

        if recycle then
            obj:SetParent(_RecycleHolder)
            if obj.ClearAllPoints then obj:ClearAllPoints() end
            recycle(obj)

            return true
        end
    end
})

-- The objservable map
local _ObsProp                  = setmetatable({}, META_WEAKKEY)

local function dispatchPropertySetting(cls, prop, setting, oldsetting, root)
    local settings              = _Property[cls]
    if not settings then return end -- So it doesn't finished the definition

    if root then
        oldsetting              = settings[prop]
    elseif settings[prop] and oldsetting ~= settings[prop] then
        return
    end

    settings[prop]              = setting

    for scls in Class.GetSubTypes(cls) do
        dispatchPropertySetting(scls, prop, setting, oldsetting)
    end
end

Runtime.OnTypeDefined           = Runtime.OnTypeDefined + function(ptype, cls)
    if ptype == Class and IsUIObjectType(cls) then
        if not _Property[cls] then
            Trace("[Scorpio.UI]Init Property List for %q", tostring(cls))

            local super         = getSuperClass(cls)
            if super and _Property[super] then
                _Property[cls]  = clone(_Property[super])
            else
                _Property[cls]  = {}
            end
        end

        local _Prop             = System.Property

        -- Scan the class's property
        for name, feature in Class.GetFeatures(cls) do
            if _Prop.Validate(feature) and not _Prop.IsStatic(feature) and _Prop.IsWritable(feature) and not _Prop.IsIndexer(feature) and not __Observable__.IsObservableProperty(feature) then
                Trace("[Scorpio.UI]Define Property %s for %s", name, tostring(cls))

                local ptype     = _Prop.GetType(feature)
                UI.Property     {
                    name        = name,
                    type        = ptype,
                    require     = cls,
                    set         = function(self, val) self[name] = val end,
                    get         = _Prop.IsReadable(feature) and function(self) return self[name] end or nil,
                    default     = _Prop.GetDefault(feature),
                    nilable     = not _Prop.IsValueRequired(feature),
                    childtype   = ptype and IsUIObjectType(ptype) and ptype or nil,
                }
            end
        end
    end
end

local applyStylesOnFrame, setCustomStyle

local function applyProperty(self, prop, value)
    --Trace("[Scorpio.UI]Apply Property:%s - %s", prop.name, tostring(value))

    -- Check the observable map
    local map                   = _ObsProp[self]
    local observer              = map and map[prop]
    if observer then
        observer.Subscription   = nil
    end

    if value == nil then
        if map then map[prop]   = nil end
        if prop.clear then return prop.clear(self) end
        if prop.default ~= nil then return prop.set(self, clone(prop.default, true)) end
        if prop.nilable then return prop.set(self, nil) end
    elseif prop.set then
        local pset              = prop.set
        if isObservable(value) then
            if not map then
                map             = {}
                _ObsProp[self]  = map
            end

            if not observer then
                if prop.clear then
                    local clear = prop.clear
                    observer    = Observer(function(val) if val ~= nil then return pset(self, val) else return clear(self) end end)
                elseif prop.nilable then
                    observer    = Observer(function(val) return pset(self, val) end)
                elseif prop.default ~= nil then
                    local dft   = prop.default
                    observer    = Observer(function(val) if val ~= nil then return pset(self, val) else return pset(self, dft) end end)
                else
                    observer    = Observer(function(val) if val ~= nil then return pset(self,val) end end )
                end
                map[prop]       = observer
            end

            value:Subscribe(observer, observer.Subscription)
        else
            -- Check for the child type
            if value == true and prop.childtype then return end
            if observer then observer:Dispose() map[prop] = nil end
            pset(self, clone(value, true))
        end
    elseif prop.childtype and isObservable(value) then
        if not map then
            map                 = {}
            _ObsProp[self]      = map
        end
        if not observer then
            observer            = Observer(function(val)
                if type(val) == "table" and getmetatable(val) == nil then
                    local child, new    = prop.get(self)
                    if child then
                        local ok, err   = pcall(setCustomStyle, child, nil, val, 1, new)
                        if not ok then
                            Error("[Scorpio.UI]Set custom style to child %q of %s failed - %s", prop.name, self:GetName(true), err)
                        end
                    else
                        Error("[Scorpio.UI]Auto-gen property child %q for %s failed", prop.name, self:GetName(true))
                    end
                else
                    prop.clear(self)
                end
            end)
            map[prop]           = observer
        end
        value:Subscribe(observer, observer.Subscription)
    end
end

local function getUIPrototype(self)
    local cls                   = getmetatable(self)
    if isUIObjectType(cls) then return cls, true end
    if self.GetObjectType then return UI[self:GetObjectType()], false end
end

----------------------------------------------
--              Helper - Style              --
----------------------------------------------
local _StyleMethods             = {}
local _StyleOwner
local _StyleAccessor
local _StyleQueue               = Queue()
local _ClearQueue               = Queue()
local _ClassQueue               = Queue()
local _Recycle                  = Recycle()

local _DefaultStyle             = {}
local _CustomStyle              = setmetatable({}, META_WEAKKEY)
local _ClassFrames              = {}

local _CurrentStyleTarget       -- The current style target could be used in other systems(ex. Reactive)

-- Combine the sharable settings
local function prepareSettings(settings, target, final, cache, paths)
    -- Simple Check
    if #settings == 0 and not final then return settings end

    local isClassSkin           = getmetatable(target) == nil
    local props                 = _Property[isClassSkin and target[#target] or getUIPrototype(target)]
    if not props then return final or settings end -- No more operations

    local needRecycle           = not cache
    cache                       = cache or _Recycle()
    if needRecycle and final then
        for k in pairs(final) do
            if type(k) == "string" then
                cache[strlower(k)] = k
            end
        end
    end
    final                       = final or {}

    local classCnt
    if isClassSkin then
        paths                   = paths or {}
        classCnt                = #target
    end

    -- Check the share features provided with N-th index
    local shares

    for k, v in pairs(settings) do
        local tk                = type(k)

        if tk == "number"       then
            shares              = shares or _Recycle()
            tinsert(shares, k)
        elseif tk == "string"   then
            local lk            = strlower(k)
            local ek            = cache[lk]
            local prop          = props[lk]

            if isClassSkin then
                local element

                paths[classCnt] = k

                for i = 1, #target do
                    if isClassType(target[i]) then
                        element = __Template__.GetElementType(target[i], unpack(paths, i))
                        if element then break end
                    end
                end

                paths[classCnt] = nil

                if element then
                    tinsert(target, element)
                    tinsert(paths,  k)

                    if not ek then
                        cache[lk]   = k
                        if type(v) == "table" and getmetatable(v) == nil then
                            final[k]= prepareSettings(v, target, nil, nil, paths)
                        else
                            -- Error but will be checked later
                            final[k]= v
                        end
                    elseif type(final[ek]) == "table" and getmetatable(final[ek]) == nil and  type(v) == "table" and getmetatable(v) == nil then
                        -- Combine the share settings
                        final[ek]   = prepareSettings(v, target, final[ek], nil, paths)
                    end

                    tremove(target)
                    tremove(paths)
                elseif prop and prop.childtype then
                    if not ek then
                        cache[lk]   = k
                        if type(v) == "table" and getmetatable(v) == nil then
                            final[k]= prepareSettings(v, { prop.childtype } )
                        else
                            -- Error or observable object will be checked later
                            final[k]= v
                        end
                    elseif type(final[ek]) == "table" and getmetatable(final[ek]) == nil and  type(v) == "table" and getmetatable(v) == nil then
                        -- Combine the share settings
                        final[ek]   = prepareSettings(v, { prop.childtype }, final[ek])
                    end
                elseif not ek then
                    -- No property check here, the error would be raised by the caller
                    cache[lk]       = k
                    final[k]        = v
                end
            else
                local element       = UIObject.GetChild(target, k) or prop and prop.childtype and { prop.childtype }

                if element then
                    if not ek then
                        cache[lk]   = k
                        if type(v) == "table" and getmetatable(v) == nil then
                            final[k]= prepareSettings(v, element)
                        else
                            -- Error or observable object will be checked later
                            final[k]= v
                        end
                    elseif type(final[ek]) == "table" and getmetatable(final[ek]) == nil and  type(v) == "table" and getmetatable(v) == nil then
                        -- Combine the share settings
                        final[ek]   = prepareSettings(v, element, final[ek])
                    end
                elseif not ek then
                    -- No property check here, the error would be raised by the caller
                    cache[lk]       = k
                    final[k]        = v
                end
            end
        elseif isClassSkin and isStylableType(k) then
            if final[k] then
                -- Only combine the share settings
                if type(final[k]) == "table" and getmetatable(final[k]) == nil and type(v) == "table" and getmetatable(v) == nil then
                    final[k]    = prepareSettings(v, { k }, final[k])
                end
            else
                -- No check here
                final[k]        = v
            end
        end
    end

    if shares then
        tsort(shares)

        for i = #shares, 1, -1 do
            -- Could have its own share skins
            prepareSettings(settings[shares[i]], target, final, cache, paths)
        end

        _Recycle(wipe(shares))
    end

    if needRecycle then _Recycle(wipe(cache)) end
    return final
end

local function collectPropertyChild(frame)
    if _ClearQueue[frame] then return end
    if _ClearQueue.Count == 0 then FireSystemEvent("SCORPIO_UI_COLLECT_PROPERTY_CHILD") end
    _ClearQueue[frame]          = true
    _ClearQueue:Enqueue(frame)
end

local function applyStyle(frame)
    if _StyleQueue[frame] then return end
    if _StyleQueue.Count == 0 then FireSystemEvent("SCORPIO_UI_APPLY_STYLE") end
    _StyleQueue[frame]          = true
    _StyleQueue:Enqueue(frame)
end

local function queueClassFrames(class)
    if not _ClassQueue[class] then
        if _ClassQueue.Count == 0 then FireSystemEvent("SCORPIO_UI_UPDATE_CLASS_SKIN") end
        _ClassQueue[class]      = true
        _ClassQueue:Enqueue(class)
    end

    for scls in Class.GetSubTypes(class) do
        queueClassFrames(scls)
    end
end

local function emptyDefaultStyle(settings)
    if settings and settings ~= NIL and settings ~= CLEAR then
        for k, v in pairs(settings) do
            if k == CHILD_SETTING or k == CHILD_CLS_SETTING then
                for child, csetting in pairs(v) do
                    emptyDefaultStyle(csetting)
                end
            else
                settings[k]     = CLEAR
            end
        end
    end

    return settings
end

function setCustomStyle(target, pname, value, stack, nodirectapply)
    local custom                = gettable(_CustomStyle, target)
    local haschanges            = false
    local directapply           = not _StyleQueue[target] and not nodirectapply

    local props                 = _Property[getUIPrototype(target)]
    if not props then error("The target has no property definitions", stack + 1) end

    if pname then
        local prop              = props[pname]
        local cval

        if prop.childtype then
            cval                = true  -- So the system can track the child property
            directapply         = false

            if value == nil or value == CLEAR or value == NIL then
                cval            = value or CLEAR
                directapply     = value == NIL
            elseif type(value) == "table" and getmetatable(value) == nil then
                local child, new= prop.get(target)
                if child then
                    setCustomStyle(child, nil, value, stack + 1, new)
                else
                    error(strformat("The target has no child element from %q", pname), stack + 1)
                end
            elseif isObservable(value) then
                -- So the property child could be generatd dynamically
                cval            = value
                directapply     = true
            else
                error(strformat("The %q is a child poperty, its setting should be a table", pname), stack + 1)
            end
        else
            if value == nil or value == NIL or value == CLEAR then
                cval            = value or CLEAR
                directapply     = value == NIL
            elseif isObservable(value) then
                cval            = value
            else
                if prop.validate then
                    local ret, msg  = prop.validate(prop.type, value)
                    if msg then error(Struct.GetErrorMessage(msg, prop.name), stack + 1) end
                    value       = ret
                end

                cval            = value
            end
        end

        if custom[pname] ~= cval then
            custom[pname]       = cval
            haschanges          = true

            if directapply then
                if cval == CLEAR or cval == NIL then cval = nil end
                return applyProperty(target, prop, cval)
            end
        end
    else
        if type(value) ~= "table" then
            error("The style settings must be property key value pair or a table contains the key-value pairs", stack + 1)
        end

        value                   = prepareSettings(value, target) -- Copy and remove the share settings

        directapply             = directapply and _Recycle()

        for pn, pv in pairs(value) do
            if type(pn) ~= "string" then
                error("The style property name must be string", stack + 1)
            end

            local child         = UIObject.GetChild(target, pn)

            if child then
                setCustomStyle(child, nil, pv, stack + 1)
            else
                local cval
                local ln        = strlower(pn)
                local prop      = props[ln]
                local isclear   = false

                if not prop then error(strformat("The %q isn't a valid property for the target", pn), stack + 1) end

                if prop.childtype then
                    cval        = true

                    if pv == CLEAR or pv == NIL then
                        cval    = pv
                        isclear = true
                    elseif type(pv) == "table" and getmetatable(pv) == nil then
                        local new
                        child, new = prop.get(target)
                        if child then
                            setCustomStyle(child, nil, pv, stack + 1, new)
                        else
                            error(strformat("The target has no child element from %q", pn), stack + 1)
                        end
                    elseif isObservable(pv) then
                        -- So the property child could be generatd dynamically
                        cval    = pv
                    else
                        error(strformat("The %q is a child poperty, its setting should be a table", pname), stack + 1)
                    end
                else
                    if pv == NIL or pv == CLEAR then
                        cval    = pv
                        isclear = true
                    elseif isObservable(pv) then
                        cval    = pv
                    else
                        if prop.validate then
                            local ret, msg = prop.validate(prop.type, pv)
                            if msg then error(Struct.GetErrorMessage(msg, prop.name), stack + 1) end
                            pv  = ret
                        end

                        cval    = pv
                    end
                end

                if custom[ln] ~= cval then
                    custom[ln]  = cval
                    haschanges  = true

                    if directapply then
                        if isclear then
                            _Recycle(wipe(directapply))
                            directapply = nil
                        else
                            directapply[ln] = cval
                        end
                    end
                end
            end
        end

        if directapply then
            -- Instant apply custon settings, no need queue it
            _CurrentStyleTarget = target
            local ok, err       = pcall(applyStylesOnFrame, target, directapply)
            if not ok then Error("[Scorpio.UI]Apply Style Failed: %s", tostring(err)) end
            _CurrentStyleTarget = nil
        end
    end

    return haschanges and applyStyle(target)
end

local function registerFrame(cls, frame)
    local map                   = _ClassFrames[cls]
    if not map then
        map                     = setmetatable({}, META_WEAKKEY)
        _ClassFrames[cls]       = map
    end

    map[frame]                  = true

    applyStyle(frame)
end

local function unregisterFrame(frame)
    _ClassFrames[getmetatable(frame)][frame] = nil
end

----------------------------------------------
--              Helper - Skin               --
----------------------------------------------
local _Skins                    = {}
local _ActiveSkin               = {}

local function copyBaseSkinSettings(container, base)
    for k, v in pairs(base) do
        if k == CHILD_SETTING or k == CHILD_CLS_SETTING then
            for name, setting in pairs(v) do
                copyBaseSkinSettings(gettable(gettable(container, k), name), setting)
            end
        else
            container[k]        = v
        end
    end
end

local function saveSkinSettings(classes, paths, container, settings, updateChildClass)
    local pathIdx                   = #classes
    local class                     = classes[pathIdx]
    local props                     = _Property[Interface.Validate(class) and Interface.GetRequireClass(class) or class]

    if type(settings) ~= "table" then throw("The skin settings for " .. class ..  " must be table") end
    settings                        = prepareSettings(settings, classes) -- Copy and remove the share settings

    -- Check inherit & key
    for name, value in pairs(settings) do
        if type(name) == "string" then
            if strlower(name)  == "inherit" then
                settings[name]      = nil

                if type(value) ~= "string" then throw("The inherit only accpet skin name as value") end
                local base          = _Skins[strlower(value)]
                if not base then
                    throw(strformat("The skin named %q doesn't existed", value))
                elseif not base[class] then
                    throw(strformat("The skin named %q doesn't provide skin for %s", value, tostring(class)))
                end

                copyBaseSkinSettings(container, base[class])

                break
            end
        elseif not isStylableType(name) then
            throw("The skin settings only accpet string values as key")
        end
    end

    for name, value in pairs(settings) do
        if type(name) == "string" then
            local element

            paths[pathIdx]          = name

            for i = 1, pathIdx do
                if isClassType(classes[i]) then
                    element         = __Template__.GetElementType(classes[i], unpack(paths, i))
                    if element then break end
                end
            end

            paths[pathIdx]          = nil

            if element then
                tinsert(classes, element)
                tinsert(paths, name)
                saveSkinSettings(classes, paths, gettable(gettable(container, CHILD_SETTING), name), value, updateChildClass)
                tremove(classes)
                tremove(paths)
            elseif props then
                name                = strlower(name)
                local prop          = props[name]
                if not prop then throw(strformat("The %q isn't a valid property for %s", name, tostring(class))) end

                if prop.childtype then
                    container[name] = true -- So we can easily track the child property settings

                    if value == NIL or value == CLEAR or isObservable(value) then
                        if container[CHILD_SETTING] then container[CHILD_SETTING][name] = nil end
                        container[name]     = value
                    elseif type(value) == "table" and getmetatable(value) == nil then
                        saveSkinSettings({ prop.childtype }, {}, gettable(gettable(container, CHILD_SETTING), name), value, updateChildClass)
                    else
                        throw(strformat("The %q is a child generated from property, need table as settings", name))
                    end
                else
                    if value == NIL or value == CLEAR then
                        container[name]     = value
                    elseif isObservable(value) then
                        container[name]     = value
                    else
                        if prop.validate then
                            local ret, msg  = prop.validate(prop.type, value)
                            if msg then throw(Struct.GetErrorMessage(msg, prop.name)) end
                            value           = ret
                        end

                        container[name]     = value
                    end
                end
            else
                throw("The " .. class .. " has no property definitions")
            end
        elseif isStylableType(name) then
            -- Don't support observable or NIL
            if value == CLEAR then
                if container[CHILD_CLS_SETTING] and container[CHILD_CLS_SETTING][name] then
                    -- Keep the styles settings for skin clear
                    emptyDefaultStyle(container[CHILD_CLS_SETTING][name])
                end
            elseif type(value) == "table" and getmetatable(value) == nil then
                local scontainer            = gettable(gettable(container, CHILD_CLS_SETTING), name)
                if not updateChildClass then emptyDefaultStyle(scontainer) end
                saveSkinSettings({ name }, {}, scontainer, value, true)
            else
                throw(strformat("The %q is a child class, need table as settings", tostring(name)))
            end
        end
    end
end

local function copyToDefault(settings, default)
    for name, value in pairs(settings) do
        if name == CHILD_SETTING or name == CHILD_CLS_SETTING then
            local childsettings = gettable(default, name)
            for element, setting in pairs(value) do
                copyToDefault(setting, gettable(childsettings, element))
            end
        else
            default[name]       = value
        end
    end
end

local function activeSkin(name, class, skin, force)
    if force and _ActiveSkin[class] and _ActiveSkin[class] ~= name then return end
    if not force and _ActiveSkin[class] == name then return end
    _ActiveSkin[class]          = name

    local default               = emptyDefaultStyle(_DefaultStyle[class]) or {}
    _DefaultStyle[class]        = default

    copyToDefault(skin, default)
    queueClassFrames(class)
end

----------------------------------------------
--                 UIObject                 --
----------------------------------------------
__Abstract__() __Sealed__()
class "UIObject"(function(_ENV)

    ----------------------------------------------
    --                 Helpers                  --
    ----------------------------------------------
    local _NameMap              = setmetatable({}, META_WEAKKEY)
    local _ChildMap             = setmetatable({}, META_WEAKKEY)

    local _SetParent            = getRealMethodCache("SetParent")
    local _GetParent            = getRealMethodCache("GetParent")
    local _GetNew               = getRealMetaMethodCache("__new")

    local validate              = Struct.ValidateValue
    local isClass               = Class.Validate

    local function raiseChildChange(self, child, isadd)
        self                    = self and GetWrapperUI(self, true)
        return self and isClass(getmetatable(self)) and OnChildChanged(self, child, isadd)
    end

    ----------------------------------------------
    --                  event                   --
    ----------------------------------------------
    -- Fired when parent is changed
    event "OnParentChanged"

    -- Fired when child is changed
    event "OnChildChanged"

    -- Fired when the ui object's style is appled
    event "OnStyleApplied"

    ----------------------------------------------
    --              Static Methods              --
    ----------------------------------------------
    --- Gets the ui object with the full name
    __Static__() __Arguments__{ NEString }
    function FromName(name)
        local obj

        for str in name:gmatch("[^%.]+") do
            if not obj then
                obj             = validate(UI, _G[str], true)
            else
                local children  = _ChildMap[obj[0]]
                obj             = children and children[str] or UIObject.GetPropertyChild(obj, str)
            end
            if not obj then return end
        end

        return obj
    end

    ----------------------------------------------
    --                 Methods                  --
    ----------------------------------------------
    --- Gets the ui object's name or full name
    __Final__() function GetName(self, full)
        local name              = _NameMap[self[0]]
        if name then
            if full then
                local globalUI      = _G[name]
                if globalUI and (globalUI == self or IsSameUI(self, globalUI)) then
                    return name
                else
                    local parent    = self:GetParent()
                    local pname     = parent and parent:GetName(true)
                    name            = _PropertyChildName[self] or name
                    if pname then return pname .. "." .. name end
                end
            else
                return name
            end
        else
            local getName           = self.GetName
            if getName ~= GetName then
                return getName(self)
            end
        end
    end

    __Final__() function GetDebugName(self)
        return self:GetName(true)
    end

    --- Gets the parent of ui object
    __Final__() function GetParent(self)
        local parent            = (_GetParent[getmetatable(self)] or self.GetParent)(self)
        return parent and GetProxyUI(parent)
    end

    --- Sets the ui object's name
    __Final__() __Arguments__{ NEString }
    function SetName(self, name)
        local oname             = _NameMap[self[0]]
        if oname == name then return end

        local globalUI          = _G[oname]
        if globalUI and (globalUI == self or IsSameUI(self, globalUI)) then
            error("Usage: UI:SetName(name) - UI with global name can't change its name", 2)
        end

        local parent            = self:GetParent()
        if parent then
            local pui           = parent[0]
            local children      = _ChildMap[pui]

            if children and children[name] then
                error("Usage: UI:SetName(name) - the name is used by another child", 2)
            end

            if not children then
                children        = {}
                _ChildMap[pui]  = children
            end

            children[oname]     = nil
            children[name]      = self
        end

        _NameMap[self[0]]       = name
    end

    --- Sets the ui object's parent
    __Final__()
    function SetParent(self, parent)
        if parent and not isUIObject(parent) then error("Usage : UI:SetParent([parent]) : the parent is not valid.", 2) end

        local oparent           = self:GetParent()
        if oparent == parent or IsSameUI(oparent, parent) then return end

        local name              = _NameMap[self[0]]
        local setParent         = _SetParent[getmetatable(self)]
        if not setParent then error("Usage : UI:SetParent([parent]) : the ui element can't change its parent.", 2) end

        if oparent then
            local pui           = oparent[0]
            if _ChildMap[pui] and _ChildMap[pui][name] == self then
                _ChildMap[pui][name] = nil
            end
        end

        if parent == nil then
            pcall(setParent, self, nil)
            OnParentChanged(self, parent, oparent)
            Next(raiseChildChange, oparent, self, false)
            return
        end

        local pui               = parent[0]
        local children          = _ChildMap[pui]

        if children and children[name] and children[name] ~= self then error("Usage : UI:SetParent([parent]) : parent has another child with the same name.", 2) end

        setParent(self, GetRawUI(parent))

        if not children then
            children            = {}
            _ChildMap[pui]      = children
        end

        children[name]          = self

        OnParentChanged(self, parent, oparent)
        Next(raiseChildChange, oparent, self, false)
        Next(raiseChildChange, parent,  self, true)
    end

    --- Gets the children of the frame
    __Iterator__() function GetChilds(self)
        local children          = _ChildMap[self[0]]
        if children then
            for name, child in pairs(children) do
                yield(name, child)
            end
        end
    end

    --- Gets the child with the given name
    __Final__()
    function GetChild(self, name)
        if name == 0 then return end

        local children          = _ChildMap[self[0]]
        return children and children[name]
    end

    ----------------------------------------------
    --                 Dispose                  --
    ----------------------------------------------
    function Dispose(self)
        local ui                = self[0]
        local name              = _NameMap[ui]

        unregisterFrame(self)

        self:SetParent(nil)

        -- Dispose the children
        local children          = _ChildMap[ui]
        if children then
            for _, obj in pairs(children) do obj:Dispose() end
        end

        -- Clear it from the _G
        local globalUI          = _G[name]
        if globalUI and (globalUI == self or IsSameUI(self, globalUI)) then
            _G[name]            = nil
        end

        -- Clear register datas
        _NameMap[ui]            = nil
        _ChildMap[ui]           = nil
    end

    ----------------------------------------------
    --               Constructor                --
    ----------------------------------------------
    __Final__() __Arguments__{ NEString, UI/UIParent, Any * 0 }
    function __exist(cls, name, parent, ...)
        local children          = _ChildMap[parent[0]]
        local object            = children and children[name]

        if object then
            if getmetatable(object) == cls then
                return object
            else
                throw(("Usage : %s(name, parent, ...) - the parent already has a child named '%s'."):format(Namespace.GetNamespaceName(cls), name))
            end
        end
    end

    __Final__() __Arguments__{ UI }
    function __exist(cls, ui)
        local proxy             = UI.GetProxyUI(ui)
        if proxy and isClass(getmetatable(proxy)) then return proxy end
    end

    __Final__() __Arguments__{ NEString, UI/UIParent, Any * 0 }
    function __new(cls, name, parent, ...)
        local self              = _GetNew[cls](cls, name, parent, ...)
        parmeta                 = parent[0]

        local children          = _ChildMap[parmeta]

        if not children then
            children            = {}
            _ChildMap[parmeta]  = children
        end

        children[name]          = self
        _NameMap[self[0]]       = name

        registerFrame(cls, self)
        Next(raiseChildChange, parent, self, true)

        return self
    end

    __Final__() __Arguments__{ UI }
    function __new(cls, ui)
        local self              = { [0] = ui[0] }
        _NameMap[ui[0]]         = ui.GetName and ui:GetName()
        UI.RegisterProxyUI(self)
        UI.RegisterRawUI(ui)
        return self
    end

    ----------------------------------------------
    --               Meta-Method                --
    ----------------------------------------------
    __index                     = GetChild
end)

----------------------------------------------
--               __Bubbling__               --
----------------------------------------------
__Sealed__()
class "__Bubbling__" (function(_ENV)
    extend "IApplyAttribute"

    local getChild              = UIObject.GetChild

    local function getTarget(owner, name)
        for s in name:gmatch("[^%.]+") do
            owner               = owner and getChild(owner, s)
        end
        return owner
    end

    -----------------------------------------------------------
    --                       property                        --
    -----------------------------------------------------------
    property "AttributeTarget"  { set = false, default = AttributeTargets.Event }

    -----------------------------------------------------------
    --                        method                         --
    -----------------------------------------------------------
    --- apply changes on the target
    -- @param   target                      the target
    -- @param   targettype                  the target type
    -- @param   manager                     the definition manager of the target
    -- @param   owner                       the target's owner
    -- @param   name                        the target's name in the owner
    -- @param   stack                       the stack level
    function ApplyAttribute(self, target, targettype, manager, owner, name, stack)
        local map               = self[1]

        Event.SetEventChangeHandler(target, function(delegate, owner, eventname)
            if not delegate.PopupeBinded then
                delegate.PopupeBinded = true

                for name, events in pairs(map) do
                    local child = type(name) == "number" and owner or getTarget(owner, name)

                    if not child then
                        error(("The child named %q doesn't existed in object of %s"):format(name, tostring(getmetatable(owner))))
                    end

                    for event in events:gmatch("%w+") do
                        child[event]    = child[event] + function(self, ...) return delegate(owner, ...) end
                    end
                end
            end
        end, stack + 1)
    end

    -----------------------------------------------------------
    --                      constructor                      --
    -----------------------------------------------------------
    __Arguments__{ struct { [NEString] = NEString } }
    function __new(_, map)
        return { map }, true
    end
end)

----------------------------------------------
--                 Template                 --
----------------------------------------------
__Sealed__()
class "__Template__" (function (_ENV)
    extend "IInitAttribute"

    local _Template             = {}
    local CHILDREN_MAP          = 1

    local isUIObjectType        = UI.IsUIObjectType
    local getSuperCTOR          = Class.GetSuperMetaMethod
    local yield                 = coroutine.yield
    local getSuperClass         = Class.GetSuperClass
    local tinsert               = table.insert
    local tremove               = table.remove
    local strformat             = string.format
    local tconcat               = table.concat

    local function getElementType(cls, ...)
        local pathCnt           = select("#", ...)
        local scls              = cls

        repeat
            local childtree     = _Template[scls]
            local i             = 1
            local name          = select(i, ...)

            while childtree and i < pathCnt do
                local child     = childtree[name]

                if child then
                    -- Check if it's created by the child
                    local type  = getElementType(child, select(i + 1, ...))
                    if type then return type, false end
                end

                childtree       = childtree[CHILDREN_MAP] and childtree[CHILDREN_MAP][name]
                i               = i + 1
                name            = select(i, ...)
            end

            local type          = childtree and i == pathCnt and childtree[name]
            if type then return type, scls == cls end

            scls                 = getSuperClass(scls)
        until not scls
    end

    local function parseChildTree(cls, path, elements, supercls, stack)
        local childtree         = {}
        local container         = _Template
        local pathCnt           = #path
        local pathIdx           = pathCnt + 1

        -- Save the tree
        if pathCnt == 0 then
            _Template[cls]      = childtree
        else
            container           = _Template[cls]

            for i = 1, #path - 1 do
                container       = container[CHILDREN_MAP][path[i]]
            end

            container[CHILDREN_MAP]             = container[CHILDREN_MAP] or {}
            container[CHILDREN_MAP][path[#path]]= childtree
        end

        -- Save settings to the tree
        local subtree

        for k, v in pairs(elements) do
            if type(k) == "string" and isUIObjectType(v) then
                path[pathIdx]   = k

                -- Check if already created by children or super class
                if getElementType(cls, unpack(path)) or (supercls and getElementType(supercls, unpack(path))) then
                    error(strformat("The the child element named %q is already defined", k), stack + 1)
                end

                path[pathIdx]   = nil

                childtree[k]    = v
            elseif type(k) == "number" and type(v) == "table" and getmetatable(v) == nil and not subtree then
                subtree         = v
            else
                error("The __Template__'s element type must be an ui object type", stack + 1)
            end
        end

        if subtree then
            childtree[CHILDREN_MAP] = {}

            for name, subelements in pairs(subtree) do
                if type(name) == "string" and type(subelements) == "table" and getmetatable(subelements) == nil then
                    path[pathIdx] = name

                    if getElementType(cls, unpack(path)) or (supercls and getElementType(supercls, unpack(path))) then
                        parseChildTree(cls, path, subelements, supercls, stack + 1)
                    else
                        error(strformat("The child with the path %q doesn't existed", tconcat(path, ".")), stack + 1)
                    end
                else
                    error("The __Template__'s element's children settings must be a table", stack + 1)
                end
            end

            path[pathIdx]       = nil
        end
    end

    local function generateChildren(self, childtree)
        local temp
        for k, v in pairs(childtree) do
            if k ~= CHILDREN_MAP then
                if INSTANT_STYLE_UI_CLASS[v] then
                    temp        = temp or _Recycle()
                    temp[k]     = v
                else
                    v(k, self)
                end
            end
        end

        if temp then
            for k, v in pairs(temp) do v(k, self) end
            _Recycle(wipe(temp))
        end

        if childtree[CHILDREN_MAP] then
            for name, tree in pairs(childtree[CHILDREN_MAP]) do
                generateChildren(self:GetChild(name), tree)
            end
        end
    end

    -----------------------------------------------------------
    --                     static method                     --
    -----------------------------------------------------------
    __Arguments__{ - UIObject, NEString * 1 }
    __Static__() GetElementType = getElementType

    -----------------------------------------------------------
    --                        method                         --
    -----------------------------------------------------------
    --- modify the target's definition
    -- @param   target                      the target
    -- @param   targettype                  the target type
    -- @param   definition                  the target's definition
    -- @param   owner                       the target's owner
    -- @param   name                        the target's name in the owner
    -- @param   stack                       the stack level
    -- @return  definition                  the new definition
    function InitDefinition(self, target, targettype, definition, owner, name, stack)
        if targettype == AttributeTargets.Method then
            if name ~= "__ctor" then error("The __Template__ can only be used on the constructor, not nomral method", stack + 1) end
            if type(self[1]) ~= "table" then error("The __Template__ lack the element settings", stack + 1) end

            parseChildTree(owner, {}, self[1], nil, stack + 1)

            return function(self, ...)
                local sctor = getSuperCTOR(owner, "__ctor")
                if sctor then sctor(self, ...) end

                generateChildren(self, _Template[owner])

                return definition(self, ...)
            end
        elseif targettype == AttributeTargets.Class then
            if type(definition) == "table" then
                local new       = { self[0] }
                local elements  = {}

                for k, v in pairs(definition) do
                    if type(k) == "string" and isUIObjectType(v) then
                        elements[k] = v
                    elseif type(k) == "number" and type(v) == "table" and elements[CHILDREN_MAP] == nil then
                        elements[CHILDREN_MAP] = v
                    else
                        new[k]  = v
                    end
                end

                parseChildTree(target, {}, elements, self[0], stack + 1)

                new.__ctor      = function(self, ...)
                    local sctor = getSuperCTOR(target, "__ctor")
                    if sctor then sctor(self, ...) end

                    generateChildren(self, _Template[target])
                end

                return new
            else
                error("The __Template__ require the class use table of element type settings as definition", stack + 1)
            end
        end
    end

    -----------------------------------------------------------
    --                       property                       --
    -----------------------------------------------------------
    property "AttributeTarget"  { type = AttributeTargets,  default = AttributeTargets.Method + AttributeTargets.Class }

    -----------------------------------------------------------
    --                      constructor                      --
    -----------------------------------------------------------
    __Arguments__{ - UIObject/nil }
    function __new(_, cls) return { [0] = cls }, true end

    __Arguments__{ RawTable }
    function __new(_, setting) return { setting }, true  end
end)

----------------------------------------------
--              Style Property              --
----------------------------------------------
__Sealed__()
struct "Scorpio.UI.Property" {
    name                        = { type  = NEString, require = true },
    type                        = { type  = AnyType },
    require                     = { type  = ClassType + struct { ClassType }, require = true },
    set                         = { type  = Function },
    get                         = { type  = Function },
    clear                       = { type  = Function },
    default                     = { type  = Any },
    nilable                     = { type  = Boolean },
    childtype                   = { type  = - UIObject },
    secure                      = { type  = Boolean },  -- Whether forbidden during combat
    depends                     = { type  = struct { NEString } },  -- Processed after other properties
    override                    = { type  = struct { NEString } },  -- override other properties
    instant                     = { type  = Boolean }, -- Whether the property child style should be applied instantly

    __valid                     = function(self)
        if not self.childtype and not self.set then
            return "%s.set is required"
        end
    end,

    __init                      = function(self)
        local setting           = {
            name                = self.name,
            type                = self.type,
            set                 = self.set,
            get                 = self.get,
            clear               = self.clear,
            default             = clone(self.default, true),
            secure              = self.secure,
            nilable             = self.nilable,
            childtype           = self.childtype,
            instant             = self.instant,
        }

        if self.childtype then
            -- The child property type should be handled specially
            local name          = self.name
            local childtype     = self.childtype
            local set           = self.set
            local nilable       = self.nilable
            local clear         = self.clear
            local childname     = strlower(self.name)

            setting.get         = function(self, try)
                local child     = _PropertyChildMap[setting][self]
                if child or try then return child, false end

                local recycle   = _PropertyChildRecycle[childtype]

                if recycle then
                    child       = recycle()
                    if child then child:SetParent(self) end
                else
                    child       = childtype(childname, self)
                end

                if child then
                    if set then set(self, child) end
                    _PropertyChildMap[setting][self]= child
                    _PropertyChildName[child]       = childname

                    -- reset the custom style
                    gettable(_CustomStyle, self)[childname] = nil
                    applyStyle(child)
                end

                return child, true
            end

            setting.clear       = function(self)
                local child     = _PropertyChildMap[setting][self]
                if not child then return end

                collectPropertyChild(child)

                if _PropertyChildRecycle[childtype] then
                    _PropertyChildMap[setting][self] = nil
                end

                if clear then
                    clear(self, child)
                elseif nilable and set then
                    set(self, nil)
                end
            end
        end

        if self.type then
            if isTypeValidDisabled then
               if Struct.Validate(self.type) and not Struct.IsImmutable(self.type) then
                    setting.validate = Struct.ValidateValue
                end
            else
                if Enum.Validate(self.type) then
                    setting.validate = Enum.ValidateValue
                elseif Struct.Validate(self.type) then
                    setting.validate = Struct.ValidateValue
                elseif Class.Validate(self.type) then
                    setting.validate = Class.ValidateValue
                elseif Interface.Validate(self.type) then
                    setting.validate = Interface.ValidateValue
                end
            end

            if setting.default ~= nil and setting.validate then
                setting.default = setting.validate(setting.type, setting.default)
            end
        end

        if self.depends then
            setting.depends     = {}
            for i, v in ipairs(self.depends) do
                setting.depends[i] = strlower(v)
            end
        end

        if self.override then
            setting.override    = {}
            for i, v in ipairs(self.override) do
                setting.override[i] = strlower(v)
            end
        end

        local name              = strlower(self.name)

        if isUIObjectType(self.require) then
            dispatchPropertySetting(self.require, name, setting, nil, true)
        else
            for _, cls in ipairs(self.require) do
                dispatchPropertySetting(cls, name, setting, nil, true)
            end
        end
    end,
}

----------------------------------------------
--              Style Accessor              --
----------------------------------------------
-- Style[UnitFrame].HealthBar.Color = { r = 1, g = 0, b = 0 }
-- Style[frame].Alpha               = 0.5
-- Style[UnitFrame].HealthBar       = { color = { r = 1, g = 0, b = 1 }, alpha = 0.8 }
local Style                     = Namespace.SaveNamespace("Scorpio.UI.Style", prototype {
    __tostring                  = Namespace.GetNamespaceName,
    __index                     = function(self, key)
        if type(key) == "string" then
            return _StyleMethods[key]
        end

        if isUIObject(key) then
            _StyleOwner         = key
            return _StyleAccessor
        end
    end,
    __newindex                  = function(self, key, value)
        if type(key) == "string" and type(value) == "function" then
            if _StyleMethods[key] then
                error(("The method named %s already existed in Scorpio.UI.Style"):format(key), 2)
            end

            if Attribute.HaveRegisteredAttributes() then
                Attribute.SaveAttributes(value, AttributeTargets.Function, 2)
                local ret       = Attribute.InitDefinition(value, AttributeTargets.Function, value, self, key, 2)
                if ret ~= value then
                    Attribute.ToggleTarget(value, ret)
                    value        = ret
                end
                Attribute.ApplyAttributes (value, AttributeTargets.Function, nil, self, key, 2)
                Attribute.AttachAttributes(value, AttributeTargets.Function, self, key, 2)
            end

            _StyleMethods[key]  = value
            return
        end

        if isUIObject(key) then
            setCustomStyle(key, nil, value, 2)
            return
        end

        error("The Scorpio.UI.Style access is denied", 2)
    end
})

_StyleAccessor                  = prototype {
    __metatable                 = Style,
    __index                     = function(self, key)
        local target            = _StyleOwner
        if target and type(key) == "string" then
            local star          = UIObject.GetChild(target, key)
            if star then
                _StyleOwner     = star
                return _StyleAccessor
            else
                _StyleOwner     = nil

                local cls       = getUIPrototype(target)
                local prop      = cls and _Property[cls] and _Property[cls][strlower(key)]
                if prop then
                    if prop.childtype then
                        _StyleOwner = prop.get(target)
                        if _StyleOwner then return _StyleAccessor end
                    elseif prop.get then
                        return prop.get(target)
                    else
                        error("The " .. key .. " property has no get method")
                    end
                end
            end
        end

        _StyleOwner             = nil
        error("The sub key must be child name or property name", 2)
    end,
    __newindex                  = function(self, key, value)
        local target            = _StyleOwner
        _StyleOwner             = nil

        if target and type(key) == "string" then
            local star          = UIObject.GetChild(target, key)
            if star then
                setCustomStyle(star, nil, value, 2)
                return
            else
                key             = strlower(key)
                local cls       = getUIPrototype(target)
                local prop      = cls and _Property[cls] and _Property[cls][key]
                if prop then
                    setCustomStyle(target, key, value, 2)
                    return
                end
            end
        end

        error("The sub key must be child name or property name", 2)
    end
}

__Iterator__() __Arguments__{ UI }
function Style.GetCustomStyles(frame)
    local custom                = _CustomStyle[frame]
    if custom then
        local props             = _Property[getmetatable(frame)]

        for name, value in pairs(custom) do
            yield(props[name].name, (clone(value)))
        end
    end
end

__Arguments__{ -UIObject, NEString * 0 } __Iterator__()
function Style.GetDefaultStyles(class, ...)
    local default               = _DefaultStyle[class]

    if default then
        if select("#", ...) > 0 then
            class               = __Template__.GetElementType(class, ...)
            if not class then return end

            for i = 1, select("#", ...) do
                local name      = select(i, ...)

                default         = default[CHILD_SETTING]
                default         = default and default[name]
                if not default then return end
            end
        end

        local props             = _Property[class]

        for name, value in pairs(default) do
            if name ~= 0 then
                yield(props[name].name, (clone(value)))
            end
        end
    end
end

----------------------------------------------
--               Skin System                --
----------------------------------------------
local SkinSettings              = struct { [ - UIObject ] = Table }
local OnStyleAppliedEvent       = Class.GetFeature(UIObject, "OnStyleApplied")

__Arguments__{ NEString, SkinSettings/nil }:Throwable()
function Style.RegisterSkin(name, settings)
    name                        = strlower(name)
    if _Skins[name] then return false end

    local skins                 = {}
    _Skins[name]                = skins

    if settings then
        for class, setting in pairs(settings) do
            local skin          = {}
            skins[class]        = skin

            saveSkinSettings({class}, {}, skin, setting, true)
        end
    end

    return true
end

__Arguments__{ NEString, SkinSettings, Boolean/nil, Boolean/nil }:Throwable()
function Style.UpdateSkin(name, settings, update, updateChildClass)
    name                        = strlower(name)
    local skins                 = _Skins[name]
    if not skins then throw("Usage: Style.UpdateSkin(name, settings[, update]) - the name doesn't existed") end

    for class, setting in pairs(settings) do
        local skin              = not update and emptyDefaultStyle(skins[class]) or skins[class] or {}
        skins[class]            = skin

        saveSkinSettings({class}, {}, skin, setting, not update or updateChildClass)
        activeSkin(name, class, skin, true)
    end
end

__Arguments__{ NEString, - UIObject/nil }:Throwable()
function Style.ActiveSkin(name, class)
    name                        = strlower(name)
    local skins                 = _Skins[name]

    if not skins then
        throw("Usage: Style.ActiveSkin(name[, uitype]) - the name doesn't existed")
    end

    if class then
        local skin              = skins[class]
        if not skin then
            throw("Usage: Style.ActiveSkin(name[, uitype]) - the skin doesn't have settings for " .. class)
        end

        return activeSkin(name, class, skin)
    else
        for cls, skin in pairs(skins) do
            activeSkin(name, cls, skin)
        end
    end
end

__Arguments__{ - UIObject }
function Style.GetActiveSkin(class)
    return _ActiveSkin[class]
end

__Arguments__{ - UIObject } __Iterator__()
function Style.GetSkins(class)
    for name, skins in pairs(_Skins) do
        if skins[class] then
            yield(name)
        end
    end
end

__Arguments__{ -UIObject } __Iterator__()
function Style.GetProperties(class)
    local props                 = _Property[class]
    if props then
        for name, prop in pairs(props) do
            yield(name, prop.childtype or prop.type)
        end
    end
end

__Arguments__{ - UIObject, String }
function Style.GetProperty(class, name)
    local props                 = _Property[class]
    local prop                  = props and props[strlower(name)]
    if prop then return prop.childtype or prop.type end
end

function Style.GetCurrentTarget()
    return _CurrentStyleTarget
end

Style.RegisterSkin("Default")

export { Scorpio.UI.Property }

----------------------------------------------
--           Skin System Services           --
----------------------------------------------
local function genPriority(props, name, styles, priority, lvl)
    lvl                         = (lvl or 0) + 1
    local maxlvl                = lvl

    for _, dep in ipairs(props[name].depends) do
        local val               = styles[dep]
        if val ~= nil and val ~= NIL and val ~= CLEAR then
            priority[dep]       = max(priority[dep] or 0, lvl)

            if props[dep].depends then
                maxlvl          = max(maxlvl, genPriority(props, dep, styles, priority, lvl))
            end
        end
    end

    return maxlvl
end

function applyStylesOnFrame(frame, styles)
    local props                 = _Property[getUIPrototype(frame)]
    if not props then return _Recycle(wipe(styles)) end

    local priority              = _Recycle()
    local maxlvl                = 0

    -- Generate the priority to apply the styles based on the depends
    for name, value in pairs(styles) do
        if value == NIL or value == CLEAR then
            applyProperty(frame, props[name], nil)
            styles[name]        = nil
        elseif props[name].depends then
            maxlvl              = max(maxlvl, genPriority(props, name, styles, priority))
        end
    end

    -- Process with priority
    for i = maxlvl, 1, -1 do
        for name, lv in pairs(priority) do
            if lv == i and styles[name] ~= nil then
                applyProperty(frame, props[name], styles[name])
                styles[name]    = nil
            end
        end
    end

    -- Process the rest
    for name, value in pairs(styles) do
        applyProperty(frame, props[name], value)
    end

    _Recycle(wipe(priority))
    _Recycle(wipe(styles))

    -- Notify the style is applied
    OnStyleAppliedEvent(frame)
end

local function clearStylesOnFrame(frame, styles)
    local props                 = _Property[getUIPrototype(frame)]
    if not props then return _Recycle(wipe(styles)) end

    local priority              = _Recycle()
    local maxlvl                = 0

    -- Generate the priority to apply the styles based on the depends
    for name, value in pairs(styles) do
        if value == NIL or value == CLEAR then
            applyProperty(frame, props[name], nil)
            styles[name]        = nil
        elseif props[name].depends then
            maxlvl              = max(maxlvl, genPriority(props, name, styles, priority))
        end
    end

    -- Process with priority
    for i = maxlvl, 1, -1 do
        for name, lv in pairs(priority) do
            if lv == i and styles[name] ~= nil then
                applyProperty(frame, props[name], nil)
            end
        end
    end

    -- Process property with no priority
    for name, value in pairs(styles) do
        if not priority[name] then
            applyProperty(frame, props[name], nil)
        end
    end

    _Recycle(wipe(priority))
    _Recycle(wipe(styles))
end

local function getDefaultByClass(styles, cls)
    local matchedType

    for stype in pairs(styles) do
        if isSubType(cls, stype) then
            if matchedType then
                if getmetatable(stype).IsSubType(stype, matchedType) then
                    matchedType             = stype
                end
            else
                matchedType                 = stype
            end
        end
    end

    return matchedType and styles[matchedType]
end

local function buildTempStyle(frame, forClear)
    local styles                            = _Recycle()
    local paths                             = _Recycle()
    local clspaths                          = _Recycle()
    local children                          = _Recycle()
    local tempClass                         = _Recycle()
    local childcls

    local props                             = _Property[getUIPrototype(frame)]
    if not props then return styles, children, childcls end -- No properties can be found

    -- Prepare the style settings
    -- Custom -> Root Parent Class -> ... -> Parent Class -> Frame Class -> Super Class
    local name                              = _PropertyChildName[frame] or UIObject.GetName(frame)
    local parent                            = UIObject.GetParent(frame)

    clspaths[1]                             = getmetatable(frame)
    while parent and name do
        local cls                           = getmetatable(parent)
        tinsert(paths, 1, name)

        if isUIObjectType(cls) then
            wipe(tempClass)

            repeat
                tinsert(tempClass, cls)
                cls                         = getSuperClass(cls)
            until not cls

            for i = #tempClass, 1, -1 do
                cls                         = tempClass[i]

                local default               = _DefaultStyle[cls]
                local index                 = 1

                while default and paths[index] do
                    default                 =  default[CHILD_SETTING]     and default[CHILD_SETTING]    [paths[index]]
                                            or default[CHILD_CLS_SETTING] and getDefaultByClass(default[CHILD_CLS_SETTING], clspaths[index])
                    index                   = index + 1
                end

                if default then
                    -- The parent -> ... -> child style settings
                    for prop, value in pairs(default) do
                        if prop == CHILD_SETTING then
                            for name in pairs(value) do
                                children[name]      = true

                                if styles[name] and styles[name] ~= true and isObservable(styles[name]) then
                                    -- So don't create the property child dynamicly
                                    styles[name]    = true
                                end
                            end
                        elseif prop == CHILD_CLS_SETTING then
                            childcls                = childcls or _Recycle()
                            for ccls in pairs(value) do
                                childcls[ccls]      = true
                            end
                        else
                            if value ~= CLEAR or styles[prop] == nil then
                                styles[prop]        = value

                                -- Check childtype
                                if props[prop].childtype then
                                    -- So we need create the property child dynamicly
                                    if forClear then
                                        children[prop]      = true
                                    elseif isObservable(value) then
                                        children[prop]      = nil
                                    end
                                end

                                -- Check override
                                if props[prop].override then
                                    if value ~= CLEAR then
                                        for _, op in ipairs(props[prop].override) do
                                            styles[op]      = nil  -- The overridden property won't be applied
                                        end
                                    else
                                        for _, op in ipairs(props[prop].override) do
                                            if styles[op]  ~= nil and styles[op] ~= CLEAR then
                                                styles[prop]= nil
                                                break
                                            else
                                                styles[op]  = nil
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end

        tinsert(clspaths, 1, cls)
        name                                = _PropertyChildName[parent] or UIObject.GetName(parent)
        parent                              = UIObject.GetParent(parent)
    end

    _Recycle(wipe(paths))
    _Recycle(wipe(clspaths))
    _Recycle(wipe(tempClass))

    if _CustomStyle[frame] then
        for prop, value in pairs(_CustomStyle[frame]) do
            if value ~= CLEAR or styles[prop] == nil then
                styles[prop]                = value

                -- Check dynamic property child
                if props[prop].childtype then
                    if forClear then
                        children[prop]      = true
                    elseif isObservable(value) then
                        children[prop]      = nil
                    end
                end

                -- Check override
                if props[prop].override then
                    if value ~= CLEAR then
                        for _, op in ipairs(props[prop].override) do
                            styles[op]      = nil  -- The overridden property won't be applied
                        end
                    else
                        for _, op in ipairs(props[prop].override) do
                            if styles[op]  ~= nil and styles[op] ~= CLEAR then
                                styles[prop]= nil
                                break
                            else
                                styles[op]  = nil
                            end
                        end
                    end
                end
            end
        end
    end

    local cls                               = getmetatable(frame)

    if isUIObjectType(cls) then
        while cls do
            local default                   = _DefaultStyle[cls]

            if default then
                for prop, value in pairs(default) do
                    if prop == CHILD_SETTING then
                        for name in pairs(value) do
                            if forClear or not isObservable(styles[name]) then
                                children[name] = true
                            end
                        end
                    elseif prop == CHILD_CLS_SETTING then
                        childcls            = childcls or _Recycle()
                        for ccls in pairs(value) do
                            childcls[ccls]  = true
                        end
                    elseif styles[prop] == nil or styles[prop] == CLEAR then
                        local noOverride    = true

                        -- Check property child
                        if forClear and props[prop].childtype then
                            children[prop]  = true
                        end

                        -- Check override
                        if props[prop].override then
                            for _, op in ipairs(props[prop].override) do
                                if styles[op] ~= nil and styles[op] ~= CLEAR then
                                    noOverride  = false
                                    break
                                else
                                    styles[op]  = nil  -- Clear the CLEAR value styles
                                end
                            end
                        end

                        if noOverride then
                            styles[prop]    = value
                        end
                    end
                end
            end

            cls                             = getSuperClass(cls)
        end
    end

    return styles, children, childcls
end

local function clearStyle(frame)
    local props                             = _Property[getUIPrototype(frame)]

    if props and not frame.Disposed then
        local debugname                     = frame:GetName(true) or frame:GetObjectType()
        local clearChilds                   = _Recycle()

        Trace("[Scorpio.UI]Clear Style: %s%s", debugname, _PropertyChildName[frame] and (" - " .. _PropertyChildName[frame]) or "")

        local styles, children, childcls    = buildTempStyle(frame, true)

        -- Clear the children
        for name in pairs(children) do
            local child                     = UIObject.GetChild(frame, name)

            if child then
                clearStyle(child)
            elseif props[name] and props[name].childtype then
                child                       = props[name].get(frame, true)
                styles[name]                = CLEAR
                if child then
                    clearChilds[child]      = true
                    _ClearQueue[child]      = true

                    clearStyle(child)
                end
            end
        end
        _Recycle(wipe(children))

        -- Clear the children of the classes, normally won't be used
        if childcls then
            for name, child in UIObject.GetChilds(frame) do
                local mcls                  = getmetatable(child)
                for ctype in pairs(childcls) do
                    if mcls == ctype or isSubType(mcls, ctype) then
                        clearStyle(child)
                        break
                    end
                end
            end

            _Recycle(wipe(childcls))
        end

        -- Apply the style settings
        local ok, err                       = pcall(clearStylesOnFrame, frame, styles)
        if not ok then Error("[Scorpio.UI]Clear Style: %s - Failed: %s", debugname, tostring(err)) end

        for child in pairs(clearChilds) do
            _ClearQueue[child]              = nil
        end

        _Recycle(wipe(clearChilds))
    end

    _CustomStyle[frame]                     = nil
    if _PropertyChildName[frame] and _PropertyChildRecycle(frame) then
        _PropertyChildName[frame]           = nil
    end

    Continue() -- Smoothing the process
end

function ApplyFrameStyles(self, styles, debugname)
    -- Apply the style settings
    _CurrentStyleTarget                     = self
    local ok, err                           = pcall(applyStylesOnFrame, self, styles)
    if not ok then Error("[Scorpio.UI]Apply Style: %s - Failed: %s", debugname, tostring(err)) end
    _CurrentStyleTarget                     = nil
end

__Service__(true)
function ApplyStyleService()
    while true do
        local frame                         = _StyleQueue:Dequeue()

        while frame do
            if _StyleQueue[frame] then
                local props                 = _Property[getUIPrototype(frame)]

                if props and not frame.Disposed then
                    local debugname         = frame:GetName(true) or frame:GetObjectType()

                    Trace("[Scorpio.UI]Apply Style: %s%s", debugname, _PropertyChildName[frame] and (" - " .. _PropertyChildName[frame]) or "")

                    local styles, children, childcls = buildTempStyle(frame)

                    -- Queue the children
                    for name in pairs(children) do
                        local child         = UIObject.GetChild(frame, name)

                        if child then
                            applyStyle(child)
                        elseif props[name] and props[name].childtype and styles[name] == true then
                            styles[name]    = nil
                            child           = props[name].get(frame)
                            if child then
                                if props[name].instant then
                                    child:InstantApplyStyle()
                                else
                                    applyStyle(child)
                                end
                            end
                        end
                    end
                    _Recycle(wipe(children))

                    -- Queue the children of the target type
                    if childcls then
                        for name, child in UIObject.GetChilds(frame) do
                            local mcls      = getmetatable(child)
                            for ctype in pairs(childcls) do
                                if mcls == ctype or isSubType(mcls, ctype) then
                                    applyStyle(child)
                                    break
                                end
                            end
                        end
                        _Recycle(wipe(childcls))
                    end

                    _StyleQueue[frame]      = nil

                    -- Apply the style settings
                    local isProtected       = frame.IsProtected
                    if isProtected and isProtected(frame) and InCombatLockdown() then
                        -- Split the secure properties
                        local secure

                        for name, value in pairs(styles) do
                            if props[name].secure then
                                secure      = secure or _Recycle()
                                secure[name]= value
                                styles[name]= nil
                            end
                        end

                        if secure then
                            NoCombat(ApplyFrameStyles, frame, secure, debugname)
                        end
                    end

                    ApplyFrameStyles(frame, styles, debugname)

                    Continue() -- Smoothing the process
                else
                    _StyleQueue[frame]      = nil
                end
            end

            frame                           = _StyleQueue:Dequeue()
        end

        NextEvent("SCORPIO_UI_APPLY_STYLE")
    end
end

__Service__(true)
function CollectPropertyChildService()
    while true do
        local frame                         = _ClearQueue:Dequeue()

        while frame do
            if _ClearQueue[frame] then
                _ClearQueue[frame]          = nil

                clearStyle(frame)
            end

            Continue()
            frame                           = _ClearQueue:Dequeue()
        end

        -- For safe
        for k, v in pairs(_ClearQueue) do if v == true then _ClearQueue[k] = nil end end

        NextEvent("SCORPIO_UI_COLLECT_PROPERTY_CHILD")
    end
end

__Service__(true)
function QueueClassFramesService()
    while true do
        local class                 = _ClassQueue:Dequeue()

        while class do
            if _ClassQueue[class] then
                _ClassQueue[class]  = nil -- Allow queue again

                local count         = 1
                local frames        = _ClassFrames[class]

                if frames then
                    for frame in pairs(frames) do
                        applyStyle(frame)
                        count       = count + 1

                        if count > 30 then
                            count   = 1
                            Continue()
                        end
                    end
                end
            end

            Continue()

            class                   = _ClassQueue:Dequeue()
        end

        NextEvent("SCORPIO_UI_UPDATE_CLASS_SKIN")
    end
end

-- Apply the style on the frame instantly
function UIObject:InstantApplyStyle(skipCheck)
    _StyleQueue[self]       = nil

    local props             = _Property[getUIPrototype(self)]
    if not props then return end

    local debugname         = self:GetName(true) or self:GetObjectType()

    Trace("[Scorpio.UI]Instant Apply Style: %s%s", debugname, _PropertyChildName[self] and (" - " .. _PropertyChildName[self]) or "")

    local styles, children, childcls = buildTempStyle(self)

    -- Check the location props, dirty but should be only property has relationship
    if not skipCheck and type(styles["location"]) == "table" then
        for _, loc in ipairs(styles["location"]) do
            if type(loc) == "table" and not UIObject.GetRelativeUI(self, loc.relativeTo) then
                _StyleQueue[self] = true
                return
            end
        end
    end

    -- Queue the children
    for name in pairs(children) do
        local child         = UIObject.GetChild(self, name)
        children[name]      = false

        if child then
            children[name]  = child
        elseif props[name] and props[name].childtype and styles[name] == true then
            styles[name]    = nil
            child           = props[name].get(self)
            if child then children[name] = child end
        end
    end

    -- Queue the children of the target class
    if childcls then
        for name, child in UIObject.GetChilds(self) do
            if children[name] == nil then
                local mcls  = getmetatable(child)
                for ctype in pairs(childcls) do
                    if mcls == ctype or isSubType(mcls, ctype) then
                        children[name] = child
                        break
                    end
                end
            end
        end
        _Recycle(wipe(childcls))
    end

    for name, child in pairs(children) do
        if child then UIObject.InstantApplyStyle(child, true) end
    end

    _Recycle(wipe(children))

    -- Apply the style settings
    _CurrentStyleTarget     = self
    local ok, err           = pcall(applyStylesOnFrame, self, styles)
    if not ok then Error("[Scorpio.UI]Apply Style: %s - Failed: %s", debugname, tostring(err)) end
    _CurrentStyleTarget     = nil
end

-- Get the child property name of the frame if it's generated by the property
function UIObject:GetChildPropertyName()
    return _PropertyChildName[self]
end

-- Get the child generated from the given property name
__Arguments__{ String, Boolean/nil }
function UIObject:GetPropertyChild(name, create)
    self                        = GetProxyUI(self)

    local props                 = _Property[getUIPrototype(self)]
    local prop                  = props and props[strlower(name)]

    return prop and prop.childtype and prop.get(self, not create)
end

-- Get the relative UI Elements based on the name, nil means parent
__Arguments__{ NEString/nil }
function UIObject:GetRelativeUI(relativeTo)
    if relativeTo then
        local rtar

        -- $parent.xxx.xxx
        if relativeTo:find(".", 1, true) then
            for pattern in relativeTo:gmatch("[^%.]+") do
                if pattern:lower() == "$parent" then
                    rtar        = (rtar or self):GetParent()
                else
                    local p     = rtar or self:GetParent()
                    rtar        = UIObject.GetChild(p, pattern) or UIObject.GetPropertyChild(p, pattern)
                end
                if not rtar then break end
            end
        else
            local p             = self:GetParent()
            rtar                = p and (UIObject.GetChild(p, relativeTo) or UIObject.GetPropertyChild(p, relativeTo))
        end

        return rtar or UIObject.FromName(relativeTo)
    else
        return self:GetParent()
    end
end

--- Gets the relative ui access name
__Arguments__{ UI }
function UIObject:GetRelativeUIName(frame)
    local parent                = self:GetParent()

    -- Use nil represent the parent
    if UI.IsSameUI(frame, parent) then return nil end

    -- Use brother's name
    if UI.IsSameUI(frame:GetParent(), parent) then return frame:GetName() end

    local fullName              = UIObject.GetName(self, true)
    local tarFullName           = UIObject.GetName(frame, true)

    -- Check the max same prefix
    local index                 = 1
    if fullName and tarFullName then
        while fullName:byte(index) == tarFullName:byte(index) do index = index + 1 end
        if index > #fullName and tarFullName:byte(index) == 46 then
            -- the frame is a child
            return tarFullName:sub(index + 1, -1)
        elseif index > #tarFullName and fullName:byte(index) == 46 then
            -- means parent
            return (fullName:sub(index + 1, -1):gsub("[^%.]+", "$parent"))
        end

        while index > 1 and fullName:byte(index - 1) ~= 46 do index = index - 1 end
        return index > 1 and (fullName:sub(index, -1):gsub("[^%.]+", "$parent") .. "." .. tarFullName:sub(index, -1)) or tarFullName
    else
        return false
    end
end

----------------------------------------------
--              Tool Attribute              --
----------------------------------------------
--- Define a child property based on the target ui class
__Sealed__() class "__ChildProperty__" (function(_ENV)
    extend "IAttachAttribute"

    -----------------------------------------------------------
    --                       property                        --
    -----------------------------------------------------------
    property "AttributeTarget"  { set = false, default = AttributeTargets.Class }

    -----------------------------------------------------------
    --                        method                         --
    -----------------------------------------------------------
    --- attach data on the target
    -- @param   target                      the target
    -- @param   targettype                  the target type
    -- @param   owner                       the target's owner
    -- @param   name                        the target's name in the owner
    -- @param   stack                       the stack level
    -- @return  data                        the attribute data to be attached
    function AttachAttribute(self, target, targettype, owner, name, stack)
        UI.Property             {
            name                = self.Name or Namespace.GetNamespaceName(target, true),
            require             = self.Require,
            childtype           = target,
        }
    end

    -----------------------------------------------------------
    --                      constructor                      --
    -----------------------------------------------------------
    __Arguments__{ - UIObject/Frame, NEString/nil }
    function __ctor(self, reqframe, name)
        self.Require            = reqframe
        self.Name               = name
    end
end)

--- Define a UI class to do the InstantApplyStyle
__Sealed__() class "__InstantApplyStyle__" (function(_ENV)
    extend "IInitAttribute" "IApplyAttribute"

    local getSuperCTOR          = Class.GetSuperMetaMethod

    -----------------------------------------------------------
    --                        method                         --
    -----------------------------------------------------------
    --- modify the target's definition
    -- @param   target                      the target
    -- @param   targettype                  the target type
    -- @param   definition                  the target's definition
    -- @param   owner                       the target's owner
    -- @param   name                        the target's name in the owner
    -- @param   stack                       the stack level
    -- @return  definition                  the new definition
    function InitDefinition(self, target, targettype, definition, owner, name, stack)
        if targettype == AttributeTargets.Method then
            if name ~= "__ctor" then error("The __InstantApplyStyle__ can only be used on the constructor, not nomral method", stack + 1) end

            INSTANT_STYLE_UI_CLASS[owner] = true

            return function(self, ...)
                definition(self, ...)
                return _StyleQueue[self] and self:InstantApplyStyle()
            end
        end
    end

    -----------------------------------------------------------
    --- apply changes on the target
    -- @param   target                      the target
    -- @param   targettype                  the target type
    -- @param   manager                     the definition manager of the target
    -- @param   owner                       the target's owner
    -- @param   name                        the target's name in the owner
    -- @param   stack                       the stack level
    function ApplyAttribute(self, target, targettype, manager, owner, name, stack)
        if targettype == AttributeTargets.Class then
            INSTANT_STYLE_UI_CLASS[target] = true

            local ctor          = Class.GetMetaMethod(target, "__ctor")

            manager.__ctor      = ctor and function(self, ...)
                ctor(self, ...)
                return _StyleQueue[self] and self:InstantApplyStyle()
            end or function(self, ...)
                local sctor     = getSuperCTOR(target, "__ctor")
                if sctor then sctor(self, ...) end
                return _StyleQueue[self] and self:InstantApplyStyle()
            end
        end
    end

    -----------------------------------------------------------
    --                       property                       --
    -----------------------------------------------------------
    property "AttributeTarget"  { type = AttributeTargets,  default = AttributeTargets.Method + AttributeTargets.Class }

    property "Priority"         { type = AttributePriority, default = AttributePriority.Lowest }
end)