content: {-# LANGUAGE OverloadedStrings #-}
  
  -- | Functions to sign HTTP requests with oAuth
  module Web.Tweet.Sign ( signRequest
                        , signRequestMem
                        , mkConfig
                        , mkConfigToml
                        , oAuthMem
                        , credentialMem ) where
  
  import           Data.ByteString        as BS
  import           Data.HashMap.Lazy
  import           Data.Monoid
  import qualified Data.Text              as T
  import           Data.Text.Encoding     (encodeUtf8)
  import qualified Data.Text.IO           as TIO
  import           Network.HTTP.Client
  import           Prelude                hiding (lookup)
  import           Text.Toml
  import           Web.Authenticate.OAuth
  import           Web.Tweet.Types
  import           Web.Tweet.Utils
  
  -- | Sign a request using your OAuth dev token, as stored in a config file.
  -- Uses the IO monad because signatures require a timestamp
  signRequest :: FilePath -> Request -> IO Request
  signRequest = (. flip signRequestMem) . (>>=) . mkConfig
  
  -- | Sign a request using a 'Config' object, avoiding the need to read token/key from file
  signRequestMem :: Config -> Request -> IO Request
  signRequestMem = uncurry signOAuth
  
  -- | Create an OAuth api key from two ByteStrings.
  oAuthMem :: BS.ByteString -- ^ API key
           -> BS.ByteString -- ^ API secret
           -> OAuth
  oAuthMem key secret = newOAuth { oauthConsumerKey = key, oauthConsumerSecret = secret, oauthServerName = "api.twitter.com" }
  
  credentialMem :: BS.ByteString -- ^ Token
                -> BS.ByteString -- ^ Token secret
                -> Credential
  credentialMem = newCredential
  
  -- | Create an OAuth api key from config data in a file
  oAuth :: FilePath -> IO OAuth
  oAuth filepath = do
      secret <- lineByKey "api-sec" <$> getConfigData filepath
      key <- lineByKey "api-key" <$> getConfigData filepath
      let url = "api.twitter.com"
      pure newOAuth { oauthConsumerKey = key , oauthConsumerSecret = secret , oauthServerName = url }
  
  getKey :: HashMap T.Text Node -> T.Text -> BS.ByteString
  getKey hm key = case lookup key hm of
      (Just (VString k)) -> encodeUtf8 k
      (Just _) -> error $ "Key: " <> T.unpack key <> " found in the config file, but it is not a string."
      Nothing -> error $ "Key: " <> T.unpack key <> " not found in config file."
  
  mkConfigToml :: FilePath -> IO Config
  mkConfigToml filepath = do
      t <- TIO.readFile filepath
      let hm = case parseTomlDoc ("failed to read .toml at: " <> filepath) t of
              Right tab -> tab
              Left e    -> error (show e)
          secret = getKey hm "api-sec"
          key = getKey hm "api-key"
          tok = getKey hm "tok"
          tokSecret = getKey hm "tok-sec"
          url = "api.twitter.com"
          o = newOAuth { oauthConsumerKey = key , oauthConsumerSecret = secret , oauthServerName = url }
          c = newCredential tok tokSecret
      pure (o, c)
  
  -- | Given a filepath, parse the contents of the file and return a configuration.
  mkConfig :: FilePath -> IO Config
  mkConfig filepath = do
      o <- oAuth filepath
      c <- credential filepath
      pure (o, c)
  
  -- | Create a new credential from a token and token secret
  credential :: FilePath -> IO Credential
  credential filepath = newCredential <$> token <*> secretToken
      where token       = lineByKey "tok" <$> getConfigData filepath
            secretToken = lineByKey "tok-sec" <$> getConfigData filepath
  