-- | Media with a content stored in a 'Vector'
module Data.MediaBus.Media.Buffer
  ( type CanBeSample
  , HasMediaBuffer(..)
  , type HasMediaBuffer'
  , type HasMediaBufferL
  , type HasMediaBufferL'
  , mediaBuffer'
  , MediaBuffer(..)
  , mediaBufferVector
  , mediaBufferLength
  , mediaBufferFromList
  , mediaBufferToList
  , mediaBufferFromByteString
  , mediaBufferToByteString
  , createMediaBuffer
  , modifyMediaBuffer
  , unsafeModifyMediaBuffer
  ) where

import Control.DeepSeq
import Control.Lens
import Control.Monad.ST (ST, runST)
import qualified Data.ByteString as B
import Data.Default
import Data.MediaBus.Media.Samples
import Data.Typeable
import qualified Data.Vector.Storable as V
import qualified Data.Vector.Storable.ByteString as Spool
import GHC.Exts (IsList(..))
import GHC.Generics (Generic)

-- ** Types using 'MediaBuffer'
-- | Types containing a 'MediaBuffer'
class HasMediaBuffer s t where
  -- | The media buffer type contained in 's'
  type MediaBufferFrom s
  -- | The type that results from changing the media buffer type in 's' to 'b'
  type MediaBufferTo t
  -- | A lens for converting the media buffer
  mediaBuffer :: Lens s t (MediaBufferFrom s) (MediaBufferTo t)

-- | Like 'HasMediaBuffer' but with @s ~ t@ and @MediaBufferFrom s ~ MediaBufferTo t@
type HasMediaBuffer' s = (HasMediaBuffer s s, MediaBufferFrom s ~ MediaBufferTo s)

-- | Like 'mediaBuffer' but with @s ~ t@ and @MediaBufferFrom s ~ MediaBufferTo t@
mediaBuffer'
  :: HasMediaBuffer' s
  => Lens' s (MediaBufferFrom s)
mediaBuffer' = mediaBuffer

-- | Like 'HasMediaBuffer' but with the typical lens type parameters @s t a b@
type HasMediaBufferL s t a b = (HasMediaBuffer s t, MediaBufferFrom s ~ a, MediaBufferTo t ~ b)

-- | Like 'HasMediaBufferL' but with the typical **simple** lens type parameters @s a@
type HasMediaBufferL' s a = (HasMediaBuffer s s, MediaBufferFrom s ~ a, MediaBufferTo s ~ a)

-- | A buffer for media data. This is just a newtype wrapper around 'V.Vector'.
newtype MediaBuffer t = MkMediaBuffer
  { _mediaBufferVector :: V.Vector t
  } deriving (Generic, NFData, Monoid, Eq)

-- | 'MediaBuffer' to 'Vector' isomorphism
mediaBufferVector :: Iso (MediaBuffer s) (MediaBuffer t) (V.Vector s) (V.Vector t)
mediaBufferVector = iso _mediaBufferVector MkMediaBuffer

-- | Traversal instance
instance (V.Storable a, V.Storable b) =>
         Each (MediaBuffer a) (MediaBuffer b) a b where
  each = mediaBufferVector . each

-- ** Instances
instance CanBeSample s =>
         IsList (MediaBuffer s) where
  type Item (MediaBuffer s) = s
  fromList = mediaBufferFromList
  toList = mediaBufferToList

instance (Show a, CanBeSample a) =>
         Show (MediaBuffer a) where
  showsPrec d m =
    showParen (d > 10) $
    showString "media buffer: " .
    showsPrec 8 (mediaBufferLength m) .
    showString " * " . showsPrec 8 (typeRep (Proxy :: Proxy a))

instance CanBeSample sampleType =>
         Default (MediaBuffer sampleType) where
  def = mempty

-- | Return the number of 'BufferElement's in the 'MediaBuffer' buffer.
mediaBufferLength
  :: CanBeSample t
  => MediaBuffer t -> Int
mediaBufferLength = view (mediaBufferVector . to V.length)

-- ** Conversion
-- *** List Conversion
-- | Convert the media buffer vector contents to a list in O(n).
mediaBufferToList
  :: CanBeSample s
  => MediaBuffer s -> [s]
mediaBufferToList = view (mediaBufferVector . to V.toList)

-- | Create a 'MediaBuffer' from a list in O(n).
mediaBufferFromList
  :: CanBeSample s
  => [s] -> MediaBuffer s
mediaBufferFromList = MkMediaBuffer . V.fromList

-- *** 'ByteString' Conversion
-- | An efficient conversion of a 'ByteString' to a 'MediaBuffer'
mediaBufferFromByteString
  :: CanBeSample a
  => B.ByteString -> MediaBuffer a
mediaBufferFromByteString = MkMediaBuffer . Spool.byteStringToVector

-- | An efficient conversion of a 'MediaBuffer' to a 'ByteString'
mediaBufferToByteString
  :: CanBeSample a
  => MediaBuffer a -> B.ByteString
mediaBufferToByteString = Spool.vectorToByteString . _mediaBufferVector

-- ** Construction
-- | Create a new 'MediaBuffer' using an 'ST` action that returns a mutable
-- 'MVector'.
createMediaBuffer
  :: CanBeSample t
  => (forall s. ST s (V.MVector s t)) -> MediaBuffer t
createMediaBuffer f = MkMediaBuffer (V.create f)

-- ** Fast manipulation
-- | Modify the underlying 'Vector' of some 'MediaBuffer' with a function that is
-- applied to the mutable vector of that 'MediaBuffer'. The function must result
-- in a 'ST' action that does the modifications.
modifyMediaBuffer
  :: CanBeSample a
  => (forall s. V.MVector s a -> ST s ()) -> MediaBuffer a -> MediaBuffer a
modifyMediaBuffer f = over mediaBufferVector (V.modify f)

-- | Modify the underlying 'Vector' of some 'MediaBuffer' with a function that is
-- applied to the mutable vector of that 'MediaBuffer'. The function must result
-- in a 'ST' action that does the modifications.
--
-- Unsafe because results can be returned, so the /thawn/ mutable vector might escape.
unsafeModifyMediaBuffer
  :: CanBeSample a
  => (forall s. V.MVector s a -> ST s r) -> MediaBuffer a -> (r, MediaBuffer a)
unsafeModifyMediaBuffer f (MkMediaBuffer !v) =
  runST $ do
    !mv <- V.unsafeThaw v
    !r <- f mv
    !v' <- V.unsafeFreeze mv
    return (r, MkMediaBuffer v')