{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
module Control.Concurrent.EQueue
  ( EQueue(registerSemi, registerQueued), EQueueW(waitEQ)
  , AnyEQueue(AEQ), ForceEdge(EEQ), MappedEQueue(MEQ), meqEQ
  , STMEQueue, STMEQueueWait(..), newSTMEQueue
  ) where

import Control.Concurrent.EQueue.Class
import Control.Concurrent.EQueue.STMEQueue (STMEQueue, STMEQueueWait(..), newSTMEQueue)
import Data.Bifunctor
import Data.Functor.Contravariant

-- | Allows us to return an unknown instance of EQueue, getting around Haskells lack of
--   existential qualification.
data AnyEQueue a where
  AEQ :: EQueue eq => eq a -> AnyEQueue a

instance EQueue AnyEQueue where
  registerSemi (AEQ eq) = registerSemi eq
  registerQueued (AEQ eq) = registerQueued eq

-- | A wrapper that translates level triggered events into events that observe the edges.
data ForceEdge a where
  EEQ :: EQueue eq => eq a -> ForceEdge a

instance EQueue ForceEdge where
  registerSemi (EEQ eq) f = (first (. f)) <$> registerQueued eq
  registerQueued (EEQ eq) = registerQueued eq

-- | A wrapper that allows us to pretend a queue of one type is of another.
data MappedEQueue eq b a where
  MEQ :: (a -> b) -> eq b -> MappedEQueue eq b a

-- | Retrieve the EQueue we're mapping to from the MappedEQueue.
meqEQ :: MappedEQueue eq b a -> eq b
meqEQ (MEQ _ eq) = eq

instance Contravariant (MappedEQueue eq b) where
  contramap f (MEQ g eq) = MEQ (g . f) eq

instance EQueue eq => EQueue (MappedEQueue eq b) where
  registerSemi (MEQ g eq) f = registerSemi eq (g . f)
  registerQueued (MEQ g eq) = (first (. g)) <$> registerQueued eq
