import 'dart:ui';

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

/// Created by Haoyi on 2022/8/15

class CurvedOffsetAnimation extends Animation<Offset> with AnimationWithParentMixin<Offset> {
  /// Creates a curved animation.
  ///
  /// The parent and curve arguments must not be null.
  CurvedOffsetAnimation({
    required this.parent,
    required this.curve,
    this.reverseCurve,
  })  : assert(parent != null),
        assert(curve != null) {
    _updateCurveDirection(parent.status);
    parent.addStatusListener(_updateCurveDirection);
  }

  /// The animation to which this animation applies a curve.
  @override
  final Animation<Offset> parent;

  /// The curve to use in the forward direction.
  Curve curve;

  /// The curve to use in the reverse direction.
  ///
  /// If the parent animation changes direction without first reaching the
  /// [AnimationStatus.completed] or [AnimationStatus.dismissed] status, the
  /// [CurvedAnimation] stays on the same curve (albeit in the opposite
  /// direction) to avoid visual discontinuities.
  ///
  /// If you use a non-null [reverseCurve], you might want to hold this object
  /// in a [State] object rather than recreating it each time your widget builds
  /// in order to take advantage of the state in this object that avoids visual
  /// discontinuities.
  ///
  /// If this field is null, uses [curve] in both directions.
  Curve? reverseCurve;

  /// The direction used to select the current curve.
  ///
  /// The curve direction is only reset when we hit the beginning or the end of
  /// the timeline to avoid discontinuities in the value of any variables this
  /// animation is used to animate.
  AnimationStatus? _curveDirection;

  /// True if this CurvedAnimation has been disposed.
  bool isDisposed = false;

  void _updateCurveDirection(AnimationStatus status) {
    switch (status) {
      case AnimationStatus.dismissed:
      case AnimationStatus.completed:
        _curveDirection = null;
        break;
      case AnimationStatus.forward:
        _curveDirection ??= AnimationStatus.forward;
        break;
      case AnimationStatus.reverse:
        _curveDirection ??= AnimationStatus.reverse;
        break;
    }
  }

  bool get _useForwardCurve {
    return reverseCurve == null || (_curveDirection ?? parent.status) != AnimationStatus.reverse;
  }

  /// Cleans up any listeners added by this CurvedAnimation.
  void dispose() {
    isDisposed = true;
    parent.removeStatusListener(_updateCurveDirection);
  }

  @override
  Offset get value {
    final Curve? activeCurve = _useForwardCurve ? curve : reverseCurve;

    final Offset t = parent.value;
    if (activeCurve == null) return t;

    double? _dx;
    if (t.dx == 0.0 || t.dx == 1.0) {
      assert(() {
        final double transformedDx = activeCurve.transform(t.dx);
        final double roundedTransformedValue = transformedDx.round().toDouble();
        if (roundedTransformedValue != t.dx) {
          throw FlutterError(
            'Invalid curve endpoint at $t.\n'
            'Curves must map 0.0 to near zero and 1.0 to near one but '
            '${activeCurve.runtimeType} mapped $t to $transformedDx, which '
            'is near $roundedTransformedValue.',
          );
        }
        return true;
      }());
      _dx = t.dx;
    }

    double? _dy;
    if (t.dy == 0.0 || t.dy == 1.0) {
      assert(() {
        final double transformedDy = activeCurve.transform(t.dy);
        final double roundedTransformedValue = transformedDy.round().toDouble();
        if (roundedTransformedValue != t.dy) {
          throw FlutterError(
            'Invalid curve endpoint at $t.\n'
            'Curves must map 0.0 to near zero and 1.0 to near one but '
            '${activeCurve.runtimeType} mapped $t to $transformedDy, which '
            'is near $roundedTransformedValue.',
          );
        }
        return true;
      }());
      _dy = t.dy;
    }
    return Offset(_dx ?? activeCurve.transform(t.dx), _dy ?? activeCurve.transform(t.dy));
  }

  @override
  String toString() {
    if (reverseCurve == null) return '$parent\u27A9$curve';
    if (_useForwardCurve) return '$parent\u27A9$curve\u2092\u2099/$reverseCurve';
    return '$parent\u27A9$curve/$reverseCurve\u2092\u2099';
  }
}
