-- | A media 'Stream' is often represented by a 'Series' of related 'Frame's
-- with a 'FrameCtx' created when the 'Stream' 'Start's. This module contains
-- the basic definitions of the building blocks of media content that is create,
-- processed, and presented by a possibly infinite 'Series' of chunks called
-- 'Frame's.
module Data.MediaBus.Media.Stream
  ( FrameCtx(..)
  , frameCtxSourceId
  , frameCtxSeqNumRef
  , frameCtxTimestampRef
  , frameCtxInit
  , EachFrameCtxInit(..)
  , Frame(..)
  , EachFrameContent(..)
  , frameSeqNum
  , frameTimestamp
  , framePayload
  , Stream(..)
  , type Streamish
  , stream
  ) where

import Control.DeepSeq (NFData)
import Control.Lens
import Data.Default
import Data.MediaBus.Basics.Sequence
import Data.MediaBus.Basics.Series
import Data.MediaBus.Basics.Ticks
import Data.MediaBus.Media.Channels
import Data.MediaBus.Media.Media
import GHC.Generics (Generic)
import Test.QuickCheck

-- | Meta information about a media stream.
data FrameCtx i s t p = MkFrameCtx
  { _frameCtxSourceId :: !i -- ^ An identifier for the stream, e.g. a track name
                           -- or an RTP SSRC
  , _frameCtxTimestampRef :: !t -- ^ The start time stamp of a stream all time
                               -- stamps in the 'Frame's are relative to this.
  , _frameCtxSeqNumRef :: !s -- ^ The start sequence number of a stream all
                            -- sequence numbers in the 'Frame's are relative to
                            -- this.
  , _frameCtxInit :: !p -- ^ An extra field for media type specific extra
                       -- information, like the init segment of an ISOBMFF
                       -- media.
  } deriving (Eq, Ord, Generic)

instance (NFData i, NFData s, NFData t, NFData p) =>
         NFData (FrameCtx i s t p)

makeLenses ''FrameCtx

-- | Class for types that have a 'Traversal' for '_frameCtxInit'
class EachFrameCtxInit s t where
  type FrameCtxInitFrom s
  type FrameCtxInitTo t
  -- | A traversal for the 'FrameCtx' initial content, skipping over any 'Frame'
  eachFrameCtxInit :: Traversal s t (FrameCtxInitFrom s) (FrameCtxInitTo t)

instance HasTimestamp (FrameCtx i s t p) where
  type GetTimestamp (FrameCtx i s t p) = t
  type SetTimestamp (FrameCtx i s t p) t' = (FrameCtx i s t' p)
  timestamp = frameCtxTimestampRef

instance HasDuration (FrameCtx i s t p) where
  getDuration _ = 0

instance HasSeqNum (FrameCtx i s t p) where
  type GetSeqNum (FrameCtx i s t p) = s
  type SetSeqNum (FrameCtx i s t p) x = FrameCtx i x t p
  seqNum = frameCtxSeqNumRef

instance (Arbitrary i, Arbitrary s, Arbitrary t, Arbitrary p) =>
         Arbitrary (FrameCtx i s t p) where
  arbitrary = MkFrameCtx <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary

instance (Default i, Default s, Default t, Default p) =>
         Default (FrameCtx i s t p) where
  def = MkFrameCtx def def def def

instance (Show i, Show s, Show t, Show p) =>
         Show (FrameCtx i s t p) where
  showsPrec d (MkFrameCtx sid tsr snr spr) =
    showParen (d > 10) $
    showString "frame context: " .
    showsPrec 11 sid .
    showChar ' ' .
    showsPrec 11 snr .
    showChar ' ' . showsPrec 11 tsr . showChar ' ' . showsPrec 11 spr

-- | A 'Frame' can be anything that has a start time and is exactly one time
-- unit long, it can respresent anything ranging from an audio buffer with 20ms
-- of audio to a single pulse coded audio sample, of course it could also be a
-- video frame or a chat message.
data Frame s t c = MkFrame
  { _frameTimestamp :: !t -- ^ The timestamp at which the representation of the
                         -- media content in this frame **begins**
  , _frameSeqNum :: !s -- ^ The sequence number of that frame, useful to detect
                      -- missing frames
  , _framePayload :: !c -- ^ The actual (media) content.
  } deriving (Eq, Ord, Generic)

instance (NFData c, NFData s, NFData t) =>
         NFData (Frame s t c)

deriving instance Functor (Frame s t)

makeLenses ''Frame

-- | Class for types that have a 'Traversal' for '_framePayload'
class EachFrameContent s t where
  type FrameContentFrom s
  type FrameContentTo t
  -- | A traversal for the frame content of all 'Frame's, skipping over any
  -- 'FrameCtx'
  eachFrameContent :: Traversal s t (FrameContentFrom s) (FrameContentTo t)

instance EachFrameContent (Frame s t c) (Frame s t c') where
  type FrameContentFrom (Frame s t c) = c
  type FrameContentTo (Frame s t c') = c'
  eachFrameContent = framePayload

instance (EachChannel c c') =>
         EachChannel (Frame s t c) (Frame s t c') where
  type ChannelsFrom (Frame s t c) = c
  type ChannelsTo (Frame s t c') = c'
  eachChannel = framePayload

instance (HasMedia c c') =>
         HasMedia (Frame s t c) (Frame s t c') where
  type MediaFrom (Frame s t c) = MediaFrom c
  type MediaTo (Frame s t c') = MediaTo c'
  media = framePayload . media

instance HasTimestamp (Frame s t c) where
  type GetTimestamp (Frame s t c) = t
  type SetTimestamp (Frame s t c) t' = Frame s t' c
  timestamp = frameTimestamp

instance HasSeqNum (Frame s t c) where
  type GetSeqNum (Frame s t c) = s
  type SetSeqNum (Frame s t c) x = Frame x t c
  seqNum = frameSeqNum

instance HasDuration c =>
         HasDuration (Frame s t c) where
  getDuration = getDuration . _framePayload

instance (Arbitrary c, Arbitrary s, Arbitrary t) =>
         Arbitrary (Frame s t c) where
  arbitrary = MkFrame <$> arbitrary <*> arbitrary <*> arbitrary

instance (Default s, Default t, Default c) =>
         Default (Frame s t c) where
  def = MkFrame def def def

instance (Show s, Show t, Show v) =>
         Show (Frame s t v) where
  showsPrec d (MkFrame ts sn v) =
    showParen (d > 10) $
    showString "frame: " .
    showsPrec 11 sn .
    showChar ' ' . showsPrec 11 ts . showChar ' ' . showsPrec 11 v

-- | A type for values that belong to a 'Series' of 'Frame's started by a
-- 'FrameCtx'. This combines the sum type 'Series' which has either 'Start' or
-- 'Next' with 'FrameCtx' and 'Frame'.
newtype Stream i s t p c = MkStream
  { _stream :: Streamish i s t p c
  } deriving (Ord, Eq, Arbitrary, Generic)

instance (NFData i, NFData s, NFData t, NFData c, NFData p) =>
         NFData (Stream i s t p c)

-- | This is the type alias that 'Stream' is a newtype wrapper of, see the
-- description of 'Stream'.
type Streamish i s t p c = Series (FrameCtx i s t p) (Frame s t c)

makeLenses ''Stream

instance EachChannel (Frame s t c) (Frame s t c') =>
         EachChannel (Stream i s t p c) (Stream i s t p c') where
  type ChannelsFrom (Stream i s t p c) = ChannelsFrom (Frame s t c)
  type ChannelsTo (Stream i s t p c') = ChannelsTo (Frame s t c')
  eachChannel = stream . _Next . eachChannel

instance HasDuration c =>
         HasDuration (Stream i s t p c) where
  getDuration = maybe 0 getDuration . preview (stream . _Next)

instance HasSeqNum (Stream i s t p c) where
  type GetSeqNum (Stream i s t p c) = s
  type SetSeqNum (Stream i s t p c) x = Stream i x t p c
  seqNum = stream . seqNum

instance HasTimestamp (Stream i s t p c) where
  type GetTimestamp (Stream i s t p c) = t
  type SetTimestamp (Stream i s t p c) t' = Stream i s t' p c
  timestamp = stream . timestamp

instance EachFrameContent (Stream i s t p c) (Stream i s t p c') where
  type FrameContentFrom (Stream i s t p c) = c
  type FrameContentTo (Stream i s t p c') = c'
  eachFrameContent = stream . _Next . framePayload

instance (Default c, Default s, Default t) =>
         Default (Stream i s t p c) where
  def = MkStream (Next (MkFrame def def def))

instance (Show i, Show s, Show t, Show c, Show p) =>
         Show (Stream i s t p c) where
  showsPrec d (MkStream s) = showsPrec d s