{-# LANGUAGE TemplateHaskell #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Control.Effect.Resource
-- Copyright   :  (c) Michael Szvetits, 2020
-- License     :  BSD3 (see the file LICENSE)
-- Maintainer  :  typedbyte@qualified.name
-- Stability   :  stable
-- Portability :  portable
--
-- The resource effect allows a computation to allocate resources which are
-- guaranteed to be released after their usage.
-----------------------------------------------------------------------------
module Control.Effect.Resource
  ( -- * Tagged Resource Effect
    Resource'(..)
    -- * Untagged Resource Effect
    -- | If you don't require disambiguation of multiple resource effects
    -- (i.e., you only have one resource effect in your monadic context),
    -- it is recommended to always use the untagged resource effect.
  , Resource
  , bracket
  , bracketOnError
    -- * Convenience Functions
    -- | If you don't require disambiguation of multiple resource effects
    -- (i.e., you only have one resource effect in your monadic context),
    -- it is recommended to always use the untagged functions.
  , finally'
  , finally
  , onException'
  , onException
    -- * Interpretations
  , LowerIO
  , runResourceIO'
  , runResourceIO
    -- * Tagging and Untagging
    -- | Conversion functions between the tagged and untagged resource effect,
    -- usually used in combination with type applications, like:
    --
    -- @
    --     'tagResource'' \@\"newTag\" program
    --     'retagResource'' \@\"oldTag\" \@\"newTag\" program
    --     'untagResource'' \@\"erasedTag\" program
    -- @
    -- 
  , tagResource'
  , retagResource'
  , untagResource'
  ) where

-- base
import qualified Control.Exception as IO
import Data.Coerce (coerce)

import Control.Effect.Machinery

-- | An effect that allows a computation to allocate resources which are
-- guaranteed to be released after their usage.
class Monad m => Resource' tag m where
  -- | Acquire a resource, use it, and then release the resource after usage.
  bracket' :: m a        -- ^ The computation which acquires the resource.
           -> (a -> m c) -- ^ The computation which releases the resource.
           -> (a -> m b) -- ^ The computation which uses the resource.
           -> m b        -- ^ The result of the computation which used the resource.

  -- | Like 'bracket'', but only performs the release computation if the usage
  -- computation throws an exception.
  bracketOnError' :: m a        -- ^ The computation which acquires the resource.
                  -> (a -> m c) -- ^ The computation which releases the resource.
                  -> (a -> m b) -- ^ The computation which uses the resource.
                  -> m b        -- ^ The result of the computation which used the resource.

makeTaggedEffect ''Resource'

-- | A simpler version of 'bracket'' where one computation is guaranteed to
-- run after another.
finally' :: forall tag m a b. Resource' tag m
         => m a -- ^ The computation to run.
         -> m b -- ^ The computation to run afterwards, even if the first
                --   computation throws an exception.
         -> m a -- ^ The result of the first computation.
finally' use free =
  bracket' @tag (pure ()) (pure free) (const use)
{-# INLINE finally' #-}

-- | The untagged version of 'finally''.
finally :: Resource m => m a -> m b -> m a
finally = finally' @G
{-# INLINE finally #-}

-- | A simpler version of 'bracketOnError'' where one computation is guaranteed
-- to run after another in case the first computation throws an exception.
onException' :: forall tag m a b. Resource' tag m
             => m a -- ^ The computation to run.
             -> m b -- ^ The computation to run afterwards, only if the first
                    --   computation throws an exception.
             -> m a -- ^ The result of the first computation.
onException' use free =
  bracketOnError' @tag (pure ()) (const free) (const use)
{-# INLINE onException' #-}

-- | The untagged version of 'onException''.
onException :: Resource m => m a -> m b -> m a
onException = onException' @G
{-# INLINE onException #-}

-- | The IO-based interpreter of the resource effect. This type implements the
-- 'Resource'' type class by using 'IO.bracket', thus requiring 'IO' at the bottom
-- of the monad transformer stack.
--
-- When interpreting the effect, you usually don\'t interact with this type directly,
-- but instead use one of its corresponding interpretation functions.
newtype LowerIO m a =
  LowerIO { _runLowerIO :: m a }
    deriving (Applicative, Functor, Monad, MonadIO)
    deriving (MonadTrans, MonadTransControl) via IdentityT
    deriving (MonadBase b, MonadBaseControl b)

instance MonadBaseControl IO m => Resource' tag (LowerIO m) where
  bracket' alloc free use =
    control $ \run ->
      IO.bracket
        ( run alloc )
        ( \a -> run (restoreM a >>= free) )
        ( \a -> run (restoreM a >>= use) )
  {-# INLINABLE bracket' #-}
  bracketOnError' alloc free use =
    control $ \run ->
      IO.bracketOnError
        ( run alloc )
        ( \a -> run (restoreM a >>= free) )
        ( \a -> run (restoreM a >>= use) )
  {-# INLINABLE bracketOnError' #-}

-- | Runs the resource effect using 'IO.bracket'.
runResourceIO' :: (Resource' tag `Via` LowerIO) m a -> m a
runResourceIO' = coerce
{-# INLINE runResourceIO' #-}

-- | The untagged version of 'runResourceIO''.
runResourceIO :: (Resource `Via` LowerIO) m a -> m a
runResourceIO = coerce
{-# INLINE runResourceIO #-}