-- Modified from Michael Snoyman's BSD3 authenticate-0.0.1
-- and http-wget-0.0.1.
-- Facilitates authentication with "http://rpxnow.com/".

module Network.Gitit.Rpxnow
    ( Identifier (..)
    , authenticate
    ) where

import Text.JSON
import Data.Maybe (isJust, fromJust)
import System.Process
import System.Exit
import System.IO
import Network.HTTP (urlEncodeVars)

-- | Make a post request with parameters to the URL and return a response.
curl :: Monad m
     => String             -- ^ URL
     -> [(String, String)] -- ^ Post parameters
     -> IO (m String)      -- ^ Response body
curl url params = do
    (Nothing, Just hout, Just herr, phandle) <- createProcess $ (proc "curl"
        [url, "-d", urlEncodeVars params]
        ) { std_out = CreatePipe, std_err = CreatePipe }
    exitCode <- waitForProcess phandle
    case exitCode of
        ExitSuccess -> hGetContents hout >>= return . return
        _           -> hGetContents herr >>= return . fail



-- | Information received from Rpxnow after a valid login.
data Identifier = Identifier
    { userIdentifier  :: String
    , userData        :: [(String, String)]
    }
    deriving Show

-- | Attempt to log a user in.
authenticate :: Monad m
             => String -- ^ API key given by RPXNOW.
             -> String -- ^ Token passed by client.
             -> IO (m Identifier)
authenticate apiKey token = do
    body <- curl
                "https://rpxnow.com/api/v2/auth_info"
                [ ("apiKey", apiKey)
                , ("token", token)
                ]
    case body of
        Left s -> return $ fail $ "Unable to connect to rpxnow: " ++ s
        Right b ->
          case decode b >>= getObject of
            Error s -> return $ fail $ "Not a valid JSON response: " ++ s
            Ok o ->
              case valFromObj "stat" o of
                Error _ -> return $ fail "Missing 'stat' field"
                Ok "ok" -> return $ parseProfile o
                Ok stat -> return $ fail $ "Login not accepted: " ++ stat

parseProfile :: Monad m => JSObject JSValue -> m Identifier
parseProfile v = do
    profile <- resultToMonad $ valFromObj "profile" v >>= getObject
    ident <- resultToMonad $ valFromObj "identifier" profile
    let pairs = fromJSObject profile
        pairs' = filter (\(k, _) -> k /= "identifier") pairs
        pairs'' = map fromJust . filter isJust . map takeString $ pairs'
    return $ Identifier ident pairs''

takeString :: (String, JSValue) -> Maybe (String, String)
takeString (k, JSString v) = Just (k, fromJSString v)
takeString _ = Nothing

getObject :: Monad m => JSValue -> m (JSObject JSValue)
getObject (JSObject o) = return o
getObject _ = fail "Not an object"

resultToMonad :: Monad m => Result a -> m a
resultToMonad (Ok x) = return x
resultToMonad (Error s) = fail s