/**
 * 力导向布局
 * @module echarts/layout/Force
 * @author pissang(http://github.com/pissang)
 */
module.exports = function (require) {
  var ForceLayoutWorker = require('./forceLayoutWorker');

  var vec2 = require('zrender/tool/vector');

  var requestAnimationFrame = window.requestAnimationFrame || window.msRequestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || function (func) {
    setTimeout(func, 16);
  };

  var ArrayCtor = typeof Float32Array == 'undefined' ? Array : Float32Array;
  var workerUrl; // function getToken() {
  //     return Math.round((new Date()).getTime() / 100) % 10000000;
  // }

  function createWorkerUrl() {
    if (typeof Worker !== 'undefined' && typeof Blob !== 'undefined') {
      try {
        var blob = new Blob([ForceLayoutWorker.getWorkerCode()]);
        workerUrl = window.URL.createObjectURL(blob);
      } catch (e) {
        workerUrl = '';
      }
    }

    return workerUrl;
  }

  var ForceLayout = function (opts) {
    if (typeof workerUrl === 'undefined') {
      createWorkerUrl();
    }

    opts = opts || {}; // 配置项

    this.width = opts.width || 500;
    this.height = opts.height || 500;
    this.center = opts.center || [this.width / 2, this.height / 2];
    this.ratioScaling = opts.ratioScaling || false;
    this.scaling = opts.scaling || 1;
    this.gravity = typeof opts.gravity !== 'undefined' ? opts.gravity : 1;
    this.large = opts.large || false;
    this.preventNodeOverlap = opts.preventNodeOverlap || false;
    this.preventNodeEdgeOverlap = opts.preventNodeEdgeOverlap || false;
    this.maxSpeedIncrease = opts.maxSpeedIncrease || 1;

    this.onupdate = opts.onupdate || function () {};

    this.temperature = opts.temperature || 1;
    this.coolDown = opts.coolDown || 0.99;
    this._layout = null;
    this._layoutWorker = null;
    var self = this;
    var _$onupdate = this._$onupdate;

    this._$onupdate = function (e) {
      _$onupdate.call(self, e);
    };
  };

  ForceLayout.prototype.updateConfig = function () {
    var width = this.width;
    var height = this.height;
    var size = Math.min(width, height);
    var config = {
      center: this.center,
      width: this.ratioScaling ? width : size,
      height: this.ratioScaling ? height : size,
      scaling: this.scaling || 1.0,
      gravity: this.gravity || 1.0,
      barnesHutOptimize: this.large,
      preventNodeOverlap: this.preventNodeOverlap,
      preventNodeEdgeOverlap: this.preventNodeEdgeOverlap,
      maxSpeedIncrease: this.maxSpeedIncrease
    };

    if (this._layoutWorker) {
      this._layoutWorker.postMessage({
        cmd: 'updateConfig',
        config: config
      });
    } else {
      for (var name in config) {
        this._layout[name] = config[name];
      }
    }
  };

  ForceLayout.prototype.init = function (graph, useWorker) {
    if (this._layoutWorker) {
      this._layoutWorker.terminate();

      this._layoutWorker = null;
    }

    if (workerUrl && useWorker) {
      try {
        if (!this._layoutWorker) {
          this._layoutWorker = new Worker(workerUrl);
          this._layoutWorker.onmessage = this._$onupdate;
        }

        this._layout = null;
      } catch (e) {
        // IE10-11 will throw security error when using blog url
        this._layoutWorker = null;

        if (!this._layout) {
          this._layout = new ForceLayoutWorker();
        }
      }
    } else {
      if (!this._layout) {
        this._layout = new ForceLayoutWorker();
      }
    }

    this.temperature = 1;
    this.graph = graph; // 节点数据

    var len = graph.nodes.length;
    var positionArr = new ArrayCtor(len * 2);
    var massArr = new ArrayCtor(len);
    var sizeArr = new ArrayCtor(len);

    for (var i = 0; i < len; i++) {
      var n = graph.nodes[i];
      positionArr[i * 2] = n.layout.position[0];
      positionArr[i * 2 + 1] = n.layout.position[1];
      massArr[i] = typeof n.layout.mass === 'undefined' ? 1 : n.layout.mass;
      sizeArr[i] = typeof n.layout.size === 'undefined' ? 1 : n.layout.size;
      n.layout.__index = i;
    } // 边数据


    len = graph.edges.length;
    var edgeArr = new ArrayCtor(len * 2);
    var edgeWeightArr = new ArrayCtor(len);

    for (var i = 0; i < len; i++) {
      var edge = graph.edges[i];
      edgeArr[i * 2] = edge.node1.layout.__index;
      edgeArr[i * 2 + 1] = edge.node2.layout.__index;
      edgeWeightArr[i] = edge.layout.weight || 1;
    }

    if (this._layoutWorker) {
      this._layoutWorker.postMessage({
        cmd: 'init',
        nodesPosition: positionArr,
        nodesMass: massArr,
        nodesSize: sizeArr,
        edges: edgeArr,
        edgesWeight: edgeWeightArr
      });
    } else {
      this._layout.initNodes(positionArr, massArr, sizeArr);

      this._layout.initEdges(edgeArr, edgeWeightArr);
    }

    this.updateConfig();
  };

  ForceLayout.prototype.step = function (steps) {
    var nodes = this.graph.nodes;

    if (this._layoutWorker) {
      // Sync back
      var positionArr = new ArrayCtor(nodes.length * 2);

      for (var i = 0; i < nodes.length; i++) {
        var n = nodes[i];
        positionArr[i * 2] = n.layout.position[0];
        positionArr[i * 2 + 1] = n.layout.position[1];
      }

      this._layoutWorker.postMessage(positionArr.buffer, [positionArr.buffer]);

      this._layoutWorker.postMessage({
        cmd: 'update',
        steps: steps,
        temperature: this.temperature,
        coolDown: this.coolDown
      });

      for (var i = 0; i < steps; i++) {
        this.temperature *= this.coolDown;
      }
    } else {
      requestAnimationFrame(this._$onupdate);

      for (var i = 0; i < nodes.length; i++) {
        var n = nodes[i];
        vec2.copy(this._layout.nodes[i].position, n.layout.position);
      }

      for (var i = 0; i < steps; i++) {
        this._layout.temperature = this.temperature;

        this._layout.update();

        this.temperature *= this.coolDown;
      }
    }
  };

  ForceLayout.prototype._$onupdate = function (e) {
    if (this._layoutWorker) {
      var positionArr = new Float32Array(e.data);

      for (var i = 0; i < this.graph.nodes.length; i++) {
        var n = this.graph.nodes[i];
        n.layout.position[0] = positionArr[i * 2];
        n.layout.position[1] = positionArr[i * 2 + 1];
      }

      this.onupdate && this.onupdate();
    } else if (this._layout) {
      for (var i = 0; i < this.graph.nodes.length; i++) {
        var n = this.graph.nodes[i];
        vec2.copy(n.layout.position, this._layout.nodes[i].position);
      }

      this.onupdate && this.onupdate();
    }
  };

  ForceLayout.prototype.dispose = function () {
    if (this._layoutWorker) {
      this._layoutWorker.terminate();
    }

    this._layoutWorker = null;
    this._layout = null;
  };

  return ForceLayout;
}(require);