import _ from "lodash";
/**
 * 元素页面飞动动画；
 * 如：添加购物车
 * @param element: 要起飞的 HTMLELEMENTObject
 * @param options: 配置参数
 * @description options配置说明：
 * iconFly(element) {
    const sourcePostion = {
      left: element.offsetLeft,  // 开始位置（必填）#fly元素会被设置成position: fixed
      top: element.offsetTop - window.scrollY - 30// 开始位置（必填）
    };
    const targetPostion = {
      left: document.querySelector('.header-btn-add-shopcart').offsetLeft, // 结束位置（必填）
      top: document.querySelector('.header-btn-add-shopcart').offsetTop - window.scrollY // 结束位置（必填）
    };
    const options = {
      start: {
        width: element.offsetWidth, // 开始时高度
        height: element.offsetHeight, // 开始时高度
        ...sourcePostion },
      end: {
        width: element.offsetWidth, // 结束时高度
        height: element.offsetHeight, // 结束时高度
        ...targetPostion
      },
      autoPlay: false, // 是否直接运动,默认true
      speed: 2.0, // 越大越快，默认1.2
      vertex_Rtop: 100, // 运动轨迹最高点top值，默认20
      // onEnd: () => {} // 结束回调
    };
    const IconFly = new Fly(element, options);
    IconFly.play();
  }
 */
class Fly {
  constructor(element, options) {
    this._ele = element.cloneNode(true);
    element.after(this._ele);
    // 默认值
    this.defaults = {
      version: "1.0.0",
      autoPlay: true,
      vertex_Rtop: 20, // 默认顶点高度top值
      speed: 1.2,
      start: {}, // top, left, width, height
      end: {},
      onEnd: () => this.destroy()
    };
    this.init(options);
  }

  /**
   * 初始化组件，new的时候即调用
   */
  init(options) {
    this.setOptions(options);
    !!this.settings.autoPlay && this.play();
  }

  /**
   * 设置组件参数
   */
  setOptions(options) {
    const _ele = this._ele;
    this.settings = { ...this.defaults, ...options };
    const settings = this.settings;
    let start = settings.start;
    const end = settings.end;

    _ele.style.marginTop = 0;
    _ele.style.marginLeft = 0;
    _ele.style.position = "fixed";
    document.querySelector("body").appendChild(_ele);
    // 运动过程中有改变大小
    if (end.width != null && end.height != null) {
      const _eleStart = {
        width: _ele.offsetWidth,
        height: _ele.offsetHeight
      };
      start = { ...start, ..._eleStart };
      settings.start = start;
    }
    // 运动轨迹最高点top值
    let vertexTop =
      Math.min(start.top, end.top) - Math.abs(start.left - end.left) / 3;
    if (vertexTop < settings.vertex_Rtop) {
      // 可能出现起点或者终点就是运动曲线顶点的情况
      vertexTop = Math.min(settings.vertex_Rtop, Math.min(start.top, end.top));
    }

    /**
     * ======================================================
     * 运动轨迹在页面中的top值可以抽象成函数 y = a * x*x + b;
     * a = curvature
     * b = vertexTop
     * ======================================================
     */

    const distance = Math.sqrt(
        (start.top - end.top) * (start.top - end.top) +
          (start.left - end.left) * (start.left - end.left)
      ),
      // 元素移动次数
      steps = Math.ceil(
        Math.min(Math.max(Math.log(distance) / 0.05 - 75, 30), 100) /
          settings.speed
      ),
      ratio =
        start.top == vertexTop
          ? 0
          : -Math.sqrt((end.top - vertexTop) / (start.top - vertexTop)),
      vertexLeft = (ratio * start.left - end.left) / (ratio - 1),
      // 特殊情况，出现顶点left==终点left，将曲率设置为0，做直线运动。
      curvature =
        end.left == vertexLeft
          ? 0
          : (end.top - vertexTop) /
            (end.left - vertexLeft) *
            (end.left - vertexLeft);

    const newSettings = {
      count: -1, // 每次重置为-1
      steps: steps,
      vertexLeft: vertexLeft,
      vertexTop: vertexTop,
      curvature: curvature
    };
    this.settings = { ...settings, ...newSettings };
  }

  /**
   * 开始运动，可自己调用
   */
  play() {
    this.move();
  }

  /**
   * 按step运动
   */
  move() {
    const _ele = this._ele;
    const settings = this.settings,
      start = settings.start,
      count = settings.count,
      steps = settings.steps,
      end = settings.end;
    // 计算left top值
    const left = start.left + (end.left - start.left) * count / steps,
      top =
        settings.curvature == 0
          ? start.top + (end.top - start.top) * count / steps
          : settings.curvature *
              (left - settings.vertexLeft) *
              (left - settings.vertexLeft) +
            settings.vertexTop;
    // 运动过程中有改变大小
    if (end.width != null && end.height != null) {
      const i = steps / 2,
        width =
          end.width -
          (end.width - start.width) *
            Math.cos(count < i ? 0 : (count - i) / (steps - i) * Math.PI / 2),
        height =
          end.height -
          (end.height - start.height) *
            Math.cos(count < i ? 0 : (count - i) / (steps - i) * Math.PI / 2);
      _ele.style.width = `${width}px`;
      _ele.style.height = `${height}px`;
      _ele.style.fontSize = `${Math.min(width, height)}px`;
    }

    _ele.style.left = `${left}px`;
    _ele.style.top = `${top}px`;
    settings.count++;
    // 定时任务
    const time = window.requestAnimationFrame(this.move.bind(this));
    // const time = window.requestAnimationFrame($.proxy(this.move, this));
    if (count == steps) {
      window.cancelAnimationFrame(time);
      // fire callback
      settings.onEnd.apply(this);
    }
  }

  /**
   * 销毁
   */
  destroy() {
    this._ele.remove();
  }
}

export default Fly;
