module Bindings.Utilities (

    GlobalVariable, writeGlobalVariable, readGlobalVariable,
    Callback(..)

  ) where

import Foreign
import Foreign.C
import Data.Int

-- | Haskell FFI imports global variables as pointers. To
-- ease manipulation of such pointers they are encapsulated
-- by 'GlobalVariable' so that values can be reached
-- directly, much like in an "Data.IORef".

newtype (Storable a) => GlobalVariable a = GlobalVariable (Ptr a)

writeGlobalVariable :: (Storable a) => GlobalVariable a -> a -> IO ()
writeGlobalVariable (GlobalVariable p) v = poke p v

readGlobalVariable :: (Storable a) => GlobalVariable a -> IO a
readGlobalVariable (GlobalVariable p) = peek p

-- | When libraries provide types for functions those
-- types are made instances of class 'Callback'. That
-- class is used to exchange between Haskell functions
-- and a representation (i.e., a hidden pointer) that
-- can be used or is provided by foreign code.

class (Storable cb) => Callback cb where

    -- | The associated type is the function type
    -- as it is used in Haskell.

    type F cb :: *

    -- | 'nullCallback' can be used like 'Foreign.Ptr.nullFunPtr'.

    nullCallback :: cb

    -- | 'makeCallback' takes a Haskell function and
    -- gives a representation of it in the form of the
    -- type expected by foreign code.

    makeCallback :: F cb -> IO cb

    -- | 'freeCallback' should be called on all values returned
    -- by 'makeCallback' after they are no longer going to be
    -- used. Most of the time this class method will just use
    -- 'Foreign.Ptr.freeHaskellFunPtr'.

    freeCallback :: cb -> IO ()

    -- | 'withCallback' just inserts an action between
    -- calls to 'makeCallback' and 'freeCallback'.
    -- Of course, it can't be used when foreign code
    -- will save such action for latter use.

    withCallback :: F cb -> (cb -> IO a) -> IO a
    withCallback f c = do
        made <- makeCallback f
        result <- c made
        freeCallback made
        return result