    -- | OSC over UDP implementation.
    module Sound.OSC.Transport.FD.UDP where

    import Control.Exception {- base -}
    import Control.Monad {- base -}
    import Data.Bifunctor {- base -}
    import qualified Network.Socket as N {- network -}
    import qualified Network.Socket.ByteString as C {- network -}

    import qualified Sound.OSC.Coding.Decode.Binary as Binary {- hosc -}
    import qualified Sound.OSC.Coding.Encode.Builder as Builder {- hosc -}
    import qualified Sound.OSC.Packet as Packet {- hosc -}
    import qualified Sound.OSC.Transport.FD as FD {- hosc -}

    -- | The UDP transport handle data type.
    data UDP = UDP {udpSocket :: N.Socket}

    -- | Return the port number associated with the UDP socket.
    udpPort :: Integral n => UDP -> IO n
    udpPort = fmap fromIntegral . N.socketPort . udpSocket

    -- | Send packet over UDP.
    upd_send_packet :: UDP -> Packet.Packet -> IO ()
    upd_send_packet (UDP fd) p = void (C.send fd (Builder.encodePacket_strict p))

    -- | Receive packet over UDP.
    udp_recv_packet :: UDP -> IO Packet.Packet
    udp_recv_packet (UDP fd) = liftM Binary.decodePacket_strict (C.recv fd 8192)

    -- | Close UDP.
    udp_close :: UDP -> IO ()
    udp_close (UDP fd) = N.close fd

    -- | 'UDP' is an instance of 'FD.Transport'.
    instance FD.Transport UDP where
       sendPacket = upd_send_packet
       recvPacket = udp_recv_packet
       close = udp_close

    -- | Bracket UDP communication.
    with_udp :: IO UDP -> (UDP -> IO t) -> IO t
    with_udp u = bracket u udp_close

    -- | Create and initialise UDP socket.
    udp_socket :: (N.Socket -> N.SockAddr -> IO ()) -> String -> Int -> IO UDP
    udp_socket f host port = do
      fd <- N.socket N.AF_INET N.Datagram 0
      i:_ <- N.getAddrInfo Nothing (Just host) (Just (show port))
      let sa = N.addrAddress i
      f fd sa
      return (UDP fd)

    -- | Set option, ie. 'N.Broadcast' or 'N.RecvTimeOut'.
    set_udp_opt :: N.SocketOption -> Int -> UDP -> IO ()
    set_udp_opt k v (UDP s) = N.setSocketOption s k v

    -- | Get option.
    get_udp_opt :: N.SocketOption -> UDP -> IO Int
    get_udp_opt k (UDP s) = N.getSocketOption s k

    -- | Make a 'UDP' connection.
    openUDP :: String -> Int -> IO UDP
    openUDP = udp_socket N.connect

    {- | Trivial 'UDP' server socket.

    > import Control.Concurrent {- base -}

    > let t0 = udpServer "127.0.0.1" 57300
    > forkIO (FD.withTransport t0 (\fd -> forever (FD.recvMessage fd >>= print)))

    > let t1 = openUDP "127.0.0.1" 57300
    > FD.withTransport t1 (\fd -> FD.sendMessage fd (Packet.message "/n" []))
    -}
    udpServer :: String -> Int -> IO UDP
    udpServer = udp_socket N.bind

    -- | Variant of 'udpServer' that doesn't require the host address.
    udp_server :: Int -> IO UDP
    udp_server p = do
      let hints =
            N.defaultHints
            {N.addrFlags = [N.AI_PASSIVE,N.AI_NUMERICSERV]
            ,N.addrSocketType = N.Datagram}
      a:_ <- N.getAddrInfo (Just hints) Nothing (Just (show p))
      s <- N.socket (N.addrFamily a) (N.addrSocketType a) (N.addrProtocol a)
      N.setSocketOption s N.ReuseAddr 1
      N.bind s (N.addrAddress a)
      return (UDP s)

    -- | Send variant to send to specified address.
    sendTo :: UDP -> Packet.Packet -> N.SockAddr -> IO ()
    sendTo (UDP fd) p = void . C.sendTo fd (Builder.encodePacket_strict p)

    -- | Recv variant to collect message source address.
    recvFrom :: UDP -> IO (Packet.Packet, N.SockAddr)
    recvFrom (UDP fd) = fmap (first Binary.decodePacket_strict) (C.recvFrom fd 8192)