{-# LANGUAGE
    ScopedTypeVariables,
    TypeFamilies,
    Rank2Types
  #-}

module Graphics.QML.Internal.Marshal where

import Graphics.QML.Internal.Types
import Graphics.QML.Internal.BindPrim
import Graphics.QML.Internal.BindObj

import Control.Exception (SomeException(SomeException), catch)
import Control.Monad.Trans.Maybe
import Data.Maybe
import Data.Tagged
import Foreign.Ptr
import System.IO

type ErrIO a = MaybeT IO a

runErrIO :: ErrIO a -> IO ()
runErrIO m = do
  r <- catch (runMaybeT m) $ \(SomeException _) -> return Nothing
  if isNothing r
  then hPutStrLn stderr "Warning: Marshalling error."
  else return ()

errIO :: IO a -> ErrIO a
errIO = MaybeT . fmap Just

tyInt, tyDouble, tyString, tyObject, tyVoid, tyJSValue :: TypeId
tyInt     = TypeId 2
tyDouble  = TypeId 6
tyString  = TypeId 10
tyObject  = TypeId 39
tyVoid    = TypeId 43
tyJSValue = TypeId hsqmlJValTypeId

type MTypeCValFunc t = Tagged t TypeId
type MFromCValFunc t = Ptr () -> ErrIO t
type MToCValFunc t = t -> Ptr () -> IO ()
type MWithCValFunc t = (forall b. t -> (Ptr () -> IO b) -> IO b)

type MFromJValFunc t = Strength -> HsQMLJValHandle -> ErrIO t
type MWithJValFunc t = (forall b. t -> (HsQMLJValHandle -> IO b) -> IO b)

type MFromHndlFunc t = HsQMLObjectHandle -> IO t
type MToHndlFunc t = t -> IO HsQMLObjectHandle

type MarshallerFor t = Marshaller t
    (MarshalMode t ICanGetFrom ()) (MarshalMode t ICanPassTo ())
    (MarshalMode t ICanReturnTo ())
    (MarshalMode t IIsObjType ()) (MarshalMode t IGetObjType ())

type MarshallerForMode t m = Marshaller t
    (m ICanGetFrom) (m ICanPassTo) (m ICanReturnTo)
    (m IIsObjType) (m IGetObjType)

-- | The class 'Marshal' allows Haskell values to be marshalled to and from the
-- QML environment.
class Marshal t where
    -- | The 'MarshalMode' associated type family specifies the marshalling
    -- capabilities offered by the instance. @c@ indicates the capability being
    -- queried. @d@ is dummy parameter which allows certain instances to type
    -- check.
    type MarshalMode t c d
    -- | Yields the 'Marshaller' for the type @t@.
    marshaller :: MarshallerFor t

-- | 'MarshalMode' for non-object types with bidirectional marshalling.
type family ModeBidi c
type instance ModeBidi ICanGetFrom = Yes
type instance ModeBidi ICanPassTo = Yes
type instance ModeBidi ICanReturnTo = Yes
type instance ModeBidi IIsObjType = No
type instance ModeBidi IGetObjType = No

-- | 'MarshalMode' for non-object types with from-only marshalling.
type family ModeFrom c
type instance ModeFrom ICanGetFrom = Yes
type instance ModeFrom ICanPassTo = No
type instance ModeFrom ICanReturnTo = No
type instance ModeFrom IIsObjType = No
type instance ModeFrom IGetObjType = No

-- | 'MarshalMode' for non-object types with to-only marshalling.
type family ModeTo c
type instance ModeTo ICanGetFrom = No
type instance ModeTo ICanPassTo = Yes
type instance ModeTo ICanReturnTo = Yes
type instance ModeTo IIsObjType = No
type instance ModeTo IGetObjType = No

-- | 'MarshalMode' for void in method returns.
type family ModeRetVoid c
type instance ModeRetVoid ICanGetFrom = No
type instance ModeRetVoid ICanPassTo = No
type instance ModeRetVoid ICanReturnTo = Yes
type instance ModeRetVoid IIsObjType = No
type instance ModeRetVoid IGetObjType = No

-- | 'MarshalMode' for object types with bidirectional marshalling.
type family ModeObjBidi a c
type instance ModeObjBidi a ICanGetFrom = Yes
type instance ModeObjBidi a ICanPassTo = Yes
type instance ModeObjBidi a ICanReturnTo = Yes
type instance ModeObjBidi a IIsObjType = Yes
type instance ModeObjBidi a IGetObjType = a

-- | 'MarshalMode' for object types with from-only marshalling.
type family ModeObjFrom a c
type instance ModeObjFrom a ICanGetFrom = Yes
type instance ModeObjFrom a ICanPassTo = No
type instance ModeObjFrom a ICanReturnTo = No
type instance ModeObjFrom a IIsObjType = Yes
type instance ModeObjFrom a IGetObjType = a

-- | 'MarshalMode' for object types with to-only marshalling.
type family ModeObjTo a c
type instance ModeObjTo a ICanGetFrom = No
type instance ModeObjTo a ICanPassTo = Yes
type instance ModeObjTo a ICanReturnTo = Yes
type instance ModeObjTo a IIsObjType = Yes
type instance ModeObjTo a IGetObjType = a

-- | Type value indicating a capability is supported.
data Yes

-- | Type value indicating a capability is not supported.
data No

-- | Type function equal to 'Yes' if the marshallable type @t@ supports being
-- received from QML.
type CanGetFrom t = MarshalMode t ICanGetFrom ()

-- | Type index into 'MarshalMode' for querying if the mode supports receiving
-- values from QML.
data ICanGetFrom

-- | Type function equal to 'Yes' if the marshallable type @t@ supports being
-- passed to QML.
type CanPassTo t = MarshalMode t ICanPassTo ()

-- | Type index into 'MarshalMode' for querying if the mode supports passing
-- values to QML.
data ICanPassTo

-- | Type function equal to 'Yes' if the marshallable type @t@ supports being
-- returned to QML.
type CanReturnTo t = MarshalMode t ICanReturnTo ()

-- | Type index into 'MarshalMode' for querying if the mode supports returning
-- values to QML.
data ICanReturnTo

-- | Type function equal to 'Yes' if the marshallable type @t@ is an object.
type IsObjType t = MarshalMode t IIsObjType ()

-- | Type index into 'MarshalMode' for querying if the mode supports an object
-- type.
data IIsObjType

-- | Type function which returns the type encapsulated by the object handles
-- used by the marshallable type @t@.
type GetObjType t = MarshalMode t IGetObjType ()

-- | Type index into 'MarshalMode' for querying the type encapsulated by the
-- mode's object handles.
data IGetObjType

-- | Encapsulates the functionality to needed to implement an instance of
-- 'Marshal' so that such instances can be defined without access to
-- implementation details.
data Marshaller t u v w x y = Marshaller {
    mTypeCVal_ :: !(MTypeCValFunc t),
    mFromCVal_ :: !(MFromCValFunc t),
    mToCVal_   :: !(MToCValFunc t),
    mWithCVal_ :: !(MWithCValFunc t),
    mFromJVal_ :: !(MFromJValFunc t),
    mWithJVal_ :: !(MWithJValFunc t),
    mFromHndl_ :: !(MFromHndlFunc t),
    mToHndl_   :: !(MToHndlFunc t)
}

mTypeCVal :: forall t. (Marshal t) => MTypeCValFunc t
mTypeCVal = mTypeCVal_ (marshaller :: MarshallerFor t)

mFromCVal :: forall t. (Marshal t) => MFromCValFunc t
mFromCVal = mFromCVal_ (marshaller :: MarshallerFor t)

mToCVal :: forall t. (Marshal t) => MToCValFunc t
mToCVal = mToCVal_ (marshaller :: MarshallerFor t)

mWithCVal :: forall t. (Marshal t) => MWithCValFunc t
mWithCVal = mWithCVal_ (marshaller :: MarshallerFor t)

mFromJVal :: forall t. (Marshal t) => MFromJValFunc t
mFromJVal = mFromJVal_ (marshaller :: MarshallerFor t)

mWithJVal :: forall t. (Marshal t) => MWithJValFunc t
mWithJVal = mWithJVal_ (marshaller :: MarshallerFor t)

mFromHndl :: forall t. (Marshal t) => MFromHndlFunc t
mFromHndl = mFromHndl_ (marshaller :: MarshallerFor t)

mToHndl :: forall t. (Marshal t) => MToHndlFunc t
mToHndl = mToHndl_ (marshaller :: MarshallerFor t)

unimplFromCVal :: MFromCValFunc t
unimplFromCVal _ = error "Type does not support mFromCVal."

unimplToCVal :: MToCValFunc t
unimplToCVal _ _ = error "Type does not support mToCVal."

unimplWithCVal :: MWithCValFunc t
unimplWithCVal _ _ = error "Type does not support mWithCVal."

unimplFromJVal :: MFromJValFunc t
unimplFromJVal _ = error "Type does not support mFromJVal."

unimplWithJVal :: MWithJValFunc t
unimplWithJVal _ _ = error "Type does not support mWithJVal."

unimplFromHndl :: MFromHndlFunc t
unimplFromHndl _ = error "Type does not support mFromHndl."

unimplToHndl :: MToHndlFunc t
unimplToHndl _ = error "Type does not support mToHndl."

jvalFromCVal :: (Marshal t) => MFromCValFunc t
jvalFromCVal = mFromJVal Strong . HsQMLJValHandle . castPtr

jvalToCVal :: (Marshal t) => MToCValFunc t
jvalToCVal val ptr = mWithJVal val $ \jval ->
    hsqmlSetJval (HsQMLJValHandle $ castPtr ptr) jval

jvalWithCVal :: (Marshal t) => MWithCValFunc t
jvalWithCVal val f = mWithJVal val $ \(HsQMLJValHandle ptr) ->
    f $ castPtr ptr

instance Marshal () where
    type MarshalMode () c d = ModeRetVoid c
    marshaller = Marshaller {
        mTypeCVal_ = Tagged tyVoid,
        mFromCVal_ = unimplFromCVal,
        mToCVal_ = \_ _ -> return (),
        mWithCVal_ = unimplWithCVal,
        mFromJVal_ = unimplFromJVal,
        mWithJVal_ = unimplWithJVal,
        mFromHndl_ = unimplFromHndl,
        mToHndl_ = unimplToHndl}
