{-# LANGUAGE
    EmptyDataDecls
  , OverloadedStrings
  , GeneralizedNewtypeDeriving
  , FlexibleInstances
  #-}
module Clay.Size
(

-- * Size type.
  Size
, Abs
, Rel
, nil
, unitless

-- * Size constructors.

, cm
, mm
, inches
, px
, pt
, pc
, em
, ex
, pct
, rem
, vw
, vh
, vmin
, vmax

-- * Shorthands for properties that can be applied separately to each box side.

, sym
, sym2
, sym3

-- * Angle type.

, Angle
, Deg
, Rad
, Grad
, Turn

-- * Constructing angles.

, deg
, rad
, grad
, turn

)
where

import Data.Monoid
import Prelude hiding (rem)

import Clay.Common
import Clay.Property
import Clay.Stylesheet

-------------------------------------------------------------------------------

-- | Sizes can be relative like percentages or rems.
data Rel

-- | Sizes can be absolute like pixels, points, etc.
data Abs

newtype Size a = Size Value
  deriving (Val, Auto, Normal, Inherit, None, Other)

-- | Zero size.
nil :: Size a
nil = Size "0"

-- | Unitless size (as recommended for line-height).
unitless :: Double -> Size a
unitless i = Size (value i)

cm, mm, inches, px, pt, pc :: Double -> Size Abs

-- | Size in centimeters.
cm i = Size (value i <> "cm")

-- | Size in millimeters.
mm i = Size (value i <> "mm")

-- | Size in inches (1in = 2.54 cm).
inches i = Size (value i <> "in")

-- | Size in pixels.
px i = Size (value i <> "px")

-- | Size in points (1pt = 1/72 of 1in).
pt i = Size (value i <> "pt")

-- | Size in picas (1pc = 12pt).
pc i = Size (value i <> "pc")

em, ex, pct, rem, vw, vh, vmin, vmax :: Double -> Size Rel

-- | Size in em's (computed value of the font-size).
em i = Size (value i <> "em")

-- | Size in ex'es (x-height of the first avaliable font).
ex i = Size (value i <> "ex")

-- | Size in percents.
pct i = Size (value i <> "%")

-- | Size in rem's (em's, but always relative to the root element).
rem i = Size (value i <> "rem")

-- | Size in vw's (1vw = 1% of viewport width).
vw i = Size (value i <> "vw")

-- | Size in vh's (1vh = 1% of viewport height).
vh i = Size (value i <> "vh")

-- | Size in vmin's (the smaller of vw or vh).
vmin i = Size (value i <> "vmin")

-- | Size in vmax's (the larger of vw or vh).
vmax i = Size (value i <> "vmax")

instance Num (Size Abs) where
  fromInteger = px . fromInteger
  (+)    = error   "plus not implemented for Size"
  (*)    = error  "times not implemented for Size"
  abs    = error    "abs not implemented for Size"
  signum = error "signum not implemented for Size"
  negate = error "negate not implemented for Size"

instance Fractional (Size Abs) where
  fromRational = px . fromRational
  recip  = error  "recip not implemented for Size"

instance Num (Size Rel) where
  fromInteger = pct . fromInteger
  (+)    = error   "plus not implemented for Size"
  (*)    = error  "times not implemented for Size"
  abs    = error    "abs not implemented for Size"
  signum = error "signum not implemented for Size"
  negate = error "negate not implemented for Size"

instance Fractional (Size Rel) where
  fromRational = pct . fromRational
  recip  = error  "recip not implemented for Size"

-------------------------------------------------------------------------------

sym :: (a -> a -> a -> a -> Css) -> a -> Css
sym k a = k a a a a

sym3 :: (a -> a -> a -> a -> Css) -> a -> a -> a -> Css
sym3 k tb l r = k tb l tb r

sym2 :: (a -> a -> a -> a -> Css) -> a -> a -> Css
sym2 k tb lr = k tb lr tb lr

-------------------------------------------------------------------------------

data Deg
data Rad
data Grad
data Turn

newtype Angle a = Angle Value
  deriving (Val, Auto, Inherit, Other)

-- | Angle in degrees.
deg :: Double -> Angle Deg
deg i = Angle (value i <> "deg")

-- | Angle in radians.
rad :: Double -> Angle Rad
rad i = Angle (value i <> "rad")

-- | Angle in gradians (also knows as gons or grades).
grad :: Double -> Angle Grad
grad i = Angle (value i <> "grad")

-- | Angle in turns.
turn :: Double -> Angle Turn
turn i = Angle (value i <> "turn")

instance Num (Angle Deg) where
  fromInteger = deg . fromInteger
  (+)    = error   "plus not implemented for Angle"
  (*)    = error  "times not implemented for Angle"
  abs    = error    "abs not implemented for Angle"
  signum = error "signum not implemented for Angle"
  negate = error "negate not implemented for Angle"

instance Fractional (Angle Deg) where
  fromRational = deg . fromRational
  recip  = error  "recip not implemented for Angle"

instance Num (Angle Rad) where
  fromInteger = rad . fromInteger
  (+)    = error   "plus not implemented for Angle"
  (*)    = error  "times not implemented for Angle"
  abs    = error    "abs not implemented for Angle"
  signum = error "signum not implemented for Angle"
  negate = error "negate not implemented for Angle"

instance Fractional (Angle Rad) where
  fromRational = rad . fromRational
  recip  = error  "recip not implemented for Angle"

instance Num (Angle Grad) where
  fromInteger = grad . fromInteger
  (+)    = error   "plus not implemented for Angle"
  (*)    = error  "times not implemented for Angle"
  abs    = error    "abs not implemented for Angle"
  signum = error "signum not implemented for Angle"
  negate = error "negate not implemented for Angle"

instance Fractional (Angle Grad) where
  fromRational = grad . fromRational
  recip  = error  "recip not implemented for Angle"

instance Num (Angle Turn) where
  fromInteger = turn . fromInteger
  (+)    = error   "plus not implemented for Angle"
  (*)    = error  "times not implemented for Angle"
  abs    = error    "abs not implemented for Angle"
  signum = error "signum not implemented for Angle"
  negate = error "negate not implemented for Angle"

instance Fractional (Angle Turn) where
  fromRational = turn . fromRational
  recip  = error  "recip not implemented for Angle"
