module Yesod.Routes ( Piece (..) , RouteHandler (..) , Dispatch , toDispatch ) where import Data.Text (Text) import qualified Data.Vector as V import Data.Maybe (fromMaybe, mapMaybe) import qualified Data.Map as Map import Data.List (sortBy) import Data.Ord (comparing) import Control.Arrow (second) data Piece = StaticPiece Text | SinglePiece type Dispatch req res = [Text] -> req -> Maybe res data RouteHandler req res = RouteHandler { rhPieces :: [Piece] , rhHasMulti :: Bool , rhDispatch :: Dispatch req res } toDispatch :: [RouteHandler req res] -> [Text] -> req -> Maybe res toDispatch rhs = bcToDispatch bc where bc = toBC rhs bcToDispatch :: ByCount req res -> Dispatch req res bcToDispatch (ByCount vec rest) ts0 req = bcToDispatch' ts0 pm0 where --pm0 :: PieceMap sub master res pm0 = fromMaybe rest $ vec V.!? length ts0 --bcToDispatch' :: [Text] -> PieceMap req res -> Maybe res bcToDispatch' _ (PieceMapEnd r) = firstJust (\f -> f ts0 req) $ map snd $ sortBy (comparing fst) r bcToDispatch' (t:ts) (PieceMap dyn sta) = bcToDispatch' ts $ case Map.lookup t sta of Nothing -> dyn Just pm -> append dyn pm bcToDispatch' [] _ = Nothing firstJust :: (a -> Maybe b) -> [a] -> Maybe b firstJust _ [] = Nothing firstJust f (a:as) = maybe (firstJust f as) Just $ f a append :: PieceMap a b -> PieceMap a b -> PieceMap a b append (PieceMapEnd a) (PieceMapEnd b) = PieceMapEnd $ a ++ b append (PieceMap a x) (PieceMap b y) = PieceMap (append a b) (Map.unionWith append x y) -- I'm sure there's some nice type-level trickery we could employ here somehow -- to ensure this never happens. append _ _ = error "Mismatched PieceMaps for append" data PieceMap req res = PieceMap { pmDynamic :: PieceMap req res , pmStatic :: Map.Map Text (PieceMap req res) } | PieceMapEnd [(Int, Dispatch req res)] toPieceMap :: Int -> [RouteHandler req res] -> PieceMap req res toPieceMap depth = toPieceMap' depth . zip [1..] toPieceMap' :: Int -> [(Int, RouteHandler req res)] -> PieceMap req res toPieceMap' 0 rhs = PieceMapEnd $ map (second rhDispatch) $ sortBy (comparing fst) rhs toPieceMap' depth rhs = PieceMap { pmDynamic = toPieceMap' depth' dynamics , pmStatic = Map.map (toPieceMap' depth') statics } where depth' = depth - 1 pairs = map toPair rhs toPair (i, RouteHandler (p:ps) b c) = (p, (i, RouteHandler ps b c)) -- if we have no more pieces, that means this is a rhHasMulti, so fill in -- with dynamic toPair (i, RouteHandler [] b c) = (SinglePiece, (i, RouteHandler [] b c)) getDynamic (SinglePiece, rh) = Just rh getDynamic _ = Nothing dynamics = mapMaybe getDynamic pairs getStatic (StaticPiece t, rh) = Just $ Map.singleton t [rh] getStatic _ = Nothing statics = Map.unionsWith (++) $ mapMaybe getStatic pairs data ByCount req res = ByCount { bcVector :: !(V.Vector (PieceMap req res)) , bcRest :: !(PieceMap req res) } toBC :: [RouteHandler req res] -> ByCount req res toBC rhs = ByCount { bcVector = V.map (\i -> toPieceMap i $ filter (canHaveLength i) rhs) $ V.enumFromN 0 (maxLen + 1) , bcRest = toPieceMap maxLen $ filter rhHasMulti rhs } where maxLen = maximum $ map (length . rhPieces) rhs canHaveLength i rh = len == i || (len < i && rhHasMulti rh) where len = length $ rhPieces rh