module Control.Effect.NonDet
  ( -- * Effects
    NonDet(..)
  , Cull(..)
  , Cut(..)
  , Split(..)
  , Logic

    -- * Actions
  , choose
  , lose
  , fromList

  , cull

  , cutfail
  , cut
  , call

  , split

    -- * Interpretations
  , runNonDet
  , runNonDet1

  , runCullCut

  , runLogic

    -- * Threading constraints
  , NonDetThreads

    -- * Carriers
  , NonDetC
  , CullCutC
  , LogicC
  ) where

import Control.Monad

import Control.Effect
import Control.Effect.Internal.NonDet
import Control.Effect.Internal.Utils

import Control.Effect.Type.Split

-- | Introduce new branches stemming from the current one using a list of values.
fromList :: Eff NonDet m => [a] -> m a
fromList = send .# FromList

-- | Introduce two new branches stemming from the current one.
choose :: Eff NonDet m => m a -> m a -> m a
choose ma mb = join $ fromList [ma, mb]
{-# INLINE choose #-}

-- | Fail the current branch and proceed to the next branch,
-- backtracking to the nearest use of 'choose'/'fromList' that
-- still has unprocessed branches.
lose :: Eff NonDet m => m a
lose = fromList []
{-# INLINE lose #-}

-- | Cull nondeterminism in the argument, limiting the number of branches
-- it may introduce to be at most 1.
--
-- @'cull' (return True `'choose'` return False) == return True@
--
-- @'cull' ('lose' `'choose'` return False) == return False@
cull :: Eff Cull m => m a -> m a
cull = send .# Cull
{-# INLINE cull #-}

-- | Fail the current branch, and prevent backtracking up until the nearest
-- enclosing use of 'call' (if any).
--
-- @'cutfail' `'choose'` m == 'cutfail'@
cutfail :: Eff Cut m => m a
cutfail = send Cutfail
{-# INLINE cutfail #-}

-- | Commit to the current branch: prevent all backtracking that would move
-- execution to before 'cut' was invoked, up until the nearest enclosing use
-- of 'call' (if any).
--
-- @'call' ('fromList' [1,2] >>= \\a -> 'cut' >> fromList [a,a+3]) == 'fromList' [1,4]@
--
-- @ call (('cut' >> return True) `choose` return False) == return True@
cut :: Effs '[NonDet, Cut] m => m ()
cut = pure () `choose` cutfail
{-# INLINE cut #-}

-- | Delimit the prevention of backtracking from uses of 'cut' and 'cutfail'.
--
-- @'call' 'cutfail' `'choose'` m = m@
call :: Eff Cut m => m a -> m a
call = send . Call
{-# INLINE call #-}

-- | Split a nondeterministic computation into its first result
-- and the rest of the computation, if possible.
--
-- Note that @'split' 'cutfail' == 'cutfail'@. If you don't want that behavior,
-- use @'split' ('call' m)@ instead of @'split' m@.
split :: Eff Split m => m a -> m (Maybe (a, m a))
split = send . Split id
{-# INLINE split #-}