--------------------------------------------------------------------------------
-- |
-- Module    : Sound.ALSA.Sequencer.Subscribe.Query
-- Copyright : (c) Dylan Simon, 2011
-- License   : BSD3
--
-- Stability : provisional
--
-- This module contains functions for working with subscriptions.
-- Reference:
-- <http://www.alsa-project.org/alsa-doc/alsa-lib/group___seq_subscribe.html>
--------------------------------------------------------------------------------

module Sound.ALSA.Sequencer.Subscribe.Query
  ( T
  , Query.Type(..)

  {-
  , alloc
  , copy
  , clone
  -}

  , getClient
  , getPort
  , getRoot
  , getType
  , getIndex
  , getNumSubs
  , getAddr
  , getQueue
  , getExclusive
  , getTimeUpdate
  , getTimeReal

  {-
  , setClient
  , setPort
  , setType
  , setIndex
  -}

  , query
  , queryAll
  ) where

import qualified Sound.ALSA.Sequencer.Marshal.QuerySubscribe as Query
import qualified Sound.ALSA.Sequencer.Marshal.Sequencer as Seq
import qualified Sound.ALSA.Sequencer.Area as Area
import qualified Sound.ALSA.Sequencer.Marshal.Address as Addr
import qualified Sound.ALSA.Sequencer.Marshal.Client as Client
import qualified Sound.ALSA.Sequencer.Marshal.Port as Port
import qualified Sound.ALSA.Sequencer.Marshal.Queue as Queue
import qualified Sound.ALSA.Exception as Exc

import Foreign.C.Error (Errno(Errno), eNOENT, )
import Foreign.C.Types (CInt, )
import Foreign.Ptr (Ptr, )
import Foreign.Marshal.Alloc (alloca, )
import Foreign.Storable (peek, poke, )
import Data.Word (Word, )

type T = Area.QuerySubscribe
type T_ = Area.QuerySubscribe_

alloc	      :: IO T
_copy	      :: T -> T -> IO ()
_clone	      :: T -> IO T
getClient     :: T -> IO Client.T
getPort	      :: T -> IO Port.T
getType	      :: T -> IO Query.Type
getIndex      :: T -> IO Word
getNumSubs    :: T -> IO Word
getQueue      :: T -> IO Queue.T
getExclusive  :: T -> IO Bool
getTimeUpdate :: T -> IO Bool
getTimeReal   :: T -> IO Bool
_setClient    :: T -> Client.T -> IO ()
_setPort      :: T -> Port.T -> IO ()
setType	      :: T -> Query.Type -> IO ()
setIndex      :: T -> Word -> IO ()

alloc	      = Area.query_subscribe_malloc
_copy	      = Area.query_subscribe_copy
_clone	      = Area.query_subscribe_clone
getClient     = Area.query_subscribe_get_client
getPort	      = Area.query_subscribe_get_port
getType	      = Area.query_subscribe_get_type
getIndex      = Area.query_subscribe_get_index
getNumSubs    = Area.query_subscribe_get_num_subs
getQueue      = Area.query_subscribe_get_queue
getExclusive  = Area.query_subscribe_get_exclusive
getTimeUpdate = Area.query_subscribe_get_time_update
getTimeReal   = Area.query_subscribe_get_time_real
_setClient    = Area.query_subscribe_set_client
_setPort      = Area.query_subscribe_set_port
setType       = Area.query_subscribe_set_type
setIndex      = Area.query_subscribe_set_index

-- | Get the client/port address of a query
getRoot :: T -> IO Addr.T
getRoot q = 
  peek =<< Area.with_query_subscribe q snd_seq_query_subscribe_get_root

foreign import ccall unsafe "alsa/asoundlib.h snd_seq_query_subscribe_get_root"
  snd_seq_query_subscribe_get_root :: Ptr T_ -> IO (Ptr Addr.T)

-- | Get the address of subscriber of query
getAddr :: T -> IO Addr.T
getAddr q = 
  peek =<< Area.with_query_subscribe q snd_seq_query_subscribe_get_addr

foreign import ccall unsafe "alsa/asoundlib.h snd_seq_query_subscribe_get_addr"
  snd_seq_query_subscribe_get_addr :: Ptr T_ -> IO (Ptr Addr.T)


-- | Set the client/port address of a query
setRoot :: T -> Addr.T -> IO ()
setRoot q c = 
  alloca $ \p -> poke p c >> Area.with_query_subscribe q (`snd_seq_query_subscribe_set_root` p)

foreign import ccall unsafe "alsa/asoundlib.h snd_seq_query_subscribe_set_root"
  snd_seq_query_subscribe_set_root :: Ptr T_ -> Ptr Addr.T -> IO ()


-- | Query port subscriber list
queryPort :: Seq.T mode -> T -> IO Bool
queryPort (Seq.Cons h) q =
  Exc.checkResultMaybe "query_port_subscribers" (const True) (\e -> if Errno (negate e) == eNOENT then Just False else Nothing) =<<
    Area.with_query_subscribe q (snd_seq_query_port_subscribers h)

foreign import ccall unsafe "alsa/asoundlib.h snd_seq_query_port_subscribers"
  snd_seq_query_port_subscribers :: Ptr Seq.Core -> Ptr T_ -> IO CInt

-- | Queries a subscriber connected to (Write) or from (Read) a given address: @'query' seq addr typ index@
query :: Seq.T mode -> Addr.T -> Query.Type -> Word -> IO (Maybe T)
query ss root t i = do
  q <- alloc
  setRoot q root
  setType q t
  setIndex q i
  r <- queryPort ss q
  return $ if r then Just q else Nothing

-- | Queries the list of subscribers accessing a port
queryAll :: Seq.T mode -> Addr.T -> Query.Type -> IO [T]
queryAll ss root t = queryRest 0 where
  queryRest i = query ss root t i >>=
    maybe (return []) (\q -> (q:) `fmap` queryRest (succ i))