-----------------------------------------------------------------------------
-- |
-- Module      :  Api.hs
--
-- Maintainer  :  adam.smyczek@gmail.com
-- Stability   :  experimental
-- Portability :  portable
--
-- ReviewBoard API
--
-- This package provides the basic ReviewBoard API calls.
-- All calls are executed inside the 'RBAction' monad that represents 
-- a session to the ReviewBoard server. A login to the server is performed
-- in the 'RBAction' run method 'runRBAction'.
--
-- All actions return the 'RBResponse' object containing the response 
-- status 'RBStatus' and the original JSon response. Errors are handled in 
-- two ways:
--
-- * Network errors, for example connection errors throw an exception.
--
-- * Response errors resulting in for example invalid request parameters are
--   handled using the 'rbErrHandler' (by default print to stdin).
--
-- The current version provides mainly API calls to handle review requests,
-- but this can be easily extended using generalized 'rbPostRequest', 'rbGetRequest'
-- or directly the main 'runRequest' functions.
--
-- Refer to project page for details:
-- <http://code.google.com/p/reviewboard/wiki/ReviewBoardAPI>
--
-- TODOs:
-- 
-- * Add more API calls, of cause!
--
-- * Add abstraction for response data, for example 'reviewRequestNew' should
-- return the @id@ of the new created request. Currently this have to be
-- parsed from the JSon response e.g. using 'rrId' function.
--
-----------------------------------------------------------------------------

module ReviewBoard.Api (

    -- Modules
    module ReviewBoard.Core,
    module ReviewBoard.Browser,

    -- API calls
    reviewRequest,
    reviewRequestNew,
    reviewRequestDelete,
    reviewRequestSet,
    reviewRequestSetField,
    reviewRequestSaveDraft,
--     reviewRequestPublishDraft, not supported yet
    reviewRequestDiffNew,
    reviewRequestList,

    -- Types
    RRField(..),

    -- Util functions
    execRBAction,
    rbPostRequest,
    rbGetRequest,
    rrId

    -- * Example
    -- $example1

    ) where

import ReviewBoard.Core
import ReviewBoard.Browser
import Network.URI
import Network.HTTP
import qualified Network.Browser as NB
import Data.Maybe
import Control.Monad.Error
import Control.Monad.State

-- | Execute a ReviewBoard action using the provided URL, user
-- and password.
--
execRBAction :: String -> String -> String -> (RBAction a) -> IO a
execRBAction url user password action = do
    r <- runRBAction url user password action
    either error return $ fst r

-- ---------------------------------------------------------------------------
-- Review request API calls

-- | Create new review request using the provided repository path and an optional
-- submit_as user. The returned response contains the @id@ of the new created
-- review request that can be accessed using 'rrId' helper function.
--
reviewRequestNew :: String -> Maybe String -> RBAction RBResponse
reviewRequestNew p (Just u) = rbPostRequest "reviewrequests/new" [("repository_path", p), ("submit_as", u)]
reviewRequestNew p Nothing  = rbPostRequest "reviewrequests/new" [("repository_path", p)]

-- | Delete review request with request @id@.
--
reviewRequestDelete :: Integer -> RBAction RBResponse
reviewRequestDelete id = rbPostRequest ("reviewrequests/" ++ show id ++ "/delete") []

-- | Get review request with @id@.
--
reviewRequest :: Integer -> RBAction RBResponse
reviewRequest id = rbPostRequest ("reviewrequests/" ++ show id ) []

-- | Save review request draft whith @id@.
--
reviewRequestSaveDraft :: Integer -> RBAction RBResponse
reviewRequestSaveDraft id = rbPostRequest ("reviewrequests/" ++ show id ++ "/draft/save") []

-- Publish review request draft for id
--
-- reviewRequestPublishDraft :: Integer -> RBAction RBResponse
-- reviewRequestPublishDraft id = rbRequest ("reviewrequests/" ++ show id ++ "/reviews/draft/publish") [("shipit", "False")]

-- | Set fields to review request draft with @id@.
--
reviewRequestSet :: Integer -> [(RRField, String)] -> RBAction RBResponse
reviewRequestSet id fs = rbPostRequest (concat ["reviewrequests/", show id, "/draft/set/"]) (map (\(f, v) -> (show f, v)) fs)

-- | Set one field for review request draft with @id@.
--
reviewRequestSetField :: Integer -> RRField -> String -> RBAction RBResponse
reviewRequestSetField id f v = rbPostRequest (concat ["reviewrequests/", show id, "/draft/set/", show f]) [("value", v)]

-- | Add a new diff to a review request with @id@, file path and the basedir parameter.
--
reviewRequestDiffNew :: Integer -> String -> String -> RBAction RBResponse
reviewRequestDiffNew id bd fp = do
    uri  <- mkURI $ concat ["reviewrequests/", show id, "/diff/new"]
    let form = Form POST uri [fileUpload "path" fp "text/plain", textField "basedir" bd]
    runRequest form return

-- | List review requests addressed to one user with a status. If user and status are Nothing,
-- 'reviewRequestList' lists all request.
--
reviewRequestList :: Maybe String -> Maybe String -> RBAction RBResponse
reviewRequestList Nothing  Nothing  = rbGetRequest "reviewrequests/all" []
reviewRequestList (Just u) Nothing  = rbGetRequest (concat ["reviewrequests/to/user/", u]) []
reviewRequestList Nothing  (Just s) = rbGetRequest "reviewrequests/all" [(show STATUS, s)]
reviewRequestList (Just u) (Just s) = rbGetRequest (concat ["reviewrequests/to/user/", u]) [(show STATUS, s)]

-- ---------------------------------------------------------------------------
-- Types

-- | Review request field type.
--
data RRField 
    = STATUS
    | PUBLIC
    | SUMMARY
    | DESCRIPTION
    | TESTING_DONE
    | BUGS_CLOSED
    | BRANCH
    | TARGET_GROUPS
    | TARGET_PEOPLE
    deriving (Eq, Enum, Bounded)

-- | Request field to name map.
--
rrFieldMap :: [(RRField, String)]
rrFieldMap =
    [ (STATUS,          "status")
    , (PUBLIC,          "public")
    , (SUMMARY,         "summary")
    , (DESCRIPTION,     "description")
    , (TESTING_DONE,    "testing_done")
    , (BUGS_CLOSED,     "bugs_closed")
    , (BRANCH,          "branch")
    , (TARGET_GROUPS,   "target_groups")
    , (TARGET_PEOPLE,   "target_people") ]

instance Show RRField where
    show = fromJust . flip lookup rrFieldMap

-- ---------------------------------------------------------------------------
-- API uitl functions

-- | Default POST request builder for text field parameters only.
--
rbPostRequest :: String -> [(String, String)] -> RBAction RBResponse
rbPostRequest = rbRequest POST

-- | Default GET request builder for text field parameters only.
--
rbGetRequest :: String -> [(String, String)] -> RBAction RBResponse
rbGetRequest = rbRequest GET

-- | Generalized request runner for text field parameters only.
--
rbRequest :: RequestMethod -> String -> [(String, String)] -> RBAction RBResponse
rbRequest rm apiUrl attrs = do
    uri  <- mkURI apiUrl
    let form = Form rm uri (map (\(n, v) -> textField n v) attrs)
    runRequest form return

-- | Get the @id@ from a review request response.
--
rrId :: RBResponse -> Maybe Integer
rrId rsp = jsInt ["review_request", "id"] (rbRspBody rsp)

{- $example1

The following RBAction creates a new review request draft, sets few fields
and uploads a diff file:

>    newRRAction :: RBAction ()
>    newRRAction = do
>        rsp <- reviewRequestNew "repository" Nothing
>        let id = fromJust . rrId $ rsp
>        reviewRequestsSetField id TARGET_PEOPLE "reviewers"
>        reviewRequestsSetField id DESCRIPTION "Request description"
>        reviewRequestsDiffNew  id "basedir" "diffFileName"
>        reviewRequestSaveDraft id
>        liftIO $ print "Done."

To run this action, execute:

>   execRBAction "url" "user" "password" newRRAction

-}