{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Web.Welshy.Response where

import Blaze.ByteString.Builder
import Data.Aeson (ToJSON)
import qualified Data.Aeson as A
import Data.ByteString (ByteString)
import Data.Conduit
import Data.Text (Text)
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
import Network.HTTP.Types
import Network.Wai

import Web.Welshy.Action

-----------------------------------------------------------------------

-- | Set the HTTP status of the response. The default is 'ok200'.
status :: Status -> Action ()
status s = modifyResponse $ \case
    (ResponseBuilder _ h b)    -> ResponseBuilder s h b
    (ResponseFile    _ h f fp) -> ResponseFile    s h f fp
    (ResponseSource  _ h cs)   -> ResponseSource  s h cs

-- | Add or replace one of the response headers.
header :: HeaderName -> ByteString -> Action ()
header k v = modifyResponse $ \case
    (ResponseBuilder s h b)    -> ResponseBuilder s (update h) b
    (ResponseFile    s h f fp) -> ResponseFile    s (update h) f fp
    (ResponseSource  s h cs)   -> ResponseSource  s (update h) cs
    where update h = (k,v) : filter ((/= k) . fst) h

-- | Set the response body to the given lazy 'TL.Text'
-- and the content-type to @text/plain@.
text :: TL.Text -> Action ()
text t = do
    header hContentType "text/plain"
    _builder $ fromLazyByteString $ TL.encodeUtf8 t

-- | Like 'text' but with a strict 'Text' value.
text' :: Text -> Action ()
text' t = do
    header hContentType "text/plain"
    _builder $ fromByteString $ T.encodeUtf8 t

-- | Set the response body to the given lazy 'TL.Text'
-- and the content-type to @text/html@.
html :: TL.Text -> Action ()
html t = do
    header hContentType "text/html"
    _builder $ fromLazyByteString $ TL.encodeUtf8 t

-- | Like 'html' but with a strict 'Text' value.
html' :: Text -> Action ()
html' t = do
    header hContentType "text/html"
    _builder $ fromByteString $ T.encodeUtf8 t

-- | Set the response body to the JSON encoding of the given value
-- and the content-type to @application/json@.
json :: ToJSON a => a -> Action ()
json v = do
    header hContentType "application/json"
    _builder $ fromLazyByteString $ A.encode v

-- | Sends the given file as the response.
file :: FilePath -> Action ()
file = flip _file Nothing

-- | 'filePart' @f offset byteCount@ sends @byteCount@ bytes of the file @f@,
-- beginning at @offset@, as the response.
filePart :: FilePath -> Integer -> Integer -> Action ()
filePart f offset byteCount = _file f (Just $ FilePart offset byteCount)

_file :: FilePath -> Maybe FilePart -> Action ()
_file f part = modifyResponse $ \case
    (ResponseBuilder s h _)   -> ResponseFile s h f part
    (ResponseFile    s h _ _) -> ResponseFile s h f part
    (ResponseSource  s h _)   -> ResponseFile s h f part

_builder :: Builder -> Action ()
_builder b = modifyResponse $ \case
    (ResponseBuilder s h _)   -> ResponseBuilder s h b
    (ResponseFile    s h _ _) -> ResponseBuilder s h b
    (ResponseSource  s h _)   -> ResponseBuilder s h b

-- | Set the response body to the given 'Source'.
source :: Source (ResourceT IO) (Flush Builder) -> Action ()
source src = modifyResponse $ \case
    (ResponseBuilder s h _)   -> ResponseSource s h src
    (ResponseFile    s h _ _) -> ResponseSource s h src
    (ResponseSource  s h _)   -> ResponseSource s h src