{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE DataKinds #-}

module Data.Apiary.Extension
    ( Has(getExtension)
    , MonadExts(..), getExt
    , Middleware'
    , Extension(..)
    , Extensions
    , noExtension
    -- * initializer constructor
    , Initializer,  initializer
    , Initializer', initializer'
    , initializerBracket
    , initializerBracket'

    -- * combine initializer
    , (+>)
    ) where

import Control.Monad(liftM)
import Data.Apiary.Extension.Internal(Has, getExtension, Initializer(Initializer))
import Control.Monad.Apiary.Action.Internal
    (MonadExts(getExts), Extension(..), Extensions(AddExtension), Middleware')

getExt :: (MonadExts es m, Has e es) => proxy e -> m e
getExt p = getExtension p `liftM` getExts

type Initializer' m a = forall i. Initializer m i (a ': i)

addExtension :: Extension e => e -> Extensions es -> Extensions (e ': es)
addExtension = AddExtension

initializer :: (Extension e, Monad m) => (Extensions es -> m e) -> Initializer m es (e ': es)
initializer m = Initializer $ \es n -> do
    e <- m es
    n (addExtension e es)

initializer' :: (Extension e, Monad m) => m e -> Initializer' m e
initializer' m = initializer (const m)

initializerBracket :: Extension e => (forall a. Extensions es -> (e -> m a) -> m a) -> Initializer m es (e ': es)
initializerBracket b = Initializer $ \es n ->
    b es $ \e -> n (addExtension e es)

initializerBracket' :: Extension e => (forall a. (e -> m a) -> m a) -> Initializer m es (e ': es)
initializerBracket' m = initializerBracket (const m)

-- | combine two Initializer. since 0.16.0.
(+>) :: Monad m => Initializer m i x -> Initializer m x o -> Initializer m i o
Initializer a +> Initializer b = Initializer $ \e m -> a e (\e' -> b e' m)

noExtension :: Monad m => Initializer m i i
noExtension = Initializer $ \es n -> n es