{-|
  Module      : $Header$
  Copyright   : (c) 2014 Edward O'Callaghan
  License     : LGPL-2.1
  Maintainer  : eocallaghan@alterapraxis.com
  Stability   : provisional
  Portability : portable

  This module encapsulates Synchronous data transmission and reception
 
  This group of functions presents synchronous, blocking calls (with optional
  timeouts) for transmitting and receiving samples.
 
  The synchronous interface is built atop the asynchronous interface, and is
  generally less complex and easier to work with.  It alleviates the need to
  explicitly spawn threads (it is done under the hood) and manually manage
  sample buffers.
 
  Under the hood, this interface spawns worker threads to handle an
  asynchronous stream and perform thread-safe buffer management.
-}

module LibBladeRF.Sync ( bladeRFSyncConfig
                       , bladeRFSyncTx
                       , bladeRFSyncRx
                       ) where

import Foreign
import Foreign.C.Types
import Foreign.C.String

import qualified Data.ByteString as BS

import Bindings.LibBladeRF
import LibBladeRF.LibBladeRF
import LibBladeRF.Types


-- | (Re)Configure a device for synchronous transmission or reception
bladeRFSyncConfig :: DeviceHandle  -- ^ Device handle
                  -> BladeRFModule -- ^ Module to use with synchronous interface
                  -> BladeRFFormat -- ^ Format to use in synchronous data transfers
                  -> Int           -- ^ The number of buffers to use in the underlying data stream.
                  -> Int           -- ^ The size of the underlying stream buffers, in samples. This value must be a multiple of 1024.
                  -> Int           -- ^ The number of active USB transfers that may be in-flight at any given time.
                  -> Int           -- ^ Timeout (milliseconds) for transfers in the underlying data stream.
                  -> IO (BladeRFReturnType ())
bladeRFSyncConfig dev m f nb sz tr to = do
  ret <- c'bladerf_sync_config (unDeviceHandle dev) ((fromIntegral . fromEnum) m) ((fromIntegral . fromEnum) f) (fromIntegral nb) (fromIntegral sz) (fromIntegral tr) (fromIntegral to)
  return $ bladeRFErrorTy ret

-- | Transmit IQ samples.
--
-- Under the hood, this call starts up an underlying asynchronous stream as
-- needed. This stream can be stopped by disabling the TX module. (See
-- 'LibBladeRF.Utils.bladeRFEnableModule' for more details.)
--
-- Samples will only be sent to the FPGA when a buffer have been filled. The
-- number of samples required to fill a buffer corresponds to the `buffer_size`
-- parameter passed to 'bladeRFSyncConfig'.
--
-- Precondition:
--
--   1. A 'bladeRFSyncConfig' call has been to configure the device
--      for synchronous data transfer.
--
--   2. A call to 'LibBladeRF.Utils.bladeRFEnableModule' should be
--      made before attempting to transmit samples. Failing to do
--      this may result in timeouts and other errors.
bladeRFSyncTx :: DeviceHandle          -- ^ Device handle
              -> BS.ByteString         -- ^ Array of samples
              -> Maybe BladeRFMetadata -- ^ Sample metadata. This must be provided when using
                                       --   the 'FORMAT_SC16_Q11_META' format, but may
                                       --   be 'Nothing' when the interface is configured for
                                       --   the 'FORMAT_SC16_Q11' format.
              -> Int                   -- ^ Timeout (milliseconds) for this call to complete. Zero implies infinite.
              -> IO (BladeRFReturnType ())
bladeRFSyncTx dev s md t = do
  -- The number of samples to write is the length of the bytestring `div` by (|IQ|=2 * |int16_t|=2)=4
  ret <- alloca $ \pmd ->
    case md of
      Nothing -> BS.useAsCStringLen s $
                   \(p, len) -> c'bladerf_sync_tx (unDeviceHandle dev) p (fromIntegral $ len `div` 4) nullPtr (fromIntegral t)
      Just md -> do
                 -- Use the following instead when switching to ghc7.8 later..
                 -- bladeRFMetadataToCBladeRFMetadata :: BladeRFMetadata -> C'bladerf_metadata
                 let meta = C'bladerf_metadata { c'bladerf_metadata'timestamp    = timestamp md
                                               , c'bladerf_metadata'flags        = flags     md
                                               , c'bladerf_metadata'status       = status    md
                                               , c'bladerf_metadata'actual_count = (fromIntegral . count) md
                                               , c'bladerf_metadata'reserved     = [0]
                                               }
                 poke pmd meta
                 BS.useAsCStringLen s $
                   \(p, len) -> c'bladerf_sync_tx (unDeviceHandle dev) p (fromIntegral $ len `div` 4) pmd (fromIntegral t)
  return $ bladeRFErrorTy ret

-- | Receive IQ samples.
--
-- Underthe hood, this call starts up an underlying asynchronous stream as
-- needed. This stream can be stopped by disabling the RX module. (See
-- 'LibBladeRF.Utils.bladeRFEnableModule' for more details.)
--
-- Precondition:
--
--   1. A 'bladeRFSyncConfig' call has been to configure the device
--      for synchronous data transfer.
--
--   2. A call to 'LibBladeRF.Utils.bladeRFEnableModule' should be
--      made before attempting to transmit samples. Failing to do
--      this may result in timeouts and other errors.
bladeRFSyncRx :: DeviceHandle    -- ^ Device handle
              -> Int             -- ^ Number of samples to read
              -> Int             -- ^ Timeout (milliseconds) for this call to complete. Zero implies infinite.
              -> IO (BladeRFReturnType (BS.ByteString, BladeRFMetadata))
bladeRFSyncRx dev n t = alloca $ \pmd -> do
  par <- allocaBytes (4 * n) $ \ptr -> do
      ret <- c'bladerf_sync_rx (unDeviceHandle dev) ptr (fromIntegral n) pmd (fromIntegral t)
      if ret < 0 then (return . Left . toEnum . fromIntegral) ret -- C ret code to typed error
      else do
          ret <- peekArray (4 * n) ptr
          return $ Right ret
  case par of
    Left ret -> return $ Left ret
    Right par' -> do
      let bs = BS.pack par'
      cmd <- peek pmd
      -- Use the following instead when switching to ghc7.8 later..
      -- bladeRFMetadataFromCBladeRFMetadata :: C'bladerf_metadata -> BladeRFMetadata
      let meta = BladeRFMetadata { timestamp = c'bladerf_metadata'timestamp    cmd
                                 , flags     = c'bladerf_metadata'flags        cmd
                                 , status    = c'bladerf_metadata'status       cmd
                                 , count     = fromIntegral $ c'bladerf_metadata'actual_count cmd
                                 }
      return $ Right (bs, meta)
