import type { ID } from '../../types';
import { EdgeData } from '../../types';
import { DagreGraph, GraphEdge } from './graph';
import { addDummyNode } from './util';

/*
 * Breaks any long edges in the graph into short segments that span 1 layer
 * each. This operation is undoable with the denormalize function.
 *
 * Pre-conditions:
 *
 *    1. The input graph is a DAG.
 *    2. Each node in the graph has a "rank" property.
 *
 * Post-condition:
 *
 *    1. All edges in the graph have a length of 1.
 *    2. Dummy nodes are added where edges have been split into segments.
 *    3. The graph is augmented with a "dummyChains" attribute which contains
 *       the first dummy in each chain of dummy nodes produced.
 */

const DUMMY_NODE_EDGE = 'edge';
const DUMMY_NODE_EDGE_LABEL = 'edge-label';

const run = (g: DagreGraph, dummyChains: ID[]) => {
  g.getAllEdges().forEach((edge) => normalizeEdge(g, edge, dummyChains));
};

const normalizeEdge = (
  g: DagreGraph,
  e: GraphEdge<EdgeData>,
  dummyChains: ID[],
) => {
  let v = e.source;
  let vRank = g.getNode(v)!.data.rank!;
  const w = e.target;
  const wRank = g.getNode(w)!.data.rank!;
  const labelRank = e.data.labelRank;

  if (wRank === vRank + 1) return;

  g.removeEdge(e.id);

  let dummy: ID;
  let nodeData: {
    width: number;
    height: number;
    originalEdge: GraphEdge<EdgeData>;
    rank: number;
    dummy?: string;
    labelpos?: string;
  };
  let i;
  for (i = 0, ++vRank; vRank < wRank; ++i, ++vRank) {
    e.data.points = [];
    nodeData = {
      originalEdge: e,
      width: 0,
      height: 0,
      rank: vRank,
    };

    dummy = addDummyNode(g, DUMMY_NODE_EDGE, nodeData, '_d');

    if (vRank === labelRank) {
      nodeData.width = e.data.width!;
      nodeData.height = e.data.height!;
      nodeData.dummy = DUMMY_NODE_EDGE_LABEL;
      nodeData.labelpos = e.data.labelpos as string;
    }

    g.addEdge({
      id: `e${Math.random()}`,
      source: v,
      target: dummy,
      data: { weight: e.data.weight },
    });
    if (i === 0) {
      dummyChains.push(dummy);
    }
    v = dummy;
  }

  g.addEdge({
    id: `e${Math.random()}`,
    source: v,
    target: w,
    data: { weight: e.data.weight },
  });
};

const undo = (g: DagreGraph, dummyChains: ID[]) => {
  dummyChains.forEach((v) => {
    let node = g.getNode(v)!;
    const { data } = node;
    const originalEdge = data.originalEdge as GraphEdge<EdgeData>;

    let w;
    // Restore original edge.
    if (originalEdge) {
      g.addEdge(originalEdge);
    }

    let currentV = v;
    while (node.data.dummy) {
      w = g.getSuccessors(currentV)![0];
      g.removeNode(currentV);
      originalEdge.data.points!.push({
        x: node.data.x!,
        y: node.data.y!,
      });
      if (node.data.dummy === DUMMY_NODE_EDGE_LABEL) {
        originalEdge.data.x = node.data.x;
        originalEdge.data.y = node.data.y;
        originalEdge.data.width = node.data.width;
        originalEdge.data.height = node.data.height;
      }
      currentV = w.id;
      node = g.getNode(currentV)!;
    }
  });
};

export { run, undo };
