'use strict';

var isGraph = require('graphology-utils/is-graph');
var colors = require('./colors-ee2e2828.cjs.prod.js');

function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }

var isGraph__default = /*#__PURE__*/_interopDefault(isGraph);

var linear = function linear(k) {
  return k;
};
var quadraticIn = function quadraticIn(k) {
  return k * k;
};
var quadraticOut = function quadraticOut(k) {
  return k * (2 - k);
};
var quadraticInOut = function quadraticInOut(k) {
  if ((k *= 2) < 1) return 0.5 * k * k;
  return -0.5 * (--k * (k - 2) - 1);
};
var cubicIn = function cubicIn(k) {
  return k * k * k;
};
var cubicOut = function cubicOut(k) {
  return --k * k * k + 1;
};
var cubicInOut = function cubicInOut(k) {
  if ((k *= 2) < 1) return 0.5 * k * k * k;
  return 0.5 * ((k -= 2) * k * k + 2);
};
var easings = {
  linear: linear,
  quadraticIn: quadraticIn,
  quadraticOut: quadraticOut,
  quadraticInOut: quadraticInOut,
  cubicIn: cubicIn,
  cubicOut: cubicOut,
  cubicInOut: cubicInOut
};

/**
 * Defaults.
 */

var ANIMATE_DEFAULTS = {
  easing: "quadraticInOut",
  duration: 150
};

/**
 * Function used to animate the nodes.
 */
function animateNodes(graph, targets, opts, callback) {
  var options = Object.assign({}, ANIMATE_DEFAULTS, opts);
  var easing = typeof options.easing === "function" ? options.easing : easings[options.easing];
  var start = Date.now();
  var startPositions = {};
  for (var node in targets) {
    var attrs = targets[node];
    startPositions[node] = {};
    for (var _k in attrs) startPositions[node][_k] = graph.getNodeAttribute(node, _k);
  }
  var frame = null;
  var _step = function step() {
    frame = null;
    var p = (Date.now() - start) / options.duration;
    if (p >= 1) {
      // Animation is done
      for (var _node in targets) {
        var _attrs = targets[_node];

        // We use given values to avoid precision issues and for convenience
        for (var _k2 in _attrs) graph.setNodeAttribute(_node, _k2, _attrs[_k2]);
      }
      if (typeof callback === "function") callback();
      return;
    }
    p = easing(p);
    for (var _node2 in targets) {
      var _attrs2 = targets[_node2];
      var s = startPositions[_node2];
      for (var _k3 in _attrs2) graph.setNodeAttribute(_node2, _k3, _attrs2[_k3] * p + s[_k3] * (1 - p));
    }
    frame = requestAnimationFrame(_step);
  };
  _step();
  return function () {
    if (frame) cancelAnimationFrame(frame);
  };
}

function identity() {
  return Float32Array.of(1, 0, 0, 0, 1, 0, 0, 0, 1);
}

// TODO: optimize
function scale(m, x, y) {
  m[0] = x;
  m[4] = typeof y === "number" ? y : x;
  return m;
}
function rotate(m, r) {
  var s = Math.sin(r),
    c = Math.cos(r);
  m[0] = c;
  m[1] = s;
  m[3] = -s;
  m[4] = c;
  return m;
}
function translate(m, x, y) {
  m[6] = x;
  m[7] = y;
  return m;
}
function multiply(a, b) {
  var a00 = a[0],
    a01 = a[1],
    a02 = a[2];
  var a10 = a[3],
    a11 = a[4],
    a12 = a[5];
  var a20 = a[6],
    a21 = a[7],
    a22 = a[8];
  var b00 = b[0],
    b01 = b[1],
    b02 = b[2];
  var b10 = b[3],
    b11 = b[4],
    b12 = b[5];
  var b20 = b[6],
    b21 = b[7],
    b22 = b[8];
  a[0] = b00 * a00 + b01 * a10 + b02 * a20;
  a[1] = b00 * a01 + b01 * a11 + b02 * a21;
  a[2] = b00 * a02 + b01 * a12 + b02 * a22;
  a[3] = b10 * a00 + b11 * a10 + b12 * a20;
  a[4] = b10 * a01 + b11 * a11 + b12 * a21;
  a[5] = b10 * a02 + b11 * a12 + b12 * a22;
  a[6] = b20 * a00 + b21 * a10 + b22 * a20;
  a[7] = b20 * a01 + b21 * a11 + b22 * a21;
  a[8] = b20 * a02 + b21 * a12 + b22 * a22;
  return a;
}
function multiplyVec2(a, b) {
  var z = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
  var a00 = a[0];
  var a01 = a[1];
  var a10 = a[3];
  var a11 = a[4];
  var a20 = a[6];
  var a21 = a[7];
  var b0 = b.x;
  var b1 = b.y;
  return {
    x: b0 * a00 + b1 * a10 + a20 * z,
    y: b0 * a01 + b1 * a11 + a21 * z
  };
}

/**
 * In sigma, the graph is normalized into a [0, 1], [0, 1] square, before being given to the various renderers. This
 * helps to deal with quadtree in particular.
 * But at some point, we need to rescale it so that it takes the best place in the screen, i.e. we always want to see two
 * nodes "touching" opposite sides of the graph, with the camera being at its default state.
 *
 * This function determines this ratio.
 */
function getCorrectionRatio(viewportDimensions, graphDimensions) {
  var viewportRatio = viewportDimensions.height / viewportDimensions.width;
  var graphRatio = graphDimensions.height / graphDimensions.width;

  // If the stage and the graphs are in different directions (such as the graph being wider that tall while the stage
  // is taller than wide), we can stop here to have indeed nodes touching opposite sides:
  if (viewportRatio < 1 && graphRatio > 1 || viewportRatio > 1 && graphRatio < 1) {
    return 1;
  }

  // Else, we need to fit the graph inside the stage:
  // 1. If the graph is "squarer" (i.e. with a ratio closer to 1), we need to make the largest sides touch;
  // 2. If the stage is "squarer", we need to make the smallest sides touch.
  return Math.min(Math.max(graphRatio, 1 / graphRatio), Math.max(1 / viewportRatio, viewportRatio));
}

/**
 * Function returning a matrix from the current state of the camera.
 */
function matrixFromCamera(state, viewportDimensions, graphDimensions, padding, inverse) {
  // TODO: it's possible to optimize this drastically!
  var angle = state.angle,
    ratio = state.ratio,
    x = state.x,
    y = state.y;
  var width = viewportDimensions.width,
    height = viewportDimensions.height;
  var matrix = identity();
  var smallestDimension = Math.min(width, height) - 2 * padding;
  var correctionRatio = getCorrectionRatio(viewportDimensions, graphDimensions);
  if (!inverse) {
    multiply(matrix, scale(identity(), 2 * (smallestDimension / width) * correctionRatio, 2 * (smallestDimension / height) * correctionRatio));
    multiply(matrix, rotate(identity(), -angle));
    multiply(matrix, scale(identity(), 1 / ratio));
    multiply(matrix, translate(identity(), -x, -y));
  } else {
    multiply(matrix, translate(identity(), x, y));
    multiply(matrix, scale(identity(), ratio));
    multiply(matrix, rotate(identity(), angle));
    multiply(matrix, scale(identity(), width / smallestDimension / 2 / correctionRatio, height / smallestDimension / 2 / correctionRatio));
  }
  return matrix;
}

/**
 * All these transformations we apply on the matrix to get it rescale the graph
 * as we want make it very hard to get pixel-perfect distances in WebGL. This
 * function returns a factor that properly cancels the matrix effect on lengths.
 *
 * [jacomyal]
 * To be fully honest, I can't really explain happens here... I notice that the
 * following ratio works (i.e. it correctly compensates the matrix impact on all
 * camera states I could try):
 * > `R = size(V) / size(M * V) / W`
 * as long as `M * V` is in the direction of W (ie. parallel to (Ox)). It works
 * as well with H and a vector that transforms into something parallel to (Oy).
 *
 * Also, note that we use `angle` and not `-angle` (that would seem logical,
 * since we want to anticipate the rotation), because the image is vertically
 * swapped in WebGL.
 */
function getMatrixImpact(matrix, cameraState, viewportDimensions) {
  var _multiplyVec = multiplyVec2(matrix, {
      x: Math.cos(cameraState.angle),
      y: Math.sin(cameraState.angle)
    }, 0),
    x = _multiplyVec.x,
    y = _multiplyVec.y;
  return 1 / Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) / viewportDimensions.width;
}

/**
 * Function returning the graph's node extent in x & y.
 */
function graphExtent(graph) {
  if (!graph.order) return {
    x: [0, 1],
    y: [0, 1]
  };
  var xMin = Infinity;
  var xMax = -Infinity;
  var yMin = Infinity;
  var yMax = -Infinity;
  graph.forEachNode(function (_, attr) {
    var x = attr.x,
      y = attr.y;
    if (x < xMin) xMin = x;
    if (x > xMax) xMax = x;
    if (y < yMin) yMin = y;
    if (y > yMax) yMax = y;
  });
  return {
    x: [xMin, xMax],
    y: [yMin, yMax]
  };
}

/**
 * Check if the graph variable is a valid graph, and if sigma can render it.
 */
function validateGraph(graph) {
  // check if it's a valid graphology instance
  if (!isGraph__default["default"](graph)) throw new Error("Sigma: invalid graph instance.");

  // check if nodes have x/y attributes
  graph.forEachNode(function (key, attributes) {
    if (!Number.isFinite(attributes.x) || !Number.isFinite(attributes.y)) {
      throw new Error("Sigma: Coordinates of node ".concat(key, " are invalid. A node must have a numeric 'x' and 'y' attribute."));
    }
  });
}

/**
 * Function used to create DOM elements easily.
 */
function createElement(tag, style, attributes) {
  var element = document.createElement(tag);
  if (style) {
    for (var k in style) {
      element.style[k] = style[k];
    }
  }
  if (attributes) {
    for (var _k in attributes) {
      element.setAttribute(_k, attributes[_k]);
    }
  }
  return element;
}

/**
 * Function returning the browser's pixel ratio.
 */
function getPixelRatio() {
  if (typeof window.devicePixelRatio !== "undefined") return window.devicePixelRatio;
  return 1;
}

/**
 * Function ordering the given elements in reverse z-order so they drawn
 * the correct way.
 */
function zIndexOrdering(_extent, getter, elements) {
  // If k is > n, we'll use a standard sort
  return elements.sort(function (a, b) {
    var zA = getter(a) || 0,
      zB = getter(b) || 0;
    if (zA < zB) return -1;
    if (zA > zB) return 1;
    return 0;
  });

  // TODO: counting sort optimization
}

/**
 * Factory returning a function normalizing the given node's position & size.
 */

function createNormalizationFunction(extent) {
  var _extent$x = colors._slicedToArray(extent.x, 2),
    minX = _extent$x[0],
    maxX = _extent$x[1],
    _extent$y = colors._slicedToArray(extent.y, 2),
    minY = _extent$y[0],
    maxY = _extent$y[1];
  var ratio = Math.max(maxX - minX, maxY - minY),
    dX = (maxX + minX) / 2,
    dY = (maxY + minY) / 2;
  if (ratio === 0 || Math.abs(ratio) === Infinity || isNaN(ratio)) ratio = 1;
  if (isNaN(dX)) dX = 0;
  if (isNaN(dY)) dY = 0;
  var fn = function fn(data) {
    return {
      x: 0.5 + (data.x - dX) / ratio,
      y: 0.5 + (data.y - dY) / ratio
    };
  };

  // TODO: possibility to apply this in batch over array of indices
  fn.applyTo = function (data) {
    data.x = 0.5 + (data.x - dX) / ratio;
    data.y = 0.5 + (data.y - dY) / ratio;
  };
  fn.inverse = function (data) {
    return {
      x: dX + ratio * (data.x - 0.5),
      y: dY + ratio * (data.y - 0.5)
    };
  };
  fn.ratio = ratio;
  return fn;
}

exports.ANIMATE_DEFAULTS = ANIMATE_DEFAULTS;
exports.animateNodes = animateNodes;
exports.createElement = createElement;
exports.createNormalizationFunction = createNormalizationFunction;
exports.cubicIn = cubicIn;
exports.cubicInOut = cubicInOut;
exports.cubicOut = cubicOut;
exports.easings = easings;
exports.getCorrectionRatio = getCorrectionRatio;
exports.getMatrixImpact = getMatrixImpact;
exports.getPixelRatio = getPixelRatio;
exports.graphExtent = graphExtent;
exports.identity = identity;
exports.linear = linear;
exports.matrixFromCamera = matrixFromCamera;
exports.multiply = multiply;
exports.multiplyVec2 = multiplyVec2;
exports.quadraticIn = quadraticIn;
exports.quadraticInOut = quadraticInOut;
exports.quadraticOut = quadraticOut;
exports.rotate = rotate;
exports.scale = scale;
exports.translate = translate;
exports.validateGraph = validateGraph;
exports.zIndexOrdering = zIndexOrdering;
