-- Copyright 2002 Warrick Gray
-- Copyright 2001,2002 Peter Thiemann
-- Copyright 2003-2006 Bjorn Bringert
-- Copyright 2009 Henning Thielemann
module Network.MoHWS.HTTP.Header (
    Group, group, ungroup,
    setGroup,
    getGroup,
    list,
    modifyMany,
    T, Hdrs.Header(..), make,
    getName, getValue, name, value,
    Name, Hdrs.HeaderName(..),
    HasHeaders,
    -- * Header parsing
    pGroup, makeName,
    -- * Header manipulation
    insert,
    insertIfMissing,
    replace, insertMany,
    lookupMany, lookup,
    -- * Constructing headers
    makeContentLength,
    makeContentType,
    makeLocation,
    makeLastModified,
    TransferCoding(..),
    makeTransferCoding,
    -- * Getting values of specific headers
    getContentType,
    getContentLength,
--    getHost,
   ) where

import qualified Network.HTTP.Headers as Hdrs
import Network.HTTP.Headers (HasHeaders, )

import Network.MoHWS.ParserUtility
import Network.MoHWS.Utility

-- import Network.Socket (HostName, )
import Network.URI (URI, )

import Control.Monad (liftM, )
import Data.Char (toLower, )
import Data.Map (Map, )
import qualified Data.Map as Map hiding (Map)
import System.Time (ClockTime, toUTCTime, )
import Text.ParserCombinators.Parsec

import qualified Data.Accessor.Basic as Accessor

import Prelude hiding (lookup, )


-- * Header

type T    = Hdrs.Header
type Name = Hdrs.HeaderName


make :: Name -> String -> T
make = Hdrs.Header

getName :: T -> Name
getName (Hdrs.Header n _v) = n

getValue :: T -> String
getValue (Hdrs.Header _n v) = v

name :: Accessor.T T Name
name =
   Accessor.fromSetGet
      (\n (Hdrs.Header _ v) -> Hdrs.Header n v)
      getName

value :: Accessor.T T String
value =
   Accessor.fromSetGet
      (\v (Hdrs.Header n _) -> Hdrs.Header n v)
      getValue


-- Translation between header names and values,
nameList :: [ (String, Name) ]
nameList =
   ("Cache-Control"        , Hdrs.HdrCacheControl      ) :
   ("Connection"           , Hdrs.HdrConnection        ) :
   ("Date"                 , Hdrs.HdrDate              ) :
   ("Pragma"               , Hdrs.HdrPragma            ) :
--   ("Trailer"              , Hdrs.HdrTrailer           ) :

   ("Transfer-Encoding"    , Hdrs.HdrTransferEncoding  ) :
   ("Upgrade"              , Hdrs.HdrUpgrade           ) :
   ("Via"                  , Hdrs.HdrVia               ) :
   ("Accept"               , Hdrs.HdrAccept            ) :
   ("Accept-Charset"       , Hdrs.HdrAcceptCharset     ) :
   ("Accept-Encoding"      , Hdrs.HdrAcceptEncoding    ) :
   ("Accept-Language"      , Hdrs.HdrAcceptLanguage    ) :
   ("Authorization"        , Hdrs.HdrAuthorization     ) :
   ("From"                 , Hdrs.HdrFrom              ) :
   ("Host"                 , Hdrs.HdrHost              ) :
   ("If-Modified-Since"    , Hdrs.HdrIfModifiedSince   ) :
   ("If-Match"             , Hdrs.HdrIfMatch           ) :
   ("If-None-Match"        , Hdrs.HdrIfNoneMatch       ) :
   ("If-Range"             , Hdrs.HdrIfRange           ) :
   ("If-Unmodified-Since"  , Hdrs.HdrIfUnmodifiedSince ) :
   ("Max-Forwards"         , Hdrs.HdrMaxForwards       ) :
   ("Proxy-Authorization"  , Hdrs.HdrProxyAuthorization) :
   ("Range"                , Hdrs.HdrRange             ) :
   ("Referer"              , Hdrs.HdrReferer           ) :
--   ("TE"                   , Hdrs.HdrTE                ) :
   ("User-Agent"           , Hdrs.HdrUserAgent         ) :
   ("Age"                  , Hdrs.HdrAge               ) :
   ("Location"             , Hdrs.HdrLocation          ) :
   ("Proxy-Authenticate"   , Hdrs.HdrProxyAuthenticate ) :
   ("Public"               , Hdrs.HdrPublic            ) :
   ("Retry-After"          , Hdrs.HdrRetryAfter        ) :
   ("Server"               , Hdrs.HdrServer            ) :
   ("Vary"                 , Hdrs.HdrVary              ) :
   ("Warning"              , Hdrs.HdrWarning           ) :
   ("WWW-Authenticate"     , Hdrs.HdrWWWAuthenticate   ) :
   ("Allow"                , Hdrs.HdrAllow             ) :
   ("Content-Base"         , Hdrs.HdrContentBase       ) :
   ("Content-Encoding"     , Hdrs.HdrContentEncoding   ) :
   ("Content-Language"     , Hdrs.HdrContentLanguage   ) :
   ("Content-Length"       , Hdrs.HdrContentLength     ) :
   ("Content-Location"     , Hdrs.HdrContentLocation   ) :
   ("Content-MD5"          , Hdrs.HdrContentMD5        ) :
   ("Content-Range"        , Hdrs.HdrContentRange      ) :
   ("Content-Type"         , Hdrs.HdrContentType       ) :
   ("ETag"                 , Hdrs.HdrETag              ) :
   ("Expires"              , Hdrs.HdrExpires           ) :
   ("Last-Modified"        , Hdrs.HdrLastModified      ) :
   ("Set-Cookie"           , Hdrs.HdrSetCookie         ) :
   ("Cookie"               , Hdrs.HdrCookie            ) :
   ("Expect"               , Hdrs.HdrExpect            ) :
   []

toNameMap :: Map String Name
toNameMap =
   Map.fromList [(map toLower x, y) | (x,y) <- nameList]

{-
fromHeaderNameMap :: Map Name String
fromHeaderNameMap = Map.fromList [(y,x) | (x,y) <- headerNames]
-}

makeName :: String -> Name
makeName s =
   Map.findWithDefault (Hdrs.HdrCustom s) (map toLower s) toNameMap


-- * Header group

newtype Group = Group { ungroup :: [T] }

group :: [T] -> Group
group = Group

instance Show Group where
   showsPrec _ =
      foldr (.) id . map shows . ungroup
--      foldr (.) id . map (\x -> shows x . showString crLf) . unGroup

instance HasHeaders Group where
   getHeaders = ungroup
   setHeaders _ = group


getGroup :: HasHeaders x => x -> Group
getGroup = group . Hdrs.getHeaders

setGroup :: HasHeaders x => x -> Group -> x
setGroup x = Hdrs.setHeaders x . ungroup

list :: HasHeaders x => x -> [T]
list = Hdrs.getHeaders

modifyMany :: HasHeaders x => ([T] -> [T]) -> x -> x
modifyMany f x = Hdrs.setHeaders x $ f $ Hdrs.getHeaders x


-- * Header manipulation functions

-- Header manipulation functions
insert, replace, insertIfMissing :: HasHeaders a =>
   Name -> String -> a -> a
insert = Hdrs.insertHeader
insertIfMissing = Hdrs.insertHeaderIfMissing
replace = Hdrs.replaceHeader

insertMany :: HasHeaders a => [T] -> a -> a
insertMany = Hdrs.insertHeaders


lookupMany :: HasHeaders a => Name -> a -> [String]
lookupMany searchName x =
   [ v | Hdrs.Header n v <- list x, searchName == n ]

lookup :: HasHeaders a => Name -> a -> Maybe String
lookup n = Hdrs.lookupHeader n . list
-- lookup n x = listToMaybe $ lookupMany n x


-- * Constructing specific headers

makeContentLength :: Integer -> T
makeContentLength i = Hdrs.Header Hdrs.HdrContentLength (show i)

makeContentType :: String -> T
makeContentType t = Hdrs.Header Hdrs.HdrContentType t

makeLocation :: URI -> T
makeLocation t = Hdrs.Header Hdrs.HdrLocation $ show t

makeLastModified :: ClockTime -> T
makeLastModified t =
   Hdrs.Header Hdrs.HdrLastModified (formatTimeSensibly (toUTCTime t))

makeTransferCoding :: TransferCoding -> T
makeTransferCoding te = Hdrs.Header Hdrs.HdrTransferEncoding (transferCodingStr te)

data TransferCoding
  = ChunkedTransferCoding
  | GzipTransferCoding
  | CompressTransferCoding
  | DeflateTransferCoding
  deriving Eq

transferCodingStr :: TransferCoding -> String
transferCodingStr ChunkedTransferCoding  = "chunked"
transferCodingStr GzipTransferCoding     = "gzip"
transferCodingStr CompressTransferCoding = "compress"
transferCodingStr DeflateTransferCoding  = "deflate"

-- validTransferCoding :: [TransferCoding] -> Bool
-- validTransferCoding codings
--   | null codings
--     || last codings == ChunkedTransferCoding
--        && ChunkedTransferCoding `notElem` init codings = True
--   | otherwise = False
;

-- * Values of specific headers

getContentType :: HasHeaders a => a -> Maybe String
getContentType x = lookup Hdrs.HdrContentType x

getContentLength :: HasHeaders a => a -> Maybe Integer
getContentLength x = lookup Hdrs.HdrContentLength x >>= readM

{-
getHost :: HasHeaders a => a -> Maybe (HostName, Maybe Int)
getHost x = lookup Hdrs.HdrHost x >>= parseHost
-}


-- * Parsing

pGroup :: Parser Group
pGroup = liftM group $ many pHeader

pHeader :: Parser T
pHeader =
    do n <- pToken
       char ':'
       many pWS1
       line <- lineString
       pCRLF
       extraLines <- many extraFieldLine
       return $ Hdrs.Header (makeName n) (concat (line:extraLines))

extraFieldLine :: Parser String
extraFieldLine =
    do sp <- pWS1
       line <- lineString
       pCRLF
       return (sp:line)

{-
parseHost :: String -> Maybe (HostName, Maybe Int)
parseHost s =
    let (host,prt) = break (==':') s
    in  case prt of
           ""       -> Just (host, Nothing)
           ':':port -> readM port >>= \p -> Just (host, Just p)
           _        -> Nothing
-}