{-# LANGUAGE NoImplicitPrelude #-}
{-|
Module      : Control.Monad.Component
Description : Sane resource allocation library for complex applications
Copyright   : (c) Roman Gonzalez, 2017-2018
License     : MIT
Maintainer  : open-source@roman-gonzalez.info
Stability   : experimental

= Why use 'ComponentM'?

'ComponentM' values wraps vanilla 'IO' sub-routines whose responsibility is to
allocate resources that your application may need (e.g. database connections,
tcp sockets, etc). Your program will execute these 'ComponentM' sub-routines at
the beginning of it's lifecyle, building an environment that your main
application needs in order to work as intended.

By using 'ComponentM' sub-routines your program will automatically:

* Compose the cleanup sub-routines of all your allocated resources

* Keep track of initialization time for each resource needed in your application

* Keep track of teardown time for each resources needed in your application.

* Isolate the teardown of each resource in your application, ensuring no thrown
  exception will affect the cleanup of resources.

* Initialize resources concurrently when using 'Applicative' notation

* Build a dependency graph of your application resources when using
  'Applicative' or 'Monad' notation; and then guarantees the execution of
  cleanup operations in a topological sorted order

* Make sure that previously allocated resources are cleaned up when a resource
  throws an exception on initialization

* Report all exceptions thrown on each resource teardown

* Document (through types) what is the purpose of some of the 'IO' sub-routines
  in your program

These properties are crucial when applications need to run for long periods of
time and they are reloaded (without a process restart). It also ensures that
resources are cleaned tightly when doing REPL driven development through GHCi.

-}
module Control.Monad.Component
  (

  -- * How to build 'ComponentM' values
  -- $howto_componentm_values
    buildComponent
  , buildComponent_

  -- * Making 'ComponentM' values useful
  -- $monad_and_runners
  , ComponentM
  , runComponentM
  , runComponentM1

  -- * Error Records
  -- $errors
  , ComponentError (..)
  , ComponentBuildError (..)

  -- * 'ComponentM' tracing accessors
  , ComponentEvent (..)
  , Build
  , buildElapsedTime
  , buildFailure
  , BuildResult
  , toBuildList


  -- * Re-exports
  , TeardownResult (..)
  , throwM

  ) where

import           Control.Monad.Catch                    (throwM)
import           Control.Monad.Component.Internal.Core  (buildComponent,
                                                         buildComponent_,
                                                         runComponentM,
                                                         runComponentM1)
import           Control.Monad.Component.Internal.Types (Build (..),
                                                         BuildResult (..),
                                                         ComponentBuildError (..),
                                                         ComponentError (..),
                                                         ComponentEvent (..),
                                                         ComponentM)
import           Control.Teardown                       (TeardownResult (..))

{- $howto_componentm_values

'ComponentM' values are built from vanilla 'IO' sub-routines that allocate resources, the
two functions provided are:

['buildComponent_']: Used when a component in your application does not allocate
  a resource

['buildComponent']: Used when a component in your application allocates a
  resource and requires a cleanup on teardown

Following is an example on how to

@
{-# LANGUAGE PackageImports #-}
import "sqlite-simple" qualified Database.SQLite.Simple as SQLite
import "componentm" Control.Monad.Component (ComponentM, buildComponent, buildComponent_)

-- | App environment record
data AppEnv = AppEnv { appDb :: !SQLite.Connection }

-- | Configuration record
data Config = Config { dbPath :: !String }

readConfig :: IO Config
readConfig =
  -- NOTE: Here we would have a more sophisticated algorithm for fetching
  -- configuration values for our app
  return (Config ":memory:")

configComponent :: ComponentM AppConfig
configComponent = buildComponent_ "Config" $ do
  readConfigFile "./resources/config.yml"

dbComponent :: FilePath -> ComponentM SQLite.Connection
dbComponent dbPath =
  buildComponent "Database" (SQLite.open dbPath) SQLite.close

buildAppEnv :: ComponentM AppEnv
buildAppEnv = do
  config <- configComponent
  AppEnv <$> dbComponent (dbPath config)

@

In the previous example, we use both 'buildComponent_' and 'buildComponent' to
create different components that our application needs.

-}

{- $monad_and_runers

To execute our 'ComponentM' sub-routines, we can use two different functions
'runComponentM' or 'runComponentM1'; following is an example:


@
appMain :: AppEnv -> IO ()
appMain = error "pending"

main :: IO ()
main =
  runComponentM1
    -- Our logging function
    print
    -- The name of our application
    "my-fancy-application"
    -- The 'ComponentM' sub-routine that builds the 'AppEnv'
    buildAppEnv
    -- Our main application runs here
    appMain
@

-}

{- $errors

There are two possible failures that the 'runComponentM' functions can thrown

['ComponentBuildFailed']: This error happens when allocation of some component's
resource fails

['ComponentRuntimeFailed']: This error happens when there is an exception thrown
from our main application callback

-}