{-# LANGUAGE Safe #-}

{- |
Module                  : Relude.Bool.Guard
Copyright               : (c) 2016 Stephen Diehl
                          (c) 2016-2018 Serokell
                          (c) 2018-2023 Kowainik
SPDX-License-Identifier : MIT
Maintainer              : Kowainik <xrom.xkov@gmail.com>
Stability               : Stable
Portability             : Portable

Monadic boolean combinators.
-}

module Relude.Bool.Guard
    ( guarded
    , guardM
    , ifM
    , unlessM
    , whenM
    , (&&^)
    , (||^)
    ) where

import Relude.Applicative (Alternative, Applicative (..), empty)
import Relude.Bool.Reexport (Bool (..), guard, unless, when)
import Relude.Function (flip)
import Relude.Monad (Monad, MonadPlus, (>>=))


-- $setup
-- >>> import Relude.Applicative (pure)
-- >>> import Relude.Bool.Reexport (Bool (..))
-- >>> import Relude.Debug (error)
-- >>> import Relude.Function (($))
-- >>> import Relude.Monad (Maybe (..))
-- >>> import Relude.Print (putTextLn)
-- >>> import Relude (Int, String, even, const)

{- | Monadic version of 'when'.
Conditionally executes the provided action.

>>> whenM (pure False) $ putTextLn "No text :("
>>> whenM (pure True)  $ putTextLn "Yes text :)"
Yes text :)
>>> whenM (Just True) (pure ())
Just ()
>>> whenM (Just False) (pure ())
Just ()
>>> whenM Nothing (pure ())
Nothing
-}
whenM :: Monad m => m Bool -> m () -> m ()
whenM p m = p >>= flip when m
{-# INLINE whenM #-}

{- | Monadic version of 'unless'. Reverse of 'whenM'.
Conditionally don't execute the provided action.

>>> unlessM (pure False) $ putTextLn "No text :("
No text :(
>>> unlessM (pure True) $ putTextLn "Yes text :)"
-}
unlessM :: Monad m => m Bool -> m () -> m ()
unlessM p m = p >>= flip unless m
{-# INLINE unlessM #-}

{- | Monadic version of @if-then-else@.

>>> ifM (pure True) (putTextLn "True text") (putTextLn "False text")
True text
>>> ifM (pure False) (putTextLn "True text") (putTextLn "False text")
False text
-}
ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM p x y = p >>= \b -> if b then x else y
{-# INLINE ifM #-}

{- | Monadic version of 'guard' that help to check that a condition ('Bool')
holds inside. Works with 'Monad's that are also 'Alternative'.

>>> guardM (Just True)
Just ()
>>> guardM (Just False)
Nothing
>>> guardM Nothing
Nothing

Here some complex but real-life example:

@
findSomePath :: IO (Maybe FilePath)

somePath :: MaybeT IO FilePath
somePath = do
    path <- MaybeT findSomePath
    guardM $ liftIO $ doesDirectoryExist path
    return path
@
-}
guardM :: MonadPlus m => m Bool -> m ()
guardM f = f >>= guard
{-# INLINE guardM #-}

{- | Either lifts a value into an alternative context or gives a
minimal value depending on a predicate. Works with 'Alternative's.

>>> guarded even 3 :: [Int]
[]
>>> guarded even 2 :: [Int]
[2]
>>> guarded (const True) "hello" :: Maybe String
Just "hello"
>>> guarded (const False) "world" :: Maybe String
Nothing

You can use this function to implement smart constructors simpler:

@
__newtype__ HttpHost = HttpHost
    { unHttpHost :: Text
    }

mkHttpHost :: Text -> Maybe HttpHost
mkHttpHost host = HttpHost \<$\> 'guarded' (not . Text.null) host
@

@since 0.6.0.0
-}
guarded :: Alternative f => (a -> Bool) -> a -> f a
guarded p a = if p a then pure a else empty
{-# INLINE guarded #-}

{- | Monadic version of '(Data.Bool.&&)' operator.

It is lazy by the second argument (similar to '(Data.Bool.||)'), meaning that if
the first argument is 'False', the function will return 'False' without evaluating
the second argument.

>>> Just False &&^ Just True
Just False
>>> Just True &&^ Just True
Just True
>>> Just True &&^ Nothing
Nothing
>>> Just False &&^ Nothing
Just False
>>> Just False &&^ error "Shouldn't be evaluated"
Just False

@since 0.4.0
-}
(&&^) :: Monad m => m Bool -> m Bool -> m Bool
(&&^) e1 e2 = ifM e1 e2 (pure False)
{-# INLINE (&&^) #-}

{- | Monadic version of '(Data.Bool.||)' operator.

It is lazy by the second argument (similar to '(Data.Bool.||)'), meaning that if
the first argument is 'True', the function will return 'True' without evaluating
the second argument.

>>> Just False ||^ Just True
Just True
>>> Just False ||^ Just False
Just False
>>> Just False ||^ Nothing
Nothing
>>> Just True ||^ Nothing
Just True
>>> Just True ||^ error "Shouldn't be evaluated"
Just True

@since 0.4.0
-}
(||^) :: Monad m => m Bool -> m Bool -> m Bool
e1 ||^ e2 = ifM e1 (pure True) e2
{-# INLINE (||^) #-}
