// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import "dart:sky";

import 'package:sky/src/animation/curves.dart';

/// The direction in which an animation is running
enum Direction {
  /// The animation is running from beginning to end
  forward,

  /// The animation is running backwards, from end to beginning
  reverse
}

/// An interface describing a variable that changes as an animation progresses.
/// 
/// AnimatedVariables, by convention, must be cheap to create. This allows them to be used in 
/// build functions in Widgets.
abstract class AnimatedVariable {
  /// Update the variable to a given time in an animation that is running in the given direction
  void setProgress(double t, Direction direction);
  String toString();
}

/// Used by [AnimationPerformance] to convert the timing of a performance to a different timescale.
/// For example, by setting different values for the interval and reverseInterval, a performance
/// can be made to take longer in one direction that the other.
class AnimationTiming {
  AnimationTiming({
    this.interval: const Interval(0.0, 1.0),
    this.reverseInterval,
    this.curve: linear,
    this.reverseCurve
  });

  /// The interval during which this timing is active in the forward direction
  Interval interval;

  /// The interval during which this timing is active in the reverse direction
  ///
  /// If this field is null, the timing defaults to using [interval] in both directions.
  Interval reverseInterval;

  /// The curve that this timing applies to the animation clock in the forward direction
  Curve curve;

  /// The curve that this timing applies to the animation clock in the reverse direction
  ///
  /// If this field is null, the timing defaults to using [curve] in both directions.
  Curve reverseCurve;

  /// Applies this timing to the given animation clock value in the given direction
  double transform(double t, Direction direction) {
    Interval interval = _getInterval(direction);
    if (interval != null)
      t = interval.transform(t);
    if (t == 1.0) // Or should we support inverse curves?
      return t;
    Curve curve = _getCurve(direction);
    if (curve != null)
      t = curve.transform(t);
    return t;
  }

  Interval _getInterval(Direction direction) {
    if (direction == Direction.forward || reverseInterval == null)
      return interval;
    return reverseInterval;
  }

  Curve _getCurve(Direction direction) {
    if (direction == Direction.forward || reverseCurve == null)
      return curve;
    return reverseCurve;
  }
}

/// An animated variable with a concrete type
class AnimatedValue<T extends dynamic> extends AnimationTiming implements AnimatedVariable {
  AnimatedValue(this.begin, { this.end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve })
    : super(interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve) {
    value = begin;
  }

  /// The current value of this variable
  T value;

  /// The value this variable has at the beginning of the animation
  T begin;

  /// The value this variable has at the end of the animation
  T end;

  /// Returns the value this variable has at the given animation clock value
  T lerp(double t) => begin + (end - begin) * t;

  /// Updates the value of this variable according to the given animation clock value and direction
  void setProgress(double t, Direction direction) {
    if (end != null) {
      t = transform(t, direction);
      value = (t == 1.0) ? end : lerp(t);
    }
  }

  String toString() => 'AnimatedValue(begin=$begin, end=$end, value=$value)';
}

/// A list of animated variables
class AnimatedList extends AnimationTiming implements AnimatedVariable {
  /// The list of variables contained in the list
  List<AnimatedVariable> variables;

  AnimatedList(this.variables, { Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve })
    : super(interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve);

  // Updates the value of all the variables in the list according to the given animation clock value and direction
  void setProgress(double t, Direction direction) {
    double adjustedTime = transform(t, direction);
    for (AnimatedVariable variable in variables)
      variable.setProgress(adjustedTime, direction);
  }

  String toString() => 'AnimatedList([$variables])';
}

/// An animated variable containing a color
///
/// This class specializes the interpolation of AnimatedValue<Color> to be
/// appropriate for colors.
class AnimatedColorValue extends AnimatedValue<Color> {
  AnimatedColorValue(Color begin, { Color end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve })
    : super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve);

  Color lerp(double t) => Color.lerp(begin, end, t);
}

/// An animated variable containing a rectangle
///
/// This class specializes the interpolation of AnimatedValue<Rect> to be
/// appropriate for rectangles.
class AnimatedRect extends AnimatedValue<Rect> {
  AnimatedRect(Rect begin, { Rect end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve })
    : super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve);

  Rect lerp(double t) => Rect.lerp(begin, end, t);
}