{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE Safe #-}
-- The following is needed to define MonadPlus instance. It is decidable
-- (there is no recursion!), but GHC cannot see that.
{-# LANGUAGE UndecidableInstances #-}

-- | Nondeterministic choice effect
module Control.Eff.Choose ( Choose (..)
                          , withChoose
                          , choose
                          , makeChoice
                          , mzero'
                          , mplus'
                          , module Control.Eff.Logic
                          ) where

import Control.Eff
import Control.Eff.Extend
import Control.Eff.Logic

import Control.Applicative
import Control.Monad
import Control.Monad.Base
import Control.Monad.Trans.Control

-- ------------------------------------------------------------------------
-- | Non-determinism (choice)
--
-- choose lst non-deterministically chooses one value from the lst
-- choose [] thus corresponds to failure
-- Unlike Reader, Choose is not a GADT because the type of values
-- returned in response to a (Choose a) request is just a, without
-- any constraints.
newtype Choose a = Choose [a]

-- | Embed a pure value
withChoose :: Monad m => a -> m [a]
withChoose = return . (:[])
-- | Given a continuation and a Choose request, respond to it.
instance Monad m => Handle Choose (m [a]) where
  handle _ (Choose []) = return []
  handle k (Choose [x]) = k x
  handle k (Choose lst) = fmap concat $ mapM k lst

instance ( MonadBase m m
         , LiftedBase m r
         ) => MonadBaseControl m (Eff (Choose ': r)) where
    type StM (Eff (Choose ': r)) a = StM (Eff r) [a]
    liftBaseWith f = raise $ liftBaseWith $ \runInBase ->
                       f (runInBase . makeChoice)
    restoreM x = do lst <- raise (restoreM x)
                    choose lst

-- | choose lst non-deterministically chooses one value from the lst
-- choose [] thus corresponds to failure
choose :: Member Choose r => [a] -> Eff r a
choose lst = send $ Choose lst

-- | MonadPlus-like operators are expressible via choose
mzero' :: Member Choose r => Eff r a
mzero' = choose []

-- | MonadPlus-like operators are expressible via choose
mplus' :: Member Choose r => Eff r a -> Eff r a -> Eff r a
mplus' m1 m2 = join $ choose [m1,m2]

-- | MonadPlus-like operators are expressible via choose
instance Member Choose r => Alternative (Eff r) where
  empty = mzero'
  (<|>) = mplus'

instance Member Choose r => MonadPlus (Eff r) where
  mzero = empty
  mplus = (<|>)

-- | Run a nondeterministic effect, returning all values.
makeChoice :: forall a r. Eff (Choose ': r) a -> Eff r [a]
makeChoice = handle_relay withChoose

instance Member Choose r => MSplit (Eff r) where
  msplit = respond_relay (flip withMSplit empty)
           (\k (Choose lst) -> hdl k lst)
    where
      hdl :: Arr r v (Maybe (a, Eff r a))
          -> [v] -> Eff r (Maybe (a, Eff r a))
      hdl _ [] = return Nothing             -- definite failure
      hdl k (h:t) = k h >>= \r -> case r of -- possibility
        Nothing -> hdl k t                  -- failure, continue exploring
        Just (a, m) -> withMSplit a (m <|> (hdl k t >>= reflect)) -- definite success