{-# LANGUAGE RecordWildCards #-}

{-| Stream samples from a Realtek RTL2832U based device -}
module SDR.RTLSDRStream (
    RTLSDRParams(..),
    defaultRTLSDRParams,
    setRTLSDRParams,
    sdrStream,
    sdrStreamFromDevice
    ) where

import           Control.Monad
import           Control.Monad.Trans.Except
import           Data.Word
import           Data.Int
import           Foreign.ForeignPtr
import           Foreign.C.Types
import           Control.Concurrent         hiding (yield)
import           Foreign.Marshal.Utils
import qualified Data.Vector.Storable       as VS

import           Pipes
import           Pipes.Concurrent
import           RTLSDR

-- | RTLSDR configuration parameters
data RTLSDRParams = RTLSDRParams {
    centerFreq     :: Word32,
    sampleRate     :: Word32,
    freqCorrection :: Int32,
    tunerGain      :: Maybe Int32
}

-- | Some reasonable default parameters
defaultRTLSDRParams :: Word32       -- ^ Frequency
                    -> Word32       -- ^ Sample rate
                    -> RTLSDRParams
defaultRTLSDRParams freq sampleRate = RTLSDRParams freq sampleRate 0 Nothing

-- | Set the configuration parameters for a device
setRTLSDRParams :: RTLSDR       -- ^ Device handle
                -> RTLSDRParams -- ^ Parameters
                -> IO ()
setRTLSDRParams dev RTLSDRParams{..} = do
    setCenterFreq     dev centerFreq
    setSampleRate dev sampleRate
    setFreqCorrection dev freqCorrection
    case tunerGain of
        Nothing -> setTunerGainMode dev False
        Just g  -> setTunerGainMode dev True  >> setTunerGain dev g
    return ()

-- | Returns a producer that streams data from a Realtek RTL2832U based device. You probably want to use `interleavedIQUnsigned256ToFloat` to turn it into a list of complex Floats. This function initializes and configures the device for you. Use `sdrStreamFromDevice` if you need more control over how the device is configured or want to configure it yourself.
sdrStream :: RTLSDRParams                                          -- ^ Configuration parameters
          -> Word32                                                -- ^ Number of buffers
          -> Word32                                                -- ^ Buffer length
          -> ExceptT String IO (Producer (VS.Vector CUChar) IO ()) -- ^ Either a string describing the error that occurred or the Producer
sdrStream params bufNum bufLen = do
    lift $ putStrLn "Initializing RTLSDR device..."

    dev' <- lift $ open 0
    dev  <- maybe (throwE "Failed to open device") return dev'

    lift $ do
        t <- getTunerType dev
        putStrLn $ "Found a: " ++ show t
        setRTLSDRParams dev params
        sdrStreamFromDevice dev bufNum bufLen

-- | Returns a producer that streams data from a Realtek RTL2832U based device. You probably want to use `interleavedIQUnsigned256ToFloat` to turn it into a list of complex Floats. This function takes a pre-configured device handle to stream from.
sdrStreamFromDevice :: RTLSDR                                 -- ^ Device handle
                    -> Word32                                 -- ^ Number of buffers
                    -> Word32                                 -- ^ Buffer length
                    -> IO (Producer (VS.Vector CUChar) IO ()) -- ^ The producer
sdrStreamFromDevice dev bufNum bufLen = do
    resetBuffer dev

    (output, input) <- spawn unbounded

    forkOS $ void $ readAsync dev bufNum bufLen $ \dat num -> void $ do
        let numBytes = fromIntegral $ bufNum * bufLen
        fp <- mallocForeignPtrArray numBytes
        withForeignPtr fp $ \fpp -> moveBytes fpp dat numBytes
        let v = VS.unsafeFromForeignPtr0 fp numBytes
        atomically (send output v)

    return $ fromInput input