module Network.NTP.Control
  ( sendTo
  , recvFrom
  , query
  , response
  , queryHost
  ) where

import Control.Exception (bracket)
import Control.Monad (void)
import Data.Serialize (decode, encode)
import Network.BSD (getHostByName, hostAddress)
import qualified Network.NTP.Control.Packet as P
import Network.Socket (Socket, SocketType(..), SockAddr(..), Family(..), socket, close, defaultProtocol)
import qualified Network.Socket.ByteString as N

-- | Send an NTP control packet
sendTo :: Socket -> SockAddr -> P.Packet -> IO Int
sendTo s sa p = N.sendTo s (encode p) sa

-- | Receive an NTP control packet
recvFrom :: Socket -> IO (P.Packet, SockAddr)
recvFrom s = do
  (b, sa) <- N.recvFrom s 1024
  case decode b of
    Left e -> fail e
    Right p -> return (p, sa)

-- | Create a socket and send an NTP control message to a server
query :: SockAddr -> P.Packet -> IO Socket
query sa p = do
  s <- socket AF_INET Datagram defaultProtocol
  void $ sendTo s sa p
  return s

-- | Wait for a response for an issued query
response :: SockAddr -> P.Packet -> Socket -> IO P.Packet
response sa p s = do
  let wait = do
        (p', sa') <- recvFrom s
        if sa' == sa && P.sequence p == P.sequence p'
          then return p'
          else wait

-- | Convenience wrapper for 'query' that looks up a host name,
-- queries its standard NTP port, and waits for a response
queryHost :: String -> P.Packet -> IO P.Packet
queryHost host p = do
  he <- getHostByName host
  let sa = SockAddrInet 123 (hostAddress he)
  bracket (query sa p) close (response sa p)