{-|
Copyright  : (c) Nathan Bloomfield, 2017
License    : GPL-3
Maintainer : nbloomf@gmail.com
Stability  : experimental

Helper functions for constructing URLs and HTML fragments.
-}

module Hakyll.Shortcode.Render (
  Scheme(..),
  buildURL,

  QueryParameter(..),
  queryValid,
  queryOneOf,
  queryYesNo,

  pathValid,
  pathYesNo,
  pathValidPre,
  pathYesNoPre,
  
  attrValid
) where

import Data.List (intercalate)
import Network.URI
import qualified Text.Blaze.Html5 as H
import Text.Blaze.Renderer.String ()
import Data.Monoid

import Hakyll.Shortcode.Types.YesNo


-- | Simple sum type representing URL schemes.
data Scheme
  = HTTPS

instance Show Scheme where
  show HTTPS = "https"


-- | Helper function for safely building URLs.
buildURL
  :: Scheme   -- ^ The scheme
  -> String   -- ^ The domain (not including ://)
  -> [String] -- ^ List of path components, to be separated by /.
  -> [String] -- ^ List of query components, to be separated by &.
  -> [String] -- ^ List of fragment components.
  -> String
buildURL scheme auth path query frag = uriToString show uri ""
  where
    uri = URI
      { uriScheme = show scheme ++ ":"
      , uriAuthority = Just $ URIAuth
          { uriUserInfo = ""
          , uriRegName  = auth
          , uriPort     = ""
          }
      , uriPath     = buildPath path
      , uriQuery    = buildQuery query
      , uriFragment = ""
      }


{-------------------}
{- Query Fragments -}
{-------------------}

sanitizeQuery :: String -> String
sanitizeQuery = escapeURIString encode
  where
    encode c = not $ (isReserved c) && (c /= '=')

buildQuery :: [String] -> String
buildQuery ps =
  let
    qs = intercalate "&"
           $ filter (/= "")
           $ map sanitizeQuery
           $ ps
  in
    if null qs
      then ""
      else '?' : qs


class QueryParameter t where
  renderQueryParameter :: t -> String


-- | Helper function for rendering @Maybe@ 'OneOf' shortcode parameters as query parameters.
queryOneOf :: (QueryParameter t)
  => Maybe t -- ^ The 'OneOf' parameter.
  -> String
queryOneOf Nothing  = ""
queryOneOf (Just x) = renderQueryParameter x

-- | Helper function for rendering @Maybe@ 'Valid' shortcode parameters as query parameters.
queryValid :: (Show t)
  => Maybe t -- ^ The 'Valid' parameter.
  -> String  -- ^ The parameter key.
  -> String
queryValid Nothing  _   = ""
queryValid (Just x) key = key ++ "=" ++ show x

-- | Helper function for rendering @Maybe@ 'YesNo' shortcode parameters as query parameters.
queryYesNo
  :: Maybe YesNo -- ^ The 'YesNo' parameter.
  -> String      -- ^ Parameter for the 'Yes' case.
  -> String      -- ^ Parameter for the 'No' case.
  -> String
queryYesNo x yes no = case x of
  Nothing  -> ""
  Just Yes -> yes
  Just No  -> no


{------------------}
{- Path Fragments -}
{------------------}

sanitizePath :: String -> String
sanitizePath = escapeURIString encode
  where
    encode c = not $ isReserved c

buildPath :: [String] -> String
buildPath = concatMap ('/':) . filter (/= "") . map sanitizePath

-- | Helper function for rendering @Maybe@ 'Valid' parameters as path components.
pathValid :: (Show t) => Maybe t -> String
pathValid Nothing  = ""
pathValid (Just x) = show x

-- | Helper function for rendering @Maybe@ 'Valid' parameters as path components, with a prefix.
pathValidPre :: (Show t)
  => String  -- ^ The prefix path.
  -> Maybe t -- ^ The 'Valid' parameter.
  -> [String]
pathValidPre _ Nothing  = []
pathValidPre p (Just x) = [p, show x]

-- | Helper function for rendering @Maybe@ 'YesNo' parameters as path components.
pathYesNo
  :: Maybe YesNo -- ^ The 'YesNo' parameter.
  -> String      -- ^ Path for the 'Yes' case.
  -> String      -- ^ Path for the 'No' case.
  -> String
pathYesNo x yes no = case x of
  Nothing  -> ""
  Just Yes -> yes
  Just No  -> no

-- | Helper function for rendering @Maybe@ 'YesNo' parameters as path components, with a prefix.
pathYesNoPre
  :: String      -- ^ The prefix path.
  -> Maybe YesNo -- ^ The 'YesNo' parameter.
  -> String      -- ^ Path for the 'Yes' case.
  -> String      -- ^ Path for the 'No' case.
  -> [String]
pathYesNoPre p x yes no = case x of
  Nothing  -> []
  Just Yes -> [p,yes]
  Just No  -> [p,no]


{--------------}
{- Attributes -}
{--------------}

-- | Helper function for optionally rendering a @Maybe@ as an HTML attribute.
attrValid :: (Monoid a, Show b) => (H.AttributeValue -> a) -> Maybe b -> a
attrValid key Nothing  = mempty
attrValid key (Just x) = key $ H.stringValue $ show x