--===========================================================================--
--                                                                           --
--                         System.Collections.Proxy                          --
--                                                                           --
--===========================================================================--

--===========================================================================--
-- Author       :   kurapica125@outlook.com                                  --
-- URL          :   http://github.com/kurapica/PLoop                         --
-- Create Date  :   2016/02/28                                               --
-- Update Date  :   2018/05/12                                               --
-- Version      :   1.1.0                                                    --
--===========================================================================--

PLoop(function(_ENV)
    import "System.Serialization"

    --- The safe dictionary, it'll create the object as proxy to the real table
    __Sealed__() __Serializable__() __Arguments__{ AnyType, AnyType }( Any, Any )
    __NoNilValue__(false):AsInheritable() __NoRawSet__(false):AsInheritable()
    class "System.Collections.Proxy"    (function (_ENV, keytype, valtype)
        extend "IKeyValueDict" "ISerializable"

        local RAW_HOLDER                = {}

        export                          {
            ipairs                      = ipairs,
            pairs                       = pairs,
            GetErrorMessage             = Struct.GetErrorMessage,
            yield                       = coroutine.yield,

            List[keytype], List[valtype]
        }

        if keytype ~= Any then
            export { kvalid             = getmetatable(keytype).ValidateValue, rawset = rawset }
        end

        if valtype ~= Any then
            export { vvalid             = getmetatable(valtype).ValidateValue, rawset = rawset }
        end

        -----------------------------------------------------------
        --                     serialization                     --
        -----------------------------------------------------------
        function Serialize(self, info)
            local key                   = {}
            local val                   = {}
            local idx                   = 1
            for k, v in self:GetIterator() do
                key[idx]                = k
                val[idx]                = v
                idx                     = idx + 1
            end

            info:SetValue(1, List[keytype](key))
            info:SetValue(2, List[valtype](val))
        end

        __Arguments__{ SerializationInfo }
        function __new(_, info)
            local key                   = info:GetValue(1, List[keytype])
            local val                   = info:GetValue(2, List[valtype])

            return this(_, key, val)
        end

        -----------------------------------------------------------
        --                        method                         --
        -----------------------------------------------------------
        function GetIterator(self)
            return pairs(self[RAW_HOLDER])
        end

        --- Update the dictionary
        if keytype == Any and valtype == Any then
            __Arguments__{ RawTable }
            function Update(self, dict)
                for k, v in pairs(dict) do self[RAW_HOLDER][k] = v end
                return self
            end

            __Arguments__{ IDictionary }
            function Update(self, dict)
                for k, v in dict:GetIterator() do self[RAW_HOLDER][k] = v end
                return self
            end

            __Arguments__{ Callable, System.Any/nil, System.Any/nil }
            function Update(self, iter, obj, idx)
                for k, v in iter, obj, idx do self[RAW_HOLDER][k] = v end
                return self
            end
        elseif keytype == Any and valtype ~= Any then
            __Arguments__{ RawTable }
            function Update(self, dict)
                for k, v in pairs(dict) do
                    local ret, msg      = vvalid(valtype, v, true)
                    if not msg then
                        self[RAW_HOLDER][k] = v
                    end
                end
                return self
            end

            __Arguments__{ IDictionary }
            function Update(self, dict)
                for k, v in dict:GetIterator() do
                    local ret, msg      = vvalid(valtype, v, true)
                    if not msg then
                        self[RAW_HOLDER][k] = v
                    end
                end
                return self
            end

            __Arguments__{ Callable, System.Any/nil, System.Any/nil }
            function Update(self, iter, obj, idx)
                for k, v in iter, obj, idx do
                    local ret, msg      = vvalid(valtype, v, true)
                    if not msg then
                        self[RAW_HOLDER][k] = v
                    end
                end
                return self
            end
        elseif keytype ~= Any and valtype == Any then
            __Arguments__{ RawTable }
            function Update(self, dict)
                for k, v in pairs(dict) do
                    local ret, msg      = kvalid(keytype, k, true)
                    if not msg then
                        self[RAW_HOLDER][k] = v
                    end
                end
                return self
            end

            __Arguments__{ IDictionary }
            function Update(self, dict)
                for k, v in dict:GetIterator() do
                    local ret, msg      = kvalid(keytype, k, true)
                    if not msg then
                        self[RAW_HOLDER][k] = v
                    end
                end
                return self
            end

            __Arguments__{ Callable, System.Any/nil, System.Any/nil }
            function Update(self, iter, obj, idx)
                for k, v in iter, obj, idx do
                    local ret, msg      = kvalid(keytype, k, true)
                    if not msg then
                        self[RAW_HOLDER][k] = v
                    end
                end
                return self
            end
        else
            __Arguments__{ RawTable }
            function Update(self, dict)
                for k, v in pairs(dict) do
                    local ret, msg      = kvalid(keytype, k, true)
                    if not msg then
                        ret, msg        = vvalid(valtype, v, true)
                        if not msg then
                            self[RAW_HOLDER][k]  = v
                        end
                    end
                end
                return self
            end

            __Arguments__{ IDictionary }
            function Update(self, dict)
                for k, v in dict:GetIterator() do
                    local ret, msg      = kvalid(keytype, k, true)
                    if not msg then
                        ret, msg        = vvalid(valtype, v, true)
                        if not msg then
                            self[RAW_HOLDER][k]  = v
                        end
                    end
                end
                return self
            end

            __Arguments__{ Callable, System.Any/nil, System.Any/nil }
            function Update(self, iter, obj, idx)
                for k, v in iter, obj, idx do
                    local ret, msg      = kvalid(keytype, k, true)
                    if not msg then
                        ret, msg        = vvalid(valtype, v, true)
                        if not msg then
                            self[RAW_HOLDER][k]  = v
                        end
                    end
                end
                return self
            end
        end

        -----------------------------------------------------------
        --                       property                        --
        -----------------------------------------------------------
        --- Gets or sets the value associated with the specified key
        __Indexer__(keytype ~= Any and keytype or nil)
        property "Item"                 {
            get                         = function(self, key) return self[RAW_HOLDER][key] end,
            set                         = function(self, key, value) self[RAW_HOLDER][key] = value end,
            type                        = valtype ~= Any and valtype or nil,
        }

        -----------------------------------------------------------
        --                      constructor                      --
        -----------------------------------------------------------
        __Arguments__{ }
        function __new() return { [RAW_HOLDER] = {} } end

        __Arguments__{ RawTable }
        function __new(_, dict) return { [RAW_HOLDER] = dict }, true end

        __Arguments__{ RawTable + IList, RawTable + IList }
        function __new(_, lstKey, lstValue)
            local dict                  = {}
            local iter, o, idx, value   = (lstValue.GetIterator or ipairs)(lstValue)
            for _, key in (lstValue.GetIterator or ipairs)(lstKey) do
                idx, value              = iter(o, idx)
                if idx then
                    dict[key]           = value
                else
                    break
                end
            end
            return { [RAW_HOLDER] = dict }, true
        end

        __Arguments__{ RawTable + IList, Any }
        function __new(_, lstKey, value)
            local dict                  = {}
            for _, key in (lstKey.GetIterator or ipairs)(lstKey) do
                dict[key]               = value
            end
            return { [RAW_HOLDER] = dict }, true
        end

        __Arguments__{ IDictionary }
        function __new(_, dict)
            local dict                  = {}
            for key, value in dict:GetIterator() do
                dict[key]               = value
            end
            return { [RAW_HOLDER] = dict }, true
        end

        __Arguments__{ Callable, Any/nil, Any/nil }
        function __new(_, iter, obj, idx)
            local dict                  = {}
            for key, value in iter, obj, idx do
                dict[key]               = value
            end
            return { [RAW_HOLDER] = dict }, true
        end

        if keytype ~= Any and valtype ~= Any then
            function __ctor(self)
                local msg
                for k, v in self:GetIterator() do
                    k, msg              = kvalid(keytype, k)
                    if msg then throw(GetErrorMessage(msg, "field")) end

                    v, msg              = vvalid(valtype, v)
                    if msg then throw(GetErrorMessage(msg, "value")) end
                    self[k]             = v
                end
            end
        elseif keytype ~= Any then
            function __ctor(self)
                local msg
                for k, v in self:GetIterator() do
                    k, msg              = kvalid(keytype, k)
                    if msg then throw(GetErrorMessage(msg, "field")) end
                end
            end
        elseif valtype ~= Any then
            function __ctor(self)
                local msg
                for k, v in self:GetIterator() do
                    v, msg              = vvalid(valtype, v)
                    if msg then throw(GetErrorMessage(msg, "value")) end
                    self[k]             = v
                end
            end
        end

        -----------------------------------------------------------
        --                      meta-method                      --
        -----------------------------------------------------------
        if keytype ~= Any or valtype ~= Any then
            __Arguments__{ keytype, valtype }
        end
        function __newindex(self, key, val)
            self[RAW_HOLDER][key]       = val
        end

        function __index(self, key)
            return self[RAW_HOLDER][key]
        end
    end)
end)
