--===========================================================================--
--                                                                           --
--                           System.Configuration                            --
--                                                                           --
--===========================================================================--

--===========================================================================--
-- Author       :   kurapica125@outlook.com                                  --
-- URL          :   http://github.com/kurapica/PLoop                         --
-- Create Date  :   2018/05/11                                               --
-- Update Date  :   2022/03/10                                               --
-- Version      :   1.0.1                                                    --
--===========================================================================--

PLoop(function(_ENV)
    namespace "System.Configuration"

    --- The config sections are used as containers for configurations.
    --
    -- the configurations are pairs of name and type that'd be registered
    -- in anywhere with or without special handlers, so we can parse the
    -- configurations in one place for the whole system.
    __Sealed__() __NoNilValue__(true) __NoRawSet__(true)
    class "ConfigSection"               (function(_ENV)
        export { "pairs", "type", "getmetatable", yield = coroutine.yield, Enum, Struct, Interface, Class, Any, ConfigSection, List }

        -----------------------------------------------------------
        --                         event                         --
        -----------------------------------------------------------
        --- When the config is parsed by the config section
        event "OnParse"

        --- When the config is parsed by the config section's field
        event "OnFieldParse"

        -----------------------------------------------------------
        --                        method                         --
        -----------------------------------------------------------
        --- Parse the config settings with arguments to be distributed
        __Arguments__{ Table, Any * 0 }
        function ParseConfig(self, config, ...)
            local msg
            local fields                = self.__Fields
            local sections              = self.__Sections

            for _, name in self.__Order:GetIterator() do
                local val               = config[name]

                if val ~= nil then
                    local fldtype       = fields[name]
                    if fldtype then
                        val, msg        = getmetatable(fldtype).ValidateValue(fldtype, val)

                        if msg then return nil, msg:gsub("%%s", "%%s" .. "." .. name) end
                        config[name]    = val

                        OnFieldParse(self, name, val, ...)
                    end

                    local subsect       = sections[name]
                    if subsect then
                        val, msg        = subsect:ParseConfig(val, ...)
                        if msg then return nil, msg:gsub("%%s", "%%s" .. "." .. name) end
                        config[name]    = val
                    end
                end
            end

            OnParse(self, config, ...)

            return config
        end

        --- Gets all fields with orders
        __Iterator__() function GetFields(self)
            local fields                = self.__Fields
            for _, name in self.__Order:GetIterator() do
                local type              = fields[name]
                if type then yield(name, type) end
            end
        end

        --- Gets all sections with orders
        __Iterator__() function GetSections(self)
            local sections              = self.__Sections
            for _, name in self.__Order:GetIterator() do
                local sect              = sections[name]
                if sect then yield(name, sect) end
            end
        end

        -----------------------------------------------------------
        --                       property                        --
        -----------------------------------------------------------
        --- The fields of the config section
        __Indexer__() property "Field" { type = EnumType + StructType,
            set                         = function(self, name, type)
                if not self.__Order:Contains(name) then self.__Order:Insert(name) end

                self.__Fields[name]     = type or Any
                self.__Sections[name]   = nil
            end,
            get                         = function(self, name) return self.__Fields[name] end,
        }

        --- The sub-sections of the config section
        __Indexer__() property "Section" { type = ConfigSection,
            set                         = function(self, name, sect)
                if not self.__Order:Contains(name) then self.__Order:Insert(name) end

                self.__Sections[name]   = sect
                self.__Fields[name]     = nil
            end,
            get                         = function(self, name)
                local secset            = self.__Sections[name]
                if not secset and not self.__Fields[name] then
                    if not self.__Order:Contains(name) then self.__Order:Insert(name) end

                    secset              = ConfigSection()
                    self.__Sections[name] = secset
                end
                return secset
            end,
        }

        -----------------------------------------------------------
        --                      constructor                      --
        -----------------------------------------------------------
        function __new(_)
            return {
                __Order                 = List(),
                __Fields                = {},
                __Sections              = {},
            }
        end

        -----------------------------------------------------------
        --                      meta-method                      --
        -----------------------------------------------------------
        function __index(self, key)
            if type(key) ~= "string" then return end
            local val                   = self.__Fields[key]
            return val or self.Section[key]
        end
    end)

    --- The binder for the config section and handler
    -- @usage :
    --      __ConfigSection__( System.Web.ConfigSection.Html.Render, { nolinebreak = Boolean, noindent = Boolean  } )
    --      function HtmlRenderConfig(config, ...)
    --          print(config.nolinebreak)
    --      end
    --
    --      __ConfigSection__( System.Web.ConfigSection.Controller, "jsonprovider", -FormatProvider)
    --      function JsonProviderConfig(field, value, ...)
    --          print("The new json provider is " .. value)
    --      end
    __Sealed__()
    class "__ConfigSection__"           (function(_ENV)
        extend "IAttachAttribute"

        export                          {
            pairs                       = pairs,
            type                        = type,
            strformat                   = string.format,
            isenum                      = Enum.Validate,
            isstruct                    = Struct.Validate,
        }

        -----------------------------------------------------------
        --                        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)
            if self[3] then
                local section           = self[1]
                local fldname           = self[2]
                section.Field[fldname]  = self[3]

                section.OnFieldParse    = section.OnFieldParse + function(self, fld, val, ...)
                    if fld == fldname then
                        return target(fld, val, ...)
                    end
                end
            else
                local section           = self[1]
                if self[2] then
                    for k, v in pairs(self[2]) do
                        if type(k) == "string" and (isenum(v) or isstruct(v)) then
                            section.Field[k] = v
                        else
                            error("The field's type can only be enum or struct", stack + 1)
                        end
                    end
                end
                section.OnParse         = section.OnParse + function(self, ...) return target(...) end
            end
        end

        -----------------------------------------------------------
        --                       property                        --
        -----------------------------------------------------------
        --- the attribute target
        property "AttributeTarget" { set = false, default = AttributeTargets.Function }

        -----------------------------------------------------------
        --                      constructor                      --
        -----------------------------------------------------------
        __Arguments__{ ConfigSection, Table/nil }
        function __new(_, section, fields)
            return { section, fields, false }, true
        end

        __Arguments__{ ConfigSection, NEString, (EnumType + StructType)/Any }
        function __new(_, section, name, type)
            return { section, name, type }, true
        end
    end)
end)
