-- |
-- Single-route path parsers (specialization of "Web.Route.Invertible.Sequence").
-- The most important type here is 'Path', which can be used to represent a single path endpoint within your application, including placeholders.
-- For example, the following represents a path of @\/item\/$id@ where @$id@ is an integer placeholder:
--
-- > Path ("item" *< parameter) :: Path Int
--
{-# LANGUAGE GeneralizedNewtypeDeriving, StandaloneDeriving, FlexibleInstances, FlexibleContexts #-}
module Web.Route.Invertible.Path
  ( PathString
  , normalizePath
  , Path(..)
  , pathValues
  , renderPath
  , urlPathBuilder
  ) where

import Prelude hiding (lookup)

import Control.Invertible.Monoidal
import qualified Data.ByteString.Builder as B
import Data.Monoid ((<>))
import qualified Data.Invertible as I
import Data.String (IsString(..))
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Network.HTTP.Types.URI as H

import Web.Route.Invertible.Parameter
import Web.Route.Invertible.Placeholder
import Web.Route.Invertible.Sequence

-- |A component of a path, such that paths are represented by @['PathString']@ (after splitting on \'/\').
-- Paths can be created by 'H.decodePath'.
type PathString = T.Text

-- |Remove double- and trailing-slashes (i.e., empty path segments).
normalizePath :: [PathString] -> [PathString]
normalizePath = filter (not . T.null)

-- |A URL path parser/generator.
-- These should typically be constructed using the 'IsString' and 'Parameterized' instances.
-- Note that the individual components are /decoded/ path segments, so a literal slash in a component (e.g., as produced with 'fromString') will match \"%2F\".
-- Example:
--
-- > "get" *< parameter >*< "value" *< parameter :: Path (String, Int)
--
-- matches (or generates) @\/get\/$x\/value\/$y@ for any string @$x@ and any int @$y@ and returns those values.
newtype Path a = Path { pathSequence :: Sequence PathString a }
  deriving (I.Functor, Monoidal, MonoidalAlt, Parameterized PathString, Show)

deriving instance IsString (Path ())

-- |Render a 'Path' as instantiated by a value to a list of placeholder values.
pathValues :: Path a -> a -> [PlaceholderValue PathString]
pathValues (Path p) = sequenceValues p

-- |Render a 'Path' as instantiated by a value to a list of string segments.
renderPath :: Path a -> a -> [PathString]
renderPath (Path p) = renderSequence p

-- |Build a 'Path' as applied to a value into a bytestring 'B.Builder' by encoding the segments with 'urlEncodePath' and joining them with \"/\".
urlPathBuilder :: Path a -> a -> B.Builder
urlPathBuilder p a = foldMap es $ renderPath p a where
  es s = B.char7 '/' <> H.urlEncodeBuilder False (TE.encodeUtf8 s)