{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
module Snap.Snaplet.SES
    ( -- * Initialization
      initAWSKeys
    , sendEmail
    , sendEmailBlaze
    , withKeys
      -- * Types
    , AWSKeys    (..)
    , HasAWSKeys (..)
      -- * Module Re-export
    , module Network.SES
    ) where

import           Control.Applicative               ((<$>), (<*>))
import           Control.Lens                      ((^#))
import           Control.Monad.IO.Class
import           Control.Monad.Representable.State (get)
import           Control.Monad.Trans.Reader        (ReaderT, ask, asks)
import qualified Data.ByteString.Lazy.Char8        as BL8
import qualified Data.Configurator                 as C
import           Data.Maybe                        (fromMaybe)
import           Data.Monoid                       (mempty)
import           Data.Text                         (Text)
import           Data.Text.Encoding                (encodeUtf8)
import           Network.SES                       hiding (sendEmail,
                                                    sendEmailBlaze)
import qualified Network.SES                       as SES
import           Snap
import qualified Text.Blaze.Html5                  as H

------------------------------------------------------------------------------
-- | Type to hold AWS Config Information
data AWSKeys = AWSKeys
    { publicKey :: PublicKey -- ^ AWS Public Key
    , secretKey :: SecretKey -- ^ AWS Secret Key
    , region    :: Region    -- ^ AWS Region Key
    , sender    :: Text      -- ^ AWS Verified Sender Email
    } deriving Show

------------------------------------------------------------------------------
-- | Class to allow extraction of `AWSKeys` from arbitrary Monads constrained by MonadIO
class MonadIO m => HasAWSKeys m where
    getKeys :: m AWSKeys

instance HasAWSKeys (Handler b AWSKeys) where
    getKeys = get

instance MonadIO m => HasAWSKeys (ReaderT (Snaplet AWSKeys) m) where
    getKeys = asks (^# snapletValue)

instance MonadIO m => HasAWSKeys (ReaderT AWSKeys m) where
    getKeys = ask

------------------------------------------------------------------------------
-- | Helper function for operating on `AWSKeys` inside of `HasAWSKeys` constrained Monads
withKeys
 :: HasAWSKeys m
 => (AWSKeys -> IO a) -- ^ Function operating on Keys
 -> m a
withKeys f = getKeys >>= liftIO . f

------------------------------------------------------------------------------
-- | Send Blaze email from a snap handler
sendEmailBlaze
    :: HasAWSKeys m
    => To               -- ^ Who the email is intended for
    -> Subject          -- ^ Email `Subject`
    -> H.Html           -- ^ Email Body
    -> m SESResult
sendEmailBlaze
   to
   subject
   body = withKeys $ \(AWSKeys secret public region sender) ->
                          SES.sendEmailBlaze
                          secret
                          public
                          region
                          (BL8.fromStrict $ encodeUtf8 sender)
                          to
                          subject
                          body

------------------------------------------------------------------------------
-- | Send a ByteString of HTML from a Snap Handler
sendEmail
    :: HasAWSKeys m
    => To               -- ^ Who the email is intended for
    -> Subject          -- ^ email `Subject`
    -> BL8.ByteString   -- ^ email Body
    -> m SESResult
sendEmail
   to
   subject
   body = withKeys $ \(AWSKeys secret public region sender) ->
                          SES.sendEmail
                          secret
                          public
                          region
                          (BL8.fromStrict $ encodeUtf8 sender)
                          to
                          subject
                          body

------------------------------------------------------------------------------
-- | Initialize snaplet
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- > {-# LANGUAGE RecordWildCards   #-}
-- > {-# LANGUAGE TemplateHaskell   #-}
-- > module Main ( main ) where
-- >
-- > import           Control.Lens
-- > import qualified Data.ByteString.Char8 as B8
-- > import qualified Data.ByteString.Lazy.Char8 as BL8
-- > import           Snap
-- > import           Snap.Snaplet.SES
-- >
-- > data App = App {
-- >    _awsKeys :: Snaplet AWSKeys
-- > }
-- >
-- > makeLenses ''App
-- >
-- > initApp :: SnapletInit App App
-- > initApp = makeSnaplet "name" "description" Nothing $ do
-- >             _awsKeys <- nestSnaplet "ses-html" awsKeys initAWSKeys
-- >             addRoutes [("/", handleKeys)]
-- >             return App {..}
-- >   where
-- >     handleKeys = method GET $ do
-- >       with awsKeys $ withKeys $ liftIO . print
-- >       result <- with awsKeys $ sendEmail ["david@solidtranslate.com"] "cookie-crisp" "<h1>TEST</h1>"
-- >       liftIO $ print result
-- >       writeBS "done"
-- >
-- > main :: IO ()
-- > main = do (_, app, _) <- runSnaplet Nothing initApp
-- >           httpServe config app
-- >   where
-- >     config = setAccessLog ConfigNoLog $
-- >              setErrorLog ConfigNoLog $
-- >              defaultConfig
--
-- .\/snaplets\/ses-html\/devel.cfg
--
-- > public = "publickey"
-- > secret = "secretkey"
-- > region = "us-east-1"
-- > sender = "sender@verifiedaddress.com"
--
initAWSKeys :: SnapletInit a AWSKeys
initAWSKeys = makeSnaplet "ses-html" "Get your aws keys" Nothing $ do
        config <- getSnapletUserConfig
        liftIO $ AWSKeys <$> ((PublicKey . encodeUtf8) <$> getPublic config)
                         <*> ((SecretKey . encodeUtf8) <$> getSecret config)
                         <*> getRegion config
                         <*> getSender config
  where
    getRegion config = do
        let f :: Text -> Region
            f "us-east-1" = USEast1
            f "us-west-2" = USWest2
            f "eu-west-1" = EUWest1
            f _           = USEast1
        fromMaybe USEast1 <$> fmap f <$> C.lookup config "region"

    getPublic : getSecret : getSender : _
         = map getConfig [ "public"
                         , "secret"
                         , "sender"
                         ]
    getConfig name config = fromMaybe mempty <$> C.lookup config name