content: {-# LANGUAGE OverloadedStrings #-}
  module Clay.Geometry
  (
  -- * Positioning.
    size, top, left, bottom, right
  
  -- * Sizing.
  , width, height, minWidth, minHeight, maxWidth, maxHeight
  
  -- ** Aspect ratio.
  , AspectRatio
  , aspectRatio
  , (%)
  , withFallback
  
  -- * Padding.
  , padding
  , paddingTop, paddingLeft, paddingRight, paddingBottom
  
  -- * Margin.
  , margin
  , marginTop, marginLeft, marginRight, marginBottom
  )
  where
  
  import qualified Data.Ratio as R
  import Data.String (fromString)
  import Clay.Common
  import Clay.Property
  import Clay.Stylesheet
  import Clay.Size
  
  -------------------------------------------------------------------------------
  
  size, top, left, bottom, right :: Size a -> Css
  
  size      = key "size"
  top       = key "top"
  left      = key "left"
  bottom    = key "bottom"
  right     = key "right"
  
  width, height, minWidth, minHeight, maxWidth, maxHeight :: Size a -> Css
  
  width     = key "width"
  height    = key "height"
  minWidth  = key "min-width"
  minHeight = key "min-height"
  maxWidth  = key "max-width"
  maxHeight = key "max-height"
  
  -------------------------------------------------------------------------------
  
  -- | Represents an aspect ratio for use with 'aspectRatio'.
  --
  -- A fixed ratio can be formed from two integers:
  --
  -- >>> let _ = 4%3 :: AspectRatio
  --
  -- An aspect ratio can also be converted from a 'Rational':
  --
  -- >>> let _ = fromRational 0.5 :: AspectRatio
  --
  data AspectRatio = AspectRatio Rational
                   | AspectRatioValue Value
                   | AspectRatioWithFallback (AspectRatio, AspectRatio)
  
  instance Auto AspectRatio where auto = AspectRatioValue auto
  instance Inherit AspectRatio where inherit = AspectRatioValue inherit
  instance Initial AspectRatio where initial = AspectRatioValue initial
  instance Unset AspectRatio where unset = AspectRatioValue unset
  instance Other AspectRatio where other = AspectRatioValue
  
  -- | An 'AspectRatio' can be converted from an 'Integer',
  -- but other operations are not supported.
  instance Num AspectRatio where
    fromInteger = AspectRatio . toRational
    (+)    = error   "plus not implemented for AspectRatio"
    (*)    = error  "times not implemented for AspectRatio"
    abs    = error    "abs not implemented for AspectRatio"
    signum = error "signum not implemented for AspectRatio"
    negate = error "negate not implemented for AspectRatio"
  
  -- | An 'AspectRatio' can be converted from a 'Rational',
  -- but other operations are not supported.
  instance Fractional AspectRatio where
    fromRational = AspectRatio
    recip  = error  "recip not implemented for AspectRatio"
  
  instance Val AspectRatio where
    value (AspectRatioValue v) = v
    value (AspectRatio r) = v
      where v = fromString $ numerator <> "/" <> denominator :: Value
            numerator = show (R.numerator r)
            denominator = show (R.denominator r)
    value (AspectRatioWithFallback (a, b)) = value a <> " " <> value b
  
  -- | Defines the width to height ratio of an element.
  -- At least one of the width or height must be of automatic size,
  -- otherwise the aspect ratio will be ignored.
  --
  -- It can be given a fixed ratio of the width to the height:
  --
  -- >>> renderWith compact [] $ aspectRatio (4%3)
  -- "{aspect-ratio:4/3}"
  --
  -- It can also be the intrinsic aspect ratio for the element:
  --
  -- >>> renderWith compact [] $ aspectRatio auto
  -- "{aspect-ratio:auto}"
  --
  -- It can be told to use the intrinsic aspect ratio for the element,
  -- but to use a fixed ratio while it is unknown or if the element does not have one:
  --
  -- >>> renderWith compact [] $ aspectRatio $ auto `withFallback` (4%3)
  -- "{aspect-ratio:auto 4/3}"
  --
  -- Corresponds to the
  -- [@aspect-ratio@](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio)
  -- property in CSS.
  aspectRatio :: AspectRatio -> Css
  aspectRatio = key "aspect-ratio"
  
  -- | The aspect ratio of the width to the height for use with 'aspectRatio'.
  --
  -- Note that this is /not/ the same @%@ operator from the "Data.Ratio" module,
  -- although they do both semantically represent ratios.  The same symbol is used
  -- to signify that the return value is a ratio.
  (%) :: Integer -> Integer -> AspectRatio
  (%) m n = fromRational $ (R.%) m n
  
  -- The same as the normal % operator.
  infixl 7 %
  
  -- | Returns an aspect ratio specifying that the intrinsic aspect
  -- ratio should be used, but when it is unknown or there is none,
  -- a fixed ratio can be used as a fallback.
  withFallback :: AspectRatio -> AspectRatio -> AspectRatio
  withFallback x@(AspectRatioValue "auto") y@(AspectRatio _) =
    AspectRatioWithFallback (x, y)
  withFallback x@(AspectRatio _) y@(AspectRatioValue "auto") =
    AspectRatioWithFallback (x, y)
  withFallback _ _ =
    error "Arguments for aspectRatio . withFallback must be auto and a ratio in either order"
  
  -------------------------------------------------------------------------------
  
  padding :: Size a -> Size a -> Size a -> Size a -> Css
  padding a b c d = key "padding" (a ! b ! c ! d)
  
  paddingTop, paddingLeft, paddingRight, paddingBottom :: Size a -> Css
  
  paddingTop    = key "padding-top"
  paddingLeft   = key "padding-left"
  paddingRight  = key "padding-right"
  paddingBottom = key "padding-bottom"
  
  -------------------------------------------------------------------------------
  
  margin :: Size a -> Size a -> Size a -> Size a -> Css
  margin a b c d = key "margin"  (a ! b ! c ! d)
  
  marginTop, marginLeft, marginRight, marginBottom :: Size a -> Css
  
  marginTop     = key "margin-top"
  marginLeft    = key "margin-left"
  marginRight   = key "margin-right"
  marginBottom  = key "margin-bottom"
  