module System.Plugins.Hotswap (
    Plugin (..), 
    newPlugin,
    usePlugin, 
    usePluginIO,
    reloadPlugin, 
    readPlugin, 
    withPlugin,
    withPluginIO,
    putPlugin
) where

import System.Plugins.Load
import Data.IORef 

data Plugin a = Plugin {
    pluginObject       :: FilePath,    -- ^ Path to object
    pluginIncludes     :: [FilePath],  -- ^ Include paths.
    pluginDataName     :: String,      -- ^ Name of the symbol to find.
    pluginData         :: IORef a,     -- ^ Loaded data.
    pluginModule       :: IORef Module -- ^ Loaded module.
}

-- | 'usePlugin' provides a simple way to use plugins of type 'Plugin (a -> b)', ie, only 
-- single argumented ones.
usePlugin :: Plugin (a -> b) -> a -> IO b
usePlugin plug x = do
    pf <- readPlugin plug
    return (pf x)

-- | 'usePlugin' for plugins returning IO.
usePluginIO :: Plugin (a -> IO b) -> a -> IO b
usePluginIO plug x = do
    pf <- readPlugin plug
    pf x

-- | 'withPlugin' provides a way to run a function on a plugin, modifying the plugin in-place.
withPlugin :: Plugin a -> (a -> a) -> IO ()
withPlugin plug f = do
    pd <- readPlugin plug 
    putPlugin plug (f pd)

-- | 'withPlugin' for functions returning IO.
withPluginIO :: Plugin a -> (a -> IO a) -> IO ()
withPluginIO plug f = do
    pd <- readPlugin plug
    r  <- f pd
    putPlugin plug r

-- | Create a new plugin. Don't use this to reload plugins.
newPlugin :: FilePath -> [FilePath] -> String -> IO (Plugin a)
newPlugin obj incs name = do
    plugin <- load_ obj incs name

    case plugin of
        LoadSuccess m f -> do
            fref <- newIORef f
            mref <- newIORef m
            return $! Plugin obj incs name fref mref

        _ -> error "no such module or function"

-- | Reload a plugin in-place.
reloadPlugin :: Plugin a -> IO ()
reloadPlugin (Plugin _ _ name fref mref) = do 
    m <- readIORef mref
    plugin <- reload m name
    case plugin of
        LoadSuccess newm newf -> do
            writeIORef mref newm
            writeIORef fref newf
        _ -> error "no such module or function"

-- | Read the 'pluginData' 'IORef'.
readPlugin :: Plugin a -> IO a
readPlugin = readIORef . pluginData

-- | Replace the contents of the 'pluginData'.
putPlugin :: Plugin a -> a -> IO ()
putPlugin = writeIORef . pluginData