{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
-- |
-- Module:       $HEADER$
-- Description:  Helper functions that aren't provided by streaming-commons.
-- Copyright:    (c) 2014-2016 Peter Trško
-- License:      BSD3
--
-- Maintainer:   peter.trsko@gmail.com
-- Stability:    unstable (internal module)
-- Portability:  GHC specific language extensions.
--
-- Module defines helper functions that would be ideally provided by
-- <http://hackage.haskell.org/package/streaming-commons streaming-commons>
-- package and some wrappers with specialised type signatures.
--
-- 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.Streaming as Internal
--
-- This module doesn't neither depend on
-- <http://hackage.haskell.org/package/resource-pool resource-pool> package nor
-- any other module of this package, with notable exception of
-- "Data.ConnectionPool.Internal.HandlerParams", and it shoud stay that way.
-- This module uses CPP to get OS specific things right. Most importantly
-- Windows doesn't support UNIX Sockets.
--
-- Please, bear above in mind when doing modifications.
module Data.ConnectionPool.Internal.Streaming
    (
    -- * TCP
      acquireTcpClientConnection
    , runTcpApp
    , runTcpAppImpl
    , fromClientSettings

#ifndef WINDOWS
    -- Windows doesn't support UNIX Sockets.

    -- * Unix Socket
    , runUnixApp
    , runUnixAppImpl
    , fromClientSettingsUnix
#endif
    -- !WINDOWS

    -- * Socket
    , close
    )
  where

import Data.Int (Int)
import Data.Maybe (Maybe(Just))
import System.IO (IO)

import Network.Socket (Socket, SockAddr)
import qualified Network.Socket as Socket
#if MIN_VERSION_network(2,4,0)
    ( close
#else
    ( sClose
#endif
    )
import Network.Socket.ByteString (sendAll)

import Data.Default.Class (Default(def))
import Data.Streaming.Network
    ( getSocketFamilyTCP
    , safeRecv
#if MIN_VERSION_streaming_commons(0,1,13)
    -- Until streaming-commons 0.1.13, read buffer size was fixed.
    , getReadBufferSize
#endif
    )
import Data.Streaming.Network.Internal
    ( AppData(AppData)
    , ClientSettings(clientPort, clientHost, clientAddrFamily)
#ifndef WINDOWS
    -- Windows doesn't support UNIX Sockets.
    , AppDataUnix(AppDataUnix)
    , ClientSettingsUnix
#if MIN_VERSION_streaming_commons(0,1,13) \
  && !MIN_VERSION_streaming_commons(0,1,14)
        -- Until streaming-commons 0.1.13, read buffer size was fixed.
        -- ClientSettingsUnix instance for HasReadBufferSize was introduced in
        -- streaming-commons version 0.1.14 and it provides much more stable
        -- API then accessor.
        ( clientReadBufferSizeUnix
        )
#endif
    -- streaming-commons >=0.1.13 && <= 0.1.14
#endif
    -- !WINDOWS
    )
import qualified Data.Streaming.Network.Internal as AppData
    ( AppData
        ( appLocalAddr'
        , appRead'
        , appSockAddr'
        , appWrite'
#if MIN_VERSION_streaming_commons(0,1,6)
        , appCloseConnection'
#endif
#if MIN_VERSION_streaming_commons(0,1,12)
        , appRawSocket'
#endif
        ))

import Data.ConnectionPool.Internal.HandlerParams
    ( HandlerParams(_readBufferSize)
    )

#ifndef WINDOWS
    -- Windows doesn't support UNIX Sockets.
import qualified Data.Streaming.Network.Internal as AppDataUnix
    (AppDataUnix(appReadUnix, appWriteUnix))
#endif
    -- !WINDOWS


-- | Wrapper for 'runTcpAppImpl' with a type signature that is more natural
-- for implementing a TCP specific
-- 'Data.ConnectionPool.Internal.ConnectionPool.withConnection'.
--
-- /Definition changed in version 0.1.3 and 0.2.1./
runTcpApp
    :: Maybe SockAddr
    -> (AppData -> m r)
    -> HandlerParams
    -- ^ Parameters passed down to connection handler @('AppData' -> m r)@ as
    -- part of definition of 'AppData'.
    -- /Since version 0.1.3./
    -> Socket
    -> SockAddr
    -> m r
runTcpApp localAddr app params sock addr =
    runTcpAppImpl localAddr sock addr bufSize app
  where
    bufSize = _readBufferSize params
{-# INLINE runTcpApp #-}

-- | Simplified 'Data.Streaming.Network.runTCPClient' and
-- 'Data.Streaming.Network.runTCPServer' that provides only construction of
-- 'AppData' and passing it to a callback function.
--
-- /Definition changed in version 0.1.3 and 0.2.1./
runTcpAppImpl
    :: Maybe SockAddr
    -> Socket
    -> SockAddr
    -> Int
    -- ^ Buffer size used while reading from socket. /Since version 0.1.3./
    -> (AppData -> m r)
    -> m r
runTcpAppImpl localAddr sock addr bufSize app = app AppData
    { AppData.appRead' = safeRecv sock bufSize  -- :: !(IO ByteString)
    , AppData.appWrite' = sendAll sock          -- :: !(ByteString -> IO ())
    , AppData.appSockAddr' = addr               -- :: !SockAddr
    , AppData.appLocalAddr' = localAddr         -- :: !(Maybe SockAddr)
#if MIN_VERSION_streaming_commons(0,1,6)
    , AppData.appCloseConnection' = close sock  -- :: !(IO ())
#endif
#if MIN_VERSION_streaming_commons(0,1,12)
    , AppData.appRawSocket' = Just sock         -- :: Maybe Socket
#endif
    }
{-# INLINE runTcpAppImpl #-}

-- | Wrapper for 'getSocketFamilyTCP' that takes 'ClientSettings' instead of
-- individual parameters.
acquireTcpClientConnection :: ClientSettings -> IO (Socket, SockAddr)
acquireTcpClientConnection settings = getSocketFamilyTCP host port addrFamily
  where
    port = clientPort settings
    host = clientHost settings
    addrFamily = clientAddrFamily settings
{-# INLINEABLE acquireTcpClientConnection #-}

-- | Construct 'HandlerParams' that are passed to individual TCP connection
-- handlers.
--
-- /Since version 0.1.3./
fromClientSettings :: ClientSettings -> HandlerParams
fromClientSettings _tcpParams = def
#if MIN_VERSION_streaming_commons(0,1,13)
    -- Until streaming-commons 0.1.13, read buffer size was fixed.
    { _readBufferSize = getReadBufferSize _tcpParams
    }
#endif
{-# INLINE fromClientSettings #-}

#ifndef WINDOWS
-- Windows doesn't support UNIX Sockets.

-- | Wrapper for 'runUnixAppImpl' with a type signature that is more natural
-- for implementing a UNIX Socket specific
-- 'Data.ConnectionPool.Internal.ConnectionPool.withConnection'.
--
-- /Definition changed in version 0.1.3 and 0.2.1./
runUnixApp
    :: (AppDataUnix -> m r)
    -> HandlerParams
    -- ^ Parameters passed down to connection handler @('AppDataUnix' -> m r)@ as
    -- part of definition of 'AppDataUnix'.
    -- /Since version 0.1.3./
    -> Socket
    -> ()
    -> m r
runUnixApp app params sock () = runUnixAppImpl sock bufSize app
  where
    bufSize = _readBufferSize params
{-# INLINE runUnixApp #-}

-- | Simplified 'Data.Streaming.Network.runUnixClient' and
-- 'Data.Streaming.Network.runUnixServer' that provides only construction of
-- 'AppDataUnix' and passing it to a callback function.
--
-- /Definition changed in version 0.1.3 and 0.2.1./
runUnixAppImpl
    :: Socket
    -> Int
    -- ^ Buffer size used while reading from socket. /Since version 0.1.3./
    -> (AppDataUnix -> m r)
    -> m r
runUnixAppImpl sock bufSize app = app AppDataUnix
    { AppDataUnix.appReadUnix = safeRecv sock bufSize
    , AppDataUnix.appWriteUnix = sendAll sock
    }
{-# INLINE runUnixAppImpl #-}

-- | Construct 'HandlerParams' that are passed to individual UNIX socket
-- connection handlers.
--
-- /Since version 0.1.3./
fromClientSettingsUnix :: ClientSettingsUnix -> HandlerParams
fromClientSettingsUnix _unixParams = def
#if MIN_VERSION_streaming_commons(0,1,13)
    -- Until streaming-commons 0.1.13, read buffer size was fixed.
    { _readBufferSize =
#if MIN_VERSION_streaming_commons(0,1,14)
        -- ClientSettingsUnix instance for HasReadBufferSize was introduced in
        -- streaming-commons version 0.1.14 and it provides much more stable
        -- API then accessor.
        getReadBufferSize _unixParams
#else
        -- In streaming-commons 0.1.13 we have to use clientReadBufferSizeUnix
        -- accessor of ClientSettingsUnix.
        clientReadBufferSizeUnix _unixParams
#endif
    }
#endif
    -- streaming-commons >= 0.1.13
#endif
    -- !WINDOWS
{-# INLINE fromClientSettingsUnix #-}

-- | Compatibility wrapper around @close\/sClose@.
--
-- Package <https://hackage.haskell.org/package/network network> deprecated
-- @sClose@ in favour of @close@ in version 2.4.0.0.
--
-- /Since version 0.2.2./
close :: Socket -> IO ()
close =
#if MIN_VERSION_network(2,4,0)
    Socket.close
#else
    Socket.sClose
#endif
{-# INLINE close #-}