--===========================================================================--
--                                                                           --
--                         System.Collections.Queue                          --
--                                                                           --
--===========================================================================--

--===========================================================================--
-- Author       :   kurapica125@outlook.com                                  --
-- URL          :   http://github.com/kurapica/PLoop                         --
-- Create Date  :   2019/12/08                                               --
-- Update Date  :   2019/12/08                                               --
-- Version      :   1.0.0                                                    --
--===========================================================================--

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

    import "System.Serialization"

    --- Represents a first-in, first-out collection of objects.
    __Sealed__() __Serializable__() __Arguments__{ AnyType }( Any )
    __NoNilValue__(false):AsInheritable() __NoRawSet__(false):AsInheritable()
    class "Queue"                       (function(_ENV, lsttype)
       extend "ICountable" "ISerializable"

        export { type = type, ipairs = ipairs, yield = coroutine.yield, select = select, unpack = _G.unpack or table.unpack, min = math.min }

        lsttype = lsttype ~= Any and lsttype or nil

        if lsttype then
            export {
                valid                   = getmetatable(lsttype).ValidateValue,
                GetErrorMessage         = Struct.GetErrorMessage,
                parseindex              = Toolset.parseindex,
            }
        end

        local FIELD_FRONT               = -1
        local FIELD_REAR                = -2
        local FIELD_CLEAR               = -3

        -----------------------------------------------------------
        --                     serialization                     --
        -----------------------------------------------------------
        function Serialize(self, info)
            for i, v in self:GetIterator() do
                info:SetValue(i, v, lsttype)
            end
        end

        __Arguments__{ SerializationInfo }
        function __new(_, info)
            local i                     = 1
            local v                     = info:GetValue(i, lsttype)
            local self                  = { [FIELD_FRONT] = 0 }
            while v ~= nil do
                self[i]                 = v
                i                       = i + 1
                v                       = info:GetValue(i, lsttype)
            end

            self[FIELD_REAR]            = i - 1

            return self, true
        end

        -----------------------------------------------------------
        --                       property                        --
        -----------------------------------------------------------
        --- Get the count of items in the object
        property "Count"                { set = false, get = function (self) return self[FIELD_REAR] - self[FIELD_FRONT] end }

        -----------------------------------------------------------
        --                        method                         --
        -----------------------------------------------------------
        --- Returns an iterator that iterates through the Queue
        __Iterator__()
        function GetIterator(self)
            local start                 = self[FIELD_FRONT]
            local stop                  = self[FIELD_REAR]

            for i = 1, stop - start do
                yield(i, self[i + start])
            end
        end

        --- Whether an item existed in the Queue
        function Contains(self, item)
            for i = self[FIELD_FRONT] + 1, self[FIELD_REAR] do
                if self[i] == item then return true end
            end
            return false
        end

        --- Adds an object to the end of the Queue
        if lsttype then __Arguments__{ lsttype * 1 } end
        function Enqueue(self, ...)
            if self[FIELD_CLEAR] then
                for i = self[FIELD_CLEAR], self[FIELD_FRONT] do
                    self[i]             = nil
                end

                self[FIELD_CLEAR]       = nil
            end

            local count                 = select("#", ...)
            local start                 = self[FIELD_REAR]

            if count == 1 then
                self[start + 1]         = ...
            elseif count == 2 then
                self[start + 1], self[start + 2] = ...
            elseif count == 3 then
                self[start + 1], self[start + 2], self[start + 3] = ...
            else
                for i = 1, count do
                    self[start + i]     = select(i, ...)
                end
            end

            self[FIELD_REAR]            = start + count

            return self
        end

        --- Removes and returns the object at the beginning of the Queue
        function Dequeue(self, count)
            count                       = min(count and type(count) == "number" and count or 1, self[FIELD_REAR] - self[FIELD_FRONT])
            if count < 1 then return end

            local start                 = self[FIELD_FRONT] + 1
            self[FIELD_CLEAR]           = self[FIELD_CLEAR] or start
            self[FIELD_FRONT]           = start + count - 1

            return unpack(self, start, self[FIELD_FRONT])
        end

        --- Clear the queue
        function Clear(self)
            for i = self[FIELD_CLEAR] or (self[FIELD_FRONT] + 1), self[FIELD_REAR] do
                self[i]                 = nil
            end
            self[FIELD_FRONT]           = 0
            self[FIELD_REAR]            = 0
            self[FIELD_CLEAR]           = nil
        end

        --- Returns the object at the beginning of the Queue without removing it
        __Arguments__{ NaturalNumber/nil }
        function Peek(self, count)
            count                       = min(count and type(count) == "number" and count or 1, self[FIELD_REAR] - self[FIELD_FRONT])
            if count < 1 then return end

            local start                 = self[FIELD_FRONT]
            return unpack(self, start + 1, start + count)
        end

        __Arguments__{ NaturalNumber, NaturalNumber }
        function Peek(self, start, count)
            start                       = self[FIELD_FRONT] + start - 1
            count                       = min(count, self[FIELD_REAR] - start)
            return unpack(self, start + 1, start + count)
        end

        -----------------------------------------------------------
        --                      constructor                      --
        -----------------------------------------------------------
        __Arguments__{ RawTable }
        function __new(_, lst)
            lst[FIELD_FRONT]            = 0
            lst[FIELD_REAR]             = #lst
            return lst, true
        end

        __Arguments__{ IList }
        function __new(_, lst)
            local i                     = 0
            local obj                   = { [FIELD_FRONT] = 0 }
            for idx, item in lst:GetIterator() do
                i                       = i + 1
                obj[i]                  = item
            end
            obj[FIELD_REAR]             = i
            return obj, true
        end

        __Arguments__{ Callable, System.Any/nil, System.Any/nil }
        function __new(_, iter, obj, idx)
            local i                     = 0
            local lst                   = { [FIELD_FRONT] = 0 }
            for key, item in iter, obj, idx do
                i                       = i + 1
                if item ~= nil then
                    lst[i]              = item
                else
                    lst[i]              = key
                end
            end
            obj[FIELD_REAR]             = i
            return lst, true
        end

        __Arguments__{ NaturalNumber, Callable }
        function __new(_, count, initValue)
            local obj                   = { [FIELD_FRONT] = 0 }
            for i = 1, count do
                obj[i]                  = initValue(i)
            end
            obj[FIELD_REAR]             = count
            return obj, true
        end

        __Arguments__{ NaturalNumber, System.Any/nil }
        function __new(_, count, initValue)
            local obj                   = { [FIELD_FRONT] = 0 }
            if initValue ~= nil then
                for i = 1, count do
                    obj[i]              = initValue
                end
            else
                for i = 1, count do
                    obj[i]              = i
                end
            end
            obj[FIELD_REAR]             = count
            return obj, true
        end

        __Arguments__{ Any * 0 }
        function __new(_, ...)
            local obj                   = { ... }
            obj[FIELD_FRONT]            = 0
            obj[FIELD_REAR]             = #obj
            return obj, true
        end

        if lsttype then
            function __ctor(self)
                local msg
                for k, v in self:GetIterator() do
                    v, msg              = valid(lsttype, v)
                    if msg then throw(GetErrorMessage(msg, parseindex(k))) end
                    self[k]= v
                end
            end
        end

        -----------------------------------------------------------
        --                      meta-method                      --
        -----------------------------------------------------------
        if lsttype then __Arguments__{ lsttype * 0 } end
        function __call(self, item, ...)
            if item ~= nil then
                return self:Enqueue(item, ...)
            else
                return self:Dequeue()
            end
        end

        function __len(self)
            return self[FIELD_REAR] - self[FIELD_FRONT]
        end
    end)
end)