import { isEqual, isNumber, isFunction } from '@antv/util';
import * as d3Timer from 'd3-timer';
import * as d3Ease from 'd3-ease';
import { interpolate, interpolateArray } from 'd3-interpolate'; // 目前整体动画只需要数值和数组的差值计算
import * as PathUtil from '../util/path';
import { isColorProp, isGradientColor } from '../util/color';
import { ICanvas, IElement } from '../interfaces';
import { Animation } from '../types';

const IDENTITY_MATRIX = [1, 0, 0, 0, 1, 0, 0, 0, 1];

/**
 * 使用 ratio 进行插值计算来更新属性
 * @param {IElement}  shape    元素
 * @param {Animation} animation 动画
 * @param {number}    ratio    比例
 * @return {boolean}  动画是否执行完成
 */
function _update(shape: IElement, animation: Animation, ratio: number) {
  const cProps = {}; // 此刻属性
  const { fromAttrs, toAttrs } = animation;
  if (shape.destroyed) {
    return;
  }
  let interf; //  差值函数
  for (const k in toAttrs) {
    if (!isEqual(fromAttrs[k], toAttrs[k])) {
      if (k === 'path') {
        let toPath = toAttrs[k];
        let fromPath = fromAttrs[k];
        if (toPath.length > fromPath.length) {
          toPath = PathUtil.parsePathString(toAttrs[k]); // 终点状态
          fromPath = PathUtil.parsePathString(fromAttrs[k]); // 起始状态
          fromPath = PathUtil.fillPathByDiff(fromPath, toPath);
          fromPath = PathUtil.formatPath(fromPath, toPath);
          animation.fromAttrs.path = fromPath;
          animation.toAttrs.path = toPath;
        } else if (!animation.pathFormatted) {
          toPath = PathUtil.parsePathString(toAttrs[k]);
          fromPath = PathUtil.parsePathString(fromAttrs[k]);
          fromPath = PathUtil.formatPath(fromPath, toPath);
          animation.fromAttrs.path = fromPath;
          animation.toAttrs.path = toPath;
          animation.pathFormatted = true;
        }
        cProps[k] = [];
        for (let i = 0; i < toPath.length; i++) {
          const toPathPoint = toPath[i];
          const fromPathPoint = fromPath[i];
          const cPathPoint = [];
          for (let j = 0; j < toPathPoint.length; j++) {
            if (isNumber(toPathPoint[j]) && fromPathPoint && isNumber(fromPathPoint[j])) {
              interf = interpolate(fromPathPoint[j], toPathPoint[j]);
              cPathPoint.push(interf(ratio));
            } else {
              cPathPoint.push(toPathPoint[j]);
            }
          }
          cProps[k].push(cPathPoint);
        }
      } else if (k === 'matrix') {
        /* 
         对矩阵进行插值时，需要保证矩阵不为空，为空则使用单位矩阵
         TODO: 二维和三维场景下单位矩阵不同，之后 WebGL 版需要做进一步处理
         */
        const matrixFn = interpolateArray(fromAttrs[k] || IDENTITY_MATRIX, toAttrs[k] || IDENTITY_MATRIX);
        const currentMatrix = matrixFn(ratio);
        cProps[k] = currentMatrix;
      } else if (isColorProp(k) && isGradientColor(toAttrs[k])) {
        cProps[k] = toAttrs[k];
      } else if (!isFunction(toAttrs[k])) {
        // 非函数类型的值才能做插值
        interf = interpolate(fromAttrs[k], toAttrs[k]);
        cProps[k] = interf(ratio);
      }
    }
  }
  shape.attr(cProps);
}

/**
 * 根据自定义帧动画函数 onFrame 来更新属性
 * @param {IElement}  shape    元素
 * @param {Animation} animation 动画
 * @param {number}    elapsed  动画执行时间(毫秒)
 * @return {boolean}  动画是否执行完成
 */
function update(shape: IElement, animation: Animation, elapsed: number) {
  const { startTime, delay } = animation;
  // 如果还没有开始执行或暂停，先不更新
  if (elapsed < startTime + delay || animation._paused) {
    return false;
  }
  let ratio;
  const duration = animation.duration;
  const easing = animation.easing;
  // 已执行时间
  elapsed = elapsed - startTime - animation.delay;
  if (animation.repeat) {
    // 如果动画重复执行，则 elapsed > duration，计算 ratio 时需取模
    ratio = (elapsed % duration) / duration;
    ratio = d3Ease[easing](ratio);
  } else {
    ratio = elapsed / duration;
    if (ratio < 1) {
      // 动画未执行完
      ratio = d3Ease[easing](ratio);
    } else {
      // 动画已执行完
      if (animation.onFrame) {
        shape.attr(animation.onFrame(1));
      } else {
        shape.attr(animation.toAttrs);
      }
      return true;
    }
  }
  if (animation.onFrame) {
    const attrs = animation.onFrame(ratio);
    shape.attr(attrs);
  } else {
    _update(shape, animation, ratio);
  }
  return false;
}

class Timeline {
  /**
   * 画布
   * @type {ICanvas}
   */
  canvas: ICanvas;
  /**
   * 执行动画的元素列表
   * @type {IElement[]}
   */
  animators: IElement[] = [];
  /**
   * 当前时间
   * @type {number}
   */
  current: number = 0;
  /**
   * 定时器
   * @type {d3Timer.Timer}
   */
  timer: d3Timer.Timer = null;

  /**
   * 时间轴构造函数，依赖于画布
   * @param {}
   */
  constructor(canvas: ICanvas) {
    this.canvas = canvas;
  }

  /**
   * 初始化定时器
   */
  initTimer() {
    let isFinished = false;
    let shape: IElement;
    let animations: Animation[];
    let animation: Animation;
    this.timer = d3Timer.timer((elapsed) => {
      this.current = elapsed;
      if (this.animators.length > 0) {
        for (let i = this.animators.length - 1; i >= 0; i--) {
          shape = this.animators[i];
          if (shape.destroyed) {
            // 如果已经被销毁，直接移出队列
            this.removeAnimator(i);
            continue;
          }
          if (!shape.isAnimatePaused()) {
            animations = shape.get('animations');
            for (let j = animations.length - 1; j >= 0; j--) {
              animation = animations[j];
              isFinished = update(shape, animation, elapsed);
              if (isFinished) {
                animations.splice(j, 1);
                isFinished = false;
                if (animation.callback) {
                  animation.callback();
                }
              }
            }
          }
          if (animations.length === 0) {
            this.removeAnimator(i);
          }
        }
        const autoDraw = this.canvas.get('autoDraw');
        // 非自动渲染模式下，手动调用 canvas.draw() 重新渲染
        if (!autoDraw) {
          this.canvas.draw();
        }
      }
    });
  }

  /**
   * 增加动画元素
   */
  addAnimator(shape) {
    this.animators.push(shape);
  }

  /**
   * 移除动画元素
   */
  removeAnimator(index) {
    this.animators.splice(index, 1);
  }

  /**
   * 是否有动画在执行
   */
  isAnimating() {
    return !!this.animators.length;
  }

  /**
   * 停止定时器
   */
  stop() {
    if (this.timer) {
      this.timer.stop();
    }
  }

  /**
   * 停止时间轴上所有元素的动画，并置空动画元素列表
   * @param {boolean} toEnd 是否到动画的最终状态，用来透传给动画元素的 stopAnimate 方法
   */
  stopAllAnimations(toEnd = true) {
    this.animators.forEach((animator) => {
      animator.stopAnimate(toEnd);
    });
    this.animators = [];
    this.canvas.draw();
  }

  /**
   * 获取当前时间
   */
  getTime() {
    return this.current;
  }
}

export default Timeline;
