{-# LANGUAGE BangPatterns #-}
module Foreign.Wrapper
    ( Wrap, mkWrap, consWrap
    , exportWrap
    , exportWrapWithABI
    , exportWrapWithCIF

    , Wrapper, wrap, wrapper
    ) where

import Foreign.LibFFI.Dynamic.Base
import Foreign.LibFFI.Dynamic.CIF
import Foreign.LibFFI.Dynamic.Closure
import Foreign.LibFFI.Dynamic.FFIType
import Foreign.Marshal
import Foreign.Ptr
import Foreign.Storable

newtype Wrap a b = Wrap
    { prepWrapper :: a -> Ptr (Ptr ()) -> Ptr (SigReturn b) -> IO ()
    }

mkWrap :: OutRet a b -> Wrap (IO b) (IO a)
mkWrap ret = Wrap
    { prepWrapper = \fun _ p -> fun >>= pokeRet ret p
    }

infixr 5 `consWrap`

consWrap :: InArg a b -> Wrap c d -> Wrap (b -> c) (a -> d)
consWrap arg wrap = wrap
    { prepWrapper =
        \fun args ret -> do
            arg0 <- peek args
            withInArg arg (castPtr arg0)
                (\arg -> prepWrapper wrap
                    (fun arg)
                    (plusPtr args (sizeOf args))
                     ret)
    }

fromEntry :: Entry -> FunPtr a
fromEntry (Entry p) = castFunPtr p

exportWrap :: SigType b => Wrap a b -> a -> IO (FunPtr b)
exportWrap = exportWrapWithABI defaultABI

exportWrapWithABI :: SigType b => ABI -> Wrap a b -> a -> IO (FunPtr b)
exportWrapWithABI = exportWrapWithCIF . cifWithABI

exportWrapWithCIF :: CIF b -> Wrap a b -> a -> IO (FunPtr b)
exportWrapWithCIF !cif !wrap !fun = do
    impl <- wrap_FFI_Impl $ \_ ret args _ -> prepWrapper wrap fun args ret

    alloca $ \entryPtr -> do
        closure <- ffi_closure_alloc sizeOfClosure entryPtr
        entry <- peek entryPtr

        ffi_prep_closure_loc closure cif impl nullPtr entry

        return (fromEntry entry)

wrapper :: Wrapper a => a -> IO (FunPtr a)
wrapper = exportWrap stdWrap

class SigType a => Wrapper a where
    stdWrap :: Wrap a a

wrap :: Wrapper a => Wrap a a
wrap = stdWrap

instance RetType a => Wrapper (IO a) where
    stdWrap = mkWrap outRet

instance (ArgType a, Wrapper b) => Wrapper (a -> b) where
    stdWrap = consWrap inArg stdWrap