content: {-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings #-}
  -----------------------------------------------------------------------------
  -- |
  -- Module      : Hasmin.Types.Numeric
  -- Copyright   : (c) 2017 Cristian Adrián Ontivero
  -- License     : BSD3
  -- Stability   : experimental
  -- Portability : non-portable
  --
  -- CSS Numeric data types: \<number\>, \<percentage\>, and \<alphavalue\>.
  -- All Rational newtypes to ensure dimension conversion precision.
  --
  -----------------------------------------------------------------------------
  module Hasmin.Types.Numeric
      ( Percentage(..)
      , toPercentage
      , Number(..)
      , toNumber
      , fromNumber
      , Alphavalue(..)
      , toAlphavalue
      , mkAlphavalue
      ) where
  
  import Data.Text (pack)
  import Hasmin.Class
  import Hasmin.Utils
  import Text.Printf (printf)
  
  -- | The \<number\> data type. Real numbers, possibly with a fractional component.
  -- When written literally, a number is either an integer, or zero or more
  -- decimal digits followed by a dot (.) followed by one or more decimal digits
  -- and optionally an exponent composed of "e" or "E" and an integer. It
  -- corresponds to the \<number-token\> production in the CSS Syntax Module
  -- [CSS3SYN]. As with integers, the first character of a number may be
  -- immediately preceded by - or + to indicate the number’s sign.
  -- Specifications:
  --
  -- 1. <https://drafts.csswg.org/css-values-3/#numbers CSS Values and Units Module Level 3 (§4.2)>
  -- 2. <https://www.w3.org/TR/CSS2/syndata.html#numbers CSS2.1 (§4.3.1)>
  -- 3. <https://www.w3.org/TR/CSS1/#units CSS1 (6 Units)>
  newtype Number = Number { getRational :: Rational }
    deriving (Eq, Show, Ord, Num, Fractional, Real, RealFrac)
  
  instance ToText Number where
    toText = pack . trimLeadingZeros . showRat . toRational -- check if scientific notation is shorter!
  
  toNumber :: Real a => a -> Number
  toNumber = Number . toRational
  
  fromNumber :: Fractional a => Number -> a
  fromNumber = fromRational . toRational
  -- | The \<alphavalue\> data type. Syntactically a \<number\>. It is the
  -- uniform opacity setting to be applied across an entire object. Any values
  -- outside the range 0.0 (fully transparent) to 1.0 (fully opaque) are clamped
  -- to this range. Specification:
  --
  -- 1. <https://www.w3.org/TR/css3-color/#transparency CSS Color Module Level 3 (§3.2)
  newtype Alphavalue = Alphavalue Rational
    deriving (Eq, Show, Ord, Real, RealFrac)
  
  instance Num Alphavalue where
    abs         = id -- numbers are never negative, so abs doesn't do anything
    a - b       = mkAlphavalue $ toRational a - toRational b
    a + b       = mkAlphavalue $ toRational a + toRational b
    a * b       = mkAlphavalue $ toRational a * toRational b
    fromInteger = toAlphavalue
    signum a | toRational a == 0 = 0
             | otherwise         = 1
  
  instance ToText Alphavalue where
    toText = pack .  trimLeadingZeros . showRat . toRational
  instance Bounded Alphavalue where
    minBound = 0
    maxBound = 1
  instance Fractional Alphavalue where
    fromRational = mkAlphavalue
    (Alphavalue a) / (Alphavalue b) = mkAlphavalue (toRational a / toRational b)
  toAlphavalue :: Real a => a -> Alphavalue
  toAlphavalue = mkAlphavalue . toRational
  
  mkAlphavalue :: Rational -> Alphavalue
  mkAlphavalue = Alphavalue . restrict 0 1
  
  -- | The \<percentage\> data type. Many CSS properties can take percentage
  -- values, often to define sizes in terms of parent objects. Percentages are
  -- formed by a \<number\> immediately followed by the percentage sign %.
  -- There is no space between the '%' and the number. Specification:
  --
  -- 1. <https://drafts.csswg.org/css-values-3/#percentages CSS Value and Units Module Level 3 (§4.3)
  -- 2. <https://www.w3.org/TR/CSS2/syndata.html#percentage-units CSS2.1 (§4.3.3)
  -- 3. <https://www.w3.org/TR/CSS1/#percentage-units CSS1 (§6.2)
  newtype Percentage = Percentage Rational
    deriving (Eq, Show, Ord, Num, Fractional, Real, RealFrac)
  
  instance ToText Percentage where
    toText = pack . (++ "%") . trimLeadingZeros . showRat . toRational
  
  toPercentage :: Real a => a -> Percentage
  toPercentage = Percentage . toRational
  
  -- Note: printf used instead of show to avoid scientific notation
  -- | Show a Rational in decimal notation, removing leading zeros,
  -- and not displaying fractional part if the number is an integer.
  showRat :: Rational -> String
  showRat r
      | abs (r - fromInteger x) < eps = printf "%d" x
      | otherwise                     = printf "%f" d
    where x = round r
          d = fromRational r :: Double
  
  trimLeadingZeros :: String -> String
  trimLeadingZeros l@(x:xs)
      | x == '-'  = x : go xs
      | otherwise = go l
    where go ('0':y:ys) = go (y:ys)
          go z = z
  trimLeadingZeros []  = ""
  