content: -----------------------------------------------------------------------------
  -- |
  -- Module      : Hasmin.Parser.Dimension
  -- Copyright   : (c) 2017 Cristian Adrián Ontivero
  -- License     : BSD3
  -- Stability   : experimental
  -- Portability : unknown
  --
  -- Parsers for CSS dimensions, i.e. \<length>, \<time>, etc.
  --
  -----------------------------------------------------------------------------
  module Hasmin.Parser.Dimension where
  
  import Data.Attoparsec.Text (Parser)
  import qualified Data.Attoparsec.Text as A
  import Data.Map.Strict (Map)
  import qualified Data.Map.Strict as Map
  import qualified Data.Char as C
  import Data.Text (Text)
  import qualified Data.Text as T
  import Control.Monad (mzero)
  import Control.Arrow ((&&&))
  
  import Hasmin.Parser.Utils
  import Hasmin.Parser.Numeric
  import Hasmin.Types.Numeric
  import Hasmin.Class
  import Hasmin.Types.Dimension
  
  distance :: Parser Length
  distance = dimensionParser distanceConstructorsMap NullLength
    where distanceConstructorsMap = Map.fromList distanceConstructorsList
  
  angle :: Parser Angle
  angle = dimensionParser angleConstructorsMap NullAngle
    where angleConstructorsMap = Map.fromList angleConstructorsList
  
  time :: Parser Time
  time = do
      n <- number
      u <- opt (A.takeWhile1 C.isAlpha)
      if T.null u
         then mzero
         else case Map.lookup (T.toLower u) timeConstructorsMap of
                Just f  -> pure $ f n
                Nothing -> mzero -- parsed units aren't angle units, fail
    where timeConstructorsMap = Map.fromList timeConstructorsList
  
  timeConstructorsList :: [(Text, Number -> Time)]
  timeConstructorsList = fmap (toText &&& flip Time) [minBound..]
  
  angleConstructorsList :: [(Text, Number -> Angle)]
  angleConstructorsList = fmap (toText &&& flip Angle) [minBound..]
  
  distanceConstructorsList :: [(Text, Number -> Length)]
  distanceConstructorsList = fmap (toText &&& flip Length) [minBound..]
  
  -- Create a numerical parser based on a Map.
  -- See for instance, the "angle" parser
  dimensionParser :: Map Text (Number -> a) -> a -> Parser a
  dimensionParser m unitlessValue = do
      n <- number
      u <- opt (A.takeWhile1 C.isAlpha)
      if T.null u
         then if n == 0
                 then pure unitlessValue -- <angle> 0, without units
                 else mzero -- Non-zero <number>, fail
         else case Map.lookup (T.toCaseFold u) m of
                Just f  -> pure $ f n
                Nothing -> mzero -- parsed units aren't angle units, fail
  