{-# LANGUAGE ScopedTypeVariables, FlexibleContexts #-}

{-| Utility functions for serializing and deserializing samples. -}
module SDR.Serialize (

    -- * Slow Serialization\/Deserialization
    -- | Slow functions for serializing\/deserializing vectors to\/from bytestrings using the Cereal library. There must be a better way to do this that doesn't involve copying.

    -- ** Floats
    floatVecToByteString,
    floatVecFromByteString,

    -- ** Doubles
    doubleVecToByteString,
    doubleVecFromByteString,

    -- * Fast Serialization\/Deserialization
    -- | Fast functions for serializing\/deserializing storable vectors to\/from bytestrings.
    toByteString,
    fromByteString,

    -- * Pipes
    -- | Pipes that perform fast serialization/deserialization to a Handle.
    toHandle,
    fromHandle
    ) where

import           Foreign.ForeignPtr
import           Foreign.Storable
import           Data.ByteString.Internal
import           Data.ByteString          as BS
import           System.IO

import qualified Data.Vector.Generic      as VG hiding ((++))
import qualified Data.Vector.Storable     as VS hiding ((++))

import           Pipes
import qualified Pipes.Prelude            as P
import qualified Pipes.ByteString         as PB
import           Data.Serialize                 hiding (Done)
import qualified Data.Serialize           as S

-- | Convert a Vector of Floats to a ByteString.
floatVecToByteString    :: VG.Vector v Float  => v Float -> ByteString
floatVecToByteString vect = runPut $ VG.mapM_ putFloat32le vect

-- | Convert a Vector of Doubles to a ByteString.
doubleVecToByteString   :: VG.Vector v Double => v Double -> ByteString
doubleVecToByteString vect = runPut $ VG.mapM_ putFloat64le vect

-- | Convert a ByteString to a Vector of Floats.
floatVecFromByteString  :: VG.Vector v Float  => ByteString -> v Float
floatVecFromByteString bs = VG.unfoldrN (BS.length bs `div` 4) go bs
    where
    go bs = case runGetPartial getFloat32le bs of
                Fail _ _    -> Nothing
                Partial _   -> error "floatVecFromByteString: Partial"
                S.Done r b  -> Just (r, b)

-- | Convert a ByteString to a Vector of Doubles.
doubleVecFromByteString  :: VG.Vector v Double  => ByteString -> v Double
doubleVecFromByteString bs = VG.unfoldrN (BS.length bs `div` 8) go bs
    where
    go bs = case runGetPartial getFloat64le bs of
                Fail _ _    -> Nothing
                Partial _   -> error "doubleVecFromByteString"
                S.Done r b  -> Just (r, b)

-- | Convert a Vector of Storable values to a ByteString. This is fast as it is just a cast.
toByteString :: forall a. Storable a => VS.Vector a -> ByteString
toByteString dat = let (fp, o, sz) = VS.unsafeToForeignPtr dat in PS (castForeignPtr fp) o (sz * sizeOf (undefined :: a))

-- | Convert a ByteString to a Vector of Storable values. This is fast as it is just a cast.
fromByteString :: forall a. Storable a => ByteString -> VS.Vector a
fromByteString  (PS fp o l) = VS.unsafeFromForeignPtr (castForeignPtr fp) o (l `quot` sizeOf (undefined :: a))

-- | Given a Handle, create a Consumer that dumps the Vectors written to it to a Handle.
toHandle :: (Storable a) => Handle -> Consumer (VS.Vector a) IO ()
toHandle handle = P.map toByteString >-> PB.toHandle handle

-- | Given a Handle, create a Producer that creates Vectors from data read from the Handle.
fromHandle :: forall a. (Storable a) => Int -> Handle -> Producer (VS.Vector a) IO ()
fromHandle samples handle = PB.hGet (samples * sizeOf (undefined :: a)) handle >-> P.map fromByteString