    {-# LANGUAGE FlexibleContexts #-}
    {-# LANGUAGE RankNTypes #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE CPP #-}
    module Data.Conduit.Network
        ( -- * Basic utilities
          sourceSocket
        , sinkSocket
          -- * Simple TCP server/client interface.
        , SN.AppData
        , appSource
        , appSink
        , SN.appSockAddr
        , SN.appLocalAddr
          -- ** Server
        , SN.ServerSettings
        , serverSettings
        , SN.runTCPServer
        , SN.runTCPServerWithHandle
        , forkTCPServer
        , runGeneralTCPServer
          -- ** Client
        , SN.ClientSettings
        , clientSettings
        , SN.runTCPClient
        , runGeneralTCPClient
          -- ** Getters
        , SN.getPort
        , SN.getHost
        , SN.getAfterBind
        , SN.getNeedLocalAddr
          -- ** Setters
        , SN.setPort
        , SN.setHost
        , SN.setAfterBind
        , SN.setNeedLocalAddr
          -- * Types
        , SN.HostPreference
        ) where

    import Prelude
    import Data.Conduit
    import Network.Socket (Socket)
    import Network.Socket.ByteString (sendAll)
    import Data.ByteString (ByteString)
    import qualified GHC.Conc as Conc (yield)
    import qualified Data.ByteString as S
    import Control.Monad.IO.Class (MonadIO (liftIO))
    import Control.Monad (unless)
    import Control.Monad.Trans.Class (lift)
    import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar, MVar, ThreadId)
    import qualified Data.Streaming.Network as SN
    import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO)

    -- | Stream data from the socket.
    --
    -- This function does /not/ automatically close the socket.
    --
    -- Since 0.0.0
    sourceSocket :: MonadIO m => Socket -> ConduitT i ByteString m ()
    sourceSocket socket =
        loop
      where
        loop = do
            bs <- lift $ liftIO $ SN.safeRecv socket 4096
            if S.null bs
                then return ()
                else yield bs >> loop

    -- | Stream data to the socket.
    --
    -- This function does /not/ automatically close the socket.
    --
    -- Since 0.0.0
    sinkSocket :: MonadIO m => Socket -> ConduitT ByteString o m ()
    sinkSocket socket =
        loop
      where
        loop = await >>= maybe (return ()) (\bs -> lift (liftIO $ sendAll socket bs) >> loop)

    serverSettings :: Int -> SN.HostPreference -> SN.ServerSettings
    serverSettings = SN.serverSettingsTCP

    clientSettings :: Int -> ByteString -> SN.ClientSettings
    clientSettings = SN.clientSettingsTCP

    appSource :: (SN.HasReadWrite ad, MonadIO m) => ad -> ConduitT i ByteString m ()
    appSource ad =
        loop
      where
        read' = SN.appRead ad
        loop = do
            bs <- liftIO read'
            unless (S.null bs) $ do
                yield bs
                loop

    appSink :: (SN.HasReadWrite ad, MonadIO m) => ad -> ConduitT ByteString o m ()
    appSink ad = awaitForever $ \d -> liftIO $ SN.appWrite ad d >> Conc.yield

    addBoundSignal::MVar ()-> SN.ServerSettings -> SN.ServerSettings
    addBoundSignal isBound set = SN.setAfterBind ( \socket -> originalAfterBind socket >>  signalBound socket) set
                                 where originalAfterBind :: Socket -> IO ()
                                       originalAfterBind = SN.getAfterBind set
                                       signalBound :: Socket -> IO ()
                                       signalBound _socket = putMVar isBound ()

    -- | Fork a TCP Server
    --
    -- Will fork the runGeneralTCPServer function but will only return from
    -- this call when the server is bound to the port and accepting incoming
    -- connections. Will return the thread id of the server
    --
    -- Since 1.1.4
    forkTCPServer
      :: MonadUnliftIO m
      => SN.ServerSettings
      -> (SN.AppData -> m ())
      -> m ThreadId
    forkTCPServer set f =
           withRunInIO $ \run -> do
             isBound <- newEmptyMVar
             let setWithWaitForBind = addBoundSignal isBound set
             threadId <- forkIO . run $ runGeneralTCPServer setWithWaitForBind f
             takeMVar isBound
             return threadId

    -- | Run a general TCP server
    --
    -- Same as 'SN.runTCPServer', except monad can be any instance of
    -- 'MonadUnliftIO'.
    --
    -- Note that any changes to the monadic state performed by individual
    -- client handlers will be discarded. If you have mutable state you want
    -- to share among multiple handlers, you need to use some kind of mutable
    -- variables.
    --
    -- Since 1.1.3
    runGeneralTCPServer
      :: MonadUnliftIO m
      => SN.ServerSettings
      -> (SN.AppData -> m ())
      -> m a
    runGeneralTCPServer set f = withRunInIO $ \run ->
        SN.runTCPServer set $ run . f

    -- | Run a general TCP client
    --
    -- Same as 'SN.runTCPClient', except monad can be any instance of 'MonadUnliftIO'.
    --
    -- Since 1.1.3
    runGeneralTCPClient
      :: MonadUnliftIO m
      => SN.ClientSettings
      -> (SN.AppData -> m a)
      -> m a
    runGeneralTCPClient set f = withRunInIO $ \run ->
        SN.runTCPClient set $ run . f