-- | Helpers for dealing with HTTP requests.
module Strive.Internal.HTTP where

import Data.Aeson (FromJSON, eitherDecode)
import Data.ByteString.Char8 (unpack)
import Data.ByteString.Lazy (ByteString)
import Data.Monoid ((<>))
import Network.HTTP.Conduit (Request, Response, checkStatus, httpLbs, method,
                             parseUrl, responseBody)
import Network.HTTP.Types (Method, Query, QueryLike, methodDelete, methodGet,
                           methodPost, methodPut, renderQuery, toQuery)
import Strive.Client (Client (client_accessToken, client_httpManager))

-- | Perform an HTTP DELETE request.
delete :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Either String j)
delete = http methodDelete

-- | Perform an HTTP GET request.
get :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Either String j)
get = http methodGet

-- | Perform an HTTP POST request.
post :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Either String j)
post = http methodPost

-- | Perform an HTTP PUT request.
put :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Either String j)
put = http methodPut

-- | Perform an HTTP request.
http :: (QueryLike q, FromJSON j) => Method -> Client -> String -> q -> IO (Either String j)
http httpMethod client resource query = do
  request <- buildRequest httpMethod client resource query
  response <- performRequest client request
  return (decodeValue response)

-- | Build a request.
buildRequest :: QueryLike q => Method -> Client -> String -> q -> IO Request
buildRequest httpMethod client resource query = do
  request <- parseUrl (buildUrl client resource query)
  return request
    { checkStatus = \ _ _ _ -> Nothing
    , method = httpMethod
    }

-- | Build a URL.
buildUrl :: QueryLike q => Client -> String -> q -> String
buildUrl client resource query = concat
  [ "https://www.strava.com/"
  , resource
  , unpack (renderQuery True (buildQuery client <> toQuery query))
  ]

-- | Build a query.
buildQuery :: Client -> Query
buildQuery client = toQuery
  [ ("access_token", client_accessToken client)
  ]

-- | Actually perform an HTTP request.
performRequest :: Client -> Request -> IO (Response ByteString)
performRequest client request = httpLbs request (client_httpManager client)

-- | Decode a response body as JSON.
decodeValue :: FromJSON j => Response ByteString -> Either String j
decodeValue response = eitherDecode (responseBody response)