{-# OPTIONS_GHC -cpp -fglasgow-exts #-}
--
-- Module      : Data.StorableVector.Base
-- License     : BSD-style
-- Maintainer  : dons@cse.unsw.edu.au
-- Stability   : experimental
-- Portability : portable, requires ffi and cpp
-- Tested with : GHC 6.4.1 and Hugs March 2005
-- 

-- | A module containing semi-public StorableVector internals. This exposes
-- the StorableVector representation and low level construction functions.
-- Modules which extend the StorableVector system will need to use this module
-- while ideally most users will be able to make do with the public interface
-- modules.
--
module Data.StorableVector.Base (

        -- * The @Vector@ type and representation
        Vector(..),             -- instances: Eq, Ord, Show, Read, Data, Typeable

        -- * Unchecked access
        unsafeHead,             -- :: Vector a -> a
        unsafeTail,             -- :: Vector a -> Vector a
        unsafeLast,             -- :: Vector a -> a
        unsafeInit,             -- :: Vector a -> Vector a
        unsafeIndex,            -- :: Vector a -> Int -> a
        unsafeTake,             -- :: Int -> Vector a -> Vector a
        unsafeDrop,             -- :: Int -> Vector a -> Vector a

        -- * Low level introduction and elimination
        create,                 -- :: Int -> (Ptr a -> IO ()) -> IO (Vector a)
        createAndTrim,          -- :: Int -> (Ptr a -> IO Int) -> IO (Vector a)
        createAndTrim',         -- :: Int -> (Ptr a -> IO (Int, Int, b)) -> IO (Vector a, b)

        unsafeCreate,           -- :: Int -> (Ptr a -> IO ()) ->  Vector a

        fromForeignPtr,         -- :: ForeignPtr a -> Int -> Vector a
        toForeignPtr,           -- :: Vector a -> (ForeignPtr a, Int, Int)

        inlinePerformIO

  ) where

import Foreign.Ptr              (Ptr)
import Foreign.ForeignPtr
import Foreign.Marshal.Array    (advancePtr, copyArray)
import Foreign.Storable         (Storable(..))

import Control.Exception        (assert)

#if defined(__GLASGOW_HASKELL__)
import qualified Foreign.Concurrent as FC (newForeignPtr)

import Data.Generics            (Data(..), Typeable(..))
import GHC.Ptr                  (Ptr(..))
import GHC.Base                 (realWorld#)
import GHC.IOBase

#if defined(__GLASGOW_HASKELL__) && !defined(SLOW_FOREIGN_PTR)
import GHC.ForeignPtr           (mallocPlainForeignPtrBytes)
#endif

#else
import Data.Char                (chr)
import System.IO.Unsafe         (unsafePerformIO)
#endif

-- CFILES stuff is Hugs only
{-# CFILES cbits/fpstring.c #-}

-- -----------------------------------------------------------------------------
--
-- Useful macros, until we have bang patterns
--

#define STRICT1(f) f a | a `seq` False = undefined
#define STRICT2(f) f a b | a `seq` b `seq` False = undefined
#define STRICT3(f) f a b c | a `seq` b `seq` c `seq` False = undefined
#define STRICT4(f) f a b c d | a `seq` b `seq` c `seq` d `seq` False = undefined
#define STRICT5(f) f a b c d e | a `seq` b `seq` c `seq` d `seq` e `seq` False = undefined

-- -----------------------------------------------------------------------------

-- | A space-efficient representation of a vector, supporting many efficient
-- operations.
--
-- Instances of Eq, Ord, Read, Show, Data, Typeable
--
data Vector a = SV {-# UNPACK #-} !(ForeignPtr a)
                   {-# UNPACK #-} !Int                -- offset
                   {-# UNPACK #-} !Int                -- length
#if defined(__GLASGOW_HASKELL__)
    deriving (Data, Typeable)
#endif

-- ---------------------------------------------------------------------
--
-- Extensions to the basic interface
--

-- | A variety of 'head' for non-empty Vectors. 'unsafeHead' omits the
-- check for the empty case, so there is an obligation on the programmer
-- to provide a proof that the Vector is non-empty.
unsafeHead :: (Storable a) => Vector a -> a
unsafeHead (SV x s l) = assert (l > 0) $
    inlinePerformIO $ withForeignPtr x $ \p -> peekElemOff p s
{-# INLINE unsafeHead #-}

-- | A variety of 'tail' for non-empty Vectors. 'unsafeTail' omits the
-- check for the empty case. As with 'unsafeHead', the programmer must
-- provide a separate proof that the Vector is non-empty.
unsafeTail :: (Storable a) => Vector a -> Vector a
unsafeTail (SV ps s l) = assert (l > 0) $ SV ps (s+1) (l-1)
{-# INLINE unsafeTail #-}

-- | A variety of 'last' for non-empty Vectors. 'unsafeLast' omits the
-- check for the empty case, so there is an obligation on the programmer
-- to provide a proof that the Vector is non-empty.
unsafeLast :: (Storable a) => Vector a -> a
unsafeLast (SV x _s l) = assert (l > 0) $
    inlinePerformIO $ withForeignPtr x $ \p -> peekElemOff p (l-1)
{-# INLINE unsafeLast #-}

-- | A variety of 'init' for non-empty Vectors. 'unsafeInit' omits the
-- check for the empty case. As with 'unsafeLast', the programmer must
-- provide a separate proof that the Vector is non-empty.
unsafeInit :: (Storable a) => Vector a -> Vector a
unsafeInit (SV ps s l) = assert (l > 0) $ SV ps s (l-1)
{-# INLINE unsafeInit #-}

-- | Unsafe 'Vector' index (subscript) operator, starting from 0, returning a
-- single element.  This omits the bounds check, which means there is an
-- accompanying obligation on the programmer to ensure the bounds are checked in
-- some other way.
unsafeIndex :: (Storable a) => Vector a -> Int -> a
unsafeIndex (SV x s l) i = assert (i >= 0 && i < l) $
    inlinePerformIO $ withForeignPtr x $ \p -> peekElemOff p (s+i)
{-# INLINE unsafeIndex #-}

-- | A variety of 'take' which omits the checks on @n@ so there is an
-- obligation on the programmer to provide a proof that @0 <= n <= 'length' xs@.
unsafeTake :: (Storable a) => Int -> Vector a -> Vector a
unsafeTake n (SV x s l) = assert (0 <= n && n <= l) $ SV x s n
{-# INLINE unsafeTake #-}

-- | A variety of 'drop' which omits the checks on @n@ so there is an
-- obligation on the programmer to provide a proof that @0 <= n <= 'length' xs@.
unsafeDrop :: (Storable a) => Int -> Vector a -> Vector a
unsafeDrop n (SV x s l) = assert (0 <= n && n <= l) $ SV x (s+n) (l-n)
{-# INLINE unsafeDrop #-}

-- ---------------------------------------------------------------------
-- Low level constructors

-- | /O(1)/ Build a Vector from a ForeignPtr
fromForeignPtr :: ForeignPtr a -> Int -> Vector a
fromForeignPtr fp l = SV fp 0 l

-- | /O(1)/ Deconstruct a ForeignPtr from a Vector
toForeignPtr (SV ps s l) = (ps, s, l)

-- | A way of creating Vectors outside the IO monad. The @Int@
-- argument gives the final size of the Vector. Unlike
-- 'createAndTrim' the Vector is not reallocated if the final size
-- is less than the estimated size.
unsafeCreate :: (Storable a) => Int -> (Ptr a -> IO ()) -> Vector a
unsafeCreate l f = unsafePerformIO (create l f)
{-# INLINE unsafeCreate #-}

-- | Wrapper of mallocForeignPtrArray.
create :: (Storable a) => Int -> (Ptr a -> IO ()) -> IO (Vector a)
create l f = do
#if defined(SLOW_FOREIGN_PTR) || !defined(__GLASGOW_HASKELL__)
    fp <- mallocForeignPtrArray l
#else
    fp <- mallocPlainForeignPtrArray l
#endif
    withForeignPtr fp $ \p -> f p
    return $! SV fp 0 l

-- | Given the maximum size needed and a function to make the contents
-- of a Vector, createAndTrim makes the 'Vector'. The generating
-- function is required to return the actual final size (<= the maximum
-- size), and the resulting byte array is realloced to this size.
--
-- createAndTrim is the main mechanism for creating custom, efficient
-- Vector functions, using Haskell or C functions to fill the space.
--
createAndTrim :: (Storable a) => Int -> (Ptr a -> IO Int) -> IO (Vector a)
createAndTrim l f = do
#if defined(SLOW_FOREIGN_PTR) || !defined(__GLASGOW_HASKELL__)
    fp <- mallocForeignPtrArray l
#else
    fp <- mallocPlainForeignPtrArray l
#endif
    withForeignPtr fp $ \p -> do
        l' <- f p
        if assert (l' <= l) $ l' >= l
            then return $! SV fp 0 l
            else create l' $ \p' -> copyArray p' p l'

createAndTrim' :: (Storable a) => Int 
                               -> (Ptr a -> IO (Int, Int, b))
                               -> IO (Vector a, b)
createAndTrim' l f = do
#if defined(SLOW_FOREIGN_PTR) || !defined(__GLASGOW_HASKELL__)
    fp <- mallocForeignPtrArray l
#else
    fp <- mallocPlainForeignPtrArray l
#endif
    withForeignPtr fp $ \p -> do
        (off, l', res) <- f p
        if assert (l' <= l) $ l' >= l
            then return $! (SV fp 0 l, res)
            else do ps <- create l' $ \p' -> copyArray p' (p `advancePtr` off) l'
                    return $! (ps, res)

-- | Just like unsafePerformIO, but we inline it. Big performance gains as
-- it exposes lots of things to further inlining. /Very unsafe/. In
-- particular, you should do no memory allocation inside an
-- 'inlinePerformIO' block. On Hugs this is just @unsafePerformIO@.
--
{-# INLINE inlinePerformIO #-}
inlinePerformIO :: IO a -> a
#if defined(__GLASGOW_HASKELL__)
inlinePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
#else
inlinePerformIO = unsafePerformIO
#endif