{-# LANGUAGE TemplateHaskell #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Control.Effect.Managed
-- Copyright   :  (c) Michael Szvetits, 2020
-- License     :  BSD3 (see the file LICENSE)
-- Maintainer  :  typedbyte@qualified.name
-- Stability   :  stable
-- Portability :  portable
--
-- The managed effect allows a computation to allocate resources which are
-- guaranteed to be released after the end of the computation. This effect
-- provides a monadic interface for managing one or more long-living
-- resources in a more readable way than nesting 'IO.bracket'-style
-- operations of the "Control.Effect.Resource" effect.
-----------------------------------------------------------------------------
module Control.Effect.Managed
  ( -- * Tagged Managed Effect
    Managed'(..)
    -- * Untagged Managed Effect
    -- | If you don't require disambiguation of multiple managed effects
    -- (i.e., you only have one managed effect in your monadic context),
    -- it is recommended to always use the untagged managed effect.
  , Managed
  , manage
    -- * Interpretations
  , Bracket
  , runManaged'
  , runManaged
    -- * Tagging and Untagging
    -- | Conversion functions between the tagged and untagged managed effect,
    -- usually used in combination with type applications, like:
    --
    -- @
    --     'tagManaged'' \@\"newTag\" program
    --     'retagManaged'' \@\"oldTag\" \@\"newTag\" program
    --     'untagManaged'' \@\"erasedTag\" program
    -- @
    -- 
  , tagManaged'
  , retagManaged'
  , untagManaged'
  ) where

-- base
import qualified Control.Exception as IO
import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)

-- transformers
import Control.Monad.Trans.Reader (ReaderT(ReaderT), runReaderT)

import Control.Effect.Machinery

-- | An effect that allows a computation to allocate resources which are
-- guaranteed to be released after the computation.
--
-- @since 0.3.0.0
class Monad m => Managed' tag m where
  -- | Acquire a resource by specifying an acquisition action and a release
  -- action to be used for cleanup after the computation.
  --
  -- @since 0.3.0.0
  manage' :: m a        -- ^ The computation which acquires the resource.
          -> (a -> m b) -- ^ The computation which releases the resource.
          -> m a        -- ^ The acquired resource.

makeTaggedEffect ''Managed'

-- | The bracket-based interpreter of the managed effect. This type implements
-- the 'Managed'' 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.
--
-- @since 0.3.0.0
newtype Bracket n m a = Bracket { runBracket :: ReaderT (IORef [n ()]) m a }
  deriving (Applicative, Functor, Monad, MonadIO)
  deriving (MonadTrans, MonadTransControl)
  deriving (MonadBase b, MonadBaseControl b)

instance MonadBase IO m => Managed' tag (Bracket m m) where
  manage' alloc free = Bracket . ReaderT $
    \ref -> do
      a <- runReaderT (runBracket alloc) ref
      liftBase $
        atomicModifyIORef' ref $
          \frees -> (runReaderT (runBracket (free a >> pure ())) ref : frees, ())
      pure a
  {-# INLINE manage' #-}

-- | Runs the managed effect using 'IO.bracket'.
--
-- @since 0.3.0.0
runManaged' :: forall tag m a. MonadBaseControl IO m => (Managed' tag `Via` Bracket m) m a -> m a
runManaged' program =
  liftedBracket
    ( allocRef )
    ( freeRef  )
    ( runReaderT (runBracket (runVia program)) )
  where
    allocRef :: forall n. MonadBase IO n => n (IORef [n ()])
    allocRef = liftBase $ newIORef []

    freeRef :: forall n. MonadBase IO n => IORef [n ()] -> n ()
    freeRef = (sequence_ =<<) . liftBase . readIORef

    liftedBracket :: forall n b c d. MonadBaseControl IO n => n b -> (b -> n c) -> (b -> n d) -> n d
    liftedBracket alloc free use =
      control $ \run ->
        IO.bracket
          ( run alloc )
          ( \a -> run (restoreM a >>= free) )
          ( \a -> run (restoreM a >>= use) )
{-# INLINE runManaged' #-}

-- | The untagged version of 'runManaged''.
--
-- @since 0.3.0.0
runManaged :: MonadBaseControl IO m => (Managed `Via` Bracket m) m a -> m a
runManaged = runManaged' @G
{-# INLINE runManaged #-}