// Copyright 2014 The Flutter 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:ui' show Color, Size, Rect;

import 'package:flutter/foundation.dart';

import 'animation.dart';
import 'animations.dart';
import 'curves.dart';

// Examples can assume:
// late Animation<Offset> _animation;
// late AnimationController _controller;

/// An object that can produce a value of type `T` given an [Animation<double>]
/// as input.
///
/// Typically, the values of the input animation are nominally in the range 0.0
/// to 1.0. In principle, however, any value could be provided.
///
/// The main subclass of [Animatable] is [Tween].
abstract class Animatable<T> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const Animatable();

  /// Returns the value of the object at point `t`.
  ///
  /// The value of `t` is nominally a fraction in the range 0.0 to 1.0, though
  /// in practice it may extend outside this range.
  ///
  /// See also:
  ///
  ///  * [evaluate], which is a shorthand for applying [transform] to the value
  ///    of an [Animation].
  ///  * [Curve.transform], a similar method for easing curves.
  T transform(double t);

  /// The current value of this object for the given [Animation].
  ///
  /// This function is implemented by deferring to [transform]. Subclasses that
  /// want to provide custom behavior should override [transform], not
  /// [evaluate].
  ///
  /// See also:
  ///
  ///  * [transform], which is similar but takes a `t` value directly instead of
  ///    an [Animation].
  ///  * [animate], which creates an [Animation] out of this object, continually
  ///    applying [evaluate].
  T evaluate(Animation<double> animation) => transform(animation.value);

  /// Returns a new [Animation] that is driven by the given animation but that
  /// takes on values determined by this object.
  ///
  /// Essentially this returns an [Animation] that automatically applies the
  /// [evaluate] method to the parent's value.
  ///
  /// See also:
  ///
  ///  * [AnimationController.drive], which does the same thing from the
  ///    opposite starting point.
  Animation<T> animate(Animation<double> parent) {
    return _AnimatedEvaluation<T>(parent, this);
  }

  /// Returns a new [Animatable] whose value is determined by first evaluating
  /// the given parent and then evaluating this object.
  ///
  /// This allows [Tween]s to be chained before obtaining an [Animation].
  Animatable<T> chain(Animatable<double> parent) {
    return _ChainedEvaluation<T>(parent, this);
  }
}

class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
  _AnimatedEvaluation(this.parent, this._evaluatable);

  @override
  final Animation<double> parent;

  final Animatable<T> _evaluatable;

  @override
  T get value => _evaluatable.evaluate(parent);

  @override
  String toString() {
    return '$parent\u27A9$_evaluatable\u27A9$value';
  }

  @override
  String toStringDetails() {
    return '${super.toStringDetails()} $_evaluatable';
  }
}

class _ChainedEvaluation<T> extends Animatable<T> {
  _ChainedEvaluation(this._parent, this._evaluatable);

  final Animatable<double> _parent;
  final Animatable<T> _evaluatable;

  @override
  T transform(double t) {
    return _evaluatable.transform(_parent.transform(t));
  }

  @override
  String toString() {
    return '$_parent\u27A9$_evaluatable';
  }
}

/// A linear interpolation between a beginning and ending value.
///
/// [Tween] is useful if you want to interpolate across a range.
///
/// To use a [Tween] object with an animation, call the [Tween] object's
/// [animate] method and pass it the [Animation] object that you want to
/// modify.
///
/// You can chain [Tween] objects together using the [chain] method, so that a
/// single [Animation] object is configured by multiple [Tween] objects called
/// in succession. This is different than calling the [animate] method twice,
/// which results in two separate [Animation] objects, each configured with a
/// single [Tween].
///
/// {@tool snippet}
///
/// Suppose `_controller` is an [AnimationController], and we want to create an
/// [Animation<Offset>] that is controlled by that controller, and save it in
/// `_animation`. Here are two possible ways of expressing this:
///
/// ```dart
/// _animation = _controller.drive(
///   Tween<Offset>(
///     begin: const Offset(100.0, 50.0),
///     end: const Offset(200.0, 300.0),
///   ),
/// );
/// ```
/// {@end-tool}
/// {@tool snippet}
///
/// ```dart
/// _animation = Tween<Offset>(
///   begin: const Offset(100.0, 50.0),
///   end: const Offset(200.0, 300.0),
/// ).animate(_controller);
/// ```
/// {@end-tool}
///
/// In both cases, the `_animation` variable holds an object that, over the
/// lifetime of the `_controller`'s animation, returns a value
/// (`_animation.value`) that depicts a point along the line between the two
/// offsets above. If we used a [MaterialPointArcTween] instead of a
/// [Tween<Offset>] in the code above, the points would follow a pleasing curve
/// instead of a straight line, with no other changes necessary.
///
/// ## Performance optimizations
///
/// Tweens are mutable; specifically, their [begin] and [end] values can be
/// changed at runtime. An object created with [Animation.drive] using a [Tween]
/// will immediately honor changes to that underlying [Tween] (though the
/// listeners will only be triggered if the [Animation] is actively animating).
/// This can be used to change an animation on the fly without having to
/// recreate all the objects in the chain from the [AnimationController] to the
/// final [Tween].
///
/// If a [Tween]'s values are never changed, however, a further optimization can
/// be applied: the object can be stored in a `static final` variable, so that
/// the exact same instance is used whenever the [Tween] is needed. This is
/// preferable to creating an identical [Tween] afresh each time a [State.build]
/// method is called, for example.
///
/// ## Types with special considerations
///
/// Classes with [lerp] static methods typically have corresponding dedicated
/// [Tween] subclasses that call that method. For example, [ColorTween] uses
/// [Color.lerp] to implement the [ColorTween.lerp] method.
///
/// Types that define `+` and `-` operators to combine values (`T + T → T` and
/// `T - T → T`) and an `*` operator to scale by multiplying with a double (`T *
/// double → T`) can be directly used with `Tween<T>`.
///
/// This does not extend to any type with `+`, `-`, and `*` operators. In
/// particular, [int] does not satisfy this precise contract (`int * double`
/// actually returns [num], not [int]). There are therefore two specific classes
/// that can be used to interpolate integers:
///
///  * [IntTween], which is an approximation of a linear interpolation (using
///    [double.round]).
///  * [StepTween], which uses [double.floor] to ensure that the result is
///    never greater than it would be using if a `Tween<double>`.
///
/// The relevant operators on [Size] also don't fulfill this contract, so
/// [SizeTween] uses [Size.lerp].
///
/// In addition, some of the types that _do_ have suitable `+`, `-`, and `*`
/// operators still have dedicated [Tween] subclasses that perform the
/// interpolation in a more specialized manner. One such class is
/// [MaterialPointArcTween], which is mentioned above. The [AlignmentTween], and
/// [AlignmentGeometryTween], and [FractionalOffsetTween] are another group of
/// [Tween]s that use dedicated `lerp` methods instead of merely relying on the
/// operators (in particular, this allows them to handle null values in a more
/// useful manner).
///
/// ## Nullability
///
/// The [begin] and [end] fields are nullable; a [Tween] does not have to
/// have non-null values specified when it is created.
///
/// If `T` is nullable, then [lerp] and [transform] may return null.
/// This is typically seen in the case where [begin] is null and `t`
/// is 0.0, or [end] is null and `t` is 1.0, or both are null (at any
/// `t` value).
///
/// If `T` is not nullable, then [begin] and [end] must both be set to
/// non-null values before using [lerp] or [transform], otherwise they
/// will throw.
class Tween<T extends Object?> extends Animatable<T> {
  /// Creates a tween.
  ///
  /// The [begin] and [end] properties must be non-null before the tween is
  /// first used, but the arguments can be null if the values are going to be
  /// filled in later.
  Tween({
    this.begin,
    this.end,
  });

  /// The value this variable has at the beginning of the animation.
  ///
  /// See the constructor for details about whether this property may be null
  /// (it varies from subclass to subclass).
  T? begin;

  /// The value this variable has at the end of the animation.
  ///
  /// See the constructor for details about whether this property may be null
  /// (it varies from subclass to subclass).
  T? end;

  /// Returns the value this variable has at the given animation clock value.
  ///
  /// The default implementation of this method uses the `+`, `-`, and `*`
  /// operators on `T`. The [begin] and [end] properties must therefore be
  /// non-null by the time this method is called.
  ///
  /// In general, however, it is possible for this to return null, especially
  /// when `t`=0.0 and [begin] is null, or `t`=1.0 and [end] is null.
  @protected
  T lerp(double t) {
    assert(begin != null);
    assert(end != null);
    assert(() {
      // Assertions that attempt to catch common cases of tweening types
      // that do not conform to the Tween requirements.
      dynamic result;
      try {
        // ignore: avoid_dynamic_calls
        result = (begin as dynamic) + ((end as dynamic) - (begin as dynamic)) * t;
        result as T;
        return true;
      } on NoSuchMethodError {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('Cannot lerp between "$begin" and "$end".'),
          ErrorDescription(
            'The type ${begin.runtimeType} might not fully implement `+`, `-`, and/or `*`. '
            'See "Types with special considerations" at https://api.flutter.dev/flutter/animation/Tween-class.html '
            'for more information.',
          ),
          if (begin is Color || end is Color)
            ErrorHint('To lerp colors, consider ColorTween instead.')
          else if (begin is Rect || end is Rect)
            ErrorHint('To lerp rects, consider RectTween instead.')
          else
            ErrorHint(
              'There may be a dedicated "${begin.runtimeType}Tween" for this type, '
              'or you may need to create one.',
            ),
        ]);
      } on TypeError {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('Cannot lerp between "$begin" and "$end".'),
          ErrorDescription(
            'The type ${begin.runtimeType} returned a ${result.runtimeType} after '
            'multiplication with a double value. '
            'See "Types with special considerations" at https://api.flutter.dev/flutter/animation/Tween-class.html '
            'for more information.',
          ),
          if (begin is int || end is int)
            ErrorHint('To lerp int values, consider IntTween or StepTween instead.')
          else
            ErrorHint(
              'There may be a dedicated "${begin.runtimeType}Tween" for this type, '
              'or you may need to create one.',
            ),
        ]);
      }
    }());
    // ignore: avoid_dynamic_calls
    return (begin as dynamic) + ((end as dynamic) - (begin as dynamic)) * t as T;
  }

  /// Returns the interpolated value for the current value of the given animation.
  ///
  /// This method returns `begin` and `end` when the animation values are 0.0 or
  /// 1.0, respectively.
  ///
  /// This function is implemented by deferring to [lerp]. Subclasses that want
  /// to provide custom behavior should override [lerp], not [transform] (nor
  /// [evaluate]).
  ///
  /// See the constructor for details about whether the [begin] and [end]
  /// properties may be null when this is called. It varies from subclass to
  /// subclass.
  @override
  T transform(double t) {
    if (t == 0.0)
      return begin as T;
    if (t == 1.0)
      return end as T;
    return lerp(t);
  }

  @override
  String toString() => '${objectRuntimeType(this, 'Animatable')}($begin \u2192 $end)';
}

/// A [Tween] that evaluates its [parent] in reverse.
class ReverseTween<T extends Object?> extends Tween<T> {
  /// Construct a [Tween] that evaluates its [parent] in reverse.
  ReverseTween(this.parent)
    : assert(parent != null),
      super(begin: parent.end, end: parent.begin);

  /// This tween's value is the same as the parent's value evaluated in reverse.
  ///
  /// This tween's [begin] is the parent's [end] and its [end] is the parent's
  /// [begin]. The [lerp] method returns `parent.lerp(1.0 - t)` and its
  /// [evaluate] method is similar.
  final Tween<T> parent;

  @override
  T lerp(double t) => parent.lerp(1.0 - t);
}

/// An interpolation between two colors.
///
/// This class specializes the interpolation of [Tween<Color>] to use
/// [Color.lerp].
///
/// The values can be null, representing no color (which is distinct to
/// transparent black, as represented by [Colors.transparent]).
///
/// See [Tween] for a discussion on how to use interpolation objects.
class ColorTween extends Tween<Color?> {
  /// Creates a [Color] tween.
  ///
  /// The [begin] and [end] properties may be null; the null value
  /// is treated as transparent.
  ///
  /// We recommend that you do not pass [Colors.transparent] as [begin]
  /// or [end] if you want the effect of fading in or out of transparent.
  /// Instead prefer null. [Colors.transparent] refers to black transparent and
  /// thus will fade out of or into black which is likely unwanted.
  ColorTween({ Color? begin, Color? end }) : super(begin: begin, end: end);

  /// Returns the value this variable has at the given animation clock value.
  @override
  Color? lerp(double t) => Color.lerp(begin, end, t);
}

/// An interpolation between two sizes.
///
/// This class specializes the interpolation of [Tween<Size>] to use
/// [Size.lerp].
///
/// The values can be null, representing [Size.zero].
///
/// See [Tween] for a discussion on how to use interpolation objects.
class SizeTween extends Tween<Size?> {
  /// Creates a [Size] tween.
  ///
  /// The [begin] and [end] properties may be null; the null value
  /// is treated as an empty size.
  SizeTween({ Size? begin, Size? end }) : super(begin: begin, end: end);

  /// Returns the value this variable has at the given animation clock value.
  @override
  Size? lerp(double t) => Size.lerp(begin, end, t);
}

/// An interpolation between two rectangles.
///
/// This class specializes the interpolation of [Tween<Rect>] to use
/// [Rect.lerp].
///
/// The values can be null, representing a zero-sized rectangle at the
/// origin ([Rect.zero]).
///
/// See [Tween] for a discussion on how to use interpolation objects.
class RectTween extends Tween<Rect?> {
  /// Creates a [Rect] tween.
  ///
  /// The [begin] and [end] properties may be null; the null value
  /// is treated as an empty rect at the top left corner.
  RectTween({ Rect? begin, Rect? end }) : super(begin: begin, end: end);

  /// Returns the value this variable has at the given animation clock value.
  @override
  Rect? lerp(double t) => Rect.lerp(begin, end, t);
}

/// An interpolation between two integers that rounds.
///
/// This class specializes the interpolation of [Tween<int>] to be
/// appropriate for integers by interpolating between the given begin
/// and end values and then rounding the result to the nearest
/// integer.
///
/// This is the closest approximation to a linear tween that is possible with an
/// integer. Compare to [StepTween] and [Tween<double>].
///
/// The [begin] and [end] values must be set to non-null values before
/// calling [lerp] or [transform].
///
/// See [Tween] for a discussion on how to use interpolation objects.
class IntTween extends Tween<int> {
  /// Creates an int tween.
  ///
  /// The [begin] and [end] properties must be non-null before the tween is
  /// first used, but the arguments can be null if the values are going to be
  /// filled in later.
  IntTween({ int? begin, int? end }) : super(begin: begin, end: end);

  // The inherited lerp() function doesn't work with ints because it multiplies
  // the begin and end types by a double, and int * double returns a double.
  @override
  int lerp(double t) => (begin! + (end! - begin!) * t).round();
}

/// An interpolation between two integers that floors.
///
/// This class specializes the interpolation of [Tween<int>] to be
/// appropriate for integers by interpolating between the given begin
/// and end values and then using [double.floor] to return the current
/// integer component, dropping the fractional component.
///
/// This results in a value that is never greater than the equivalent
/// value from a linear double interpolation. Compare to [IntTween].
///
/// The [begin] and [end] values must be set to non-null values before
/// calling [lerp] or [transform].
///
/// See [Tween] for a discussion on how to use interpolation objects.
class StepTween extends Tween<int> {
  /// Creates an [int] tween that floors.
  ///
  /// The [begin] and [end] properties must be non-null before the tween is
  /// first used, but the arguments can be null if the values are going to be
  /// filled in later.
  StepTween({ int? begin, int? end }) : super(begin: begin, end: end);

  // The inherited lerp() function doesn't work with ints because it multiplies
  // the begin and end types by a double, and int * double returns a double.
  @override
  int lerp(double t) => (begin! + (end! - begin!) * t).floor();
}

/// A tween with a constant value.
class ConstantTween<T> extends Tween<T> {
  /// Create a tween whose [begin] and [end] values equal [value].
  ConstantTween(T value) : super(begin: value, end: value);

  /// This tween doesn't interpolate, it always returns the same value.
  @override
  T lerp(double t) => begin as T;

  @override
  String toString() => '${objectRuntimeType(this, 'ConstantTween')}(value: $begin)';
}

/// Transforms the value of the given animation by the given curve.
///
/// This class differs from [CurvedAnimation] in that [CurvedAnimation] applies
/// a curve to an existing [Animation] object whereas [CurveTween] can be
/// chained with another [Tween] prior to receiving the underlying [Animation].
/// ([CurvedAnimation] also has the additional ability of having different
/// curves when the animation is going forward vs when it is going backward,
/// which can be useful in some scenarios.)
///
/// {@tool snippet}
///
/// The following code snippet shows how you can apply a curve to a linear
/// animation produced by an [AnimationController] `controller`:
///
/// ```dart
/// final Animation<double> animation = _controller.drive(
///   CurveTween(curve: Curves.ease),
/// );
/// ```
/// {@end-tool}
///
/// See also:
///
///  * [CurvedAnimation], for an alternative way of expressing the sample above.
///  * [AnimationController], for examples of creating and disposing of an
///    [AnimationController].
class CurveTween extends Animatable<double> {
  /// Creates a curve tween.
  ///
  /// The [curve] argument must not be null.
  CurveTween({ required this.curve })
    : assert(curve != null);

  /// The curve to use when transforming the value of the animation.
  Curve curve;

  @override
  double transform(double t) {
    if (t == 0.0 || t == 1.0) {
      assert(curve.transform(t).round() == t);
      return t;
    }
    return curve.transform(t);
  }

  @override
  String toString() => '${objectRuntimeType(this, 'CurveTween')}(curve: $curve)';
}
