import 'dart:collection';
import 'dart:math';

import 'package:flutter/animation.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/number/range.dart';

import 'keyframe/key_frame.dart';

/// Created by Haoyi on 2020/6/17
///
///

const repeat_infinity = -1;

enum VisualFeastAnimationState { init, paused, waiting, playing, finallyRender, completed }

typedef AnimationStartedCallback = void Function();
typedef AnimationUpdateCallback = void Function(double value, double percent, int elapseInMillis);
typedef AnimationAnchorCallback = void Function(double percent, int elapseInMillis);
typedef AnimationCompletedCallback = void Function();
typedef AnimationWillCompleteCallback = void Function();
typedef AnimationRepeatCallback = void Function(int repeat);
typedef AnimationNextCallback = void Function(BaseVisualFeastAnimation animation);
typedef AnimationSetUpdateCallback = void Function(double percent, int elapseInMillis);

abstract class BaseVisualFeastAnimation {
  static int _idPools = 1;
  VisualFeastAnimationState saveState = VisualFeastAnimationState.init;
  VisualFeastAnimationState state = VisualFeastAnimationState.init;

  bool get isCompleted => state == VisualFeastAnimationState.completed;

  bool get isFinallyRender => state == VisualFeastAnimationState.finallyRender;

  bool get isStarted =>
      state == VisualFeastAnimationState.playing ||
      state == VisualFeastAnimationState.finallyRender;

  bool get isPaused => state == VisualFeastAnimationState.paused;

  final int id = _idPools++;

  final Map<String, dynamic> properties = {};

  dynamic extras;

  final String name;

  BaseVisualFeastAnimation({this.name = ""});

  void start() {
    if (state == VisualFeastAnimationState.init) {
      state = VisualFeastAnimationState.waiting;
    }
  }

  void pause() {
    saveState = state;
    state = VisualFeastAnimationState.paused;
  }

  void resume() {
    state = saveState;
  }

  void stop() {
    state = VisualFeastAnimationState.completed;
  }

  void update(double t);

  void setProperty(String key, dynamic value) {
    properties[key] = value;
  }

  T getProperty<T>(String key) {
    return properties[key];
  }

  T getAndRemoveProperty<T>(String key) {
    return properties.remove(key);
  }

  void setExtras(dynamic value) {
    extras = value;
  }

  T? getExtras<T>() {
    return extras;
  }

  T getAndRemoveExtras<T>() {
    final T result = extras;
    extras = null;
    return result;
  }

  void directComplete();
}

class VisualFeastFramesAnimation extends BaseVisualFeastAnimation {
  final int fps;
  final int frames;
  final int repeat;

  double _elapsed = 0;
  double _frame = 0;
  int _repeatCount = 0;
  AnimationStartedCallback? onStarted;
  AnimationUpdateCallback? onUpdate;
  AnimationCompletedCallback? onCompleted;
  AnimationRepeatCallback? onRepeat;

  VisualFeastFramesAnimation({
    required this.fps,
    required this.frames,
    required this.repeat,
    this.onStarted,
    this.onUpdate,
    this.onCompleted,
    this.onRepeat,
  });

  VisualFeastFramesAnimation copyWith({
    int? fps,
    int? frames,
    int? repeat,
    AnimationStartedCallback? onStarted,
    AnimationUpdateCallback? onUpdate,
    AnimationCompletedCallback? onCompleted,
    AnimationRepeatCallback? onRepeat,
  }) {
    return VisualFeastFramesAnimation(
        fps: fps ?? this.fps,
        frames: frames ?? this.frames,
        repeat: repeat ?? this.repeat,
        onStarted: onStarted ?? this.onStarted,
        onUpdate: onUpdate ?? this.onUpdate,
        onRepeat: onRepeat ?? this.onRepeat,
        onCompleted: onCompleted ?? this.onCompleted);
  }

  void reset(
      {int repeatCount = -1,
      AnimationStartedCallback? onStarted,
      AnimationUpdateCallback? onUpdate,
      AnimationRepeatCallback? onRepeat,
      AnimationCompletedCallback? onCompleted}) {
    _frame = 0;
    _repeatCount = repeatCount;
    _elapsed = 0;
    saveState = VisualFeastAnimationState.init;
    state = VisualFeastAnimationState.init;
    if (onStarted != null) {
      this.onStarted = onStarted;
    }
    if (onUpdate != null) {
      this.onUpdate = onUpdate;
    }
    if (onRepeat != null) {
      this.onRepeat = onRepeat;
    }
    if (onCompleted != null) {
      this.onCompleted = onCompleted;
    }
  }

  @override
  void update(double t) {
    switch (state) {
      case VisualFeastAnimationState.completed:
      case VisualFeastAnimationState.paused:
      case VisualFeastAnimationState.init:
        return;
      case VisualFeastAnimationState.waiting:
        state = VisualFeastAnimationState.playing;
        onStarted?.call();
        break;
      default:
        break;
    }

    _elapsed += fps * t;

    final lastFrame = _frame;
    _frame = _elapsed % frames;
    if (lastFrame >= _frame) {
      // Log.d("repeat: $repeat _repeatCount:$_repeatCount lastFrame:$lastFrame _frame:$_frame");

      if (repeat < 0 || _repeatCount < repeat) {
        _repeatCount++;
        state = VisualFeastAnimationState.playing;
        onRepeat?.call(_repeatCount);
        onUpdate?.call(_frame, _frame / frames, _elapsed.toInt() * 1000);
      } else {
        _complete();
      }
    } else {
      onUpdate?.call(_frame, _frame / frames, _elapsed.toInt() * 1000);
    }
  }

  @override
  void directComplete() {
    if (state != VisualFeastAnimationState.completed) {
      if (_elapsed == 0) {
        onStarted?.call();
      }
      _complete();
    }
  }

  void _complete() {
    onUpdate?.call(frames - 1, 1.0, _elapsed.toInt() * 1000);
    state = VisualFeastAnimationState.completed;
    onCompleted?.call();
  }
}

class VisualFeastAnimation extends BaseVisualFeastAnimation {
  final Duration delayed;
  final Duration duration;
  Curve curve;
  int _elapseInMillis = 0;
  int repeat = 0;
  int _repeatCount = 0;
  bool reverse = false;
  double _latestAnchor = 0.0;
  final double from;
  final double to;
  final double anchor;

  AnimationStartedCallback? onStarted;
  AnimationUpdateCallback? onUpdate;
  AnimationCompletedCallback? onCompleted;
  AnimationWillCompleteCallback? onWillComplete;
  AnimationRepeatCallback? onRepeat;
  AnimationAnchorCallback? onAnchor;

  VisualFeastAnimation(
      {this.from = 0.0,
      this.to = 1.0,
      this.anchor = -1,
      required this.duration,
      this.repeat = 0,
      this.curve = Curves.linear,
      this.reverse = false,
      this.delayed = const Duration(seconds: 0),
      this.onStarted,
      this.onUpdate,
      this.onAnchor,
      this.onWillComplete,
      this.onCompleted,
      this.onRepeat,
      String name = ""})
      : super(name: name);

  VisualFeastAnimation.wait({required this.duration, this.onCompleted})
      : from = 0.0,
        to = 1.0,
        anchor = -1,
        repeat = 0,
        curve = Curves.linear,
        reverse = false,
        delayed = const Duration(seconds: 0),
        super(name: "");

  @override
  void directComplete() {
    if (state != VisualFeastAnimationState.completed) {
      if (state == VisualFeastAnimationState.waiting || _elapseInMillis == 0) {
        onStarted?.call();
      }
      _complete();
    }
  }

  void _complete() {
    onUpdate?.call(to, 1.0, duration.inMilliseconds);
    state = VisualFeastAnimationState.completed;
    onCompleted?.call();
    // Log.d("VisualFeast[$id-$name] onComplete", tag: "VisualFeast");
  }

  @override
  void update(double t) {
    if (state == VisualFeastAnimationState.completed ||
        state == VisualFeastAnimationState.paused ||
        state == VisualFeastAnimationState.init) {
      return;
    } else if (state == VisualFeastAnimationState.finallyRender) {
      if (repeat < 0 || _repeatCount < repeat) {
        _repeatCount++;
        _elapseInMillis = 0;
        state = VisualFeastAnimationState.playing;
        onRepeat?.call(_repeatCount);
      } else {
        _complete();
      }
      return;
    }

    int timeInTick = (t * 1000).toInt();
    if (state == VisualFeastAnimationState.waiting) {
      if (delayed.inMilliseconds > 0) {
        _elapseInMillis += timeInTick;
      }
      timeInTick = _elapseInMillis - delayed.inMilliseconds;
      if (timeInTick < 0) {
        return;
      }
      _elapseInMillis = 0;
      state = VisualFeastAnimationState.playing;
      // Log.d("VisualFeast[$id-$name] onStart", tag: "VisualFeast");
      onStarted?.call();
    }

    _elapseInMillis = min(_elapseInMillis + timeInTick, duration.inMilliseconds);
    double percent;
    if (duration.inMilliseconds == 0) {
      percent = 1.0;
    } else {
      if (reverse && (_repeatCount & 0x01 == 1)) {
        percent = 1 - _elapseInMillis.toDouble() / duration.inMilliseconds;
      } else {
        percent = _elapseInMillis.toDouble() / duration.inMilliseconds;
      }
    }
    final value = curve.transform(percent);
    onUpdate?.call(from + (value * (to - from)), percent, _elapseInMillis);
    if (_latestAnchor < anchor && percent >= anchor) {
      onAnchor?.call(percent, _elapseInMillis);
      _latestAnchor = percent;
    }
    if (_elapseInMillis >= duration.inMilliseconds) {
      state = VisualFeastAnimationState.finallyRender;
      onWillComplete?.call();
    }
  }
}

class VisualFeastKeyFrameAnimation extends BaseVisualFeastAnimation {
  final Duration delayed;

  final List<KeyFrame> keyFrames;
  int _latestCompletedElapsedInMillis = 0;
  double _latestCompletedPercent = 0;
  final Curve curve;
  int repeat = 0;
  int _repeatCount = 0;
  bool reverse = false;
  final double from;
  final double to;
  final Object? extra;
  AnimationStartedCallback? onStarted;
  AnimationUpdateCallback? onUpdate;
  AnimationCompletedCallback? onCompleted;
  AnimationRepeatCallback? onRepeat;

  int _keyFrameIndex = 0;

  final Duration duration;

  KeyFrame? getCurrentKeyFrame() {
    if (_keyFrameIndex < keyFrames.length) {
      return keyFrames[_keyFrameIndex];
    }
    return null;
  }

  void moveNextKeyFrame() {
    // TODO: 这里还没有使用reversed时的状态
    _keyFrameIndex = _keyFrameIndex + 1;
  }

  VisualFeastKeyFrameAnimation(
      {this.from = 0.0,
      this.to = 1.0,
      required List<double> stops,
      required List<Duration> durations,
      this.curve = Curves.linear,
      this.repeat = 0,
      this.reverse = false,
      this.delayed = const Duration(seconds: 0),
      this.extra,
      this.onStarted,
      this.onUpdate,
      this.onCompleted,
      this.onRepeat,
      String name = ""})
      : assert(durations.length == stops.length),
        keyFrames = List.generate(
            stops.length,
            (index) =>
                KeyFrame(Range(index > 0 ? stops[index - 1] : 0, stops[index]), durations[index]),
            growable: false),
        duration = durations.reduce((value, element) => value + element),
        super(name: name);

  @override
  void directComplete() {
    if (state != VisualFeastAnimationState.completed) {
      if (state != VisualFeastAnimationState.playing) {
        onStarted?.call();
      }
      _complete();
    }
  }

  void _complete() {
    onUpdate?.call(to, 1.0, duration.inMilliseconds);
    state = VisualFeastAnimationState.completed;
    onCompleted?.call();
    Log.d("_complete id:$id");
  }

  @override
  void update(double t) {
    if (state == VisualFeastAnimationState.completed ||
        state == VisualFeastAnimationState.paused ||
        state == VisualFeastAnimationState.init) {
      return;
    } else if (state == VisualFeastAnimationState.finallyRender) {
      if (repeat < 0 || _repeatCount < repeat) {
        _repeatCount++;
        _latestCompletedElapsedInMillis = 0;
        state = VisualFeastAnimationState.playing;
        onRepeat?.call(_repeatCount);
      } else {
        _complete();
      }
      return;
    }

    int timeInTick = (t * 1000).toInt();
    if (state == VisualFeastAnimationState.waiting) {
      if (delayed.inMilliseconds > 0) {
        _latestCompletedElapsedInMillis += timeInTick;
      }
      timeInTick = _latestCompletedElapsedInMillis - delayed.inMilliseconds;
      if (timeInTick < 0) {
        return;
      }
      _latestCompletedElapsedInMillis = 0;
      state = VisualFeastAnimationState.playing;
      onStarted?.call();
    }
    final reversed = reverse && (_repeatCount & 0x01 == 1);
    final keyFrame = getCurrentKeyFrame();
    if (keyFrame != null) {
      keyFrame.tick(timeInTick, reversed);

      final value = curve.transform(_latestCompletedPercent + keyFrame.relativePercent);
      double percent = _latestCompletedPercent + keyFrame.relativePercent;
      int elapsedInMillis = _latestCompletedElapsedInMillis + keyFrame.elapseInMillis;
      if (keyFrame.isCompleted()) {
        _latestCompletedElapsedInMillis += keyFrame.duration.inMilliseconds;
        _latestCompletedPercent += keyFrame.stop.interval.toDouble();
        percent = _latestCompletedPercent;
        elapsedInMillis = _latestCompletedElapsedInMillis;
        moveNextKeyFrame();
      }
      onUpdate?.call(from + (value * (to - from)), percent, elapsedInMillis);
    } else {
      state = VisualFeastAnimationState.finallyRender;
    }
  }
}

class VisualFeastAnimationSet extends BaseVisualFeastAnimation {
  final List<BaseVisualFeastAnimation> animations;
  final bool sequentially;

  int current = 0;

  final AnimationStartedCallback? onStarted;
  final AnimationCompletedCallback? onCompleted;
  final AnimationNextCallback? onNext;

  VisualFeastAnimationSet(this.animations,
      {this.sequentially = false, this.onStarted, this.onCompleted, this.onNext, String name = ""})
      : super(name: name);

  VisualFeastAnimationSet.sequentially(this.animations,
      {this.onStarted, this.onCompleted, this.onNext, String name = ""})
      : sequentially = true,
        super(name: name);

  VisualFeastAnimationSet.together(this.animations,
      {this.onStarted, this.onCompleted, this.onNext, String name = ""})
      : sequentially = false,
        super(name: name);

  @override
  void start() {
    if (sequentially) {
      animations[current].start();
    } else {
      for (var animation in animations) {
        animation.start();
      }
    }
    super.start();
  }

  void _complete() {
    state = VisualFeastAnimationState.completed;
    onCompleted?.call();
    Log.d("[$id]animation set complete!");
  }

  @override
  void directComplete() {
    if (state != VisualFeastAnimationState.completed) {
      if (state != VisualFeastAnimationState.playing) {
        onStarted?.call();
      }
      for (var animation in animations) {
        animation.directComplete();
      }
      onCompleted?.call();
    }
  }

  @override
  void update(double t) {
    if (state == VisualFeastAnimationState.completed ||
        state == VisualFeastAnimationState.paused ||
        state == VisualFeastAnimationState.init) {
      return;
    }
    if (sequentially) {
      if (current >= animations.length) {
        return;
      }
      final animation = animations[current];
      animation.update(t);
      switch (animation.state) {
        case VisualFeastAnimationState.completed:
          current++;
          if (current < animations.length) {
            animations[current].start();
          } else {
            _complete();
          }
          break;
        case VisualFeastAnimationState.playing:
          if (current == 0) {
            onStarted?.call();
            state = VisualFeastAnimationState.playing;
          } else {
            onNext?.call(animation);
          }
          break;
        default:
          break;
      }
    } else {
      bool completed = true;
      for (var animation in animations) {
        animation.update(t);
        if (current == 0 && animation.isStarted) {
          onStarted?.call();
          state = VisualFeastAnimationState.playing;
          current++;
        }
        completed &= animation.isCompleted;
      }

      if (completed) {
        _complete();
        Log.d("[$id]together complete!");
      }
    }
  }
}
