-------------------------------------------------------------------------------
-- |
-- Module     : Network/Mom/Patterns/Basic/Client.hs
-- Copyright  : (c) Tobias Schoofs
-- License    : LGPL 
-- Stability  : experimental
-- Portability: portable
-- 
-- Client side of Client/Server
-------------------------------------------------------------------------------
module Network.Mom.Patterns.Basic.Client (
         Client, clService, withClient, request, checkResult)
where

  import qualified System.ZMQ            as Z
  import           Network.Mom.Patterns.Types
  import           Network.Mom.Patterns.Streams

  ------------------------------------------------------------------------
  -- | Client data type
  ------------------------------------------------------------------------
  data Client = Client {clSock    :: Z.Socket Z.Req,
                        clService :: Service} -- currently unused

  ------------------------------------------------------------------------
  -- | Create a client
  --   with name 'Service',
  --   linking to address 'String',
  --   connecting or binding the address according to 'LinkType'
  --   and finally entering the action, in whose scope
  --   the client lives.
  ------------------------------------------------------------------------
  withClient :: Context          ->
                Service          -> 
                String           ->
                LinkType         ->
                (Client -> IO a) -> IO a
  withClient ctx srv add lt act =
    Z.withSocket ctx Z.Req $ \s -> do
      link lt s add []
      act $ Client s srv

  ------------------------------------------------------------------------
  -- | Request a service:
  --
  --   * 'Client' - The client, through which the service is requested
  --
  --   * 'Timeout' - Timeout in microseconds, -1 to wait eternally.
  --                 With timeout = 0, the function returns immediately
  --                 with 'Nothing'.
  --                 When the timeout expires, request is abandoned. 
  --                 In this case, the result of the request
  --                 is Nothing.
  --               
  --   * 'Source'  - The source of the request stream;
  --                 the format of the request will probably comply
  --                 with some communication protocol,
  --                 as, for instance, in the majordomo pattern.
  --
  --   * 'SinkR'   - The sink receiving the reply. The result of the sink
  --                 is returned as the request's overall result.
  --                 Note that the sink may perform different 
  --                 actions on the segments of the resulting stream,
  --                 /e.g./ storing data in a database,
  --                 and return the number of records received.
  --
  -- A \'hello world\' Example:
  --
  -- >  import qualified Data.Conduit          as C
  -- >  import qualified Data.ByteString.Char8 as B
  -- >  import           Network.Mom.Patterns.Basic.Client
  -- >  import           Network.Mom.Patterns.Types
  --
  -- >  main :: IO ()
  -- >  main = withContext 1 $ \ctx -> 
  -- >           withClient ctx "test" 
  -- >               "tcp://localhost:5555" Connect $ \c -> do
  -- >             mbX <- request c (-1) src snk
  -- >             case mbX of
  -- >               Nothing -> putStrLn "No Result"
  -- >               Just x  -> putStrLn $ "Result: " ++ x
  -- >    where src = C.yield (B.pack "hello world")
  -- >          snk = do mbX <- C.await 
  -- >                   case mbX of
  -- >                     Nothing -> return Nothing
  -- >                     Just x  -> return $ Just $ B.unpack x
  ------------------------------------------------------------------------
  request :: Client -> Timeout -> Source -> SinkR (Maybe r) -> IO (Maybe r)
  request c tmo src snk = do
    runSender   (clSock c) src
    if tmo /= 0 then runReceiver (clSock c) tmo snk
                else return Nothing

  ------------------------------------------------------------------------
  -- | Check for a of a previously requested result;
  --   use case: request with timout 0, do some work
  --             and check for a result later.
  --   Do not use this function without having requested the service
  --      previously.
  --   The parameters equal those of 'request',
  --   but do not include a 'Source'.
  ------------------------------------------------------------------------
  checkResult :: Client -> Timeout -> SinkR (Maybe r) -> IO (Maybe r)
  checkResult c tmo snk | tmo == 0  = return Nothing
                        | otherwise = runReceiver (clSock c) tmo snk