{-# LANGUAGE ForeignFunctionInterface, JavaScriptFFI, UnliftedFFITypes,
             MagicHash, PolyKinds, BangPatterns
  #-}
{-|
    GHCJS implements the ByteArray# primitive with a JavaScript object
    containing an ArrayBuffer and various TypedArray views. This module
    contains utilities for manipulating and converting the buffer as
    a JavaScript object.

    None of the properties of a Buffer object should be written to in foreign
    code. Changing the contents of a MutableBuffer in foreign code is allowed.
 -}

-- fixme alignment not done yet!
module GHCJS.Buffer
    ( Buffer
    , MutableBuffer
    , create
    , createFromArrayBuffer
    , thaw, freeze, clone
      -- * JavaScript properties
    , byteLength
    , getArrayBuffer
    , getUint8Array
    , getUint16Array
    , getInt32Array
    , getDataView
    , getFloat32Array
    , getFloat64Array
      -- * primitive
    , toByteArray, fromByteArray
    , toByteArrayPrim, fromByteArrayPrim
    , toMutableByteArray, fromMutableByteArray
    , toMutableByteArrayPrim, fromMutableByteArrayPrim
      -- * bytestring
    , toByteString, fromByteString
      -- * pointers
    , toPtr, unsafeToPtr
    ) where

import GHC.Exts (ByteArray#, MutableByteArray#, Addr#, Ptr(..), Any)

import GHCJS.Buffer.Types
import GHCJS.Prim
import GHCJS.Internal.Types

import Data.Int
import Data.Word
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Unsafe as BS
import qualified Data.ByteString.Internal as BS
import Data.Primitive.ByteArray

import qualified JavaScript.TypedArray.Internal.Types as I
import           JavaScript.TypedArray.ArrayBuffer.Internal (SomeArrayBuffer)
import           JavaScript.TypedArray.DataView.Internal    (SomeDataView)
import qualified JavaScript.TypedArray.Internal as I

import GHC.ForeignPtr

create :: Int -> IO MutableBuffer
create n | n >= 0    = js_create n
         | otherwise = error "create: negative size"
{-# INLINE create #-}

createFromArrayBuffer :: SomeArrayBuffer any -> SomeBuffer any
createFromArrayBuffer buf = js_wrapBuffer buf
{-# INLINE createFromArrayBuffer #-}

getArrayBuffer :: SomeBuffer any -> SomeArrayBuffer any
getArrayBuffer buf = js_getArrayBuffer buf
{-# INLINE getArrayBuffer #-}

getInt32Array :: SomeBuffer any -> I.SomeInt32Array any
getInt32Array buf = js_getInt32Array buf
{-# INLINE getInt32Array #-}

getUint8Array :: SomeBuffer any -> I.SomeUint8Array any
getUint8Array buf = js_getUint8Array buf
{-# INLINE getUint8Array #-}

getUint16Array :: SomeBuffer any -> I.SomeUint16Array any
getUint16Array buf = js_getUint16Array buf
{-# INLINE getUint16Array #-}

getFloat32Array :: SomeBuffer any -> I.SomeFloat32Array any
getFloat32Array buf = js_getFloat32Array buf
{-# INLINE getFloat32Array #-}

getFloat64Array :: SomeBuffer any -> I.SomeFloat64Array any
getFloat64Array buf = js_getFloat64Array buf
{-# INLINE getFloat64Array #-}

getDataView :: SomeBuffer any -> SomeDataView any
getDataView buf = js_getDataView buf
{-# INLINE getDataView #-}

freeze :: MutableBuffer -> IO Buffer
freeze x = js_clone x
{-# INLINE freeze #-}

thaw :: Buffer -> IO MutableBuffer
thaw buf  = js_clone buf
{-# INLINE thaw #-}

clone :: MutableBuffer -> IO (SomeBuffer any2)
clone buf = js_clone buf
{-# INLINE clone #-}

fromByteArray :: ByteArray -> Buffer
fromByteArray (ByteArray ba) = fromByteArrayPrim ba
{-# INLINE fromByteArray #-}

toByteArray :: Buffer -> ByteArray
toByteArray buf = ByteArray (toByteArrayPrim buf)
{-# INLINE toByteArray #-}

fromMutableByteArray :: MutableByteArray s -> Buffer
fromMutableByteArray (MutableByteArray mba) = fromMutableByteArrayPrim mba
{-# INLINE fromMutableByteArray #-}

fromByteArrayPrim :: ByteArray# -> Buffer
fromByteArrayPrim ba = SomeBuffer (js_fromByteArray ba)
{-# INLINE fromByteArrayPrim #-}

toByteArrayPrim :: Buffer -> ByteArray#
toByteArrayPrim buf = js_toByteArray buf
{-# INLINE toByteArrayPrim #-}

fromMutableByteArrayPrim :: MutableByteArray# s -> Buffer
fromMutableByteArrayPrim mba = SomeBuffer (js_fromMutableByteArray mba)
{-# INLINE fromMutableByteArrayPrim #-}

toMutableByteArray :: Buffer -> MutableByteArray s
toMutableByteArray buf = MutableByteArray (toMutableByteArrayPrim buf)
{-# INLINE toMutableByteArray #-}

toMutableByteArrayPrim :: Buffer -> MutableByteArray# s
toMutableByteArrayPrim (SomeBuffer buf) = js_toMutableByteArray buf
{-# INLINE toMutableByteArrayPrim #-}

-- | Convert a 'ByteString' into a triple of (buffer, offset, length)
-- Warning: if the 'ByteString''s internal 'ForeignPtr' has a
-- finalizer associated with it, the returned 'Buffer' will not count
-- as a reference for the purpose of determining when that finalizer
-- should run.
fromByteString :: ByteString -> (Buffer, Int, Int)
fromByteString (BS.PS fp off len) =
  -- not super happy with this.  What if the bytestring's foreign ptr
  -- has a nontrivial finalizer attached to it?  I don't think there's
  -- a way to do that without someone else messing with the PS constructor
  -- directly though.
  let !(Ptr addr) = unsafeForeignPtrToPtr fp
  in (js_fromAddr addr, off, len)
{-# INLINE fromByteString #-}

-- | Wrap a 'Buffer' into a 'ByteString' using the given offset
-- and length.
toByteString :: Int -> Maybe Int -> Buffer -> ByteString
toByteString off _ buf
  | off < 0                    = error "toByteString: negative offset"
  | off > byteLength buf       = error "toByteString: offset past end of buffer"
toByteString off (Just len) buf
  | len < 0                    = error "toByteString: negative length"
  | len > byteLength buf - off = error "toByteString: length past end of buffer"
  | otherwise                  = unsafeToByteString off len buf
toByteString off Nothing buf   = unsafeToByteString off (byteLength buf - off) buf

unsafeToByteString :: Int -> Int -> Buffer -> ByteString
unsafeToByteString off len buf@(SomeBuffer bufRef) =
  let fp = ForeignPtr (js_toAddr buf) (PlainPtr (js_toMutableByteArray bufRef))
  in BS.PS fp off len

toPtr :: MutableBuffer -> Ptr a
toPtr buf = Ptr (js_toAddr buf)
{-# INLINE toPtr #-}

unsafeToPtr :: Buffer -> Ptr a
unsafeToPtr buf = Ptr (js_toAddr buf)
{-# INLINE unsafeToPtr #-}

byteLength :: SomeBuffer any -> Int
byteLength buf = js_byteLength buf
{-# INLINE byteLength #-}

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

foreign import javascript unsafe
  "h$newByteArray" js_create :: Int -> IO MutableBuffer
foreign import javascript unsafe
  "h$wrapBuffer" js_wrapBuffer :: SomeArrayBuffer any -> SomeBuffer any
foreign import javascript unsafe
  "h$wrapBuffer($1.buf.slice($1.u8.byteOffset, $1.len))"
  js_clone :: SomeBuffer any1 -> IO (SomeBuffer any2)
foreign import javascript unsafe
  "$1.len" js_byteLength :: SomeBuffer any -> Int
foreign import javascript unsafe
  "$1.buf" js_getArrayBuffer    :: SomeBuffer any -> SomeArrayBuffer any
foreign import javascript unsafe
  "$1.i3" js_getInt32Array      :: SomeBuffer any -> I.SomeInt32Array any
foreign import javascript unsafe
  "$1.u8" js_getUint8Array      :: SomeBuffer any -> I.SomeUint8Array  any
foreign import javascript unsafe
  "$1.u1" js_getUint16Array     :: SomeBuffer any -> I.SomeUint16Array any
foreign import javascript unsafe
  "$1.f3" js_getFloat32Array    :: SomeBuffer any -> I.SomeFloat32Array  any
foreign import javascript unsafe
  "$1.f6" js_getFloat64Array    :: SomeBuffer any -> I.SomeFloat64Array any
foreign import javascript unsafe
  "$1.dv" js_getDataView        :: SomeBuffer any -> SomeDataView any

-- ----------------------------------------------------------------------------
-- these things have the same representation (modulo boxing),
-- conversion is free

foreign import javascript unsafe
  "$r = $1;" js_toByteArray          :: SomeBuffer any      -> ByteArray#
foreign import javascript unsafe
  "$r = $1;" js_fromByteArray        :: ByteArray#          -> JSVal
foreign import javascript unsafe
  "$r = $1;" js_fromMutableByteArray :: MutableByteArray# s -> JSVal
foreign import javascript unsafe
  "$r = $1;" js_toMutableByteArray   :: JSVal               -> MutableByteArray# s
foreign import javascript unsafe
  "$r1 = $1; $r2 = 0;"  js_toAddr    :: SomeBuffer any      -> Addr#
foreign import javascript unsafe
  "$r = $1;" js_fromAddr             :: Addr#               -> SomeBuffer any