{-# LANGUAGE OverloadedStrings #-}

module Ketchup.Utils
( breakBS
, fallback
, parseBody
, sendBadRequest
, sendNotFound
, sendReply
, statusMsg
, subBS
, trim
) where

import qualified Data.ByteString.Char8 as B
import           Data.Char (isSpace)
import qualified Data.List as List
import           Network
import           Network.Socket.ByteString

-- |Status Messages
-- Returns status message from a given status id
statusMsg :: Int          -- ^ Status code (ex. 404)
          -> B.ByteString -- ^ Status message (ex. "404 Not Found")
statusMsg stat
    -- 200 Success
    | stat == 200 = "200 OK"
    | stat == 201 = "201 Created"
    | stat == 204 = "204 No Content"
    -- 400 Client Errors
    | stat == 400 = "400 Bad Request"
    | stat == 401 = "401 Unauthorized"
    | stat == 402 = "402 Payment Required"
    | stat == 403 = "403 Forbidden"
    | stat == 404 = "404 Not Found"
    | stat == 405 = "405 Method Not Allowed"
    | stat == 410 = "410 Gone"
    -- 500 Server Errors
    | stat == 500 = "500 Internal Server Error"
    | stat == 501 = "501 Not Implemented"
    | stat == 502 = "502 Bad Gateway"
    | stat == 503 = "503 Service Unavailable"
    | otherwise   = "500 Internal Server Error"

-- Premade HTTP replies

-- |Send 400 Bad Request error
sendBadRequest :: Socket -> IO ()
sendBadRequest client =
    sendReply client 400 [("Content-Type",["text/plain"])] "400 Bad Request!\n"

-- |Send 404 Not Found error
sendNotFound :: Socket -> IO ()
sendNotFound client =
    sendReply client 404 [("Content-Type",["text/plain"])] "404 Not Found!\n"

-- |Send a HTTP reply
-- Sends a reply with the given parameters
sendReply :: Socket                           -- ^ Socket to write to
          -> Int                              -- ^ Status Code to send
          -> [(B.ByteString, [B.ByteString])] -- ^ HTTP headers ("Header",["value1", "value2"])
          -> B.ByteString                     -- ^ Body
          -> IO ()
sendReply client status headers body =
    sendAll client reply
    where
    reply = B.concat
        [ "HTTP/1.1 ", statusMsg status,"\r\n"
        , "Content-Length: ", B.pack $ show $ B.length body, "\r\n"
        , "Connection: close\r\n", heads, "\r\n", body]
    -- Turn ("a", ["b", "c"]) headers into "a: b,c"
    heads = B.concat $ map toHeader headers
    toHeader x = B.concat [fst x, ": "
                          ,B.concat $ List.intersperse "," $ snd x
                          ,"\r\n"]

-- |Trim whitespace from headers
trim :: B.ByteString -> B.ByteString
trim = f . f
    where f = B.reverse . B.dropWhile isSpace

-- |ByteString breakSubstring wrapper that drops delimiters
breakBS :: B.ByteString -> B.ByteString -> (B.ByteString, B.ByteString)
breakBS delimiter source =
    (first, second)
    where
    first  = fst broke
    second = B.drop (B.length delimiter) $ snd broke
    broke  = B.breakSubstring delimiter source

-- |Get a substring of a ByteString
subBS :: Int -> Int -> B.ByteString -> B.ByteString
subBS start length = B.take length . B.drop start

-- |Parse a URL-encoded Request
parseBody :: B.ByteString -> [(B.ByteString, B.ByteString)]
parseBody body = map (breakBS "=") $ B.split '&' body

-- |Maybe a -> a with fallback (if Nothing)
fallback :: Maybe a -> a -> a
fallback (Just value) fallback = value
fallback Nothing      fallback = fallback