{-|
Module:  Network.CoAP.Client
Description: CoAP client library
Maintainer: ulf.lilleengen@gmail.com
License: BSD3

The CoAP client API is intended to provide the minimal building block needed for sending CoAP requests. The API exposes CoAP request and response types and handles all internal messaging details of the CoAP protocol.

Example:

@
    client <- createClient (createUDPTransport socket)
    doRawRequest client (SockAddrInet 5683 0) (Request GET [UriPath path] Nothing True)
@
-}
module Network.CoAP.Client
( Request(..)
, Client(..)
, Method(..)
, Response(..)
, ResponseCode(..)
, Option(..)
, OptionString
, MediaType(..)
, createClient
) where

import Network.CoAP.Messaging
import Network.CoAP.Types
import Control.Monad.State
import Control.Concurrent.Async
import Control.Concurrent.STM
import Network.Socket
import Data.ByteString hiding (putStrLn)
import Data.Word
import System.Random
import Network.URI
import Network.CoAP.Internal
                    
-- | A client that can perform CoAP requests.
data Client = Client { -- | Send a CoAP request to a given endpoint represented by an URI. Returns a CoAP response.
                       doRequest :: URI -> Request -> IO Response
                       -- | Send a CoAP request to a given endpoint. Returns a CoAP response.
                     , doRawRequest :: Endpoint -> Request -> IO Response
                       -- | Stop a client. Ensures that no threads are running and that messaging
                       -- layer is shut down.
                     , shutdownClient :: IO () }

-- | Create a client using a given transport. This will spawn internal messaging threads making the
-- client ready to send requests.
createClient :: Transport -> IO Client
createClient transport = do
  state <- createMessagingState transport
  msgThreads <- startMessaging state
  return Client { doRequest = doRequestInternal state
                , doRawRequest = doRawRequestInternal state
                , shutdownClient = stopClient state msgThreads }

-- | Shuts down the internal messaging threads and stops the client
stopClient :: MessagingState -> [Async ()] -> IO ()
stopClient state threads = do
  stopMessaging state threads
  mapM_ wait threads

generateToken :: Int -> IO [Word8]
generateToken 0 = return []
generateToken len = do
  tkn <- randomIO
  next <- generateToken (len - 1)
  return (tkn:next)

doRequestInternal :: MessagingState -> URI -> Request -> IO Response
doRequestInternal state uri (Request method options payload reliable) = do
  dest <- createEndpoint uri
  let newOpts = createOpts uri
  doRawRequestInternal state dest (Request method (options ++ newOpts) payload reliable)

doRawRequestInternal :: MessagingState -> Endpoint -> Request -> IO Response
doRawRequestInternal state dest (Request method options payload reliable) = do
  tokenLen <- randomRIO (0, 8)
  token <- generateToken tokenLen
  let msg = Message { messageVersion = 1
                    , messageType = if reliable then CON else NON
                    , messageCode = CodeRequest method
                    , messageId = 0
                    , messageToken = pack token
                    , messageOptions = options
                    , messagePayload = payload }
  sendRequest msg dest state
  responseCtx <- recvResponse msg dest state
  let (Message _ _ (CodeResponse rCode) _ _ opts pload) = message responseCtx
  return Response { responseCode = rCode
                  , responseOptions = opts
                  , responsePayload = pload }