import Dagre from 'dagre-layout';
import { Graph } from 'graphlibrary';
import { Polyline } from './polyline';
export class FlowGraph {
    constructor(tree) {
        this.EDGE_SIZE = 75;
        this.NODE_START = 'NODE_START';
        this.NODE_END = 'NODE_END';
        this.NODE_SIZE = 50;
        this.NODE_PADDING = 50;
        this.edgeIndex = 0;
        this.tree = tree;
        const graph = new Graph();
        graph.setGraph({ rankdir: 'LR', edgesep: 0, ranksep: this.EDGE_SIZE });
        graph.setDefaultEdgeLabel(() => ({}));
        // Add start and end node
        graph.setNode(this.NODE_START, {
            id: this.NODE_START,
            width: this.NODE_SIZE,
            height: this.NODE_SIZE,
            start: true
        });
        graph.setEdge(this.NODE_START, this.tree.flow.start.id, {
            index: this.edgeIndex++,
            head: this.NODE_START,
            tail: this.tree.flow.start.id,
            start: true
        });
        graph.setNode(this.NODE_END, {
            id: this.NODE_END,
            width: this.NODE_SIZE,
            height: this.NODE_SIZE,
            end: true
        });
        this.initBranch(graph, this.tree.root);
        for (const orphan of this.tree.orphans)
            this.initBranch(graph, orphan, true);
        Dagre.layout(graph);
        this.graph = graph;
    }
    get dimensions() {
        const graph = this.graph.graph();
        return {
            width: graph.width + this.NODE_SIZE,
            height: graph.height + this.NODE_SIZE
        };
    }
    get edges() {
        const padding = this.NODE_SIZE / 2;
        return this.graph.edges().map((e) => {
            const edge = this.graph.edge(e);
            if (edge.head) {
                const { x, y } = this.graph.node(edge.head);
                edge.points[0] = { x, y };
            }
            if (edge.tail) {
                const { x, y } = this.graph.node(edge.tail);
                edge.points[edge.points.length - 1] = { x, y };
            }
            const points = edge.points.map((point) => ({
                x: point.x + padding,
                y: point.y + padding
            }));
            const [cx, cy] = new Polyline(points).middle;
            return Object.assign(Object.assign({}, edge), { coords: points, points: this.toPoints(points), center: { x: cx, y: cy } });
        });
    }
    get nodes() {
        const padding = this.NODE_PADDING / 2;
        return this.graph.nodes().map((n) => {
            const node = this.graph.node(n);
            const { width, height } = node;
            return Object.assign(Object.assign({}, node), { x: node.x - padding, y: node.y - padding, width: width + this.NODE_PADDING, height: height + this.NODE_PADDING });
        });
    }
    initBranch(graph, root, orphan = false) {
        for (const node of root.nodes) {
            if (!node.id)
                continue;
            const label = this.tree.getLabel(node.id, node.version);
            graph.setNode(node.id, {
                id: node.id,
                version: node.version,
                label,
                width: this.NODE_SIZE,
                height: this.NODE_SIZE
            });
            if (!node.children.length && !orphan) {
                graph.setEdge(node.id, this.NODE_END, {
                    index: this.edgeIndex++,
                    head: node.id,
                    tail: this.NODE_END,
                    end: true
                });
            }
            for (const child of node.children) {
                if (orphan && !child.id)
                    continue;
                graph.setEdge(node.id, child.id || this.NODE_END, {
                    index: this.edgeIndex++,
                    head: node.id,
                    tail: child.id || this.NODE_END,
                    end: !child.id
                });
            }
        }
    }
    toPoints(points) {
        return points.map(p => `${p.x},${p.y}`).join(' ');
    }
}
