{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies      #-}

module Snap.Snaplet.Router
   ( RouterState (..)
   , HasRouter (..)
   , initRouter
   , urlPath
   , urlPathParams
   , redirectURL
   , redirectURLParams
   , routeWith
   , routeWithDebug
   , urlSplice
   , urlParamsSplice
   ) where


import           Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as B
import           Data.Text
import           Snap.Core (MonadSnap)
import qualified Snap.Core as SC
import           Snap.Snaplet
import           Web.Routes (PathInfo, fromPathInfo)
------------------------------------------------------------------------------
import           Snap.Snaplet.Router.HeistSplices
import           Snap.Snaplet.Router.URL
import           Snap.Snaplet.Router.Internal.Types


------------------------------------------------------------------------------
-- | Snaplet initializer.
initRouter :: Text  -- ^ Prefix to add to paths.
           -> SnapletInit b RouterState
initRouter prefix = makeSnaplet "router" "Snap router" Nothing $
    return $ RouterState prefix


------------------------------------------------------------------------------
-- | Given a routing function, routes matching requests or calls
-- 'Snap.Core.pass'.
routeWith :: (PathInfo url, MonadSnap m) =>
             (url -> m ()) -- ^ routing function
          -> m ()
routeWith = flip routeWithOr $ const SC.pass


------------------------------------------------------------------------------
-- | Given a routing function, routes matching requests or returns debugging
-- information. This is __not suitable for production__, but can be useful in
-- seeing what paths are available or determining why a path isn't routing as
-- expected.
routeWithDebug :: (PathInfo url, MonadSnap m) =>
                  (url -> m ()) -- ^ routing function
               -> m ()
routeWithDebug = flip routeWithOr (failIfNotLocal . SC.writeText)


------------------------------------------------------------------------------
routeWithOr
    :: (PathInfo url, MonadSnap m) => (url -> m ()) -> (Text -> m ()) -> m ()
routeWithOr router onLeft = do
    rqPath <- rqPathInfoNormalized
    case fromPathInfo rqPath of
         (Left e)    -> onLeft . pack $ e
         (Right url) -> router url

rqPathInfoNormalized
    :: (MonadSnap m) => m ByteString
rqPathInfoNormalized = do
    rq <- SC.getRequest
    return . dropTrailingSlashes $ SC.rqPathInfo rq
  where
    dropTrailingSlashes s =
        if B.singleton '/' `B.isSuffixOf` s
        then dropTrailingSlashes $ B.init s
        else s