{-# LANGUAGE CPP               #-}
{-# LANGUAGE DeriveFunctor     #-}
{-# LANGUAGE GADTs             #-}
{-# LANGUAGE OverloadedStrings #-}
module Servant.Server.Internal.Router where
import           Prelude ()
import           Prelude.Compat
import           Data.Function
                 (on)
import           Data.Map
                 (Map)
import qualified Data.Map                                   as M
import           Data.Text
                 (Text)
import qualified Data.Text                                  as T
import           Network.Wai
                 (Response, pathInfo)
import           Servant.Server.Internal.RoutingApplication
import           Servant.Server.Internal.RouteResult
import           Servant.Server.Internal.ServerError
type Router env = Router' env RoutingApplication
data Router' env a =
    StaticRouter  (Map Text (Router' env a)) [env -> a]
      
      
      
  | CaptureRouter (Router' (Text, env) a)
      
      
  | CaptureAllRouter (Router' ([Text], env) a)
      
      
  | RawRouter     (env -> a)
      
  | Choice        (Router' env a) (Router' env a)
      
  deriving Functor
pathRouter :: Text -> Router' env a -> Router' env a
pathRouter t r = StaticRouter (M.singleton t r) []
leafRouter :: (env -> a) -> Router' env a
leafRouter l = StaticRouter M.empty [l]
choice :: Router' env a -> Router' env a -> Router' env a
choice (StaticRouter table1 ls1) (StaticRouter table2 ls2) =
  StaticRouter (M.unionWith choice table1 table2) (ls1 ++ ls2)
choice (CaptureRouter router1)   (CaptureRouter router2)   =
  CaptureRouter (choice router1 router2)
choice router1 (Choice router2 router3) = Choice (choice router1 router2) router3
choice router1 router2 = Choice router1 router2
data RouterStructure =
    StaticRouterStructure  (Map Text RouterStructure) Int
  | CaptureRouterStructure RouterStructure
  | RawRouterStructure
  | ChoiceStructure        RouterStructure RouterStructure
  deriving (Eq, Show)
routerStructure :: Router' env a -> RouterStructure
routerStructure (StaticRouter m ls) =
  StaticRouterStructure (fmap routerStructure m) (length ls)
routerStructure (CaptureRouter router) =
  CaptureRouterStructure $
    routerStructure router
routerStructure (CaptureAllRouter router) =
  CaptureRouterStructure $
    routerStructure router
routerStructure (RawRouter _) =
  RawRouterStructure
routerStructure (Choice r1 r2) =
  ChoiceStructure
    (routerStructure r1)
    (routerStructure r2)
sameStructure :: Router' env a -> Router' env b -> Bool
sameStructure r1 r2 =
  routerStructure r1 == routerStructure r2
routerLayout :: Router' env a -> Text
routerLayout router =
  T.unlines (["/"] ++ mkRouterLayout False (routerStructure router))
  where
    mkRouterLayout :: Bool -> RouterStructure -> [Text]
    mkRouterLayout c (StaticRouterStructure m n) = mkSubTrees c (M.toList m) n
    mkRouterLayout c (CaptureRouterStructure r)  = mkSubTree c "<capture>" (mkRouterLayout False r)
    mkRouterLayout c  RawRouterStructure         =
      if c then ["├─ <raw>"] else ["└─ <raw>"]
    mkRouterLayout c (ChoiceStructure r1 r2)     =
      mkRouterLayout True r1 ++ ["┆"] ++ mkRouterLayout c r2
    mkSubTrees :: Bool -> [(Text, RouterStructure)] -> Int -> [Text]
    mkSubTrees _ []             0 = []
    mkSubTrees c []             n =
      concat (replicate (n - 1) (mkLeaf True) ++ [mkLeaf c])
    mkSubTrees c [(t, r)]       0 =
      mkSubTree c    t (mkRouterLayout False r)
    mkSubTrees c ((t, r) : trs) n =
      mkSubTree True t (mkRouterLayout False r) ++ mkSubTrees c trs n
    mkLeaf :: Bool -> [Text]
    mkLeaf True  = ["├─•","┆"]
    mkLeaf False = ["└─•"]
    mkSubTree :: Bool -> Text -> [Text] -> [Text]
    mkSubTree True  path children = ("├─ " <> path <> "/") : map ("│  " <>) children
    mkSubTree False path children = ("└─ " <> path <> "/") : map ("   " <>) children
tweakResponse :: (RouteResult Response -> RouteResult Response) -> Router env -> Router env
tweakResponse f = fmap (\a -> \req cont -> a req (cont . f))
runRouter :: Router () -> RoutingApplication
runRouter r = runRouterEnv r ()
runRouterEnv :: Router env -> env -> RoutingApplication
runRouterEnv router env request respond =
  case router of
    StaticRouter table ls ->
      case pathInfo request of
        []   -> runChoice ls env request respond
        
        [""] -> runChoice ls env request respond
        first : rest | Just router' <- M.lookup first table
          -> let request' = request { pathInfo = rest }
             in  runRouterEnv router' env request' respond
        _ -> respond $ Fail err404
    CaptureRouter router' ->
      case pathInfo request of
        []   -> respond $ Fail err404
        
        [""] -> respond $ Fail err404
        first : rest
          -> let request' = request { pathInfo = rest }
             in  runRouterEnv router' (first, env) request' respond
    CaptureAllRouter router' ->
      let segments = pathInfo request
          request' = request { pathInfo = [] }
      in runRouterEnv router' (segments, env) request' respond
    RawRouter app ->
      app env request respond
    Choice r1 r2 ->
      runChoice [runRouterEnv r1, runRouterEnv r2] env request respond
runChoice :: [env -> RoutingApplication] -> env -> RoutingApplication
runChoice ls =
  case ls of
    []       -> \ _ _ respond -> respond (Fail err404)
    [r]      -> r
    (r : rs) ->
      \ env request respond ->
      r env request $ \ response1 ->
      case response1 of
        Fail _ -> runChoice rs env request $ \ response2 ->
          respond $ highestPri response1 response2
        _      -> respond response1
  where
    highestPri (Fail e1) (Fail e2) =
      if worseHTTPCode (errHTTPCode e1) (errHTTPCode e2)
        then Fail e2
        else Fail e1
    highestPri (Fail _) y = y
    highestPri x _ = x
worseHTTPCode :: Int -> Int -> Bool
worseHTTPCode = on (<) toPriority
  where
    toPriority :: Int -> Int
    toPriority 404 = 0 
    toPriority 405 = 1 
    toPriority 401 = 2 
    toPriority 415 = 3 
    toPriority 406 = 4 
    toPriority 400 = 6 
    toPriority _   = 5