module Control.InjFun (
    -- * Inject function
    InjFun
  , cfapply
  , inject
    -- * Exploding and merging
  , explode
  , merge
    -- * Combinators
  , (||->)
  , (|->)
  , (-<)
  , (-<|)
  , (>-)
  , (>-|)
  ) 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 c0 i0 m o0                -- ^ First function
                   -> InjFun c1 i1 m o1                -- ^ Second function
                   -> InjFun (c0,c1) (i0,i1) m (o0,o1) -- ^ Merged function
merge f g = fg
  where fg = InjFun $ \(c0,c1) (i0,i1) -> do
                r0 <- cfapply f c0 i0
                r1 <- cfapply g c1 i1
                return (r0,r1)

-- |Explode an `InjFun` and feed two other ones with exploded parts of it.
--
-- In that version, each of the three functions has its own inject parameter.
(-<) :: (Monad m) => InjFun c i m (o0,o1)                           -- ^ Function to explode
                  -> (InjFun c' o0 m o0',InjFun c'' o1 m o1')       -- ^ Functions to feed
                  -> (InjFun (c,c') i m o0',InjFun (c,c'') i m o1') -- ^ Exploded and fed functions
f -< (g,h) = (g',h')
  where g'       = f'  ||-> g
        h'       = f'' ||-> h
        (f',f'') = explode f

-- |Explode an `InjFun` and feed two other ones with exploded parts of it.
--
-- In that version, all the three functions share the same inject parameter.
(-<|) :: (Monad m) => InjFun c i m (o0,o1)                  -- ^ Function to explode
                   -> (InjFun c o0 m o0',InjFun c o1 m o1') -- ^ Functions to feed
                   -> (InjFun c i m o0',InjFun c i m o1')   -- ^ Exploded and fed functions
f -<| (g,h) = (g',h')
  where g'       = f'  |-> g 
        h'       = f'' |-> h
        (f',f'') = explode f

-- |Merge two `InjFun` and feed another one with the merged function.
--
-- In that version, each of the three functions has it its own inject parameter.
(>-) :: (Monad m) => (InjFun c0 i0 m o0,InjFun c1 i1 m o1) -- ^ Functions to merge
                  -> InjFun c2 (o0,o1) m o'                -- ^ Function to feed
                  -> InjFun (c0,c1,c2) (i0,i1) m o'        -- ^ Merged and fed function
(g,h) >- f = inject $ \(c0,c1,c2) i -> cfapply (merge g h) (c0,c1) i >>= cfapply f c2

-- |Merge two `InjFun` and feed another one with the merged function.
--
-- In that version, all the three functions share the same inject parameter.
(>-|) :: (Monad m) => (InjFun c i0 m o0,InjFun c i1 m o1) -- ^ Functions to merge
                   -> InjFun c (o0,o1) m o'               -- ^ Function to feed
                   -> InjFun c (i0,i1) m o'               -- ^ Merged and fed function
(g,h) >-| f = inject $ \c i -> cfapply (merge g h) (c,c) i >>= cfapply f c