{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Context.Implicit
  ( -- * Introduction
    -- $intro

    -- * Storage
    Store
  , withNonEmptyStore
  , withEmptyStore

    -- * Operations
    -- ** Registering context
  , use
  , adjust
  , withAdjusted

    -- ** Asking for context
  , mine
  , mines

  , mineMay
  , minesMay

    -- * Views
  , module Context.View

    -- * Exceptions
  , NotFoundException(NotFoundException, threadId)

    -- * Concurrency
  , module Context.Concurrent

    -- * Lower-level storage
  , module Context.Storage
  ) where

import Context
  ( NotFoundException(NotFoundException, threadId), Store, withEmptyStore, withNonEmptyStore
  )
import Context.Concurrent
import Context.Storage
import Context.View
import Control.Monad.Catch (MonadMask, MonadThrow)
import Control.Monad.IO.Class (MonadIO)
import Prelude
import qualified Context

-- | Register a context in the implicit 'Store' on behalf of the calling
-- thread, for the duration of the specified action.
--
-- @since 0.1.0.0
use
  :: forall m ctx a
   . (MonadIO m, MonadMask m, ?contextStore :: Store ctx)
  => ctx
  -> m a
  -> m a
use :: forall (m :: * -> *) ctx a.
(MonadIO m, MonadMask m, ?contextStore::Store ctx) =>
ctx -> m a -> m a
use = Store ctx -> ctx -> m a -> m a
forall (m :: * -> *) ctx a.
(MonadIO m, MonadMask m) =>
Store ctx -> ctx -> m a -> m a
Context.use ?contextStore::Store ctx
Store ctx
?contextStore

-- | Adjust the calling thread's context in the implicit 'Store' for the
-- duration of the specified action. Throws a 'NotFoundException' when the
-- calling thread has no registered context.
--
-- @since 0.1.0.0
adjust
  :: forall m ctx a
   . (MonadIO m, MonadMask m, ?contextStore :: Store ctx)
  => (ctx -> ctx)
  -> m a
  -> m a
adjust :: forall (m :: * -> *) ctx a.
(MonadIO m, MonadMask m, ?contextStore::Store ctx) =>
(ctx -> ctx) -> m a -> m a
adjust = Store ctx -> (ctx -> ctx) -> m a -> m a
forall (m :: * -> *) ctx a.
(MonadIO m, MonadMask m) =>
Store ctx -> (ctx -> ctx) -> m a -> m a
Context.adjust ?contextStore::Store ctx
Store ctx
?contextStore

-- | Convenience function to 'adjust' the context then supply the adjusted
-- context to the inner action. This function is equivalent to calling 'adjust'
-- and then immediately calling 'mine' in the inner action of 'adjust', e.g.:
--
-- > doStuff :: Store Thing -> (Thing -> Thing) -> IO ()
-- > doStuff store f = do
-- >   adjust store f do
-- >     adjustedThing <- mine store
-- >     ...
--
-- Throws a 'NotFoundException' when the calling thread has no registered
-- context.
--
-- @since 0.2.0.0
withAdjusted
  :: forall m ctx a
   . (MonadIO m, MonadMask m, ?contextStore :: Store ctx)
  => (ctx -> ctx)
  -> (ctx -> m a)
  -> m a
withAdjusted :: forall (m :: * -> *) ctx a.
(MonadIO m, MonadMask m, ?contextStore::Store ctx) =>
(ctx -> ctx) -> (ctx -> m a) -> m a
withAdjusted = Store ctx -> (ctx -> ctx) -> (ctx -> m a) -> m a
forall (m :: * -> *) ctx a.
(MonadIO m, MonadMask m) =>
Store ctx -> (ctx -> ctx) -> (ctx -> m a) -> m a
Context.withAdjusted ?contextStore::Store ctx
Store ctx
?contextStore

-- | Provide the calling thread its current context from the implicit
-- 'Store'. Throws a 'NotFoundException' when the calling thread has no
-- registered context.
--
-- @since 0.1.0.0
mine
  :: forall m ctx
   . (MonadIO m, MonadThrow m, ?contextStore :: Store ctx)
  => m ctx
mine :: forall (m :: * -> *) ctx.
(MonadIO m, MonadThrow m, ?contextStore::Store ctx) =>
m ctx
mine = Store ctx -> m ctx
forall (m :: * -> *) ctx.
(MonadIO m, MonadThrow m) =>
Store ctx -> m ctx
Context.mine ?contextStore::Store ctx
Store ctx
?contextStore

-- | Provide the calling thread a selection from its current context in the
-- implicit 'Store'. Throws a 'NotFoundException' when the calling
-- thread has no registered context.
--
-- @since 0.1.0.0
mines
  :: forall m ctx a
   . (MonadIO m, MonadThrow m, ?contextStore :: Store ctx)
  => (ctx -> a)
  -> m a
mines :: forall (m :: * -> *) ctx a.
(MonadIO m, MonadThrow m, ?contextStore::Store ctx) =>
(ctx -> a) -> m a
mines = Store ctx -> (ctx -> a) -> m a
forall (m :: * -> *) ctx a.
(MonadIO m, MonadThrow m) =>
Store ctx -> (ctx -> a) -> m a
Context.mines ?contextStore::Store ctx
Store ctx
?contextStore

-- | Provide the calling thread its current context from the implicit
-- 'Store', if present.
--
-- @since 0.1.0.0
mineMay
  :: forall m ctx
   . (MonadIO m, ?contextStore :: Store ctx)
  => m (Maybe ctx)
mineMay :: forall (m :: * -> *) ctx.
(MonadIO m, ?contextStore::Store ctx) =>
m (Maybe ctx)
mineMay = Store ctx -> m (Maybe ctx)
forall (m :: * -> *) ctx. MonadIO m => Store ctx -> m (Maybe ctx)
Context.mineMay ?contextStore::Store ctx
Store ctx
?contextStore

-- | Provide the calling thread a selection from its current context in the
-- implicit 'Store', if present.
--
-- @since 0.1.0.0
minesMay
  :: forall m ctx a
   . (MonadIO m, ?contextStore :: Store ctx)
  => (ctx -> a)
  -> m (Maybe a)
minesMay :: forall (m :: * -> *) ctx a.
(MonadIO m, ?contextStore::Store ctx) =>
(ctx -> a) -> m (Maybe a)
minesMay = Store ctx -> (ctx -> a) -> m (Maybe a)
forall (m :: * -> *) ctx a.
MonadIO m =>
Store ctx -> (ctx -> a) -> m (Maybe a)
Context.minesMay ?contextStore::Store ctx
Store ctx
?contextStore

-- $intro
--
-- This module provides the same interface provided by "Context", but uses an
-- implicit 'Store' where applicable. Usage of this module requires that the
-- implicit parameter is named @contextStore@ in calling code.
--
-- This module is designed to be imported qualified:
--
-- > import qualified Context.Implicit
--
-- If you are only working with an implicit 'Store' in your application, you may
-- prefer shortening the import name:
--
-- > import qualified Context.Implicit as Context
--
-- Usage of this module might look something like this:
--
-- > main :: IO ()
-- > main = do
-- >   let config = -- ...
-- >   withDependencies config \deps -> do
-- >     Context.withNonEmptyStore deps \depsStore -> do
-- >       let ?contextStore = depsStore
-- >       doStuff
-- >
-- > doStuff :: (?contextStore :: Store Dependencies) => IO ()
-- > doStuff = do
-- >   deps <- Context.mine
-- >   -- ...
--
-- If the application is using a 'Store' in a global variable, then this 'Store'
-- can be conveniently injected in for use with this module via a global
-- implicit parameter:
--
-- > module Dependencies where
-- >
-- > import qualified Context.Implicit as Context
-- > import GHC.Classes(IP(ip)) -- from 'ghc-prim' package
-- >
-- > data Dependencies = -- ...
-- >
-- > depsStore :: Store Dependencies
-- > depsStore = unsafePerformIO $ Context.newStore Context.defaultPropagation Nothing
-- > {-# NOINLINE depsStore #-}
-- >
-- > instance IP "contextStore" (Store Dependencies) where
-- >   ip = depsStore
--
-- With a global implicit parameter, the @(?contextStore :: Store Dependencies)@
-- constraint does not need to be threaded throughout the application's
-- signatures, but it still can be overridden within local scopes as needed.
--
-- For an intro to global implicit parameters, see this post:
-- <https://kcsongor.github.io/global-implicit-parameters/>