module Sound.Frame
   (C(..),
   numberOfChannelsFoldable,
   sizeOfElementFoldable,
   sizeOfElementType,
   paddedSizeOf,
   withSignal,
   ) where

import Data.Word (Word8, Word16, Word32, )
import Data.Int (Int8, Int16, Int32, )
import Foreign.Storable (Storable, sizeOf, alignment, )

import qualified Data.Foldable as Fold


{- |
This is a class for nested tuples used as sample frames.

Should we make Storable a superclass of 'C'?
-}
class C y where
   {- | The argument is not touched and can be undefined -}
   numberOfChannels :: y -> Int
   {- |
   Size of elements.
   In a nested record type, like @Stereo (Stereo a)@,
   it is the size of the atomic element, in our example @a@.
   We assume that the atomic element values all have the same size,
   such that @sizeOfElement undefined@ is defined.
   -}
   sizeOfElement :: y -> Int

{- |
Default implementations for a foldable Frame.
-}
numberOfChannelsFoldable ::
   (C y, Fold.Foldable f) => f y -> Int
numberOfChannelsFoldable =
   Fold.foldl' (\n y -> n + numberOfChannels y) 0
--   Fold.sum . fmap numberOfChannels

sizeOfElementFoldable ::
   (C y, Fold.Foldable f) => f y -> Int
sizeOfElementFoldable =
   sizeOfElement . head . Fold.toList

{- |
Returns the size of an undefined element.
This might be more efficient than 'sizeOfElementFoldable'.
-}
sizeOfElementType ::
   (C y) => f y -> Int
sizeOfElementType =
   sizeOfElement . elementType


{-# INLINE elementType #-}
elementType :: f a -> a
elementType _ =
   error "Sound.Frame.sizeOfElement may not depend on element values"


instance C Word8 where
   numberOfChannels _ = 1
   sizeOfElement = sizeOf

instance C Int8 where
   numberOfChannels _ = 1
   sizeOfElement = sizeOf

instance C Word16 where
   numberOfChannels _ = 1
   sizeOfElement = sizeOf

instance C Int16 where
   numberOfChannels _ = 1
   sizeOfElement = sizeOf

instance C Word32 where
   numberOfChannels _ = 1
   sizeOfElement = sizeOf

instance C Int32 where
   numberOfChannels _ = 1
   sizeOfElement = sizeOf

instance C Float where
   numberOfChannels _ = 1
   sizeOfElement = sizeOf

instance C Double where
   numberOfChannels _ = 1
   sizeOfElement = sizeOf


{- |
Space that an element consumes in a Storable Array.
This is space for the element plus padding.
-}
-- cf. storable-record:FixedArray.roundUp
paddedSizeOf :: Storable a => a -> Int
paddedSizeOf x =
   sizeOf x + mod (- sizeOf x) (alignment x)


withSignal :: (y -> a) -> (sig y -> a)
withSignal f _ = f (error "Frame: dummy signal parameter touched")