{-# LANGUAGE ScopedTypeVariables #-}
module Graphics.OpenGLES.Buffer (
  -- * Buffer
  -- ** Constructing Mutable Buffers
  Buffer,
  glNewBuffer, glLoadList, glLoadBS,
  -- ** Updating Mutable Buffers
  unsafeObtainStorableArray, withStorableArraySize,
  glRenewBuffer, glReloadList, glReloadBS,
  BufferUsage, app2gl, app2glDyn, app2glStream,
  gl2app, gl2appDyn, gl2appStream, gl2gl, gl2glDyn, gl2glStream,
  -- ** Raw Buffer Operations
  bindBuffer, bindBufferRange, bindBufferBase,
  bufferData, bufferSubData, 
  -- | /3+ | GL_OES_mapbuffer/ glUnmapBufferOES
  unmapBuffer,
  -- | /GL_OES_mapbuffer/
  -- (*GL_APIENTRY glMapBufferOES (GLenum target, GLenum access);
  -- define GL_WRITE_ONLY_OES                 0x88B9
  -- | /3+ | GL_EXT_map_buffer_range/
  -- glMapBufferRangeEXT glFlushMappedBufferRangeEXT
  mapBufferRange, flashMappedBufferRange,
  map_read_bit, map_write_bit, map_invalidate_range_bit,
  map_invalidate_buffer_bit, map_flush_explicit_bit, map_unsynchronized_bit,
  -- | /3+ | GL_NV_copy_buffer/ glCopyBufferSubDataNV
  copyBufferSubData,
  BufferSlot, array_buffer, element_array_buffer,
  pixel_pack_buffer, pixel_unpack_buffer,
  uniform_buffer, transform_feedback_buffer,
  copy_read_buffer, copy_write_buffer
  ) where
import Control.Applicative
import Data.Array.Base (getNumElements)
import Data.Array.Storable
import Data.Array.Storable.Internals
import qualified Data.ByteString as B
import qualified Data.ByteString.Internal as B
import Data.IORef
import Graphics.OpenGLES.Base
import Graphics.OpenGLES.Internal
import Foreign

-- ** Constructing Mutable Buffers

glNewBuffer :: forall a. Storable a => BufferUsage -> Int -> GL (Buffer a)
glNewBuffer usage elems = do
	array <- newArray_ (0, elems)
	glo <- newBuffer
	bufferData array_buffer usage (elems * sizeOf (undefined :: a)) nullPtr
	Buffer usage glo <$> newIORef array

glLoadList
	:: forall a. Storable a => BufferUsage
	-> (Int, Int)
	-> [a]
	-> GL (Buffer a)
glLoadList usage ix xs = do
	array <- newListArray ix xs
	glo <- newBuffer
	withStorableArraySize array $ \size ptr ->
		bufferData array_buffer usage size ptr
	Buffer usage glo <$> newIORef array

glLoadBS :: forall a. Storable a => BufferUsage -> B.ByteString -> GL (Buffer a)
glLoadBS usage bs@(B.PS foreignPtr offset len) = do
	let fp | offset == 0 = foreignPtr
	       | otherwise = case B.copy bs of (B.PS f _ _) -> f
	let elems = (len `div` sizeOf (undefined :: a))
	let array = StorableArray 0 (elems-1) elems (castForeignPtr fp)
	glo <- newBuffer
	withForeignPtr fp $ \ptr ->
		bufferData array_buffer usage len ptr
	Buffer usage glo <$> newIORef array


-- ** Updating Mutable Buffers


unsafeObtainStorableArray :: Buffer a -> IO (StorableArray Int a)
unsafeObtainStorableArray (Buffer _ _ arr) = readIORef arr

withStorableArraySize
	:: forall i e a. Storable e
	=> StorableArray i e -> (Int -> Ptr e -> IO a) -> IO a
withStorableArraySize (StorableArray _ _ n fp) f =
	withForeignPtr fp (f size)
	where size = n * sizeOf (undefined :: e)

-- Performance hint http://www.opentk.com/node/1930
glRenewBuffer :: forall a. Storable a => BufferUsage -> Int -> Buffer a -> GL ()
glRenewBuffer usage elems buf@(Buffer _ _ ref) = do
	array <- newArray_ (0, elems)
	bindBuffer array_buffer buf
	bufferData array_buffer usage (elems * sizeOf (undefined :: a)) nullPtr
	writeIORef ref array

glReloadList :: forall a. Storable a =>
	BufferUsage -> (Int, Int) -> [a] -> Buffer a -> GL ()
glReloadList usage ix xs buf@(Buffer _ _ ref) = do
	array <- newListArray ix xs
	bindBuffer array_buffer buf
	withStorableArraySize array $ \size ptr -> do
		bufferData array_buffer usage size ptr
	writeIORef ref array

glReloadBS :: forall a. Storable a =>
	BufferUsage -> B.ByteString -> Buffer a -> GL ()
glReloadBS usage bs@(B.PS foreignPtr offset len) buf = do
	let fp | offset == 0 = foreignPtr
	       | otherwise = case B.copy bs of (B.PS f _ _) -> f
	let elems = (len `div` sizeOf (undefined :: a))
	let array = StorableArray 0 (elems-1) elems (castForeignPtr fp)
	withForeignPtr fp $ \ptr -> do
		--bufferData array_buffer usage 0 nullPtr
		bufferData array_buffer usage len ptr


-- ** Buffer Slots

-- | STATIC_DRAW (Default)
app2gl = BufferUsage 0x88E4
-- | DYNAMIC_DRAW
app2glDyn = BufferUsage 0x88E8
-- | STREAM_DRAW
app2glStream = BufferUsage 0x88E0
-- *** GL ES 3+
-- | STATIC_READ
gl2app = BufferUsage 0x88E5
-- | DYNAMIC_READ
gl2appDyn = BufferUsage 0x88E9
-- | STREAM_READ
gl2appStream = BufferUsage 0x88E1
-- | STATIC_COPY
gl2gl = BufferUsage 0x88E6
-- | DYNAMIC_COPY
gl2glDyn = BufferUsage 0x88EA
-- | STREAM_COPY
gl2glStream = BufferUsage 0x88E2


-- ** Raw Buffer Operations

bindBuffer :: BufferSlot -> Buffer a -> GL ()
bindBuffer (BufferSlot target) (Buffer _ glo _) =
	glBindBuffer target . fst =<< readIORef glo

bindBufferRange :: BufferSlot -> GLuint -> Buffer a -> Int -> Int -> GL ()
bindBufferRange (BufferSlot t) index (Buffer _ glo _) offset size = do
	buf <- fmap fst $ readIORef glo
	glBindBufferRange t index buf offset size

bindBufferBase :: BufferSlot -> GLuint -> Buffer a -> GL ()
bindBufferBase (BufferSlot t) index (Buffer _ glo _) = do
	glBindBufferBase t index . fst =<< readIORef glo

bufferData :: BufferSlot -> BufferUsage -> Int -> Ptr a -> GL ()
bufferData (BufferSlot target) (BufferUsage usage) size ptr =
	glBufferData target size (castPtr ptr) usage

bufferSubData :: BufferSlot -> Int -> Int -> Ptr a -> GL ()
bufferSubData (BufferSlot target) offset size ptr =
	glBufferSubData target offset size (castPtr ptr)

-- *** 3+ | GL_OES_mapbuffer glUnmapBufferOES
unmapBuffer :: BufferSlot -> GL Bool
unmapBuffer (BufferSlot target) =
	glUnmapBuffer target >>= return . (/= 0)

-- *** GL_OES_mapbuffer 
-- (*GL_APIENTRY glMapBufferOES (GLenum target, GLenum access);
-- define GL_WRITE_ONLY_OES                 0x88B9

-- *** 3+ | GL_EXT_map_buffer_range
-- glMapBufferRangeEXT glFlushMappedBufferRangeEXT
mapBufferRange :: BufferSlot -> Int -> Int -> GLbitfield -> GL (Ptr a)
mapBufferRange (BufferSlot target) offset size access =
	fmap castPtr $ glMapBufferRange target offset size access

flashMappedBufferRange :: BufferSlot -> Int -> Int -> GL ()
flashMappedBufferRange (BufferSlot target) offset size =
	glFlushMappedBufferRange target offset size

map_read_bit = 1 :: GLbitfield
map_write_bit = 2 :: GLbitfield
map_invalidate_range_bit = 4 :: GLbitfield
map_invalidate_buffer_bit = 8 :: GLbitfield
map_flush_explicit_bit = 16 :: GLbitfield
map_unsynchronized_bit = 32 :: GLbitfield

-- *** 3+ | GL_NV_copy_buffer glCopyBufferSubDataNV
copyBufferSubData :: BufferSlot -> BufferSlot -> Int -> Int -> Int -> GL ()
copyBufferSubData (BufferSlot read) (BufferSlot write) roffset woffset size =
	glCopyBufferSubData read write roffset woffset size

array_buffer = BufferSlot 0x8892
element_array_buffer = BufferSlot 0x8893
-- *** 3+
pixel_pack_buffer = BufferSlot 0x88EB
pixel_unpack_buffer = BufferSlot 0x88EC
uniform_buffer = BufferSlot 0x8A11
transform_feedback_buffer = BufferSlot 0x8C8E
-- *** 3+ | GL_NV_copy_buffer
copy_read_buffer = BufferSlot 0x8F36
copy_write_buffer = BufferSlot 0x8F37