content: -- | Offline mode / RC file / -e support module.  Handles spooling lists
  -- of commands (from haskeline, files, or the command line) into the vchat
  -- layer.
  module Lambdabot.Plugin.Core.OfflineRC ( offlineRCPlugin ) where
  
  import Lambdabot.Config.Core
  import Lambdabot.IRC
  import Lambdabot.Monad
  import Lambdabot.Plugin
  import Lambdabot.Util
  
  import Control.Concurrent.Lifted
  import Control.Exception.Lifted ( evaluate, finally )
  import Control.Monad( void, when )
  import Control.Monad.State( gets, modify )
  import Control.Monad.Trans( lift, liftIO )
  import Data.Char
  import qualified Data.Map as M
  import qualified Data.Set as S
  import System.Console.Haskeline (InputT, Settings(..), runInputT, defaultSettings, getInputLine)
  import System.IO
  import System.Timeout.Lifted
  import Codec.Binary.UTF8.String
  
  -- We need to track the number of active sourcings so that we can
  -- unregister the server (-> allow the bot to quit) when it is not
  -- being used.
  type OfflineRCState = Integer
  type OfflineRC = ModuleT OfflineRCState LB
  
  offlineRCPlugin :: Module OfflineRCState
  offlineRCPlugin = newModule
      { moduleDefState = return 0
      , moduleInit = do
          lb . modify $ \s -> s
              { ircPrivilegedUsers = S.insert (Nick "offlinerc" "null") (ircPrivilegedUsers s)
              }
          -- note: moduleInit is invoked with exceptions masked
          void . forkUnmasked $ do
              waitForInit
              lockRC
              cmds <- getConfig onStartupCmds
              mapM_ feed cmds `finally` unlockRC
  
      , moduleCmds = return
          [ (command "offline")
              { privileged = True
              , help = say "offline. Start a repl"
              , process = const . lift $ do
                  lockRC
                  histFile <- lb $ findLBFileForWriting "offlinerc"
                  let settings = defaultSettings { historyFile = Just histFile }
                  _ <- fork (runInputT settings replLoop `finally` unlockRC)
                  return ()
              }
          , (command "rc")
              { privileged = True
              , help = say "rc name. Read a file of commands (asynchronously). TODO: better name."
              , process = \fn -> lift $ do
                  txt <- io $ readFile fn
                  io $ evaluate $ foldr seq () txt
                  lockRC
                  _ <- fork (mapM_ feed (lines txt) `finally` unlockRC)
                  return ()
              }
          ]
      }
  
  feed :: String -> OfflineRC ()
  feed msg = do
      cmdPrefix <- fmap head (getConfig commandPrefixes)
      let msg' = case msg of
              '>':xs -> cmdPrefix ++ "run " ++ xs
              '!':xs -> xs
              _      -> cmdPrefix ++ dropWhile (== ' ') msg
      -- note that `msg'` is unicode, but lambdabot wants utf-8 lists of bytes
      lb . void . timeout (15 * 1000 * 1000) . received $
                IrcMessage { ircMsgServer = "offlinerc"
                           , ircMsgLBName = "offline"
                           , ircMsgPrefix = "null!n=user@null"
                           , ircMsgCommand = "PRIVMSG"
                           , ircMsgParams = ["offline", ":" ++ encodeString msg' ] }
  
  handleMsg :: IrcMessage -> OfflineRC ()
  handleMsg msg = liftIO $ do
      let str = case (tail . ircMsgParams) msg of
              []    -> []
              (x:_) -> tail x
      -- str contains utf-8 list of bytes; convert to unicode
      hPutStrLn stdout (decodeString str)
      hFlush stdout
  
  replLoop :: InputT OfflineRC ()
  replLoop = do
      line <- getInputLine "lambdabot> "
      case line of
          Nothing -> return ()
          Just x -> do
              let s' = dropWhile isSpace x
              when (not $ null s') $ do
                  lift $ feed s'
              continue <- lift $ lift $ gets (M.member "offlinerc" . ircPersists)
              when continue replLoop
  
  lockRC :: OfflineRC ()
  lockRC = do
      withMS $ \ cur writ -> do
          when (cur == 0) $ do
              registerServer "offlinerc" handleMsg
              lift $ modify $ \state' ->
                  state' { ircPersists = M.insert "offlinerc" True $ ircPersists state' }
          writ (cur + 1)
  
  unlockRC :: OfflineRC ()
  unlockRC = withMS $ \ cur writ -> do
      when (cur == 1) $ unregisterServer "offlinerc"
      writ (cur - 1)
  