-- |Representations of values that can serve as placeholders, being parsed from "Web.Route.Invertible.String" data.
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, ScopedTypeVariables, ExistentialQuantification, EmptyCase, DefaultSignatures, FunctionalDependencies #-}
module Web.Route.Invertible.Parameter
  ( Parameter(..)
  , Parameterized(..)
  , param
  , ParameterType(..)
  , parameterTypeOf
  , parseParameterAs
  ) where

import Control.Monad (guard)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
import Data.Hashable (Hashable(..))
import Data.Int (Int8, Int16, Int32, Int64)
import Data.Word (Word8, Word16, Word32, Word64)
import Data.Proxy (Proxy(Proxy))
import Data.String (IsString(..))
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Read as T
import Data.Typeable (Typeable, typeRep)
import Data.Void (Void, absurd)
import Text.Read (readMaybe)

import Web.Route.Invertible.String

-- |A parameter value @a@ that can be parsed from or rendered into string data @s@.
-- @parseParameter@ must invert @renderParameter@:
--
--   * @parseParameter . renderParameter == Just@
--   
class (RouteString s, Typeable a) => Parameter s a where
  -- |Parse string data into a value.
  -- Often equivalent (and defaults) to 'readMaybe'.
  parseParameter :: s -> Maybe a
  -- |Render a value into a string.
  -- Often equivalent (and defaults) to 'show'.
  renderParameter :: a -> s

  default parseParameter :: Read a => s -> Maybe a
  parseParameter = readMaybe . toString
  default renderParameter :: Show a => a -> s
  renderParameter = fromString . show

instance {-# OVERLAPPABLE #-} (RouteString s) => Parameter s String where
  parseParameter = Just . toString
  renderParameter = fromString
instance Parameter T.Text T.Text where
  parseParameter = Just
  renderParameter = id
instance Parameter BS.ByteString BS.ByteString where
  parseParameter = Just
  renderParameter = id
instance Parameter T.Text BS.ByteString where
  parseParameter = Just . TE.encodeUtf8
  renderParameter = TE.decodeUtf8
instance Parameter BS.ByteString T.Text where
  parseParameter = either (const Nothing) Just . TE.decodeUtf8'
  renderParameter = TE.encodeUtf8

instance Parameter String Char where
  parseParameter [c] = Just c
  parseParameter _ = Nothing
  renderParameter c = [c]
instance Parameter T.Text Char where
  parseParameter = parseParameter . T.unpack
  renderParameter = T.singleton
instance Parameter BS.ByteString Char where
  parseParameter = parseParameter . BSC.unpack
  renderParameter = BSC.singleton

instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Integer
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Int
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Int8
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Int16
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Int32
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Int64
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Word
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Word8
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Word16
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Word32
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Word64
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Float
instance {-# OVERLAPPABLE #-} RouteString s => Parameter s Double

readText :: T.Reader a -> T.Text -> Maybe a
readText = (.) $ either (const Nothing) (\(a, t) -> a <$ guard (T.null t))

instance Parameter T.Text Integer where parseParameter = readText (T.signed T.decimal)
instance Parameter T.Text Int     where parseParameter = readText (T.signed T.decimal)
instance Parameter T.Text Int8    where parseParameter = readText (T.signed T.decimal)
instance Parameter T.Text Int16   where parseParameter = readText (T.signed T.decimal)
instance Parameter T.Text Int32   where parseParameter = readText (T.signed T.decimal)
instance Parameter T.Text Int64   where parseParameter = readText (T.signed T.decimal)
instance Parameter T.Text Word    where parseParameter = readText T.decimal
instance Parameter T.Text Word8   where parseParameter = readText T.decimal
instance Parameter T.Text Word16  where parseParameter = readText T.decimal
instance Parameter T.Text Word32  where parseParameter = readText T.decimal
instance Parameter T.Text Word64  where parseParameter = readText T.decimal
instance Parameter T.Text Float   where parseParameter = readText T.rational
instance Parameter T.Text Double  where parseParameter = readText T.double

instance RouteString s => Parameter s Void where
  parseParameter _ = Nothing
  renderParameter = absurd


-- |Parsers 'p' that operate over string data 's', and so can parse placeholder 'Parameter' values.
class Parameterized s p | p -> s where
  -- |Create a parser for a parameter of type 'a'.
  parameter :: Parameter s a => p a

-- |Create a placeholder 'parameter' with the type of the argument, which is ignored.
param :: (Parameterized s p, Parameter s a) => a -> p a
param _ = parameter

-- |An existential representation of an instance of @'Parameter' s@, that functions similarly to 'Data.Typeable.TypeRep' but also provides witness to a concrete instance.
data ParameterType s = forall a . Parameter s a => ParameterType !(Proxy a)

instance Eq (ParameterType s) where
  ParameterType a == ParameterType b = typeRep a == typeRep b
instance Ord (ParameterType s) where
  ParameterType a `compare` ParameterType b = typeRep a `compare` typeRep b
instance Hashable (ParameterType s) where
  hashWithSalt s (ParameterType d) = hashWithSalt s (typeRep d)
instance Show (ParameterType s) where
  showsPrec d (ParameterType p) = showParen (d > 10) $
    showString "ParameterType " . showsPrec 11 (typeRep p)

-- |Similar to 'typeRep'.
parameterTypeOf :: forall s proxy a . Parameter s a => proxy a -> ParameterType s
parameterTypeOf _ = ParameterType (Proxy :: Proxy a)

-- |Constrain the type of 'parseParameter', ignoring the first parameter.
parseParameterAs :: forall s proxy a . Parameter s a => proxy a -> s -> Maybe a
parseParameterAs _ = parseParameter