{-# 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)