{-
Copyright (c) Sebastiaan Visser 2008

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. Neither the name of the author nor the names of his contributors
   may be used to endorse or promote products derived from this software
   without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
-}
{-# LANGUAGE TemplateHaskell, TypeOperators #-}
module Network.Protocol.Http.Data where

import Control.Category
import Data.Char
import Data.List
import Data.List.Split
import Data.Label
import Network.Protocol.Http.Status
import Network.Protocol.Uri
import Prelude hiding ((.), id, lookup, mod)

-- | List of HTTP request methods.

data Method =
    OPTIONS
  | GET
  | HEAD
  | POST
  | PUT
  | DELETE
  | TRACE
  | CONNECT
  | OTHER String
  deriving (Read, Show, Eq)

-- | HTTP protocol version.

data Version = Version {_major :: Int, _minor :: Int}
  deriving (Read, Show, Eq, Ord)

type Key   = String
type Value = String

-- | HTTP headers as mapping from keys to values.

newtype Headers = Headers { unHeaders :: [(Key, Value)] } -- order seems to matter
  deriving (Read, Show, Eq)

-- | Request specific part of HTTP messages.

data Request = Request  { __method :: Method, __uri :: String }
  deriving (Read, Show, Eq)

-- | Response specific part of HTTP messages.

data Response = Response { __status :: Status }
  deriving (Read, Show, Eq)

-- | An HTTP message. The message body is *not* included.

data Http a = Http
  { _headline :: a
  , _version  :: Version
  , _headers  :: Headers
  } deriving (Read, Show, Eq)

-- | All recognized method constructors as a list.

methods :: [Method]
methods = [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT]

-- | Create HTTP 1.0 version.

http10 :: Version
http10 = Version 1 0

-- | Create HTTP 1.1 version.

http11 :: Version
http11 = Version 1 1

-- | Create an empty set of headers.

emptyHeaders :: Headers
emptyHeaders = Headers []

-- | Create an empty HTTP request message.

emptyRequest :: Http Request
emptyRequest = Http (Request GET "") http11 emptyHeaders

-- | Create an empty HTTP response message.

emptyResponse :: Http Response
emptyResponse = Http (Response OK) http11 emptyHeaders

$(mkLabels [''Version, ''Request, ''Response, ''Http])

-- | Label to access the method part of an HTTP request message.

method :: Http Request :-> Method
method = _method . headline

-- | Label to access the URI part of an HTTP request message.

uri :: Http Request :-> String
uri = _uri . headline

-- | Label to access the URI part of an HTTP request message and access it as a
-- true URI data type.

asUri :: Http Request :-> Uri
asUri = (Bij toUri showUri) `iso` uri

-- | Label to access the status part of an HTTP response message.

status :: Http Response :-> Status
status = _status . headline

-- | Normalize the capitalization of an HTTP header key.

normalizeHeader :: Key -> Key
normalizeHeader = intercalate "-" . map casing . splitOn "-"
  where
  casing ""     = ""
  casing (x:xs) = toUpper x : map toLower xs

-- | Generic label to access an HTTP header field by key. Returns first matching
-- field

header :: Key -> Http a :-> Maybe Value
header key = lens
  (lookup (normalizeHeader key) . unHeaders . get headers)
  (\x -> modify headers (Headers . alter (normalizeHeader key) x . unHeaders))
  where
  alter :: Eq a => a -> Maybe b -> [(a, b)] -> [(a, b)]
  alter k v []                      = maybe [] (\w -> (k, w):[]) v
  alter k v ((x, y):xs) | k == x    = maybe xs (\w -> (k, w):xs) v
                        | otherwise = (x, y) : alter k v xs

-- | Label to access HTTP header fields, return a list of matching fields

headerMany :: Key -> Http a :-> [Value]
headerMany key = lens
  (map snd . filter (key_is (normalizeHeader key)) . unHeaders . get headers)
  (\x -> modify headers (Headers . alter (normalizeHeader key) x . unHeaders))
  where
  alter :: Eq a => a -> [b] -> [(a, b)] -> [(a, b)]
  alter k []     []                      = []
  alter k []     ((x, y):xs) | k == x    =          alter k [] xs
                             | otherwise = (x, y) : alter k [] xs
  alter k (v:vs) []                      = (k, v) : alter k vs []
  alter k (v:vs) ((x, y):xs) | k == x    = (k, v) : alter k vs xs
                             | otherwise = (x, y) : alter k (v:vs) xs
  key_is k' (k,v) = k==k'