import “../arrays/map”; import “selection”;

d3_selectionPrototype.data = function(value, key) {

var i = -1,
    n = this.length,
    group,
    node;

// If no value is specified, return the first value.
if (!arguments.length) {
  value = new Array(n = (group = this[0]).length);
  while (++i < n) {
    if (node = group[i]) {
      value[i] = node.__data__;
    }
  }
  return value;
}

function bind(group, groupData) {
  var i,
      n = group.length,
      m = groupData.length,
      n0 = Math.min(n, m),
      updateNodes = new Array(m),
      enterNodes = new Array(m),
      exitNodes = new Array(n),
      node,
      nodeData;

  if (key) {
    var nodeByKeyValue = new d3_Map,
        dataByKeyValue = new d3_Map,
        keyValues = [],
        keyValue;

    for (i = -1; ++i < n;) {
      keyValue = key.call(node = group[i], node.__data__, i);
      if (nodeByKeyValue.has(keyValue)) {
        exitNodes[i] = node; // duplicate selection key
      } else {
        nodeByKeyValue.set(keyValue, node);
      }
      keyValues.push(keyValue);
    }

    for (i = -1; ++i < m;) {
      keyValue = key.call(groupData, nodeData = groupData[i], i);
      if (node = nodeByKeyValue.get(keyValue)) {
        updateNodes[i] = node;
        node.__data__ = nodeData;
      } else if (!dataByKeyValue.has(keyValue)) { // no duplicate data key
        enterNodes[i] = d3_selection_dataNode(nodeData);
      }
      dataByKeyValue.set(keyValue, nodeData);
      nodeByKeyValue.remove(keyValue);
    }

    for (i = -1; ++i < n;) {
      if (nodeByKeyValue.has(keyValues[i])) {
        exitNodes[i] = group[i];
      }
    }
  } else {
    for (i = -1; ++i < n0;) {
      node = group[i];
      nodeData = groupData[i];
      if (node) {
        node.__data__ = nodeData;
        updateNodes[i] = node;
      } else {
        enterNodes[i] = d3_selection_dataNode(nodeData);
      }
    }
    for (; i < m; ++i) {
      enterNodes[i] = d3_selection_dataNode(groupData[i]);
    }
    for (; i < n; ++i) {
      exitNodes[i] = group[i];
    }
  }

  enterNodes.update
      = updateNodes;

  enterNodes.parentNode
      = updateNodes.parentNode
      = exitNodes.parentNode
      = group.parentNode;

  enter.push(enterNodes);
  update.push(updateNodes);
  exit.push(exitNodes);
}

var enter = d3_selection_enter([]),
    update = d3_selection([]),
    exit = d3_selection([]);

if (typeof value === "function") {
  while (++i < n) {
    bind(group = this[i], value.call(group, group.parentNode.__data__, i));
  }
} else {
  while (++i < n) {
    bind(group = this[i], value);
  }
}

update.enter = function() { return enter; };
update.exit = function() { return exit; };
return update;

};

function d3_selection_dataNode(data) {

return {__data__: data};

}