{-# LANGUAGE OverloadedStrings, NamedFieldPuns #-}

{- | Route middleware for wai.

It's Heavy inspired by @vhost@ middleware from @wai-extra@ but 
gives some sugar to life.
-}

module Network.Wai.Middleware.Route (
    -- * Dispatching
    dispatch, 
    -- * Creating rules
    onRegex,
    onPrefix,
    onExact
) where

import Data.List (find)
import Data.ByteString (ByteString, isPrefixOf)
import Text.Regex.Posix ((=~))
import Control.Applicative ((<$>), (<*>))
import Network.HTTP.Types (Method)
import Network.Wai (Request(..), Application)

{- | Dispatch. Seek for first route where the \"rule\" gives a positive result.
Uses 'find' instead 'filter' in @vhost@

> dispatch [
>     ((=="/") . rawPathInfo, auth . cache . Issues.get)
>   , ((=="/issues") . rawPathInfo, cache . About.handle)
>   ] defaultApp
-}
dispatch :: 
       [(Request -> Bool, Application)]      
                    -- ^ List of routes
    -> Application  -- ^ Default 'Application' 
    -> Application  -- ^ Returns founded 'Application'. If not found - returns
                    --   default.
dispatch routes def req =  
    case find (\(b, _) -> b req) routes of
        Just (_, app) -> app req
        Nothing -> def req

-- | Most frequently case: HTTP Method and Request path regex pattern.
--
-- > ("*" `rule` "^/issue/", app)
-- > (methodGet `rule` "^/issues$", anotherApp)
onRegex :: 
       Method       -- ^ HTTP Method. Use @\"*\"@ for any method
    -> ByteString   -- ^ Request path pattern. 
    -> Request      -- ^ Request 
    -> Bool
onRegex method needle = 
    withMethod method $ (=~ needle) . rawPathInfo
    
-- | HTTP Method and Request path prefix.
--
-- > ("*" `rule` "^/issue/", app)
-- > (methodGet `rule` "^/issues$", anotherApp)
onPrefix :: 
       Method       -- ^ HTTP Method. Use @\"*\"@ for any method
    -> ByteString   -- ^ Request path prefix. 
    -> Request      -- ^ Request 
    -> Bool         -- ^ Routing rule
onPrefix method needle = 
    withMethod method $ (needle `isPrefixOf`) . rawPathInfo

-- | HTTP Method and Request path.
--
-- > ("*" `rule` "^/issue/", app)
-- > (methodGet `rule` "^/issues$", anotherApp)
onExact :: 
       Method       -- ^ HTTP Method. Use @\"*\"@ for any method
    -> ByteString   -- ^ Request path. 
    -> Request      -- ^ Request 
    -> Bool         -- ^ Routing rule
onExact method needle = 
    withMethod method $ (needle ==) . rawPathInfo

withMethod :: 
       Method
    -> (Request -> Bool) 
    -> Request 
    -> Bool
withMethod "*" p = p
withMethod m p = (&&) <$> (== m) . requestMethod <*> p