{-# LANGUAGE BangPatterns #-}
module Foreign.Dynamic
    ( Dyn, mkDyn, consDyn
    , importDyn
    , importDynWithABI
    , importDynWithCIF
    , importDynWithCall

    , Dynamic, dynamic, dyn
    ) where

import Control.Exception
import Foreign.LibFFI.Dynamic.CIF
import Foreign.LibFFI.Dynamic.FFIType
import Foreign.Marshal
import Foreign.Ptr
import Foreign.Storable

type Call t = Ptr (SigReturn t) -> Ptr (Ptr ()) -> IO ()
type WithArgs = Int -> (Ptr (Ptr ()) -> IO ()) -> IO ()

newtype Dyn a b = Dyn
    { prepDynamic :: Int -> WithArgs -> Call a -> b
    }

instance Functor (Dyn a) where
    fmap f (Dyn prep) = Dyn (\n withArgs call -> f (prep n withArgs call))

mkDyn :: InRet a b -> Dyn (IO a) (IO b)
mkDyn ret = Dyn
    { prepDynamic = \n withArgs call ->
        withInRet ret (withArgs n . call)
    }

infixr 5 `consDyn`

consDyn :: OutArg a b -> Dyn c d -> Dyn (a -> c) (b -> d)
consDyn arg dyn = dyn
    { prepDynamic = \i withArgs call x ->
        let withMoreArgs n action =
                withOutArg arg x $ \p ->
                    withArgs n $ \args -> do
                        pokeElemOff args i (castPtr p)
                        action args
         in prepDynamic dyn (i+1) withMoreArgs call
    }

importDyn :: SigType a => Dyn a b -> FunPtr a -> b
importDyn = importDynWithCIF cif

importDynWithABI :: SigType a => ABI -> Dyn a b -> FunPtr a -> b
importDynWithABI = importDynWithCIF . cifWithABI

importDynWithCIF :: CIF a -> Dyn a b -> FunPtr a -> b
importDynWithCIF = importDynWithCall . callWithCIF

importDynWithCall :: (FunPtr a -> Ptr (SigReturn a) -> Ptr (Ptr ()) -> IO ()) -> Dyn a b -> FunPtr a -> b
importDynWithCall !call !dyn =
    prepDynamic dyn 0 withArgs . call
        where
            withArgs n = bracket (mallocArray n) free

dynamic :: Dynamic a => FunPtr a -> a
dynamic = importDyn stdDyn

dyn :: Dynamic a => Dyn a a
dyn = stdDyn

class SigType a => Dynamic a where
    stdDyn :: Dyn a a

instance RetType a => Dynamic (IO a) where
    stdDyn = mkDyn inRet

instance (ArgType a, Dynamic b) => Dynamic (a -> b) where
    stdDyn = consDyn outArg stdDyn