{-# LANGUAGE RecordWildCards #-}

module Periodic.Server.GrabQueue
  ( GrabQueue
  , newGrabQueue
  , GrabItem (..)
  , pushAgent
  , popAgentSTM
  , popAgentList
  ) where

import           Control.Arrow         ((&&&))
import           Control.Monad         (unless)
import           Control.Monad.STM     (STM, retry)
import           Metro.Session         (ident)
import           Periodic.IOList       (IOList, append, delete, deleteSTM, elem,
                                        elemSTM, newIOList, toList, toListSTM)
import           Periodic.Node         (Nid)
import           Periodic.Server.Types (CSEnv)
import           Periodic.Types        (FuncName, JobHandle, Msgid)
import           Prelude               hiding (elem)
import           UnliftIO              (MonadIO (..))

data GrabItem tp = GrabItem
    { gFuncList :: IOList FuncName
    , gAgent    :: CSEnv tp
    , gJobQueue :: IOList JobHandle
    }

instance Eq (GrabItem tp) where
    (==) = eqGrabItem

key :: GrabItem tp -> (Nid, Msgid)
key GrabItem{gAgent = a} = ident a

eqGrabItem :: GrabItem tp -> GrabItem tp -> Bool
eqGrabItem a b = key a == key b

type GrabQueue tp = IOList (GrabItem tp)

newGrabQueue :: MonadIO m => m (GrabQueue tp)
newGrabQueue = newIOList

pushAgent :: MonadIO m => GrabQueue tp -> IOList FuncName -> IOList JobHandle -> CSEnv tp -> m ()
pushAgent q gFuncList gJobQueue gAgent = do
  has <- elem q i
  unless has $ append q i
  where i = GrabItem {..}

popAgentSTM :: GrabQueue tp -> FuncName -> STM (IOList JobHandle, CSEnv tp)
popAgentSTM q n = do
  item <- go =<< toListSTM q
  deleteSTM q item
  return (gJobQueue item, gAgent item)

 where go :: [GrabItem tp] -> STM (GrabItem tp)
       go [] = retry
       go (x:xs) = do
         has <- elemSTM (gFuncList x) n
         if has then return x
                else go xs

popAgentList :: MonadIO m => GrabQueue tp -> FuncName -> m [(IOList JobHandle, CSEnv tp)]
popAgentList q n = do
  items <- go =<< toList q
  mapM_ (delete q) items
  pure $ map (gJobQueue &&& gAgent) items

 where go :: MonadIO m => [GrabItem tp] -> m [GrabItem tp]
       go [] = return []
       go (x:xs) = do
         has <- elem (gFuncList x) n
         xs' <- go xs
         if has then pure (x:xs')
                else pure xs'