{-# LANGUAGE KindSignatures, ConstraintKinds,
             TypeFamilies, MultiParamTypeClasses #-}

module System.Plugins.Criteria.LoadCriterion (LoadCriterion(..), Criterion(..)) where

-- The 'Constraint' kind is defined in 'GHC.Exts'

import GHC.Exts
import System.Plugins.DynamicLoader
import Data.Dynamic
import Control.Monad.IO.Class

class LoadCriterion (c :: Constraint) t where
  data Criterion c t
  type Effective c t :: *
  addDynamicLibrary :: Criterion c t -> String -> IO ()
  addDynamicLibrary _ = addDLL
  resolveSymbols :: Criterion c t -> IO ()
  resolveSymbols _ = resolveFunctions
  loadQualified :: c => Criterion c t -> String -> Effective c t


-- Safe criteria follow

-- | When the symbol's type is Typeable we load from the suffixed symbol and
-- | try to resolve it.
instance LoadCriterion (Typeable t) t where
  data Criterion (Typeable t) t = DynamicCriterion
  type Effective (Typeable t) t = IO (Maybe t)
  loadQualified DynamicCriterion name = loadQualifiedDynFunction (adornSymbol name)
    where adornSymbol n = n ++ "Dyn"


loadQualifiedDynFunction :: Typeable t => String -> IO (Maybe t)
loadQualifiedDynFunction name = fmap fromDynamic dyn
  where dyn :: IO Dynamic
        dyn = loadQualifiedFunction name


-- | When the symbol's type is Typeable and we are in a monad that can
-- | reliably fail, we load from the suffixed symbol and try to resolve it,
-- | failing when the type does not correspond with the expectation.
instance LoadCriterion (Typeable t, MonadIO m) t where
  data Criterion (Typeable t, MonadIO m) t = DynamicFailableCriterion
  type Effective (Typeable t, MonadIO m) t = m t
  loadQualified DynamicFailableCriterion name = do sym <- liftIO $ loadQualifiedDynFunction (adornSymbol name)
                                                   case sym of
                                                     Nothing -> liftIO $ fail ("symbol " ++ name ++ " does not have the expected type")
                                                     Just it -> return it
    where adornSymbol n = n ++ "Dyn"