content: {-# LANGUAGE CPP, GeneralizedNewtypeDeriving, OverloadedStrings #-}
  module Haste.Binary.Types (
      Ix, ArrView, Blob (..), BlobData (..),
      blobSize, blobDataSize, toByteString, fromByteString, toBlob, strToBlob,
      toUArray, fromUArray
    ) where
  import Haste.Prim
  import Haste.Prim.Foreign
  import Haste.Foreign.Array
  import qualified Data.ByteString.Lazy as BS
  import Data.Array.Unboxed
  #ifndef __HASTE__
  import qualified Data.ByteString.UTF8 as BU
  #else
  import System.IO.Unsafe
  #endif
  
  #ifdef __HASTE__
  -- | In a browser context, BlobData is essentially a DataView, with an
  --   accompanying offset and length for fast slicing.
  --   In a server context, it is simply a 'BS.ByteString'.
  data BlobData = BlobData Int Int JSAny
  
  -- | A JavaScript Blob on the client, a 'BS.ByteString' on the server.
  newtype Blob = Blob JSAny deriving (ToAny, FromAny)
  
  -- | The size, in bytes, of the contents of the given blob.
  blobSize :: Blob -> Int
  blobSize = unsafePerformIO . ffi "(function(b){return b.size;})"
  
  -- | The size, in bytes, of the contents of the given blob data.
  blobDataSize :: BlobData -> Int
  blobDataSize (BlobData _ len _) = len
  
  -- | Convert a 'BlobData' to an unboxed array; client-side only.
  toUArray :: (Ix i, ArrView e) => BlobData -> UArray i e
  toUArray (BlobData from to buf) = unsafePerformIO $ toUArray' from to buf
  
  -- | Convert a an unboxed array into a 'Blob'; client-side only.
  fromUArray :: (Ix i, ArrView e) => UArray i e -> Blob
  fromUArray = unsafePerformIO . fromUArray'
  
  fromUArray' :: (Ix i, ArrView e) => UArray i e -> IO Blob
  fromUArray' = ffi "(function(arr){return new Blob([arr]);})"
  
  toUArray' :: (Ix i, ArrView e) => Int -> Int -> JSAny -> IO (UArray i e)
  toUArray' = ffi "(function(from, to, buf){return new Uint8Array(buf.buffer.slice(from, to+from));})"
  
  -- | Convert a BlobData to a ByteString. Only usable server-side.
  toByteString :: BlobData -> BS.ByteString
  toByteString =
    error "Haste.Binary.Types.toByteString called in browser context!"
  
  -- | Convert a ByteString to a BlobData. Only usable server-side.
  fromByteString :: BS.ByteString -> BlobData
  fromByteString =
    error "Haste.Binary.Types.toByteString called in browser context!"
  
  -- | Convert a piece of BlobData back into a Blob.
  toBlob :: BlobData -> Blob
  toBlob (BlobData 0 len buf) =
    case newBlob buf of
      b | blobSize b > len -> sliceBlob b 0 len
        | otherwise        -> b
  toBlob (BlobData off len buf) =
    sliceBlob (newBlob buf) off (off+len)
  
  -- | Create a Blob from a JSString.
  strToBlob :: JSString -> Blob
  strToBlob = newBlob . toAny
  
  sliceBlob :: Blob -> Int -> Int -> Blob
  sliceBlob b off len = unsafePerformIO $ jsSlice b off len
  
  jsSlice :: Blob -> Int -> Int -> IO Blob
  jsSlice = ffi "(function(b,off,len){return b.slice(off,len);})"
  
  newBlob :: JSAny -> Blob
  newBlob = unsafePerformIO . jsNewBlob
  
  jsNewBlob :: JSAny -> IO Blob
  jsNewBlob =
    ffi "(function(b){try {return new Blob([b]);} catch (e) {return new Blob([b.buffer]);}})"
  #else
  
  -- | In a browser context, BlobData is essentially a DataView, with an
  --   accompanying offset and length for fast slicing.
  --   In a server context, it is simply a 'BS.ByteString'.
  newtype BlobData = BlobData BS.ByteString
  
  -- | A JavaScript Blob on the client, a 'BS.ByteString' on the server.
  newtype Blob = Blob BS.ByteString
  
  -- Never used except for type checking
  clientOnly :: a
  clientOnly = error "ToAny/FromAny only usable client-side!"
  instance ToAny BlobData where toAny = clientOnly
  instance FromAny BlobData where fromAny = clientOnly
  instance ToAny Blob where toAny = clientOnly
  instance FromAny Blob where fromAny = clientOnly
  
  -- | Convert a 'BlobData' to an unboxed array; client-side only.
  toUArray :: (Ix i, ArrView e) => BlobData -> UArray i e
  toUArray _ = error "toUArray only usable client-side!"
  
  -- | Convert a an unboxed array into a 'Blob'; client-side only.
  fromUArray :: (Ix i, ArrView e) => UArray i e -> Blob
  fromUArray = error "fromUArray only usable client-side!"
  
  -- | The size, in bytes, of the contents of the given blob.
  blobSize :: Blob -> Int
  blobSize (Blob b) = fromIntegral $ BS.length b
  
  -- | The size, in bytes, of the contents of the given blob data.
  blobDataSize :: BlobData -> Int
  blobDataSize (BlobData bd) = fromIntegral $ BS.length bd
  
  -- | Convert a BlobData to a ByteString. Only usable server-side.
  toByteString :: BlobData -> BS.ByteString
  toByteString (BlobData bd) = bd
  
  -- | Convert a ByteString to a BlobData. Only usable server-side.
  fromByteString :: BS.ByteString -> BlobData
  fromByteString = BlobData
  
  -- | Convert a piece of BlobData back into a Blob.
  toBlob :: BlobData -> Blob
  toBlob (BlobData bs) = Blob bs
  
  -- | Create a Blob from a JSString.
  strToBlob :: JSString -> Blob
  strToBlob s = Blob $ BS.fromChunks [BU.fromString $ fromJSStr s]
  
  #endif
  