import zipObject from '../../../../../../lodash-es@4.17.23/node_modules/lodash-es/zipObject.mjs';
import map from '../../../../../../lodash-es@4.17.23/node_modules/lodash-es/map.mjs';
import flatten from '../../../../../../lodash-es@4.17.23/node_modules/lodash-es/flatten.mjs';
import sortBy from '../../../../../../lodash-es@4.17.23/node_modules/lodash-es/sortBy.mjs';
import forEach from '../../../../../../lodash-es@4.17.23/node_modules/lodash-es/forEach.mjs';

/*
 * A function that takes a layering (an array of layers, each with an array of
 * ordererd nodes) and a graph and returns a weighted crossing count.
 *
 * Pre-conditions:
 *
 *    1. Input graph must be simple (not a multigraph), directed, and include
 *       only simple edges.
 *    2. Edges in the input graph must have assigned weights.
 *
 * Post-conditions:
 *
 *    1. The graph and layering matrix are left unchanged.
 *
 * This algorithm is derived from Barth, et al., "Bilayer Cross Counting."
 */
function crossCount(g, layering) {
  var cc = 0;
  for (var i = 1; i < layering.length; ++i) {
    cc += twoLayerCrossCount(g, layering[i - 1], layering[i]);
  }
  return cc;
}

function twoLayerCrossCount(g, northLayer, southLayer) {
  // Sort all of the edges between the north and south layers by their position
  // in the north layer and then the south. Map these edges to the position of
  // their head in the south layer.
  var southPos = zipObject(
    southLayer,
    map(southLayer, function (v, i) {
      return i;
    }),
  );
  var southEntries = flatten(
    map(northLayer, function (v) {
      return sortBy(
        map(g.outEdges(v), function (e) {
          return { pos: southPos[e.w], weight: g.edge(e).weight };
        }),
        'pos',
      );
    }),
  );

  // Build the accumulator tree
  var firstIndex = 1;
  while (firstIndex < southLayer.length) firstIndex <<= 1;
  var treeSize = 2 * firstIndex - 1;
  firstIndex -= 1;
  var tree = map(new Array(treeSize), function () {
    return 0;
  });

  // Calculate the weighted crossings
  var cc = 0;
  forEach(
    // @ts-expect-error
    southEntries.forEach(function (entry) {
      var index = entry.pos + firstIndex;
      tree[index] += entry.weight;
      var weightSum = 0;
      while (index > 0) {
        if (index % 2) {
          weightSum += tree[index + 1];
        }
        index = (index - 1) >> 1;
        tree[index] += entry.weight;
      }
      cc += entry.weight * weightSum;
    }),
  );

  return cc;
}

export { crossCount };
