;(function(undefined) {
  'use strict';

  if (typeof sigma === 'undefined')
    throw 'sigma is not declared';

  // Initialize packages:
  sigma.utils.pkg('sigma.middlewares');
  sigma.utils.pkg('sigma.utils');

  /**
   * This middleware will rescale the graph such that it takes an optimal space
   * on the renderer.
   *
   * As each middleware, this function is executed in the scope of the sigma
   * instance.
   *
   * @param {?string} readPrefix  The read prefix.
   * @param {?string} writePrefix The write prefix.
   * @param {object}  options     The parameters.
   */
  sigma.middlewares.rescale = function(readPrefix, writePrefix, options) {
    var i,
        l,
        a,
        b,
        c,
        d,
        scale,
        margin,
        n = this.graph.nodes(),
        e = this.graph.edges(),
        settings = this.settings.embedObjects(options || {}),
        bounds = settings('bounds') || sigma.utils.getBoundaries(
          this.graph,
          readPrefix,
          true
        ),
        minX = bounds.minX,
        minY = bounds.minY,
        maxX = bounds.maxX,
        maxY = bounds.maxY,
        sizeMax = bounds.sizeMax,
        weightMax = bounds.weightMax,
        w = settings('width') || 1,
        h = settings('height') || 1,
        rescaleSettings = settings('autoRescale'),
        validSettings = {
          nodePosition: 1,
          nodeSize: 1,
          edgeSize: 1
        };

    /**
     * What elements should we rescale?
     */
    if (!(rescaleSettings instanceof Array))
      rescaleSettings = ['nodePosition', 'nodeSize', 'edgeSize'];

    for (i = 0, l = rescaleSettings.length; i < l; i++)
      if (!validSettings[rescaleSettings[i]])
        throw new Error(
          'The rescale setting "' + rescaleSettings[i] + '" is not recognized.'
        );

    var np = ~rescaleSettings.indexOf('nodePosition'),
        ns = ~rescaleSettings.indexOf('nodeSize'),
        es = ~rescaleSettings.indexOf('edgeSize');

    /**
     * First, we compute the scaling ratio, without considering the sizes
     * of the nodes : Each node will have its center in the canvas, but might
     * be partially out of it.
     */
    scale = settings('scalingMode') === 'outside' ?
      Math.max(
        w / Math.max(maxX - minX, 1),
        h / Math.max(maxY - minY, 1)
      ) :
      Math.min(
        w / Math.max(maxX - minX, 1),
        h / Math.max(maxY - minY, 1)
      );

    /**
     * Then, we correct that scaling ratio considering a margin, which is
     * basically the size of the biggest node.
     * This has to be done as a correction since to compare the size of the
     * biggest node to the X and Y values, we have to first get an
     * approximation of the scaling ratio.
     **/
    margin =
      (
        settings('rescaleIgnoreSize') ?
          0 :
          (settings('maxNodeSize') || sizeMax) / scale
      ) +
      (settings('sideMargin') || 0);
    maxX += margin;
    minX -= margin;
    maxY += margin;
    minY -= margin;

    // Fix the scaling with the new extrema:
    scale = settings('scalingMode') === 'outside' ?
      Math.max(
        w / Math.max(maxX - minX, 1),
        h / Math.max(maxY - minY, 1)
      ) :
      Math.min(
        w / Math.max(maxX - minX, 1),
        h / Math.max(maxY - minY, 1)
      );

    // Size homothetic parameters:
    if (!settings('maxNodeSize') && !settings('minNodeSize')) {
      a = 1;
      b = 0;
    } else if (settings('maxNodeSize') === settings('minNodeSize')) {
      a = 0;
      b = +settings('maxNodeSize');
    } else {
      a = (settings('maxNodeSize') - settings('minNodeSize')) / sizeMax;
      b = +settings('minNodeSize');
    }

    if (!settings('maxEdgeSize') && !settings('minEdgeSize')) {
      c = 1;
      d = 0;
    } else if (settings('maxEdgeSize') === settings('minEdgeSize')) {
      c = 0;
      d = +settings('minEdgeSize');
    } else {
      c = (settings('maxEdgeSize') - settings('minEdgeSize')) / weightMax;
      d = +settings('minEdgeSize');
    }

    // Rescale the nodes and edges:
    for (i = 0, l = e.length; i < l; i++)
      e[i][writePrefix + 'size'] =
        e[i][readPrefix + 'size'] * (es ? c : 1) + (es ? d : 0);

    for (i = 0, l = n.length; i < l; i++) {
      n[i][writePrefix + 'size'] =
        n[i][readPrefix + 'size'] * (ns ? a : 1) + (ns ? b : 0);
      n[i][writePrefix + 'x'] =
        (n[i][readPrefix + 'x'] - (maxX + minX) / 2) * (np ? scale : 1);
      n[i][writePrefix + 'y'] =
        (n[i][readPrefix + 'y'] - (maxY + minY) / 2) * (np ? scale : 1);
    }
  };

  sigma.utils.getBoundaries = function(graph, prefix, doEdges) {
    var i,
        l,
        e = graph.edges(),
        n = graph.nodes(),
        weightMax = -Infinity,
        sizeMax = -Infinity,
        minX = Infinity,
        minY = Infinity,
        maxX = -Infinity,
        maxY = -Infinity;

    if (doEdges)
      for (i = 0, l = e.length; i < l; i++)
        weightMax = Math.max(e[i][prefix + 'size'], weightMax);

    for (i = 0, l = n.length; i < l; i++) {
      sizeMax = Math.max(n[i][prefix + 'size'], sizeMax);
      maxX = Math.max(n[i][prefix + 'x'], maxX);
      minX = Math.min(n[i][prefix + 'x'], minX);
      maxY = Math.max(n[i][prefix + 'y'], maxY);
      minY = Math.min(n[i][prefix + 'y'], minY);
    }

    weightMax = weightMax || 1;
    sizeMax = sizeMax || 1;

    return {
      weightMax: weightMax,
      sizeMax: sizeMax,
      minX: minX,
      minY: minY,
      maxX: maxX,
      maxY: maxY
    };
  };
}).call(this);