{-# OPTIONS_HADDOCK hide #-}

{-# LANGUAGE NoImplicitPrelude #-}

module Imj.Graphics.Class.DiscreteInterpolation
        ( DiscreteInterpolation(..)
          -- * Reexport
        , module Imj.Graphics.Class.DiscreteDistance
        ) where

import           Imj.Prelude

import           Data.List(length)

import           Imj.Graphics.Class.DiscreteDistance
import           Imj.Util


{- | Instances should statisfy the following constraints:

* An interpolation between A and B starts at A and ends at B:

\( \forall (\, from, to)\, \in v, \)

@
    d = distance from to
    interpolate from to 0 == from
    interpolate from to d == to
@

* The interpolation path is composed of strictly distinct points:

@
    length $ nubOrd $ map (interpolate from to) [0..pred d] == d
@

* Given any points A,B belonging the path generated by an interpolation,
  the interpolation beween A and B will be the points of the path between A and B:

\( \forall med \in [\,0,d]\,, \forall low \in [\,0,med]\,, \forall high \in [\,med,d]\,, \)

@
    distance from med + distance med to == 1 + distance from to
    medVal = interpolate from to med
    interpolate from to low  == interpolate from medVal low
    interpolate from to high == interpolate medVal to $ high-med
@
-}
class (DiscreteDistance v) => DiscreteInterpolation v where
  -- | Implement this function if you want to interpolate /by value/, i.e the result of
  -- the interpolation between two \(v\) is a \(v\).
  interpolate :: v -- ^ first value
              -> v -- ^ last value
              -> Int -- ^ the current step
              -> v -- ^ the interpolated value

  interpolateSuccessive :: Successive v
                        -> Int
                        -> v
  interpolateSuccessive (Successive []) _ = error "empty successive"
  interpolateSuccessive (Successive [a]) _ = a
  interpolateSuccessive (Successive l@(a:b:_)) i
    | i <= 0      = a
    | i >= lf = interpolateSuccessive (Successive $ tail l) $ i-lf
    | otherwise = interpolate a b i
    where lf = pred $ distance a b

-- | Naïve interpolation.
instance DiscreteInterpolation Int where
  interpolate i i' progress =
    i + signum (i'-i) * clamp progress 0 (abs (i-i'))


-- | Interpolate in parallel between 2 lists : each pair of same-index elements
-- is interpolated at the same time.
instance (DiscreteInterpolation a)
      => DiscreteInterpolation ([] a) where
  interpolate l l' progress =
    zipWith (\e e' -> interpolate e e' progress) l $ assert (length l == length l') l'