module Control.InjFun (
    -- * Inject function
    InjFun
  , cfapply
  , inject
    -- * Sequencing, exploding and merging
  , (|->)
  , (||->)
  , explode
  , merge
  ) where

-- |Function able to be injected parameters in.
-- `c` is the injected control parameters, `i` represents its input, `m` is the resulting monad
-- and `o` is the output.
newtype InjFun c i m o = InjFun {
  -- |Feed a `InjFun` with its regular parameters and injected parameters.
  cfapply :: c -> i -> m o
  }

-- |Create an inject function.
inject :: (c -> i -> m o) -> InjFun c i m o
inject f = InjFun f

-- |Sequencing operator. It’s a helper function that composes with `>>=` the two `InjFun`, respecting
-- the order. That version (with a single `|`) means that both the two injected parameters are considered
-- the same; then they’re shared as a single `c`.
(|->) :: (Monad m) => InjFun c i m o  -- ^ First function
                   -> InjFun c o m o' -- ^ Second function
                   -> InjFun c i m o' -- ^ Resulting sequencing function
f |-> g = InjFun $ \c i -> cfapply f c i >>= cfapply g c

-- |Sequencing operator. It’s a helper function that composes with `>>=` the two `InjFun`, respecting
-- the order. That version (with double `|`) means that the two injected parameters are considered
-- different.
(||->) :: (Monad m) => InjFun c i m o       -- ^ First function
                    -> InjFun c' o m o'     -- ^ Second function
                    -> InjFun (c,c') i m o' -- ^ Resulting sequencing function
f ||-> g = InjFun $ \(c,c') i -> cfapply f c i >>= cfapply g c'

-- |Explode an `InjFun` that outputs two values into two other `InjFun`.
explode :: (Monad m) => InjFun c i m (o0,o1)              -- ^ Function to explode
                     -> (InjFun c i m o0,InjFun c i m o1) -- ^ Exploded functions
explode f = (f',f'')
  where f'  = cf fst
        f'' = cf snd
        cf sel = InjFun $ \c i -> cfapply f c i >>= return . sel

-- |Merge two `InjFun` into one.
merge :: (Monad m) => InjFun c i m o                -- ^ First function
                   -> InjFun c' i' m o'             -- ^ Second function
                   -> InjFun (c,c') (i,i') m (o,o') -- ^ Merged function
merge f g = fg
  where fg = InjFun $ \(c,c') (i,i') -> do
                r0 <- cfapply f c i
                r1 <- cfapply g c' i'
                return (r0,r1)