content: {-# LANGUAGE OverloadedStrings #-}
  -----------------------------------------------------------------------------
  -- |
  -- Module      : Hasmin.Types.TimingFunction
  -- Copyright   : © 2017 Cristian Adrián Ontivero
  -- License     : BSD3
  -- Stability   : experimental
  -- Portability : unknown
  --
  -----------------------------------------------------------------------------
  module Hasmin.Types.TimingFunction
      ( TimingFunction(..)
      , StepPosition(..)
      ) where
  
  import Control.Monad.Reader (ask)
  import Data.Monoid ((<>))
  import Prelude hiding (sin, cos, acos, tan, atan)
  import Data.Maybe (isNothing, fromJust)
  import Data.Text.Lazy.Builder (singleton)
  import Hasmin.Config
  import Hasmin.Class
  import Hasmin.Utils
  import Hasmin.Types.Numeric
  
  -- | CSS <https://drafts.csswg.org/css-timing-1/#typedef-single-timing-function \<single-transition-timing-function\>> data type.
  data TimingFunction = CubicBezier Number Number Number Number
                      | Steps Int (Maybe StepPosition)
                      | Ease
                      | EaseIn
                      | EaseInOut
                      | EaseOut
                      | Linear
                      | StepEnd
                      | StepStart
    deriving (Show)
  
  instance Eq TimingFunction where
    Steps i1 ms1 == Steps i2 ms2 = i1 == i2 && (ms1 == ms2
                                      || (isNothing ms1 && fromJust ms2 == End)
                                      || (fromJust ms1 == End && isNothing ms2))
    s@Steps{} == x         = toSteps x == Just s
      where toSteps :: TimingFunction -> Maybe TimingFunction
            toSteps StepEnd   = Just $ Steps 1 (Just End)
            toSteps StepStart = Just $ Steps 1 (Just Start)
            toSteps _         = Nothing
    x == s@Steps{} = s == x
    CubicBezier x1 x2 x3 x4 == CubicBezier y1 y2 y3 y4 =
        x1 == y1 && x2 == y2 && x3 == y3 && x4 == y4
    c@CubicBezier{} == x   = toCubicBezier x == Just c
      where toCubicBezier :: TimingFunction -> Maybe TimingFunction
            toCubicBezier Ease      = Just $ CubicBezier 0.25 0.1 0.25 1
            toCubicBezier EaseIn    = Just $ CubicBezier 0.42 0   1    1
            toCubicBezier EaseInOut = Just $ CubicBezier 0.42 0   0.58 1
            toCubicBezier Linear    = Just $ CubicBezier 0    0   1    1
            toCubicBezier EaseOut   = Just $ CubicBezier 0    0   0.58 1
            toCubicBezier _         = Nothing
    x == c@CubicBezier{}   = c == x
    StepEnd == StepEnd     = True
    StepStart == StepStart = True
    Ease == Ease           = True
    Linear == Linear       = True
    EaseIn == EaseIn       = True
    EaseOut == EaseOut     = True
    EaseInOut == EaseInOut = True
    _ == _                 = False
  
  -- | A step position, as used by the
  -- <https://drafts.csswg.org/css-timing-1/#step-timing-functions step timing function>.
  data StepPosition = Start
                    | End -- End is the default value
    deriving (Show, Eq, Enum, Bounded)
  instance ToText StepPosition where
    toBuilder Start = "start"
    toBuilder End   = "end"
  instance ToText TimingFunction where
    toBuilder (CubicBezier a b c d) = "cubic-bezier("
        <> mconcatIntersperse toBuilder (singleton ',') [a,b,c,d]
        <> singleton ')'
    toBuilder (Steps i ms) = "steps(" <> toBuilder i <> sp <> singleton ')'
      where sp = maybe mempty (\x -> singleton ',' <> toBuilder x) ms
    toBuilder Ease      = "ease"
    toBuilder Linear    = "linear"
    toBuilder EaseIn    = "ease-in"
    toBuilder EaseOut   = "ease-out"
    toBuilder EaseInOut = "ease-in-out"
    toBuilder StepStart = "step-start"
    toBuilder StepEnd   = "step-end"
  
  instance Minifiable TimingFunction where
    minify tf = do
        conf <- ask
        pure $ if shouldMinifyTimingFunctions conf
                  then minifyTimingFunction tf
                  else tf
      where minifyTimingFunction :: TimingFunction -> TimingFunction
            minifyTimingFunction x@(CubicBezier a b c 1)
                | a == 0.25 && b == 0.1 && c == 0.25 = Ease
                | b == 0 = if a == 0.42
                              then if c == 1
                                      then EaseIn
                                      else if c == 0.58
                                              then EaseInOut
                                              else x
                              else if a == 0
                                      then if c == 1
                                              then Linear
                                              else if c == 0.58
                                                      then EaseOut
                                                      else x
                                      else x
            minifyTimingFunction x@CubicBezier{} = x
            minifyTimingFunction (Steps 1 ms) =
                case ms of
                  Just Start -> StepStart
                  _          -> StepEnd
            minifyTimingFunction (Steps i (Just End)) = Steps i Nothing
            minifyTimingFunction x = x
  