content: {-# LANGUAGE TemplateHaskell #-}
  module Lambdabot.Main
      ( lambdabotVersion
      
      , Config
      , DSum(..)
      , (==>)
      , lambdabotMain
  
      , Modules
      , modules
  
      , module Lambdabot.Plugin.Core
      , Priority(..)
      ) where
  
  import Lambdabot.Bot
  import Lambdabot.Config
  import Lambdabot.Logging
  import Lambdabot.Module
  import Lambdabot.Monad
  import Lambdabot.Plugin.Core
  import Lambdabot.Util
  import Lambdabot.Util.Signals
  
  import Control.Exception.Lifted as E
  import Control.Monad.Identity
  import Data.Dependent.Sum
  import Data.List
  import Data.IORef
  import Data.Some
  import Data.Version
  import Language.Haskell.TH
  import Paths_lambdabot_core (version)
  import System.Exit
  import System.Log.Formatter
  import qualified System.Log.Logger as L
  import System.Log.Handler.Simple
  import Network.Socket (withSocketsDo)
  
  lambdabotVersion :: Version
  lambdabotVersion = version
  
  setupLogging :: LB ()
  setupLogging = do
      stream <- getConfig consoleLogHandle
      level  <- getConfig consoleLogLevel
      format <- getConfig consoleLogFormat
      
      unformattedHandler <- io (streamHandler stream level)
      let consoleHandler = unformattedHandler
              { formatter = simpleLogFormatter format }
      
      setRoot <- getConfig replaceRootLogger
      
      io $ if setRoot
          then L.updateGlobalLogger L.rootLoggerName
              (L.setLevel level . L.setHandlers [consoleHandler])
          else L.updateGlobalLogger "Lambdabot"
              (L.setLevel level . L.addHandler consoleHandler)
  
  -- | The Lambdabot entry point.
  -- Initialise plugins, connect, and run the bot in the LB monad
  --
  -- Also, handle any fatal exceptions (such as non-recoverable signals),
  -- (i.e. print a message and exit). Non-fatal exceptions should be dealt
  -- with in the mainLoop or further down.
  lambdabotMain :: Modules -> [DSum Config Identity] -> IO ExitCode
  lambdabotMain initialise cfg = withSocketsDo . withIrcSignalCatch $ do
      rost <- initRoState cfg
      rwst <- newIORef initRwState
      runLB (lambdabotRun initialise) (rost, rwst)
          `E.catch` \e -> do
              -- clean up and go home
              case fromException e of
                  Just code -> return code
                  Nothing   -> do
                      errorM (show e)
                      return (ExitFailure 1)
  
  lambdabotRun :: Modules -> LB ExitCode
  lambdabotRun ms = do
      setupLogging
      infoM "Initialising plugins"
      withModules ms $ do
          infoM "Done loading plugins"
          reportInitDone
          
          waitForQuit `E.catch`
              (\e@SomeException{} -> errorM (show e)) -- catch anything, print informative message, and clean up
      
      -- clean up any dynamically loaded modules
      mapM_ ircUnloadModule =<< listModules
      return ExitSuccess
  
  ------------------------------------------------------------------------
  
  type Modules = [(String, Some Module)]
  
  modules :: [String] -> Q Exp
  modules xs = [| $(listE $ map instalify (nub xs)) |]
      where
          instalify x =
              let module' = varE $ mkName (x ++ "Plugin")
               in [| (x, Some $module') |]
  
  withModules :: Modules -> LB a -> LB a
  withModules []      = id
  withModules ((n, Some m):ms)  = withModule n m . withModules ms
  
  withModule :: String -> Module st -> LB a -> LB a
  withModule name m = bracket_ (ircLoadModule name m) (ircUnloadModule name)
  