{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RecordWildCards #-}
-- |
-- Module:       $HEADER$
-- Description:  ConnectionPool data type which is a specialized Pool wrapper.
-- Copyright:    (c) 2014-2015 Peter Trško
-- License:      BSD3
--
-- Maintainer:   peter.trsko@gmail.com
-- Stability:    unstable (internal module)
-- Portability:  GHC specific language extensions.
--
-- Internal packages are here to provide access to internal definitions for
-- library writers, but they should not be used in application code.
--
-- Preferably use qualified import, e.g.:
--
-- > import qualified Data.ConnectionPool.Internal.ConnectionPool as Internal
--
-- This module doesn't depend on
-- <http://hackage.haskell.org/package/streaming-commons streaming-commons> and
-- other non-HaskellPlatform packages with exception of two packages
-- <http://hackage.haskell.org/package/resource-pool resource-pool> and
-- <http://hackage.haskell.org/package/between between>. Another notable thing
-- is that this package is not OS specific. Please, bear this in mind when
-- doing modifications.
module Data.ConnectionPool.Internal.ConnectionPool
    (
    -- * Data Type For Building Connection Pools
      ConnectionPool(ConnectionPool, _handlerParams, _resourcePool)
    -- ** Lenses
    , resourcePool
    , handlerParams
    , HasConnectionPool(connectionPool)

    -- * Lifted Resource Pool Operations
    --
    -- | Operations on 'Pool' lifted to work on 'ConnectionPool' data type.
    , createConnectionPool
    , destroyAllConnections
    , withConnection
    , tryWithConnection
    )
  where

import Data.Function ((.))
import Data.Functor (Functor, (<$>))
import Data.Maybe (Maybe)
import Data.Tuple (fst, uncurry)
import Data.Typeable (Typeable)
import GHC.Generics (Generic)
import System.IO (IO)
import Text.Show (Show(showsPrec), showChar, shows, showString)

import Control.Monad.Trans.Control (MonadBaseControl)

import Data.Function.Between.Strict ((~@@^>))
import Data.Pool (Pool)
import qualified Data.Pool as Pool
    ( createPool
    , destroyAllResources
    , tryWithResource
    , withResource
    )

import Data.ConnectionPool.Internal.ResourcePoolParams (ResourcePoolParams)
import qualified Data.ConnectionPool.Internal.ResourcePoolParams
  as ResourcePoolParams (ResourcePoolParams(..))


-- | Simple specialized wrapper for 'Pool'.
--
-- /Definition changed in version 0.1.3 and 0.2./
-- /Instance for 'Generic' introduced in version 0.2./
data ConnectionPool handlerParams connection connectionInfo = ConnectionPool
    { _resourcePool :: !(Pool (connection, connectionInfo))
    -- ^ See 'resourcePool' for details.
    --
    -- /Since version 0.1.3; changed in 0.2./
    , _handlerParams :: !handlerParams
    -- ^ See 'handlerParams' for details.
    --
    -- /Since version 0.1.3./
    }
  deriving (Generic, Typeable)

-- | /Since version 0.1.3./
instance Show handlerParams => Show (ConnectionPool handlerParams c i) where
    showsPrec _ ConnectionPool{..} =
        showString "ConnectionPool {resourcePool = " . shows _resourcePool
        . showString ", handlerParams = " . shows _handlerParams . showChar '}'

-- | Lens for accessing underlying resource pool @'Pool' (connection,
-- connectionInfo)@. Where @connection@ represents network connection and
-- @connectionInfo@ is a protocol specific information associated with the same
-- network connection as the @connection@ is.
--
-- /Since version 0.1.3; changed in 0.2./
resourcePool
    :: Functor f
    => (Pool (c, i) -> f (Pool (c', i')))
    -> ConnectionPool p c i -> f (ConnectionPool p c' i')
resourcePool = _resourcePool ~@@^> \s b -> s{_resourcePool = b}
{-# INLINE resourcePool #-}

-- | Lens for accessing parameters passed down to connection handler. These
-- information will usually be implementation specific. E.g. for
-- <https://hackage.haskell.org/package/streaming-commons streaming-commons> >=
-- 1.13 we use this to pass around read buffer size, for more details see
-- module "Data.ConnectionPool.Internal.HandlerParams".
--
-- /Since version 0.1.3./
handlerParams
    :: Functor f
    => (handlerParams -> f handlerParams')
    -> ConnectionPool handlerParams c i
    -> f (ConnectionPool handlerParams' c i)
handlerParams = _handlerParams ~@@^> \s b -> s{_handlerParams = b}
{-# INLINE handlerParams #-}

-- | Specialized wrapper for 'Pool.createPool', see its documentation for
-- details.
--
-- Definition changed in /version 0.1.3 and version 0.2/.
createConnectionPool
    :: handlerParams
    -- ^ Data type passed down to individual connection handlers.
    --
    -- /Since version 0.1.3./
    -> IO (connection, connectionInfo)
    -- ^ Acquire a connection which is represented by a @connection@. There
    -- might be additional information associated with specific connection that
    -- we pass as a sencond value in a tuple. Such information are considered
    -- read only and aren't passed to release function (see next argument).
    --
    -- /Changed in version 0.2./
    -> (connection -> IO ())
    -- ^ Release a connection which is represented by a @connection@.
    --
    -- /Changed in version 0.2./
    -> ResourcePoolParams
    -- ^ Data type representing all 'Pool.createPool' parameters that describe
    -- internal 'Pool' parameters.
    -> IO (ConnectionPool handlerParams connection connectionInfo)
    -- ^ Created connection pool that is parametrised by additional connection
    -- details.
createConnectionPool hParams acquire release params =
    mkConnectionPool <$> Pool.createPool
        acquire
        (release . fst)
        (ResourcePoolParams._numberOfStripes params)
        (ResourcePoolParams._resourceIdleTimeout params)
        (ResourcePoolParams._numberOfResourcesPerStripe params)
  where
    mkConnectionPool pool = ConnectionPool
        { _resourcePool = pool
        , _handlerParams = hParams
        }
{-# INLINE createConnectionPool #-}

-- | Specialized wrapper for 'Pool.withResource'.
--
-- /Changed in version 0.2./
withConnection
    :: MonadBaseControl IO m
    => ConnectionPool handlerParams connection connectionInfo
    -> (handlerParams -> connection -> connectionInfo -> m r)
    -> m r
withConnection ConnectionPool{..} f =
    Pool.withResource _resourcePool (uncurry (f _handlerParams))
{-# INLINE withConnection #-}

-- | Specialized wrapper for 'Pool.tryWithResource'.
--
-- /Since version 0.2./
tryWithConnection
    :: MonadBaseControl IO m
    => ConnectionPool handlerParams connection connectionInfo
    -> (handlerParams -> connection -> connectionInfo -> m r)
    -> m (Maybe r)
tryWithConnection ConnectionPool{..} f =
    Pool.tryWithResource _resourcePool (uncurry (f _handlerParams))
{-# INLINE tryWithConnection #-}

-- | Destroy all connections that might be still open in a connection pool.
-- This is useful when one needs to release all resources at once and not to
-- wait for idle timeout to be reached.
--
-- For more details see 'Pool.destroyAllResources'.
--
-- /Since version 0.1.1.0./
destroyAllConnections :: ConnectionPool p c i -> IO ()
destroyAllConnections ConnectionPool{_resourcePool} =
    Pool.destroyAllResources _resourcePool
{-# INLINE destroyAllConnections #-}

-- | /Since version 0.2./
class HasConnectionPool p c i s | s -> p, s -> c, s -> i where
    -- | Lens for accessing 'ConnectionPool' wrapped in a data type.
    connectionPool
        :: Functor f
        => (ConnectionPool p c i -> f (ConnectionPool p c i))
        -> s -> f s