{-# LANGUAGE TypeFamilies #-}
-- | Use 1-length mutable storable vectors for mutable references.
--
-- Motivated by: <http://stackoverflow.com/questions/27261813/why-is-my-little-stref-int-require-allocating-gigabytes> and ArrayRef.
module Data.Mutable.SRef
    ( -- * Types
      SRef
    , IOSRef
      -- * Functions
    , asSRef
    , MutableRef (..)
    ) where

import           Data.Mutable.Class
import Foreign.ForeignPtr
import Foreign.Storable
import Control.Monad.Primitive

-- | A storable vector reference, supporting any monad.
--
-- Since 0.2.0
newtype SRef s a = SRef (ForeignPtr a)

-- |
-- Since 0.2.0
asSRef :: SRef s a -> SRef s a
asSRef x = x
{-# INLINE asSRef #-}

-- | A storable IO vector reference.
type IOSRef = SRef (PrimState IO)

instance MutableContainer (SRef s a) where
    type MCState (SRef s a) = s
instance Storable a => MutableRef (SRef s a) where
    type RefElement (SRef s a) = a

    newRef x = unsafePrimToPrim $ do
        fptr <- mallocForeignPtr
        withForeignPtr fptr $ flip poke x
        return $! SRef fptr
    {-# INLINE newRef#-}

    readRef (SRef fptr) = unsafePrimToPrim $ withForeignPtr fptr peek
    {-# INLINE readRef #-}

    writeRef (SRef fptr) x = unsafePrimToPrim $ withForeignPtr fptr $ flip poke x
    {-# INLINE writeRef #-}

    modifyRef (SRef fptr) f = unsafePrimToPrim $ withForeignPtr fptr $ \ptr ->
        peek ptr >>= poke ptr . f
    {-# INLINE modifyRef #-}

    modifyRef' = modifyRef
    {-# INLINE modifyRef' #-}