{-|
Module      : Gegl.Buffer
Description : Functions for Buffer manipulation
Copyright   : (c) Amedeo Molnár, 2015-2016
License     : LGPL-3
Maintainer  : nek0@nek0.eu
Stability   : experimental
Portability : POSIX

This module contains all functions necessary for manipulate 'GeglBuffer's.
Contents is roughly comparable to the functions provided by @gegl-buffer.h@ in
gegl-0.3.8.
-}
{-# LANGUAGE RecordWildCards #-}
module GEGL.Buffer
  ( FFI.GeglBuffer(..)
  , gegl_buffer_new
  , gegl_buffer_default_new
  , gegl_buffer_save
  , gegl_buffer_load
  , gegl_buffer_flush
  , gegl_buffer_create_sub_buffer
  , gegl_buffer_get_extent
  , gegl_buffer_set_extent
  -- | Some convenience functions that operate on the basis of
  -- 'gegl_buffer_get_extent'.
  , gegl_buffer_get_x
  , gegl_buffer_get_y
  , gegl_buffer_get_height
  , gegl_buffer_get_width
  , gegl_buffer_get_pixel_count
  -- | Returning to main functions
  , gegl_buffer_set_color
  , gegl_buffer_set_pattern
  -- , gegl_buffer_iterator_new
  , gegl_buffer_clear
  , gegl_buffer_get
  ) where

import qualified GEGL.FFI.Buffer as FFI
import qualified GEGL.FFI.Rectangle as FFI (GeglRectangle(..))
import qualified GEGL.FFI.Color as FFI (GeglColor(..))

import GEGL.Enums (GeglAccessMode, GeglAbyssPolicy)

import BABL.Format (PixelFormat, babl_format)
import BABL.FFI.Format (BablFormatPtr(..))

import Foreign.Storable (peek)
import Foreign.C.String
import Foreign.C.Types (CDouble(..), CInt(..))
import Foreign.Ptr (nullPtr, Ptr(..))
import Foreign.Marshal (free)
import Foreign.Marshal.Utils (new)
import Foreign.Marshal.Alloc (malloc)

-- | Create a new 'GeglBuffer'.
gegl_buffer_new
  :: Maybe FFI.GeglRectangle -- ^ Geometry of the buffer. If 'Nothing' is passed
                             -- , the buffer is created empty
  -> BablFormatPtr           -- ^ Pixel format of the buffer
  -> IO FFI.GeglBuffer
gegl_buffer_new mrect (BablFormatPtr format) = do
  ptr <- case mrect of
    Just rect -> new rect
    Nothing   -> return nullPtr
  val <- FFI.GeglBuffer <$> FFI.c_gegl_buffer_new ptr format
  free ptr
  return val

-- | Creates a new 'GeglBuffer', which defaults to an empty buffer with the 'RGBAfloat' format.
gegl_buffer_default_new :: IO FFI.GeglBuffer
gegl_buffer_default_new = FFI.GeglBuffer <$> FFI.c_gegl_buffer_new nullPtr nullPtr

-- | Create a new 'GeglBuffer' from a backend. If @Nothing@ is passed as geometry,
--   The extent will be inherited from the backend
gegl_buffer_new_for_backend
  :: Maybe FFI.GeglRectangle -- ^ Geometry
  -> Ptr a                   -- ^ Backend
  -> IO FFI.GeglBuffer       -- ^ New buffer
gegl_buffer_new_for_backend mext backend = do
  rectPtr <- case mext of
    Just ext -> new ext
    Nothing  -> return nullPtr
  buf <- FFI.GeglBuffer <$> FFI.c_gegl_buffer_new_for_backend rectPtr backend
  free rectPtr
  return buf

-- | Open an existing on-disk 'GeglBuffer'. The buffer is opened in a monitored
--   state so multiple instances of gegl can share the same buffer. Sets on one
--   buffer are reflected in th eother.
gegl_buffer_open
  :: FilePath          -- ^ Path to buffer
  -> IO FFI.GeglBuffer -- ^ The buffer
gegl_buffer_open fp = do
  cfp <- newCString fp
  ret <- FFI.GeglBuffer <$> FFI.c_gegl_buffer_open cfp
  free cfp
  return ret

-- | Write a 'GeglBuffer' to a file.
gegl_buffer_save
  :: FFI.GeglBuffer     -- Buffer to save
  -> FilePath           -- Path where to save
  -> FFI.GeglRectangle  -- Region of interest to write
  -> IO ()
gegl_buffer_save (FFI.GeglBuffer buf) fp roi = do
  croi <- new roi
  cfp  <- newCString fp
  FFI.c_gegl_buffer_save buf cfp croi
  free croi
  free cfp

-- | Load an existing buffer from disk.
gegl_buffer_load
  :: FilePath          -- File path to saved buffer
  -> IO FFI.GeglBuffer -- Resulting buffer
gegl_buffer_load path = do
  cpath <- newCString path
  ret <- FFI.GeglBuffer <$> FFI.c_gegl_buffer_load cpath
  free cpath
  return ret

-- | Flush all unsaved data to disk. This is not necessary for shared 'GeglBuffer's
--   opened with "gegl_buffer_open" since they auto-sync on writes.
gegl_buffer_flush
  :: FFI.GeglBuffer -- ^ Buffer to flush
  -> IO ()
gegl_buffer_flush (FFI.GeglBuffer buf) =
  FFI.c_gegl_buffer_flush buf

-- | Create a new sub 'GeglBuffer', that is a view on a larger buffer.
gegl_buffer_create_sub_buffer
  :: FFI.GeglBuffer    -- ^ Parent buffer
  -> FFI.GeglRectangle -- ^ Coordinates of new buffer
  -> IO FFI.GeglBuffer -- ^ Child buffer
gegl_buffer_create_sub_buffer (FFI.GeglBuffer buf) rect = do
  crect <- new rect
  ret <- FFI.GeglBuffer <$> FFI.c_gegl_buffer_create_sub_buffer buf crect
  free crect
  return ret

-- | Retrieve position and size of the area considered active in a buffer.
gegl_buffer_get_extent
  :: FFI.GeglBuffer       -- ^ The buffer
  -> IO FFI.GeglRectangle -- ^ active area
gegl_buffer_get_extent (FFI.GeglBuffer buffer) = do
  rectPtr <- FFI.c_gegl_buffer_get_extent buffer
  rect <- peek rectPtr
  -- free rectPtr
  return rect

-- | Change postition and size of the area considered active in a buffer.
gegl_buffer_set_extent
  :: FFI.GeglBuffer    -- ^ The buffer
  -> FFI.GeglRectangle -- ^ New extent of the buffer
  -> IO Bool           -- ^ Returns 'True' on success, otherwise 'False'
gegl_buffer_set_extent (FFI.GeglBuffer buffer) rect = do
  ptr <- new rect
  val <- FFI.c_gegl_buffer_set_extent buffer ptr
  free ptr
  return val

-- | get X coordinate of the upper left corner of a buffer
gegl_buffer_get_x
  :: FFI.GeglBuffer -- ^ The buffer
  -> IO Int
gegl_buffer_get_x buffer = do
  FFI.GeglRectangle{..} <- gegl_buffer_get_extent buffer
  return rectangleX

-- | get Y coordinate of the upper left corner of a buffer
gegl_buffer_get_y
  :: FFI.GeglBuffer -- ^ The buffer
  -> IO Int
gegl_buffer_get_y buffer = do
  FFI.GeglRectangle{..} <- gegl_buffer_get_extent buffer
  return rectangleY

-- | get height of a buffer
gegl_buffer_get_height
  :: FFI.GeglBuffer -- ^ The buffer
  -> IO Int
gegl_buffer_get_height buffer = do
  FFI.GeglRectangle{..} <- gegl_buffer_get_extent buffer
  return rectangleHeight

-- | get width of a buffer
gegl_buffer_get_width
  :: FFI.GeglBuffer -- ^ The buffer
  -> IO Int
gegl_buffer_get_width buffer = do
  FFI.GeglRectangle{..} <- gegl_buffer_get_extent buffer
  return rectangleWidth

-- | get pixel count of a buffer
gegl_buffer_get_pixel_count
  :: FFI.GeglBuffer -- ^ The buffer
  -> IO Int
gegl_buffer_get_pixel_count buffer = do
  FFI.GeglRectangle{..} <- gegl_buffer_get_extent buffer
  return $ rectangleWidth * rectangleHeight

-- | Sets a region covered by a 'GeglRectangle' to a specified colour.
gegl_buffer_set_color
  :: FFI.GeglBuffer    -- ^ The buffer
  -> FFI.GeglRectangle -- ^ Area of the buffer to fill
  -> FFI.GeglColor     -- ^ The colour to fill with
  -> IO ()
gegl_buffer_set_color (FFI.GeglBuffer buffer) rect (FFI.GeglColor color) = do
  rectPtr <- new rect
  FFI.c_gegl_buffer_set_color buffer rectPtr color
  free rectPtr

-- | Fill a region with a repeating pattern.
gegl_buffer_set_pattern
  :: FFI.GeglBuffer    -- ^ Target buffer
  -> FFI.GeglRectangle -- ^ Area of target buffer to fill
  -> FFI.GeglBuffer    -- ^ A buffer to be repeated as pattern
  -> Double            -- ^ X offset
  -> Double            -- ^ Y offset
  -> IO ()
gegl_buffer_set_pattern
  (FFI.GeglBuffer buffer)
  rect
  (FFI.GeglBuffer pattern)
  xoff
  yoff
  = do
    rectPtr <- new rect
    let (cx, cy) = (CDouble xoff, CDouble yoff)
    FFI.c_gegl_buffer_set_pattern
      buffer
      rectPtr
      pattern
      cx
      cy
    free rectPtr

-- | Clear a provided rectangular region by setting all associated memory to 0.
gegl_buffer_clear
  :: FFI.GeglBuffer    -- ^ Target buffer
  -> FFI.GeglRectangle -- ^ Area to clear
  -> IO ()
gegl_buffer_clear (FFI.GeglBuffer buf) rect = do
  rectPtr <- new rect
  FFI.c_gegl_buffer_clear buf rectPtr
  free rectPtr

-- | Fetch a rectangular linear buffer of pixel data from the 'GeglBuffer',
-- convert it if neccessary and push it into a memory location.
gegl_buffer_get
  :: FFI.GeglBuffer          -- ^ Buffer to retrieve data from
  -> Maybe FFI.GeglRectangle -- ^ Coordinates to retrieve data from. If set to
                             -- 'Nothing' the whole buffer is taken.
  -> Double                  -- ^ Sampling scale
  -> Maybe BablFormatPtr     -- ^ Format to convert the data to. If set to 'Nothing'
                             -- no conversion is performed.
  -> Ptr a                   -- The memory destination to write the data to.
  -> Int                     -- Rowstride in bytes or @0@ to compute it
                             -- automatically from format
  -> GeglAbyssPolicy         -- Abyss policy
  -> IO ()
gegl_buffer_get (FFI.GeglBuffer buf) mrect scale mformat dest stride ap = do
  rect <- case mrect of
    Just r -> new r
    Nothing -> return nullPtr
  let format = case mformat of
        Just (BablFormatPtr ptr) -> ptr
        Nothing -> nullPtr
  FFI.c_gegl_buffer_get
    buf
    rect
    (CDouble scale)
    format
    dest
    (CInt $ fromIntegral stride)
    (CInt $ fromIntegral $ fromEnum ap)
  free rect