{-# LANGUAGE Strict #-}

{-|
Module      : Data.DelayedQueue
Copyright   : (c) Naoto Shimazaki 2018-2020
License     : MIT (see the file LICENSE)
Maintainer  : https://github.com/nshimaza
Stability   : experimental

Queue with delay before elements become available to dequeue.

'DelayedQueue' is a FIFO but it does NOT make element available to pop
immediately after the element was pushed.  DelayedQueue looks like empty until
its delay-buffer is filled up by pushed elements.  When a value is pushed to a
DelayedQueue where delay-buffer of the queue is already full-filled, the oldest
element becomes available to dequeue.

Entire elements within DelayedQueue are always inlined in enqueued order.  Only
older elements overflowed from delay-buffer are available to dequeue.  If
delay-buffer is not yet filled, no element is available to dequeue.  Once
delay-buffer is filled, delay-buffer always keeps given number of newest
elements.
-}

module Data.DelayedQueue
    ( DelayedQueue
    , newEmptyDelayedQueue
    , push
    , pop
    ) where

import           Data.Sequence (Seq, ViewL ((:<), EmptyL), empty, viewl, (|>))

-- | Queue with delay before elements become available to dequeue.
data DelayedQueue a = DelayedQueue Int (Seq a) deriving (Eq, Show)

-- | Create a new 'DelayedQueue' with given delay.
newEmptyDelayedQueue
    :: Int              -- ^ Delay of the queue in number of element.
    -> DelayedQueue a   -- ^ Created queue.
newEmptyDelayedQueue n = DelayedQueue n empty

-- | Enqueue a value to a 'DelayedQueue'.
push
    :: a                -- ^ value to be queued.
    -> DelayedQueue a   -- ^ queue where the value to be queued.
    -> DelayedQueue a   -- ^ new queue where the value is pushed.
push a (DelayedQueue delay sq) = DelayedQueue delay $ sq |> a

-- | Dequeue a value from a 'DelayedQueue'.
pop
    :: DelayedQueue a               -- ^ 'DelayedQueue' from which a value to be pulled.
    -> Maybe (a, DelayedQueue a)    -- ^ Nothing if there is no available element.
                                    --   Returns pulled value and 'DelayedQueue' with the value removed wrapped in Just.
pop (DelayedQueue delay sq) | length sq > delay = case viewl sq of
                                                    (x :< xs)   -> Just (x, DelayedQueue delay xs)
                                                    EmptyL      -> Nothing
                            | otherwise         = Nothing