{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies          #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Diagrams.Animation
-- Copyright   :  (c) 2011 diagrams-lib team (see LICENSE)
-- License     :  BSD-style (see LICENSE)
-- Maintainer  :  diagrams-discuss@googlegroups.com
--
-- An animation is a time-varying diagram, together with start and end
-- times.  Most of the tools for working with animations can actually
-- be found in the @active@ package, which defines the 'Active' type.
--
-- XXX more documentation and examples should go here
--
-----------------------------------------------------------------------------

module Diagrams.Animation
       ( -- * Types for animations
         QAnimation
       , Animation

         -- * Animation combinators and tools
         -- $animComb
       , animEnvelope, animEnvelope'

       , animRect, animRect'

       ) where

import           Diagrams.Core

import           Diagrams.Animation.Active ()
import           Diagrams.BoundingBox
import           Diagrams.Combinators
import           Diagrams.TrailLike
import           Diagrams.TwoD.Shapes
import           Diagrams.TwoD.Types

import           Data.Active
import           Data.Semigroup

import           Control.Applicative       ((<$>))
import           Data.Foldable             (foldMap)
import           Data.VectorSpace

-- | A value of type @QAnimation b v m@ is an animation (a
--   time-varying diagram with start and end times) that can be
--   rendered by backspace @b@, with vector space @v@ and monoidal
--   annotations of type @m@.
type QAnimation b v m = Active (QDiagram b v m)

-- | A value of type @Animation b v@ is an animation (a time-varying
--   diagram with start and end times) in vector space @v@ that can be
--   rendered by backspace @b@.
--
--   Note that @Animation@ is actually a synonym for @QAnimation@
--   where the type of the monoidal annotations has been fixed to
--   'Any' (the default).
type Animation b v = QAnimation b v Any

-- $animComb
-- Most combinators for working with animations are to be found in the
-- @active@ package, which defines the 'Active' type.  This module
-- defines just a few combinators specifically for working with
-- animated diagrams.

-- It would be cool to have a variant of animEnvelope that tries to do
-- some sort of smart adaptive sampling to get good results more
-- quickly.  One could also imagine trying to use some sort of
-- automatic differentiation but that probably wouldn't work in all
-- cases we want to handle.

-- | Automatically assign fixed a envelope to the entirety of an
--   animation by sampling the envelope at a number of points in time
--   and taking the union of all the sampled envelopes to form the
--   \"hull\".  This hull is then used uniformly throughout the
--   animation.
--
--   This is useful when you have an animation that grows and shrinks
--   in size or shape over time, but you want it to take up a fixed
--   amount of space, /e.g./ so that the final rendered movie does not
--   zoom in and out, or so that it occupies a fixed location with
--   respect to another animation, when combining animations with
--   something like '|||'.
--
--   By default, 30 samples per time unit are used; to adjust this
--   number see 'animEnvelope''.
--
--   See also 'animRect' for help constructing a background to go
--   behind an animation.
animEnvelope :: (Backend b v, OrderedField (Scalar v), InnerSpace v, Monoid' m)
           => QAnimation b v m -> QAnimation b v m
animEnvelope = animEnvelope' 30

-- | Like 'animEnvelope', but with an adjustible sample rate.  The first
--   parameter is the number of samples per time unit to use.  Lower
--   rates will be faster but less accurate; higher rates are more
--   accurate but slower.
animEnvelope' :: (Backend b v, OrderedField (Scalar v), InnerSpace v, Monoid' m)
            => Rational -> QAnimation b v m -> QAnimation b v m
animEnvelope' r a = withEnvelope (simulate r a) <$> a

-- | @animRect@ works similarly to 'animEnvelope' for 2D diagrams, but
--   instead of adjusting the envelope, simply returns the smallest
--   bounding rectangle which encloses the entire animation.  Useful
--   for /e.g./ creating a background to go behind an animation.
--
--   Uses 30 samples per time unit by default; to adjust this number
--   see 'animRect''.
animRect :: (TrailLike t, Enveloped t, Transformable t, Monoid t, V t ~ R2
            , Monoid' m)
         => QAnimation b R2 m -> t
animRect = animRect' 30

-- | Like 'animRect', but with an adjustible sample rate.  The first
--   parameter is the number of samples per time unit to use.  Lower
--   rates will be faster but less accurate; higher rates are more
--   accurate but slower.
animRect' :: (TrailLike t, Enveloped t, Transformable t, Monoid t, V t ~ R2
             , Monoid' m)
          => Rational -> QAnimation b R2 m -> t
animRect' r anim
    | null results = rect 1 1
    | otherwise    = boxFit (foldMap boundingBox results) (rect 1 1)
  where
    results = simulate r anim