import { GNode } from "./node";
import { Edge, MINIMUM_EDGE_SEPARATION } from "./edge";

export class Graph {
  nodeMap: Array<GNode>;
  minGraphX: number;
  maxGraphX: number;
  minGraphY: number;
  maxGraphY: number;
  maxGraphNodeX: number;
  maxBackEdgeNumber: number;
  width: number;
  height: number;

  constructor(data: any) {
    this.nodeMap = [];

    this.minGraphX = 0;
    this.maxGraphX = 1;
    this.minGraphY = 0;
    this.maxGraphY = 1;
    this.width = 1;
    this.height = 1;

    data.nodes.forEach((jsonNode: any) => {
      this.nodeMap[jsonNode.id] = new GNode(jsonNode.nodeLabel);
    });

    data.edges.forEach((e: any) => {
      const t = this.nodeMap[e.target];
      const s = this.nodeMap[e.source];
      const newEdge = new Edge(t, e.index, s, e.type);
      t.inputs.push(newEdge);
      s.outputs.push(newEdge);
      if (e.type == 'control') {
        // Every source of a control edge is a CFG node.
        s.cfg = true;
      }
    });

  }

  *nodes(p = (n: GNode) => true) {
    for (const node of this.nodeMap) {
      if (!node || !p(node)) continue;
      yield node;
    }
  }

  *filteredEdges(p: (e: Edge) => boolean) {
    for (const node of this.nodes()) {
      for (const edge of node.inputs) {
        if (p(edge)) yield edge;
      }
    }
  }

  forEachEdge(p: (e: Edge) => void) {
    for (const node of this.nodeMap) {
      if (!node) continue;
      for (const edge of node.inputs) {
        p(edge);
      }
    }
  }

  redetermineGraphBoundingBox(showTypes: boolean): [[number, number], [number, number]] {
    this.minGraphX = 0;
    this.maxGraphNodeX = 1;
    this.maxGraphX = undefined;  // see below
    this.minGraphY = 0;
    this.maxGraphY = 1;

    for (const node of this.nodes()) {
      if (!node.visible) {
        continue;
      }

      if (node.x < this.minGraphX) {
        this.minGraphX = node.x;
      }
      if ((node.x + node.getTotalNodeWidth()) > this.maxGraphNodeX) {
        this.maxGraphNodeX = node.x + node.getTotalNodeWidth();
      }
      if ((node.y - 50) < this.minGraphY) {
        this.minGraphY = node.y - 50;
      }
      if ((node.y + node.getNodeHeight(showTypes) + 50) > this.maxGraphY) {
        this.maxGraphY = node.y + node.getNodeHeight(showTypes) + 50;
      }
    }

    this.maxGraphX = this.maxGraphNodeX +
      this.maxBackEdgeNumber * MINIMUM_EDGE_SEPARATION;

    this.width = this.maxGraphX - this.minGraphX;
    this.height = this.maxGraphY - this.minGraphY;

    const extent: [[number, number], [number, number]] = [
      [this.minGraphX - this.width / 2, this.minGraphY - this.height / 2],
      [this.maxGraphX + this.width / 2, this.maxGraphY + this.height / 2]
    ];

    return extent;
  }

}
