module System.Console.Haskeline.InputT where


import System.Console.Haskeline.Command.History
import System.Console.Haskeline.Monads as Monads
import System.Console.Haskeline.Prefs
import System.Console.Haskeline.Command(Layout)
import System.Console.Haskeline.Completion
import System.Console.Haskeline.Backend
import System.Console.Haskeline.Term

import System.Directory(getHomeDirectory)
import System.FilePath
import Control.Applicative
import Control.Monad(liftM, ap)

-- | Application-specific customizations to the user interface.
data Settings m = Settings {complete :: CompletionFunc m, -- ^ Custom tab completion
                            historyFile :: Maybe FilePath,
                            handleSigINT :: Bool -- ^ Throw an 'Interrupt'
                            -- exception if the user presses Ctrl-C
                            }

-- | Because 'complete' is the only field of 'Settings' depending on @m@,
-- the expression @defaultSettings {completionFunc = f}@ leads to a type error
-- from being too general.  This function may become unnecessary if another field
-- depending on @m@ is added.

setComplete :: CompletionFunc m -> Settings m -> Settings m
setComplete f s = s {complete = f}


-- | A monad transformer which carries all of the state and settings
-- relevant to a line-reading application.
newtype InputT m a = InputT {unInputT :: ReaderT (RunTerm (InputCmdT m))
                                (StateT History (ReaderT Prefs 
                                (ReaderT (Settings m) m))) a}
                            deriving (Monad,MonadIO, MonadState History,
                                        MonadReader Prefs, MonadReader (Settings m),
                                        MonadReader (RunTerm (InputCmdT m)))

instance Monad m => Functor (InputT m) where
    fmap = liftM

instance Monad m => Applicative (InputT m) where
    pure = return
    (<*>) = ap

instance MonadTrans InputT where
    lift = InputT . lift . lift . lift . lift

instance MonadException m => MonadException (InputT m) where
    block = InputT . block . unInputT
    unblock = InputT . unblock . unInputT
    catch f h = InputT $ Monads.catch (unInputT f) (unInputT . h)

-- for internal use only
type InputCmdT m = ReaderT Layout (StateT HistLog (ReaderT Prefs (ReaderT (Settings m) m)))

instance MonadIO m => MonadLayout (InputCmdT m) where

runInputCmdT :: forall m a . MonadIO m => InputCmdT m a -> InputT m a
runInputCmdT f = InputT $ do
    run :: RunTerm (InputCmdT m) <- ask
    layout <- liftIO $ getLayout run
    lift $ runHistLog $ runReaderT' layout f


liftCmdT :: Monad m => m a -> InputCmdT m a
liftCmdT = lift  . lift . lift . lift

runInputTWithPrefs :: MonadException m => Prefs -> Settings m -> InputT m a -> m a
runInputTWithPrefs prefs settings (InputT f) = liftIO myRunTerm >>= \run -> 
    runReaderT' settings $ runReaderT' prefs 
        $ runHistoryFromFile (historyFile settings) (maxHistorySize prefs) 
        $ runReaderT f run
        
-- | Run a line-reading application, reading user 'Prefs' from 
-- @~/.haskeline@
runInputT :: MonadException m => Settings m -> InputT m a -> m a
runInputT settings f = do
    prefs <- liftIO readPrefsFromHome
    runInputTWithPrefs prefs settings f

-- | Read 'Prefs' from @~/.haskeline.@   If there is an error reading the file,
-- the 'defaultPrefs' will be returned.
readPrefsFromHome :: IO Prefs
readPrefsFromHome = handle (\_ -> return defaultPrefs) $ do
    home <- getHomeDirectory
    readPrefs (home </> ".haskeline")