import { clone } from '@antv/util';
import type { ID } from '../../../types';
import { DagreGraph } from '../graph';
import { buildLayerMatrix, maxRank } from '../util';
import { addSubgraphConstraints } from './add-subgraph-constraints';
import { buildLayerGraph } from './build-layer-graph';
import { crossCount } from './cross-count';
import { initOrder } from './init-order';
import { sortSubgraph } from './sort-subgraph';

/*
 * 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.
 */
export const order = (g: DagreGraph, keepNodeOrder?: boolean) => {
  const mxRank = maxRank(g);
  const range1 = [];
  const range2 = [];
  for (let i = 1; i < mxRank + 1; i++) range1.push(i);
  for (let i = mxRank - 1; i > -1; i--) range2.push(i);
  const downLayerGraphs = buildLayerGraphs(g, range1, 'in');
  const upLayerGraphs = buildLayerGraphs(g, range2, 'out');

  let layering = initOrder(g);
  assignOrder(g, layering);

  let bestCC = Number.POSITIVE_INFINITY;
  let best: string[][];
  for (let i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) {
    sweepLayerGraphs(
      i % 2 ? downLayerGraphs : upLayerGraphs,
      i % 4 >= 2,
      false,
      keepNodeOrder,
    );

    layering = buildLayerMatrix(g);
    const cc = crossCount(g, layering);
    if (cc < bestCC) {
      lastBest = 0;
      best = clone(layering);
      bestCC = cc;
    }
  }

  // consider use previous result, maybe somewhat reduendant
  layering = initOrder(g);
  assignOrder(g, layering);
  for (let i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) {
    sweepLayerGraphs(
      i % 2 ? downLayerGraphs : upLayerGraphs,
      i % 4 >= 2,
      true,
      keepNodeOrder,
    );

    layering = buildLayerMatrix(g);
    const cc = crossCount(g, layering);
    if (cc < bestCC) {
      lastBest = 0;
      best = clone(layering);
      bestCC = cc;
    }
  }
  assignOrder(g, best!);
};

const buildLayerGraphs = (
  g: DagreGraph,
  ranks: number[],
  direction: 'in' | 'out',
) => {
  return ranks.map((rank) => {
    return buildLayerGraph(g, rank, direction);
  });
};

const sweepLayerGraphs = (
  layerGraphs: DagreGraph[],
  biasRight: boolean,
  usePrev?: boolean,
  keepNodeOrder?: boolean,
) => {
  const cg = new DagreGraph();
  layerGraphs?.forEach((lg) => {
    // const root = lg.graph().root as string;
    const root = lg.getRoots()[0].id;
    const sorted = sortSubgraph(
      lg,
      root,
      cg,
      biasRight,
      usePrev,
      keepNodeOrder,
    );
    for (let i = 0; i < sorted.vs?.length || 0; i++) {
      const lnode = lg.getNode(sorted.vs[i]);
      if (lnode) {
        lnode.data.order = i;
      }
    }
    addSubgraphConstraints(lg, cg, sorted.vs);
  });
};

const assignOrder = (g: DagreGraph, layering: ID[][]) => {
  layering?.forEach((layer) => {
    layer?.forEach((v: ID, i: number) => {
      g.getNode(v).data.order = i;
    });
  });
};
