{-# LANGUAGE OverloadedStrings #-}

-- | This module contains helpers for use "Yesod.Routes.Dispatch" with 
--   "Network.Wai".
--
--   This 'Middleware' uses first 'D.Piece' in path to route @HTTP@ method.
--   'D.Static' means concrete method. 'D.Dynamic' means any method.

module Network.Wai.Middleware.Route (

    -- * Middleware
    dispatch,

    -- * Route helpers #route_helpers#
    -- $route_helpers
    
    -- ** Simple paths
    -- $simple_paths
    
    -- *** Fixed length
    sGET, sPOST, sHEAD, sPUT, sDELETE, sTRACE, sCONNECT, sOPTIONS,
    sANY,
    -- *** Variable length
    mGET, mPOST, mHEAD, mPUT, mDELETE, mTRACE, mCONNECT, mOPTIONS,
    mANY, 
    
    -- ** Native paths
    -- *** Fixed length
    sGET', sPOST', sHEAD', sPUT', sDELETE', sTRACE', sCONNECT', sOPTIONS',
    sANY', sRoute,
    -- *** Variable length
    mGET', mPOST', mHEAD', mPUT', mDELETE', mTRACE', mCONNECT', mOPTIONS',
    mANY', mRoute,
    -- * 'D.Piece' helper
    mkP
) where

import Control.Applicative ((<$>), (<*>))

import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8)

import Network.Wai (Application, requestMethod, pathInfo)
import qualified Yesod.Routes.Dispatch as D

-----------------------------------------------------------------------------
-- Docs chunks
-----------------------------------------------------------------------------

-- $route_helpers
-- Functions below simplify process of creating 'D.Route's. Each helper 
-- prepends corresponding @HTTP@ method to path.

-- $simple_paths
-- All functions below is combinations of native path helpers and 'mkP'.

-----------------------------------------------------------------------------
-- Middleware.
-----------------------------------------------------------------------------

-- | Dispatch function.
-- 
-- > rs :: Dispatch Application
-- > rs = toDispatch [
-- >      -- simple paths 
-- >      sGET  "foo"  fooGetApp
-- >    , sPOST "foo"  fooPostApp
-- >    , sGET  "foo/" fooGetDynApp
-- > 
-- >      -- native paths
-- >    , sGET'  [Static "bar", Dynamic] barGetDynApp
-- >
-- >      -- simple paths, any method 
-- >    , sANY  "any"  anyMethodApp
-- >    ]
-- > 
-- > app :: Application
-- > app = dispatch rs (error "Not dispatched")

dispatch :: 
       D.Dispatch Application   
            -- ^ Dispatch function. 
            --   Use 'D.toDispatch' and route helpers below.
    -> Application 
            -- ^ Default (@404@) application. 
    -> Application
dispatch mappings defApp req = 
    case mappings . needle $ req of
        Nothing -> defApp req
        (Just app) -> app req 
  where
    needle = (:) <$> decodeUtf8 . requestMethod <*> pathInfo

-----------------------------------------------------------------------------
-- 'D.Route' helpers for simple paths.
-----------------------------------------------------------------------------

-- | 'D.Route' helpers for concrete @HTTP@ methods with fixed-length 
--   simple path.
sGET, sPOST, sHEAD, sPUT, sDELETE, sTRACE, sCONNECT, sOPTIONS :: 
       T.Text       -- ^ Path
    -> Application  -- ^ Routable application 
    -> D.Route Application
sGET = sGET' . mkP
sPOST = sPOST' . mkP
sHEAD = sHEAD' . mkP
sPUT = sPUT' . mkP
sDELETE = sDELETE' . mkP
sTRACE = sTRACE' . mkP
sCONNECT = sCONNECT' . mkP
sOPTIONS = sOPTIONS' . mkP

-- | 'D.Route' helper for any @HTTP@ method with fixed-length simple path.
sANY :: 
       T.Text       -- ^ Path
    -> Application  -- ^ Routable application 
    -> D.Route Application
sANY = sRoute D.Dynamic . mkP

-- | 'D.Route' helpers for concrete @HTTP@ methods with vary-length 
--   simple path.
mGET, mPOST, mHEAD, mPUT, mDELETE, mTRACE, mCONNECT, mOPTIONS :: 
       T.Text       -- ^ Path
    -> Application  -- ^ Routable application 
    -> D.Route Application
mGET = mGET' . mkP
mPOST = mPOST' . mkP
mHEAD = mHEAD' . mkP
mPUT = mPUT' . mkP
mDELETE = mDELETE' . mkP
mTRACE = mTRACE' . mkP
mCONNECT = mCONNECT' . mkP
mOPTIONS = mOPTIONS' . mkP

-- | 'D.Route' helper for any @HTTP@ method with vary-length simple path.
mANY :: 
       T.Text       -- ^ Path
    -> Application  -- ^ Routable application 
    -> D.Route Application
mANY = mRoute D.Dynamic . mkP

-----------------------------------------------------------------------------
-- 'D.Route' helpers for native 'D.Piece' paths (Fixed-length)
-----------------------------------------------------------------------------

-- | 'D.Route' helpers for concrete @HTTP@ methods with fixed-length native 
--   @yesod-routes@ path.
sGET', sPOST', sHEAD', sPUT', sDELETE', sTRACE', sCONNECT', sOPTIONS' :: 
       [D.Piece]        -- ^ Path
    -> Application      -- ^ Routable application
    -> D.Route Application
sGET' = sRoute (D.Static "GET")
sPOST' = sRoute (D.Static "POST")
sHEAD' = sRoute (D.Static "HEAD")
sPUT' = sRoute (D.Static "PUT")
sDELETE' = sRoute (D.Static "DELETE")
sTRACE' = sRoute (D.Static "TRACE")
sCONNECT' = sRoute (D.Static "CONNECT")
sOPTIONS' = sRoute (D.Static "OPTIONS")

-- | 'D.Route' helper for any @HTTP@ method with fixed-length native 
--   @yesod-routes@ path.
sANY' :: 
       [D.Piece]        -- ^ Path
    -> Application      -- ^ Routable application
    -> D.Route Application
sANY' = sRoute D.Dynamic

-- | Generalized 'D.Route' helper for fixed-length native 
--   @yesod-routes@ path.
sRoute ::  
       D.Piece      -- ^ Method piece. 'D.Dynamic' means any method. 
    -> [D.Piece]    -- ^ Path pieces
    -> Application  -- ^ Routed application
    -> D.Route Application
sRoute = (`defRoute` False)

-----------------------------------------------------------------------------
-- Generalized 'D.Route' helpers for native 'D.Piece' paths
-----------------------------------------------------------------------------

-- | 'D.Route' helpers for concrete @HTTP@ methods with vary-length native 
--   @yesod-routes@ path.
mGET', mPOST', mHEAD', mPUT', mDELETE', mTRACE', mCONNECT', mOPTIONS' :: 
       [D.Piece]        -- ^ Path
    -> Application      -- ^ Routable application
    -> D.Route Application
mGET' = mRoute (D.Static "GET")
mPOST' = mRoute (D.Static "POST")
mHEAD' = mRoute (D.Static "HEAD")
mPUT' = mRoute (D.Static "PUT")
mDELETE' = mRoute (D.Static "DELETE")
mTRACE' = mRoute (D.Static "TRACE")
mCONNECT' = mRoute (D.Static "CONNECT")
mOPTIONS' = mRoute (D.Static "OPTIONS")

-- | 'D.Route' helper for any @HTTP@ method with fixed-length native 
--   @yesod-routes@ path.
mANY' :: 
       [D.Piece]        -- ^ Path
    -> Application      -- ^ Routable application
    -> D.Route Application
mANY' = sRoute D.Dynamic

-- | Generalized 'D.Route' helper for vary-length native 
--   @yesod-routes@ path.
mRoute ::  
       D.Piece      -- ^ Method piece. 'D.Dynamic' means any method. 
    -> [D.Piece]    -- ^ Path pieces
    -> Application  -- ^ Routed application
    -> D.Route Application
mRoute = (`defRoute` True)

-----------------------------------------------------------------------------
-- 'D.Piece' helpers
-----------------------------------------------------------------------------

-- | Make 'D.Piece's from 'T.Text'. Splits path on slashes. Dual slashes means
--   'D.Dynamic' 'D.Piece's.
--
-- > mkP ""             -- []
-- > mkP "foo/bar"      -- [Static "foo", Static "bar"]
-- > mkP "foo//bar/"    -- [Static "foo", Dynamic, Static "bar", Dynamic]
mkP :: T.Text -> [D.Piece]
mkP = 
    map chunk . T.split (=='/')
  where
    chunk "" = D.Dynamic
    chunk c = D.Static c

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

defRoute :: 
       D.Piece      -- ^ Method piece. 'D.Dynamic' means any method. 
    -> Bool         -- ^ 'D.rhHasMulti'
    -> [D.Piece]    -- ^ Path pieces
    -> Application  -- ^ Routed application
    -> D.Route Application
defRoute m hasMulti pieces = 
    D.Route (m:pieces) hasMulti . const . Just