module Control.Monad.Trans.Order.Strict (

    -- * The Order monad

    Order,
    evalOrder,
    evalOrderWith,

    -- * The OrderT monad transformer

    OrderT,
    evalOrderT,
    force,

    -- * Elements

    Element,
    newMinimum,
    newMaximum,
    newAfter,
    newBefore,

    -- * Converting between lazy and strict OrderT

    lazyToStrictOrderT,
    strictToLazyOrderT

) where

-- Control

import           Control.Monad
import           Control.Applicative
import           Control.Monad.Trans.Class
import           Control.Monad.IO.Class
import qualified Control.Monad.Trans.State.Lazy
                     as Lazy
import           Control.Monad.Trans.State.Strict
import           Control.Monad.Trans.Order.Lazy
                     (Element)
import qualified Control.Monad.Trans.Order.Lazy
                     as Lazy
import           Control.Monad.Trans.Order.Lazy.Internals
                     (OrderRep, emptyOrderRep)
import qualified Control.Monad.Trans.Order.Lazy.Internals
                     as Lazy
import           Control.Monad.Trans.Order.Algorithm
import           Control.Monad.Trans.Order.Algorithm.Type

-- Data

import Data.Functor.Identity

-- * The Order monad

type Order o = OrderT o Identity

evalOrder :: (forall o . Order o a) -> a
evalOrder order = runIdentity (evalOrderT order)

evalOrderWith :: Algorithm -> (forall o . Order o a) -> a
evalOrderWith alg order = runIdentity (evalOrderTWith alg order)

-- * The OrderT monad transformer

newtype OrderT o m a = OrderT (StateT (OrderRep o) m a) deriving (
    Functor,
    Applicative,
    Alternative,
    Monad,
    MonadPlus,
    MonadTrans,
    MonadIO)
    -- FIXME: Should we also have a MonadFix instance?

evalOrderT :: Monad m => (forall o . OrderT o m a) -> m a
evalOrderT = evalOrderTWith defaultAlgorithm

evalOrderTWith :: Monad m => Algorithm -> (forall o . OrderT o m a) -> m a
evalOrderTWith (Algorithm rawAlg) (OrderT stateT) = monad where

    monad = evalStateT stateT (emptyOrderRep rawAlg)

force :: Monad m => OrderT o m ()
force = lazyToStrictOrderT Lazy.force

-- * Elements

newMinimum :: Monad m => OrderT o m (Element o)
newMinimum = lazyToStrictOrderT Lazy.newMinimum

newMaximum :: Monad m => OrderT o m (Element o)
newMaximum = lazyToStrictOrderT Lazy.newMaximum

newAfter :: Monad m => Element o -> OrderT o m (Element o)
newAfter = lazyToStrictOrderT . Lazy.newAfter

newBefore :: Monad m => Element o -> OrderT o m (Element o)
newBefore = lazyToStrictOrderT . Lazy.newBefore

-- * Converting between lazy and strict OrderT

lazyToStrictOrderT :: Lazy.OrderT o m a -> OrderT o m a
lazyToStrictOrderT (Lazy.OrderT (Lazy.StateT fun)) = OrderT (StateT fun)

strictToLazyOrderT :: OrderT o m a -> Lazy.OrderT o m a
strictToLazyOrderT (OrderT (StateT fun)) = Lazy.OrderT (Lazy.StateT fun)