'use strict';

var graph = require('../../graphlib/graph.js');
var util = require('../util.js');
var addSubgraphConstraints = require('./add-subgraph-constraints.js');
var buildLayerGraph = require('./build-layer-graph.js');
var crossCount = require('./cross-count.js');
var initOrder = require('./init-order.js');
var sortSubgraph = require('./sort-subgraph.js');
var range = require('../../../../../../lodash-es@4.17.21/node_modules/lodash-es/range.js');
var cloneDeep = require('../../../../../../lodash-es@4.17.21/node_modules/lodash-es/cloneDeep.js');
var map = require('../../../../../../lodash-es@4.17.21/node_modules/lodash-es/map.js');
var forEach = require('../../../../../../lodash-es@4.17.21/node_modules/lodash-es/forEach.js');

/*
 * Applies heuristics to minimize edge crossings in the graph and sets the best
 * order solution as an order attribute on each node.
 *
 * Pre-conditions:
 *
 *    1. Graph must be DAG
 *    2. Graph nodes must be objects with a "rank" attribute
 *    3. Graph edges must have the "weight" attribute
 *
 * Post-conditions:
 *
 *    1. Graph nodes will have an "order" attribute based on the results of the
 *       algorithm.
 */
function order(g) {
  var maxRank = util.maxRank(g),
    downLayerGraphs = buildLayerGraphs(g, range(1, maxRank + 1), 'inEdges'),
    upLayerGraphs = buildLayerGraphs(g, range(maxRank - 1, -1, -1), 'outEdges');

  var layering = initOrder.initOrder(g);
  assignOrder(g, layering);

  var bestCC = Number.POSITIVE_INFINITY,
    best;

  for (var i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) {
    sweepLayerGraphs(i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2);

    layering = util.buildLayerMatrix(g);
    var cc = crossCount.crossCount(g, layering);
    if (cc < bestCC) {
      lastBest = 0;
      best = cloneDeep(layering);
      bestCC = cc;
    }
  }

  assignOrder(g, best);
}

function buildLayerGraphs(g, ranks, relationship) {
  return map(ranks, function (rank) {
    return buildLayerGraph.buildLayerGraph(g, rank, relationship);
  });
}

function sweepLayerGraphs(layerGraphs, biasRight) {
  var cg = new graph.Graph();
  forEach(layerGraphs, function (lg) {
    var root = lg.graph().root;
    var sorted = sortSubgraph.sortSubgraph(lg, root, cg, biasRight);
    forEach(sorted.vs, function (v, i) {
      lg.node(v).order = i;
    });
    addSubgraphConstraints.addSubgraphConstraints(lg, cg, sorted.vs);
  });
}

function assignOrder(g, layering) {
  forEach(layering, function (layer) {
    forEach(layer, function (v, i) {
      g.node(v).order = i;
    });
  });
}

exports.order = order;
