-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.

{-# LANGUAGE OverloadedStrings #-}

module Network.Wai.Predicate.Request
    ( Req
    , HasMethod  (..)
    , HasHeaders (..)
    , HasCookies (..)
    , HasQuery   (..)
    , HasPath    (..)
    , HasRequest (..)

    , fromRequest
    , lookupHeader
    , lookupQuery
    , lookupCookie
    , lookupSegment
    ) where

import Data.ByteString (ByteString)
import Data.Maybe (mapMaybe)
import Data.Vector (Vector, (!?))
import Data.Word
import Network.HTTP.Types
import Network.Wai (Request)
import Web.Cookie

import qualified Data.ByteString as B
import qualified Network.Wai     as Wai
import qualified Data.Vector     as Vec

class HasRequest a where
    getRequest :: a -> Wai.Request

class HasMethod a where
    method :: a -> Method

class HasHeaders a where
    headers :: a -> RequestHeaders

class HasCookies a where
    cookies :: a -> Cookies

class HasQuery a where
    queryItems :: a -> Query

class HasPath a where
    segments :: a -> Vector ByteString

data Req = Req
    { _request  :: Request
    , _cookies  :: Cookies
    , _segments :: Vector ByteString
    }

instance HasRequest Req where
    getRequest = _request

instance HasMethod Req where
    method = Wai.requestMethod . getRequest

instance HasMethod Wai.Request where
    method = Wai.requestMethod

instance HasHeaders Req where
    headers = Wai.requestHeaders . getRequest

instance HasHeaders Wai.Request where
    headers = Wai.requestHeaders

instance HasQuery Req where
    queryItems = Wai.queryString . getRequest

instance HasQuery Wai.Request where
    queryItems = Wai.queryString

instance HasCookies Req where
    cookies = _cookies

instance HasPath Req where
    segments = _segments

fromRequest :: Request -> Req
fromRequest rq =
    Req rq (concatMap parseCookies (getHeaders "Cookie" rq))
           (Vec.fromList . splitSegments . Wai.rawPathInfo $ rq)

lookupHeader :: HasHeaders r => HeaderName -> r -> [ByteString]
lookupHeader name = getHeaders name

lookupSegment :: HasPath r => Word -> r -> Maybe ByteString
lookupSegment i r = segments r !? fromIntegral i

lookupCookie :: HasCookies r => ByteString -> r -> [ByteString]
lookupCookie name = map snd . filter ((name ==) . fst) . cookies

lookupQuery :: HasQuery r => ByteString -> r -> [ByteString]
lookupQuery name = mapMaybe snd . filter ((name ==) . fst) . queryItems

getHeaders :: HasHeaders r => HeaderName -> r -> [ByteString]
getHeaders name = map snd . filter ((name ==) . fst) . headers

-----------------------------------------------------------------------------
-- Internal

splitSegments :: ByteString -> [ByteString]
splitSegments a
    | B.null a  = []
    | "/" == a  = []
    | otherwise = if B.head a == slash then go (B.tail a) else go a
  where
    go b =
        let (x, y) = B.breakByte slash b
        in urlDecode False x : if B.null y then [] else go (B.tail y)
    slash = 47
