{-# LANGUAGE OverloadedStrings #-}

-- | Module to catch uncaught exceptions and send a notification email
module System.ExceptionMailer
    ( exceptionMailerTag
    , setupExceptionMailer, setupExceptionMailer', setupExceptionMailer_adv
    , mkAddress
    , mailError

    -- * Re-exported for convenience
    , Address
    ) where

import Prelude hiding (catch)
import System.Environment (getProgName)
import Data.String (fromString)
import Data.Maybe
import qualified Data.Text.Lazy as LT
import Control.Exception (SomeException, catch)
import GHC.Conc (setUncaughtExceptionHandler)
import Network.Mail.Mime
import System.Log.Logger (errorM)

-- | String tag used for logging to "System.Log.Logger"
exceptionMailerTag :: String
exceptionMailerTag = "ExceptionMailer"

-- | Setup the global exception notifier.  This will catch any otherwise uncaught exceptions and send an email to the
-- given address.
--
-- For example,
--
-- > setupExceptionMailer (mkAddress "My Program" "noreply@example.com")
-- >                        (mkAddress "Sysadmin" "sysadmin@example.com")
setupExceptionMailer :: Address -- ^ Make the email appear to be from this address
                     -> Address -- ^ Send the email to here
                     -> Maybe String -- ^ Subject
                     -> String -- ^ Prefix to put in the email head
                     -> IO ()
setupExceptionMailer from to subj pre =
    setupExceptionMailer_adv from to subj pre (\_ -> return ())

-- | Convenience version of 'setupExceptionMailer' that just accepts the email addresses
setupExceptionMailer' :: String -- ^ Make the email appear to be from this address
                      -> String -- ^ Send the email to here
                      -> Maybe String -- ^ Subject
                      -> String -- ^ Prefix to put in the email head
                      -> IO ()
setupExceptionMailer' from to subj pre = setupExceptionMailer (Address Nothing $ fromString from) (Address Nothing $ fromString to) subj pre

-- | Setup the global exception notifier.  Like 'setupExceptionMailer' but allows a
-- custom action after the email is send
setupExceptionMailer_adv :: Address -- ^ Make the email appear to be from this address
                         -> Address -- ^ Send the email to here
                         -> Maybe String -- ^ Subject
                         -> String -- ^ Prefix to put in the email head
                         -> (SomeException -> IO ())
                         -> IO ()
setupExceptionMailer_adv from to subj pre action =
    setUncaughtExceptionHandler $ \e -> do
      emailException from to subj pre e
      action e

-- | Helper function to convert a name and email address into a proper 'Address'
mkAddress :: String -> String -> Address
mkAddress name email = Address (Just $ fromString name) $ fromString email

-- | Send an error email.  Exported so that it may be re-used from your own exception handling routines
mailError :: Address -> Address -> Maybe String -> String -> IO ()
mailError from to subj msg = do
  prog <- getProgName
  let m = simpleMail' to from (fromString $ fromMaybe "Exception Mailer" subj)
                 (LT.concat ["Program: ", fromString $ prog ++ "\n"
                            ,"Exception:\n", fromString msg])
  renderSendMail m

emailException :: Show a => Address -> Address -> Maybe String -> String -> a -> IO ()
emailException from to subj pre e = do
  errorM exceptionMailerTag $ "Uncaught exception.  emailing ("++
            show (addressEmail to)++")  : "++show e
  catch (mailError from to subj (pre ++ show e))
        (\e2 -> errorM exceptionMailerTag $ "Unable to send email : "++show (e2 :: SomeException))
  return ()