function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

import Vector from './Vector';
import Point from './Point';
import { Node, Edge } from './Elements';
import Spring from './Spring';
import { getDegree } from '../utils/graph';

var ForceLayout = function ForceLayout(options) {
  var _this = this;

  _classCallCheck(this, ForceLayout);

  /**
   * Iterate options to update this.props
   * @param {*} options
   */
  this.updateOptions = function (options) {
    if (!options) {
      return;
    }

    Object.keys(options).forEach(function (key) {
      _this.props[key] = options[key];
    });
  };

  this.setData = function (data) {
    // clean all data
    _this.nodes = [];
    _this.edges = [];
    _this.nodeSet = {};
    _this.edgeSet = {};
    _this.nodePoints = new Map();
    _this.edgeSprings = new Map();
    _this.sourceData = data;
    _this.averageDistance = 0; // add nodes and edges

    if ('nodes' in data || 'edges' in data) {
      _this.addNodes(data.nodes); // eslint-disable-next-line


      _this.addEdges(data.edges);
    }
  };

  this.getMass = function (node) {
    var _ref = node.layout || {},
        _ref$degree = _ref.degree,
        degree = _ref$degree === void 0 ? getDegree(node, _this.edges) : _ref$degree,
        force = _ref.force;
    /** 当你在layout.force.mass中制定了才使用 */


    if (force && force.mass) {
      // eslint-disable-next-line prefer-destructuring
      return force.mass;
    }
    /** 默认质量都是通过节点的度数自动计算的 */


    return degree < 5 ? 1 : degree * 10;
  };

  this.init = function () {
    /** 初始化点和边的信息 */
    var _this$props = _this.props,
        width = _this$props.width,
        height = _this$props.height;

    _this.nodes.forEach(function (node) {
      var x = node.data.x || width / 2;
      var y = node.data.y || height / 2;
      var vec = new Vector(x, y);

      if (!node.data.layout) {
        node.data.layout = {};
      }

      var degree = getDegree(node, _this.edges);
      node.data.layout.degree = degree;

      var mass = _this.getMass(node.data);

      _this.nodePoints.set(node.id, new Point(vec, String(node.id), node.data, mass));
    });

    _this.edges.forEach(function (edge) {
      var source = _this.nodePoints.get(edge.source.id);

      var target = _this.nodePoints.get(edge.target.id);

      var length = _this.props.defSpringLen(edge, source, target);

      _this.edgeSprings.set(edge.id, new Spring(source, target, length));
    });
    /** 其他参数设置 */


    var size = _this.nodePoints.size;
    var vv = Math.pow(10, 2);
    _this.props.minEnergyThreshold = 1 / 2 * Math.pow(1, 2) * 1 * size * vv;
  };

  this.start = function () {
    /** 初始化节点 */
    _this.init();

    if (_this.props.animation) _this.animation();else _this.slienceForce();
  };

  this.calTotalEnergy = function () {
    var energy = 0.0;

    _this.nodes.forEach(function (node) {
      var point = _this.nodePoints.get(node.id);

      var speed = point.v.magnitude();
      var m = point.m; // 1;

      energy += m * Math.pow(speed, 2) * 0.5; // p = 1/2*(mv^2)
    });

    return energy;
  };

  this.slienceForce = function () {
    var done = _this.props.done;

    for (var i = 0; _this.averageDistance > 0.5 || i < 1; i++) {
      _this.tick(_this.props.tickInterval);

      _this.iterations++;
    }

    _this.render();

    done && done(); // eslint-disable-line
  };
  /** polyfill: support webworker requestAnimationFrame */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any


  this.requestAnimationFrame = function (fn) {
    var enableWorker = _this.props.enableWorker;

    if (enableWorker) {
      return setInterval(function () {
        fn();
      }, 16);
    }

    return window.requestAnimationFrame(fn);
  };

  this.cancelAnimationFrame = function (handleId) {
    var enableWorker = _this.props.enableWorker;

    if (enableWorker) {
      clearInterval(handleId);
    } else {
      window.cancelAnimationFrame(handleId);
    }
  };

  this.animation = function () {
    if (_this.props.enableWorker) {
      var startTimer = new Date().valueOf();
      var firstTickInterval = 0.22;

      var interval = function interval(step) {
        // 目标：迭代10次，稳定在2s，函数选择需要后续考虑
        return step > 10 ? 2000 : 20 * step * step;
      };

      for (var i = 0; i < _this.props.MaxIterations; i++) {
        var tickInterval = Math.max(0.02, firstTickInterval - i * 0.002);

        _this.tick(tickInterval);

        var diff = new Date().valueOf() - startTimer;

        if (diff >= interval(i)) {
          _this.render();

          startTimer = new Date().valueOf();
        }

        var energy = _this.calTotalEnergy();
        /** 如果需要监控信息，则提供给用户 */


        var monitor = _this.registers.get('monitor');

        if (monitor) {
          monitor(_this.reportMointor(energy));
        } // console.log('average', this.averageDistance);


        if (_this.averageDistance < 0.5) {
          _this.render();

          if (_this.props.done) {
            _this.props.done();
          }

          return;
        }
      }

      return;
    }

    var step = function step() {
      var _this$props2 = _this.props,
          done = _this$props2.done,
          tickInterval = _this$props2.tickInterval;

      _this.tick(tickInterval);

      _this.render();

      _this.iterations++;

      var energy = _this.calTotalEnergy();
      /** 如果需要监控信息，则提供给用户 */


      var monitor = _this.registers.get('monitor');

      if (monitor) {
        monitor(_this.reportMointor(energy));
      } // console.log('average', this.averageDistance);


      if (_this.averageDistance < 0.5) {
        _this.cancelAnimationFrame(_this.timer);

        _this.iterations = 0;
        _this.done = true;

        if (done) {
          done();
        }
      } else {
        _this.timer = _this.requestAnimationFrame(step);
      }
    };

    _this.timer = _this.requestAnimationFrame(step);
  };

  this.render = function () {
    var render = _this.registers.get('render');

    var nodes = [];

    _this.nodePoints.forEach(function (node) {
      nodes.push(Object.assign(Object.assign({}, _this.nodeSet[node.id] && _this.nodeSet[node.id].data), {
        x: node.p.x,
        y: node.p.y
      }));
    });

    if (render) {
      render({
        nodes: nodes,
        edges: _this.sourceData.edges
      });
    } else {
      // eslint-disable-next-line no-console
      console.error('need a render function');
    }
  };

  this.reportMointor = function (energy) {
    var params = {
      energy: energy,
      iterations: _this.iterations,
      nodes: _this.nodes,
      edges: _this.edges
    };
    return params;
  };

  this.tick = function (interval) {
    _this.updateCoulombsLaw();

    _this.updateHookesLaw();

    _this.attractToCentre();

    _this.updateVelocity(interval);

    _this.updatePosition(interval);
  };
  /** 布局算法 */


  this.updateCoulombsLaw = function () {
    var len = _this.nodes.length;

    for (var i = 0; i < len; i++) {
      for (var j = i + 1; j < len; j++) {
        // eslint-disable-next-line no-continue
        if (i === j) continue;
        var iNode = _this.nodes[i];
        var jNode = _this.nodes[j];
        var coulombDisScale = _this.props.coulombDisScale;
        var repulsion = _this.props.repulsion;

        var v = _this.nodePoints.get(iNode.id).p.subtract(_this.nodePoints.get(jNode.id).p);

        var dis = (v.magnitude() + 0.1) * coulombDisScale;
        var direction = v.normalise(); // 向量的方向：基向量

        var factor = 1;

        _this.nodePoints.get(iNode.id).updateAcc( // 得到库伦力
        direction.scalarMultip(repulsion * factor).divide(Math.pow(dis, 2)));

        _this.nodePoints.get(jNode.id).updateAcc(direction.scalarMultip(repulsion * factor).divide(-Math.pow(dis, 2)));
      }
    }
  };

  this.updateHookesLaw = function () {
    _this.edges.forEach(function (edge) {
      var spring = _this.edgeSprings.get(edge.id);

      var v = spring.target.p.subtract(spring.source.p);
      var displacement = spring.length - v.magnitude();
      var direction = v.normalise();
      spring.source.updateAcc(direction.scalarMultip(-_this.props.stiffness * displacement));
      spring.target.updateAcc(direction.scalarMultip(_this.props.stiffness * displacement));
    });
  };

  this.attractToCentre = function () {
    var implementForce = function implementForce(node, center) {
      var radio = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 2;

      var point = _this.nodePoints.get(node.id);

      var direction = point.p.subtract(center);
      point.updateAcc(direction.scalarMultip(-radio));
    };

    _this.nodes.forEach(function (node) {
      // 默认的向心力指向画布中心
      var degree = node.data && node.data.layout && node.data.layout.degree;
      var leafNode = degree === 1;
      var singleNode = degree === 0;
      /** 默认的向心力配置 */

      var defaultRadio = {
        leaf: 2,
        single: 2,
        others: 1,
        // eslint-disable-next-line
        center: function center(_node) {
          return {
            x: _this.props.width / 2,
            y: _this.props.height / 2
          };
        }
      };

      var _Object$assign = Object.assign(Object.assign({}, defaultRadio), _this.props.centripetalOptions),
          leaf = _Object$assign.leaf,
          single = _Object$assign.single,
          others = _Object$assign.others,
          center = _Object$assign.center;

      var _center = center(node),
          x = _center.x,
          y = _center.y;

      var centerVector = new Vector(x, y);
      /** 如果radio为0，则认为忽略向心力 */

      if (leaf === 0 || single === 0 || others === 0) {
        return;
      }

      if (singleNode) {
        implementForce(node, centerVector, single);
        return;
      }

      if (leafNode) {
        implementForce(node, centerVector, leaf);
        return;
      }
      /** others */


      implementForce(node, centerVector, others);
    });
  };

  this.updateVelocity = function (interval) {
    _this.nodes.forEach(function (node) {
      var point = _this.nodePoints.get(node.id);

      point.v = point.v.add(point.a.scalarMultip(interval)) // 根据加速度求速度公式 V_curr= a*@t + V_pre
      .scalarMultip(_this.props.damping);

      if (point.v.magnitude() > _this.props.maxSpeed) {
        point.v = point.v.normalise().scalarMultip(_this.props.maxSpeed);
      }

      point.a = new Vector(0, 0);
    });
  };

  this.updatePosition = function (interval) {
    var sum = 0;

    _this.nodes.forEach(function (node) {
      var point = _this.nodePoints.get(node.id);

      var distance = point.v.scalarMultip(interval);
      sum = sum + distance.magnitude();
      point.p = point.p.add(point.v.scalarMultip(interval)); // 路程公式 s = v * t
    });

    _this.averageDistance = sum / _this.nodes.length;
  };
  /**
   * add one Node
   * @param {[type]} node [description]
   */


  this.addNode = function (node) {
    var ignore = _this.props.ignore;

    if (ignore && ignore(node)) {
      return;
    }

    if (!(node.id in _this.nodeSet)) {
      _this.nodes.push(node);
    }

    _this.nodeSet[node.id] = node;
  };
  /**
   * add Nodes
   * @param {[type]} data [description]
   */


  this.addNodes = function (data) {
    data.forEach(function (node) {
      _this.addNode(new Node(node));
    });
  };
  /**
   * add one Edge
   * @param {[type]} edge [description]
   */


  this.addEdge = function (edge) {
    if (!(edge.id in _this.edgeSet)) {
      _this.edges.push(edge);
    }

    _this.edgeSet[edge.id] = edge;
    return edge;
  };
  /**
   * add Edges
   * @param {[type]} data [description]
   */


  this.addEdges = function (data) {
    try {
      var len = data.length;

      for (var i = 0; i < len; i++) {
        var e = data[i];
        var sourceId = e.source;
        var targetId = e.target; // eslint-disable-next-line

        var node1 = _this.nodeSet[sourceId];

        if (node1 === undefined) {
          throw new TypeError("invalid node name: ".concat(e.source));
        } // eslint-disable-next-line


        var node2 = _this.nodeSet[targetId];

        if (node2 === undefined) {
          throw new TypeError("invalid node name: ".concat(e.target));
        }

        var attr = e.data;
        var edge = new Edge(String(_this.nextEdgeId++), node1, node2, attr);

        _this.addEdge(edge);
      }
    } catch (error) {
      console.error(error);
    }
  }; // eslint-disable-next-line @typescript-eslint/no-explicit-any


  this.register = function (type, options) {
    _this.registers.set(type, options); // 将用户的自定义函数注册进来

  };

  this.restart = function (dragNode, graph) {
    /** 将位置更新到nodePoint中 */
    var ignore = _this.props.ignore;
    graph.getNodes().forEach(function (nodeItem) {
      var node = nodeItem.get('model');

      if (ignore && ignore(node)) {
        return;
      }

      var vec = new Vector(node.x, node.y);

      var point = _this.nodePoints.get(node.id);

      if (point) {
        point.p = vec;

        _this.nodePoints.set(node.id, point);
      }
    });

    var changeNodePosition = function changeNodePosition(node) {
      var vec = new Vector(node.x, node.y); // const mass = (node.layout && node.layout.force && node.layout.force.mass) || 100000;

      var mass = _this.getMass(node);

      _this.nodePoints.set(node.id, new Point(vec, node.id, node.data, mass));

      _this.edges.forEach(function (edge) {
        var source = _this.nodePoints.get(edge.source.id);

        var target = _this.nodePoints.get(edge.target.id);

        if (source.id === node.id || target.id === node.id) {
          var length = _this.edgeSprings.get(edge.id).length || 100;

          _this.edgeSprings.set(edge.id, new Spring(source, target, length));
        }
      });
    }; // TODO:支持多点拖拽


    dragNode.forEach(changeNodePosition);
    if (_this.props.restartAnimation) _this.animation();else _this.slienceForce();
  };

  this.stop = function () {
    window.cancelAnimationFrame(_this.timer);
    _this.done = true;
  };

  this.props = {
    stiffness: 200.0,
    enableWorker: false,
    defSpringLen: function defSpringLen(edge) {
      return edge.data.spring || 200;
    },
    repulsion: 200.0 * 5,
    centripetalOptions: {
      leaf: 2,
      single: 2
    },
    damping: 0.9,
    minEnergyThreshold: 0.1,
    maxSpeed: 1000,
    coulombDisScale: 0.005,
    tickInterval: 0.02,
    groupFactor: 4,
    MaxIterations: 10000,
    animation: true,
    restartAnimation: true,
    width: 200,
    height: 200
  };
  this.updateOptions(options);
  /** 存放器：节点与边 */

  this.sourceData = {
    nodes: [],
    edges: [],
    combos: []
  };
  this.nodes = [];
  this.edges = [];
  this.nodeSet = {};
  this.edgeSet = {};
  this.nodePoints = new Map();
  this.edgeSprings = new Map();
  this.registers = new Map();
  this.done = false;
  this.averageDistance = 0;
  /** 计数器 */

  this.iterations = 0;
  this.nextEdgeId = 0; // 边属性计数自增

  this.timer = 0;
  this.center = new Vector(0, 0);
};

export default ForceLayout;