hintSnap :: Typeable a
         => [String]
             -- ^ A list of command-line options for the interpreter
         -> [String]
             -- ^ A list of modules that need to be interpreted. This should
             -- contain only the modules which contain the initialization,
             -- cleanup, and handler actions. Everything else they require will
             -- be loaded transitively.
         -> [String]
             -- ^ A list of paths to watch for updates
         -> String
             -- ^ The name of the function to load
         -> a
             -- ^ The value to apply the loaded function to
         -> IO (Snap (), IO ())
hintSnap opts modules srcPaths action value = do
    load <- runInterpreterThread
    protectedHintEvaluator getCurrentState testState load
  where
    --------------------------------------------------------------------------
    witness x = undefined $ x `asTypeOf` value :: HintLoadable

#if MIN_VERSION_base(4,7,0)
    --------------------------------------------------------------------------
    witnessModules = filter (`notElem` inPrelude) . map dropInternal .
                     map tyConModule . tyCons . typeOf $ witness

    --------------------------------------------------------------------------
    inPrelude = ["GHC.Prim", "GHC.Types", "GHC.Tuple"]

    --------------------------------------------------------------------------
    tyCons x = let (c, rs) = splitTyConApp x in c : concatMap tyCons rs

    --------------------------------------------------------------------------
    dropInternal s = case stripPrefix "Snap.Internal." s of
        Nothing      -> s
        Just "Types" -> "Snap.Core"
        Just x       -> "Snap." ++ x
#else
    --------------------------------------------------------------------------
    -- This is somewhat fragile, and probably can be cleaned up with a future
    -- version of Typeable. For the moment, and backwards-compatibility, this
    -- is the approach being taken.
    witnessModules = map (reverse . drop 1 . dropWhile (/= '.') . reverse) .
                     filter (elem '.') . groupBy typePart . show . typeOf $
                     witness

    --------------------------------------------------------------------------
    typePart x y = (isAlphaNum x && isAlphaNum  y) || x == '.' || y == '.'
#endif

    --------------------------------------------------------------------------
    runInterpreterThread = do
        input  <- newEmptyMVar
        output <- newEmptyMVar    
        
        forkIO . forever $ do
            restore <- protectHandlers
            err <- unsafeRunInterpreterWithArgs opts $ do
                liftIO $ restore
                forever $ do
                    liftIO $ takeMVar input

                    loadModules . nub $ modules
                    setImports . nub $ "Prelude" : "Snap.Core" :
                        witnessModules ++ modules
                    f <- interpret action witness

                    liftIO . putMVar output $ f value
                    reset

            putMVar output $ formatOnError err

        return $ putMVar input () >> takeMVar output

    --------------------------------------------------------------------------
    formatOnError (Left err) = error $ format err
    formatOnError (Right a) = a

    --------------------------------------------------------------------------
    getCurrentState = liftM2 (,) getCurrentTime $ getTreeStatus srcPaths

    --------------------------------------------------------------------------
    testState (prevTime, ts) = do
        now <- getCurrentTime
        if diffUTCTime now prevTime < 3
            then return True
            else checkTreeStatus ts


------------------------------------------------------------------------------
-- | Convert an InterpreterError to a String for presentation
hintSnap :: Typeable a
         => [String]
             -- ^ A list of command-line options for the interpreter
         -> [String]
             -- ^ A list of modules that need to be interpreted. This should
             -- contain only the modules which contain the initialization,
             -- cleanup, and handler actions. Everything else they require will
             -- be loaded transitively.
         -> [String]
             -- ^ A list of paths to watch for updates
         -> String
             -- ^ The name of the function to load
         -> a
             -- ^ The value to apply the loaded function to
         -> IO (Snap (), IO ())
hintSnap opts modules srcPaths action value = do
    load <- runInterpreterThread
    protectedHintEvaluator getCurrentState testState load
  where
    --------------------------------------------------------------------------
    witness x = undefined $ x `asTypeOf` value :: HintLoadable

#if MIN_VERSION_base(4,7,0)
    --------------------------------------------------------------------------
    witnessModules = filter (`notElem` inPrelude) . map dropInternal .
                     map tyConModule . tyCons . typeOf $ witness

    --------------------------------------------------------------------------
    inPrelude = ["GHC.Prim", "GHC.Types", "GHC.Tuple"]

    --------------------------------------------------------------------------
    tyCons x = let (c, rs) = splitTyConApp x in c : concatMap tyCons rs

    --------------------------------------------------------------------------
    dropInternal s = case stripPrefix "Snap.Internal." s of
        Nothing      -> s
        Just "Types" -> "Snap.Core"
        Just x       -> "Snap." ++ x
#else
    --------------------------------------------------------------------------
    -- This is somewhat fragile, and probably can be cleaned up with a future
    -- version of Typeable. For the moment, and backwards-compatibility, this
    -- is the approach being taken.
    witnessModules = map (reverse . drop 1 . dropWhile (/= '.') . reverse) .
                     filter (elem '.') . groupBy typePart . show . typeOf $
                     witness

    --------------------------------------------------------------------------
    typePart x y = (isAlphaNum x && isAlphaNum  y) || x == '.' || y == '.'
#endif

    --------------------------------------------------------------------------
    runInterpreterThread = do
        input  <- newEmptyMVar
        output <- newEmptyMVar    
        
        forkIO . forever $ do
            restore <- protectHandlers
            err <- unsafeRunInterpreterWithArgs opts $ do
                liftIO $ restore
                forever $ do
                    liftIO $ takeMVar input

                    loadModules . nub $ modules
                    setImports . nub $ "Prelude" : "Snap.Core" :
                        witnessModules ++ modules
                    f <- interpret action witness

                    liftIO . putMVar output $ f value
                    reset

            putMVar output $ formatOnError err

        return $ putMVar input () >> takeMVar output

    --------------------------------------------------------------------------
    formatOnError (Left err) = error $ format err
    formatOnError (Right a) = a

    --------------------------------------------------------------------------
    getCurrentState = liftM2 (,) getCurrentTime $ getTreeStatus srcPaths

    --------------------------------------------------------------------------
    testState (prevTime, ts) = do
        now <- getCurrentTime
        if diffUTCTime now prevTime < 3
            then return True
            else checkTreeStatus ts


------------------------------------------------------------------------------
-- | Convert an InterpreterError to a String for presentation
hintSnap opts modules srcPaths action value = do
    load <- runInterpreterThread
    protectedHintEvaluator getCurrentState testState load
  where
    --------------------------------------------------------------------------
    witness x = undefined $ x `asTypeOf` value :: HintLoadable

#if MIN_VERSION_base(4,7,0)
    --------------------------------------------------------------------------
    witnessModules = filter (`notElem` inPrelude) . map dropInternal .
                     map tyConModule . tyCons . typeOf $ witness

    --------------------------------------------------------------------------
    inPrelude = ["GHC.Prim", "GHC.Types", "GHC.Tuple"]

    --------------------------------------------------------------------------
    tyCons x = let (c, rs) = splitTyConApp x in c : concatMap tyCons rs

    --------------------------------------------------------------------------
    dropInternal s = case stripPrefix "Snap.Internal." s of
        Nothing      -> s
        Just "Types" -> "Snap.Core"
        Just x       -> "Snap." ++ x
#else
    --------------------------------------------------------------------------
    -- This is somewhat fragile, and probably can be cleaned up with a future
    -- version of Typeable. For the moment, and backwards-compatibility, this
    -- is the approach being taken.
    witnessModules = map (reverse . drop 1 . dropWhile (/= '.') . reverse) .
                     filter (elem '.') . groupBy typePart . show . typeOf $
                     witness

    --------------------------------------------------------------------------
    typePart x y = (isAlphaNum x && isAlphaNum  y) || x == '.' || y == '.'
#endif

    --------------------------------------------------------------------------
    runInterpreterThread = do
        input  <- newEmptyMVar
        output <- newEmptyMVar    
        
        forkIO . forever $ do
            restore <- protectHandlers
            err <- unsafeRunInterpreterWithArgs opts $ do
                liftIO $ restore
                forever $ do
                    liftIO $ takeMVar input

                    loadModules . nub $ modules
                    setImports . nub $ "Prelude" : "Snap.Core" :
                        witnessModules ++ modules
                    f <- interpret action witness

                    liftIO . putMVar output $ f value
                    reset

            putMVar output $ formatOnError err

        return $ putMVar input () >> takeMVar output

    --------------------------------------------------------------------------
    formatOnError (Left err) = error $ format err
    formatOnError (Right a) = a

    --------------------------------------------------------------------------
    getCurrentState = liftM2 (,) getCurrentTime $ getTreeStatus srcPaths

    --------------------------------------------------------------------------
    testState (prevTime, ts) = do
        now <- getCurrentTime
        if diffUTCTime now prevTime < 3
            then return True
            else checkTreeStatus ts


------------------------------------------------------------------------------
-- | Convert an InterpreterError to a String for presentation