part of 'transform_animation.dart';

/// Created by @Haoyi on 2021/11/12

const Color TRANSPARENT = Color(0x00000000);

const infinite = -1;

class TransformAnimation {
  static const int infinite = -1;

  final Duration delayed;
  final Duration duration;
  final Duration interval;
  final int intervalCount;
  final Offset fromTranslation;
  final Offset toTranslation;
  final double fromOpacity;
  final double toOpacity;
  final double fromXScale;
  final double toXScale;
  final double fromYScale;
  final double toYScale;

  final double fromColorOpacity;
  final double toColorOpacity;
  final double fromAngle;
  final double toAngle;
  final Curve curve;
  final Curve reverseCurve;
  final OffsetCurve? translationCurve;
  final int repeat;
  final bool reverse;
  final bool medianPending;
  final String label;

  final VoidCallback? onCompleted;

  TransformAnimation(
      {this.delayed = const Duration(milliseconds: 0),
      this.duration = const Duration(milliseconds: 800),
      this.repeat = 0,
      this.interval = const Duration(milliseconds: 0),
      this.intervalCount = 1,
      this.medianPending = false,
      this.reverse = false,
      this.curve = Curves.linear,
      this.translationCurve,
      this.reverseCurve = Curves.linear,
      this.fromOpacity = 1.0,
      this.toOpacity = 1.0,
      this.fromTranslation = Offset.zero,
      this.toTranslation = Offset.zero,
      this.fromAngle = 0.0,
      this.toAngle = 0.0,
      this.fromColorOpacity = 1.0,
      this.toColorOpacity = 1.0,
      double? fromScale,
      double? toScale,
      double? fromXScale,
      double? toXScale,
      double? fromYScale,
      double? toYScale,
      this.onCompleted,
      this.label = ""})
      : assert((fromScale != null && toScale != null) || (fromScale == null && toScale == null)),
        assert((fromXScale != null && toXScale != null) || (fromXScale == null && toXScale == null)),
        assert((fromYScale != null && toYScale != null) || (fromYScale == null && toYScale == null)),
        fromXScale = fromXScale ?? fromScale ?? 1.0,
        toXScale = toXScale ?? toScale ?? 1.0,
        fromYScale = fromYScale ?? fromScale ?? 1.0,
        toYScale = toYScale ?? toScale ?? 1.0;
}

enum AnimatorState { pending, started, disposed }

class _Animator {
  final AnimationController controller;
  final TransformAnimation animation;
  final Key? widgetKey;
  late Animation<double> _scaleXAnimation;
  late Animation<double> _scaleYAnimation;
  late Animation<double> _translationXAnimation;
  late Animation<double> _translationYAnimation;
  late Animation<double> _rotationAnimation;
  late Animation<double> _opacityAnimation;
  late Animation<double> _colorOpacityAnimation;
  late Offset _translationOffset;
  VoidCallback? onCompleted;
  VoidCallback? onStarted;
  int _repeatCount = 0;
  Matrix4 matrix = Matrix4.identity();
  final String name;

  Timer? _intervalTimer;
  Timer? _delayedTimer;
  //用于repeat时判断方向,解决reverse为false时,会先回调dismissed的问题
  bool repeatForward = true;

  AnimatorState _state = AnimatorState.pending;

  bool get isStarted => _state == AnimatorState.started;

  bool get isDisposed => _state == AnimatorState.disposed;

  bool _scheduleRepeat() {
    if (animation.repeat == TransformAnimation.infinite || _repeatCount < animation.repeat) {
      _repeatCount++;
      repeatForward = !animation.reverse || (_repeatCount & 0x01 != 1);
      if (animation.interval.inMilliseconds > 0 && (_repeatCount % animation.intervalCount == 0)) {
        _intervalTimer?.cancel();
        _intervalTimer = Timer(animation.interval, () {
          if (!isDisposed) {
            if (repeatForward) {
              _forward(from: 0);
            } else {
              _reverse(from: 1);
            }
          }
        });
      } else {
        if (repeatForward) {
          _forward(from: 0);
        } else {
          _reverse(from: 1);
        }
      }
      return true;
    }
    return false;
  }

  void _forward({double from = 0.0}) {
    try {
      controller.forward(from: from);
    } catch (error) {}
  }

  void _reverse({double from = 1.0}) {
    try {
      controller.reverse(from: from);
    } catch (error) {}
  }

  void dispatchCompleted() {
    animation.onCompleted?.call();
    final completeCallback = onCompleted;
    if (completeCallback != null) {
      completeCallback.call();
      onCompleted = null;
    }
  }

  void handleAnimationStateChanged(AnimationStatus status) {
    // print("handleAnimationStateChanged[$widgetKey]: $status");

    if (isDisposed) {
      return;
    }
    bool repeat = false;
    if (status == AnimationStatus.completed) {
      repeat = _scheduleRepeat();
    } else if (!repeatForward && status == AnimationStatus.dismissed) {
      repeat = _scheduleRepeat();
    }
    if (!repeat && (status == AnimationStatus.completed || status == AnimationStatus.dismissed)) {
      print("completed!!");
      dispatchCompleted();
    }
  }

  Matrix4 _refreshMatrix({BoxConstraints? constraints}) {
    final translationX = animation.translationCurve != null ? _translationOffset.dx : _translationXAnimation.value;
    final translationY = animation.translationCurve != null ? _translationOffset.dy : _translationYAnimation.value;
    final radian = _rotationAnimation.value;
    late Matrix4 _matrix;
    if (constraints != null) {
      _matrix = Matrix4.identity();
      _matrix.translate(translationX, translationY);
      _matrix.translate(constraints.maxWidth / 2, constraints.maxHeight / 2);
      if (animation.fromAngle != 0 || animation.toAngle != 0) {
        _matrix.rotateZ(radian);
      }
      if ((animation.fromXScale != animation.toXScale) || animation.fromYScale != animation.toYScale) {
        _matrix.scale(_scaleXAnimation.value, _scaleYAnimation.value);
      }
      _matrix.translate(-constraints.maxWidth / 2, -constraints.maxHeight / 2);
    } else {
      if ((animation.fromXScale != animation.toXScale) || animation.fromYScale != animation.toYScale) {
        // _matrix.scale(_scaleXAnimation.value, _scaleYAnimation.value);
        _matrix = Matrix4.diagonal3Values(_scaleXAnimation.value, _scaleYAnimation.value, 1.0);
      } else {
        _matrix = Matrix4.identity();
      }
      _matrix.translate(translationX, translationY);
      if (animation.fromAngle != 0 || animation.toAngle != 0) {
        _matrix.rotateZ(radian);
      }
    }
    matrix = _matrix;
    return _matrix;
  }

  double _refreshOpacity() {
    if (animation.fromOpacity == animation.toOpacity) {
      return animation.toOpacity;
    }
    return _opacityAnimation.value.clamp(0.0, 1.0);
  }

  Color? _refreshColorOpacity(Color? color) {
    if (color == null) {
      return null;
    }
    if (animation.fromColorOpacity == animation.toColorOpacity) {
      return color.withOpacity(animation.toColorOpacity);
    }
    return color.withOpacity(_colorOpacityAnimation.value.clamp(0.0, 1.0));
  }

  _Animator.schedule(this.animation,
      {required TickerProvider vsync,
      this.widgetKey,
      AnimatorState initState = AnimatorState.pending,
      this.onStarted,
      this.onCompleted,
      this.name = ""})
      : controller = AnimationController(vsync: vsync, duration: animation.duration) {
    controller.addStatusListener(handleAnimationStateChanged);
    final curved = CurvedAnimation(parent: controller, curve: animation.curve);

    if ((animation.fromXScale != animation.toXScale) || (animation.fromYScale == animation.toYScale)) {
      _scaleXAnimation = Tween(begin: animation.fromXScale, end: animation.toXScale).animate(curved);
      _scaleYAnimation = Tween(begin: animation.fromYScale, end: animation.toYScale).animate(curved);
    }
    final translateCurve = animation.translationCurve;
    if (translateCurve == null) {
      _translationXAnimation = Tween(begin: animation.fromTranslation.dx, end: animation.toTranslation.dx).animate(curved);
      _translationYAnimation = Tween(begin: animation.fromTranslation.dy, end: animation.toTranslation.dy).animate(curved);
    } else {
      _translationOffset = animation.fromTranslation;
      controller.addListener(() {
        _translationOffset = translateCurve.transform(controller.value);
      });
    }
    _rotationAnimation = Tween(begin: MathUtils.toRadian(animation.fromAngle), end: MathUtils.toRadian(animation.toAngle)).animate(curved);
    _opacityAnimation = Tween(begin: animation.fromOpacity, end: animation.toOpacity).animate(curved);

    _colorOpacityAnimation = Tween(begin: animation.fromColorOpacity, end: animation.toColorOpacity).animate(curved);
    _state = initState;
    if (animation.delayed.inMilliseconds > 0) {
      _delayedTimer?.cancel();
      _delayedTimer = Timer(animation.delayed, () {
        if (!isDisposed) {
          _state = AnimatorState.started;
          controller.forward(from: 0);
          onStarted?.call();
        }
      });
    } else {
      Future.microtask(() {
        controller.forward(from: 0);
        onStarted?.call();
      });
      _state = AnimatorState.started;
    }
  }

  void dispose() {
    print("[$name] dispose: $_state $isDisposed");
    if (!isDisposed) {
      _state = AnimatorState.disposed;
      controller.dispose();
      _intervalTimer?.cancel();
      _delayedTimer?.cancel();
//      dispatchCompleted();
    }
  }
}

enum AnimatedBehavior {
  show,
  hide,
}
