-- | Monad class implementing an Open Sound Control transport.
module Sound.OSC.Transport.Monad where

import Control.Monad {- base -}
import Data.List {- base -}
import Data.Maybe {- base -}

import qualified Control.Monad.Trans.Reader as R {- transformers -}
import qualified Control.Monad.IO.Class as M {- transformers -}

import qualified Sound.OSC.Datum as Datum {- hosc -}
import qualified Sound.OSC.Transport.FD as FD {- hosc -}
import qualified Sound.OSC.Packet as Packet {- hosc -}
import qualified Sound.OSC.Wait as Wait {- hosc -}

-- | Sender monad.
class Monad m => SendOSC m where
   -- | Encode and send an OSC packet.
   sendPacket :: Packet.Packet -> m ()

-- | Receiver monad.
class Monad m => RecvOSC m where
   -- | Receive and decode an OSC packet.
   recvPacket :: m Packet.Packet

-- | 'DuplexOSC' is the union of 'SendOSC' and 'RecvOSC'.
class (SendOSC m,RecvOSC m) => DuplexOSC m where

-- | 'Transport' is 'DuplexOSC' with a 'MonadIO' constraint.
class (DuplexOSC m,M.MonadIO m) => Transport m where

-- | 'SendOSC' over 'ReaderT'.
instance (FD.Transport t,M.MonadIO io) => SendOSC (R.ReaderT t io) where
   sendPacket p = R.ReaderT (M.liftIO . flip FD.sendPacket p)

-- | 'RecvOSC' over 'ReaderT'.
instance (FD.Transport t,M.MonadIO io) => RecvOSC (R.ReaderT t io) where
   recvPacket = R.ReaderT (M.liftIO . FD.recvPacket)

-- | 'DuplexOSC' over 'ReaderT'.
instance (FD.Transport t,M.MonadIO io) => DuplexOSC (R.ReaderT t io) where

-- | 'Transport' over 'ReaderT'.
instance (FD.Transport t,M.MonadIO io) => Transport (R.ReaderT t io) where

-- | Transport connection.
type Connection t a = R.ReaderT t IO a

-- | Bracket Open Sound Control communication.
withTransport :: FD.Transport t => IO t -> Connection t a -> IO a
withTransport u = FD.withTransport u . R.runReaderT

-- * Send

-- | Type restricted synonym for 'sendOSC'.
sendMessage :: SendOSC m => Packet.Message -> m ()
sendMessage = sendPacket . Packet.Packet_Message

-- | Type restricted synonym for 'sendOSC'.
sendBundle :: SendOSC m => Packet.Bundle -> m ()
sendBundle = sendPacket . Packet.Packet_Bundle

-- * Receive

-- | Variant of 'recvPacket' that runs 'packet_to_bundle'.
recvBundle :: (RecvOSC m) => m Packet.Bundle
recvBundle = liftM Packet.packet_to_bundle recvPacket

-- | Variant of 'recvPacket' that runs 'packet_to_message'.
recvMessage :: (RecvOSC m) => m (Maybe Packet.Message)
recvMessage = liftM Packet.packet_to_message recvPacket

-- | Erroring variant.
recvMessage_err :: RecvOSC m => m Packet.Message
recvMessage_err = fmap (fromMaybe (error "recvMessage")) recvMessage

-- | Variant of 'recvPacket' that runs 'packetMessages'.
recvMessages :: (RecvOSC m) => m [Packet.Message]
recvMessages = liftM Packet.packetMessages recvPacket

-- * Wait

-- | Wait for a 'Packet' where the supplied predicate is 'True',
-- discarding intervening packets.
waitUntil :: (RecvOSC m) => (Packet.Packet -> Bool) -> m Packet.Packet
waitUntil f = Wait.untilPredicate f recvPacket

-- | Wait for a 'Packet' where the supplied function does not give
-- 'Nothing', discarding intervening packets.
waitFor :: (RecvOSC m) => (Packet.Packet -> Maybe a) -> m a
waitFor f = Wait.untilMaybe f recvPacket

-- | 'waitUntil' 'packet_is_immediate'.
waitImmediate :: RecvOSC m => m Packet.Packet
waitImmediate = waitUntil Packet.packet_is_immediate

-- | 'waitFor' 'packet_to_message', ie. an incoming 'Message' or
-- immediate mode 'Bundle' with one element.
waitMessage :: RecvOSC m => m Packet.Message
waitMessage = waitFor Packet.packet_to_message

-- | A 'waitFor' for variant using 'packet_has_address' to match on
-- the 'Address_Pattern' of incoming 'Packets'.
waitAddress :: RecvOSC m => Packet.Address_Pattern -> m Packet.Packet
waitAddress s =
    let f o = if Packet.packet_has_address s o then Just o else Nothing
    in waitFor f

-- | Variant on 'waitAddress' that returns matching 'Message'.
waitReply :: RecvOSC m => Packet.Address_Pattern -> m Packet.Message
waitReply s =
    let f = fromMaybe (error "waitReply: message not located?") .
            find (Packet.message_has_address s) .
            Packet.packetMessages
    in liftM f (waitAddress s)

-- | Variant of 'waitReply' that runs 'messageDatum'.
waitDatum :: RecvOSC m => Packet.Address_Pattern -> m [Datum.Datum]
waitDatum = liftM Packet.messageDatum . waitReply