"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.setConnectedTo = exports.makeRelated = exports.build_subgraph = exports.remove_node_from_graph = exports.change_link_source_on_graph = exports.change_link_target_on_graph = exports.remove_link_from_graph = exports.add_link_to_graph = exports.add_node_to_graph = void 0;
exports.getSubPinSpecNodeId = getSubPinSpecNodeId;
exports.getSubPinSpecNodeId_ = getSubPinSpecNodeId_;
exports.build_graph = build_graph;
const SELF_1 = require("../constant/SELF");
const spec_1 = require("../spec/util/spec");
const f_1 = __importDefault(require("../system/core/object/ForEachKeyValue/f"));
const f_2 = require("../system/f/object/Keys/f");
const array_1 = require("../util/array");
const id_1 = require("../util/id");
const id_2 = require("./id");
function getSubPinSpecNodeId(type, subPinSpec) {
    const { mergeId, unitId, pinId, kind = type } = subPinSpec;
    if (mergeId) {
        return (0, id_2.getMergeNodeId)(mergeId);
    }
    else {
        return (0, id_2.getPinNodeId)(unitId, kind, pinId);
    }
}
function getSubPinSpecNodeId_(type, subPinSpec) {
    const { mergeId, unitId, pinId, kind = type } = subPinSpec;
    if (mergeId) {
        return (0, id_2.getMergeNodeId)(mergeId);
    }
    else {
        if (pinId === SELF_1.SELF) {
            return unitId;
        }
        else {
            return (0, id_2.getPinNodeId)(unitId, kind, pinId);
        }
    }
}
function build_graph(spec, specs, pinToDatum = {}) {
    const graph = {};
    const { units = {}, merges = {} } = spec;
    (0, f_1.default)(units, (unit, unitId) => {
        graph[unitId] = { next: {}, previous: {} };
        const unitSpec = specs[unit.id];
        const errNodeId = (0, id_2.getErrNodeId)(unitId);
        graph[errNodeId] = { next: {}, previous: { [unitId]: graph[unitId] } };
        graph[unitId].next[errNodeId] = graph[errNodeId];
        (0, f_1.default)(unitSpec.inputs || {}, (input, inputId) => {
            const inputNodeId = (0, id_2.getInputNodeId)(unitId, inputId);
            graph[inputNodeId] = { next: { [unitId]: graph[unitId] }, previous: {} };
            graph[unitId].previous[inputNodeId] = graph[inputNodeId];
            const datumNodeId = pinToDatum[inputNodeId];
            if (datumNodeId) {
                graph[datumNodeId] = {
                    next: { [inputNodeId]: graph[inputNodeId] },
                    previous: {},
                };
                graph[inputNodeId].previous[datumNodeId] = graph[datumNodeId];
            }
            const inputTypeNodeId = (0, id_2.getMetadataNodeId)(inputNodeId, 'type');
            graph[inputTypeNodeId] = {
                next: { [inputNodeId]: graph[inputNodeId] },
                previous: {},
            };
            graph[inputNodeId].previous[inputTypeNodeId] = graph[inputTypeNodeId];
        });
        (0, f_1.default)(unitSpec.outputs || {}, (output, outputId) => {
            const outputNodeId = (0, id_2.getOutputNodeId)(unitId, outputId);
            graph[outputNodeId] = { next: {}, previous: { [unitId]: graph[unitId] } };
            graph[unitId].next[outputNodeId] = graph[outputNodeId];
            const datumNodeId = pinToDatum[outputNodeId];
            if (datumNodeId) {
                graph[datumNodeId] = {
                    next: {},
                    previous: { [outputNodeId]: graph[outputNodeId] },
                };
                graph[outputNodeId].next[datumNodeId] = graph[datumNodeId];
            }
            const outputTypeNodeId = (0, id_2.getMetadataNodeId)(outputNodeId, 'type');
            graph[outputTypeNodeId] = {
                next: { [outputNodeId]: graph[outputNodeId] },
                previous: {},
            };
            graph[outputNodeId].previous[outputTypeNodeId] = graph[outputTypeNodeId];
        });
    });
    (0, f_1.default)(merges, (merge, mergeId) => {
        const mergeNodeId = (0, id_2.getMergeNodeId)(mergeId);
        graph[mergeNodeId] = { next: {}, previous: {} };
        (0, spec_1.forEachPinOnMerge)(merge, (unitId, type, pinId) => {
            if (type === 'input') {
                graph[mergeNodeId].next[unitId] = graph[unitId];
                graph[unitId].previous[mergeNodeId] = graph[mergeNodeId];
            }
            else {
                graph[mergeNodeId].previous[unitId] = graph[unitId];
                graph[unitId].next[mergeNodeId] = graph[mergeNodeId];
            }
        });
        const mergeTypeNodeId = (0, id_2.getMetadataNodeId)(mergeNodeId, 'type');
        graph[mergeTypeNodeId] = {
            next: { [mergeNodeId]: graph[mergeNodeId] },
            previous: {},
        };
        graph[mergeNodeId].previous[mergeTypeNodeId] = graph[mergeTypeNodeId];
    });
    (0, f_1.default)(spec.inputs || {}, (pinSpec, exposedPinId) => {
        const { plug = {} } = pinSpec;
        (0, f_1.default)(plug, (subPinSpec, subPinId) => {
            const extInputNodeId = (0, id_2.getExtNodeId)('input', exposedPinId, subPinId);
            graph[extInputNodeId] = { next: {}, previous: {} };
            const pinNodeId = getSubPinSpecNodeId('input', subPinSpec);
            graph[extInputNodeId].next[pinNodeId] = graph[pinNodeId];
            graph[pinNodeId].previous[extInputNodeId] = graph[extInputNodeId];
            const extInputTypeNodeId = (0, id_2.getMetadataNodeId)(extInputNodeId, 'type');
            graph[extInputTypeNodeId] = {
                next: { [extInputNodeId]: graph[extInputNodeId] },
                previous: {},
            };
            graph[extInputNodeId].previous[extInputTypeNodeId] =
                graph[extInputTypeNodeId];
        });
    });
    (0, f_1.default)(spec.outputs || {}, (pinSpec, exposedPinId) => {
        const { plug = {} } = pinSpec;
        (0, f_1.default)(plug, (subPinSpec, subPinId) => {
            const extOutputNodeId = (0, id_2.getExtNodeId)('output', exposedPinId, subPinId);
            graph[extOutputNodeId] = { next: {}, previous: {} };
            const pinNodeId = getSubPinSpecNodeId('output', subPinSpec);
            graph[extOutputNodeId].previous[pinNodeId] = graph[pinNodeId];
            graph[pinNodeId].next[extOutputNodeId] = graph[extOutputNodeId];
            const extOutputTypeNodeId = (0, id_2.getMetadataNodeId)(extOutputNodeId, 'type');
            graph[extOutputTypeNodeId] = {
                next: { [extOutputNodeId]: graph[extOutputNodeId] },
                previous: {},
            };
            graph[extOutputNodeId].previous[extOutputTypeNodeId] =
                graph[extOutputTypeNodeId];
        });
    });
    return graph;
}
const add_node_to_graph = (graph, node_id) => {
    graph[node_id] = {
        next: {},
        previous: {},
    };
};
exports.add_node_to_graph = add_node_to_graph;
const add_link_to_graph = (graph, source_id, target_id) => {
    const source = graph[source_id];
    const target = graph[target_id];
    source.next[target_id] = target;
    target.previous[source_id] = source;
};
exports.add_link_to_graph = add_link_to_graph;
const remove_link_from_graph = (graph, source_id, target_id) => {
    const source = graph[source_id];
    const target = graph[target_id];
    delete source.next[target_id];
    delete target.previous[source_id];
};
exports.remove_link_from_graph = remove_link_from_graph;
const change_link_target_on_graph = (graph, source_id, target_id, next_target_id) => {
    const target = graph[target_id];
    const source = graph[source_id];
    delete source?.next[target_id];
    delete target?.previous[source_id];
    const next_target = graph[next_target_id];
    source.next[next_target_id] = next_target;
    next_target.previous[source_id] = source;
};
exports.change_link_target_on_graph = change_link_target_on_graph;
const change_link_source_on_graph = (graph, source_id, next_source_id, target_id) => {
    const target = graph[target_id];
    const source = graph[source_id];
    const next_source = graph[next_source_id];
    delete source.next[target_id];
    delete target.previous[source_id];
    next_source.next[target_id] = target;
    target.previous[next_source_id] = next_source;
};
exports.change_link_source_on_graph = change_link_source_on_graph;
const remove_node_from_graph = (graph, node_id) => {
    const node = graph[node_id];
    const { next, previous } = node;
    for (let n_id in next) {
        const n = graph[n_id];
        delete n.previous[node_id];
    }
    for (let p_id in previous) {
        const p = graph[p_id];
        delete p.next[node_id];
    }
    delete graph[node_id];
};
exports.remove_node_from_graph = remove_node_from_graph;
const build_subgraph = (graph, node_to_subgraph, subgraph_to_node, visited = {}) => {
    (0, f_1.default)(graph, (_, id) => {
        _build_subgraph(id, graph, node_to_subgraph, subgraph_to_node, visited);
    });
};
exports.build_subgraph = build_subgraph;
const _build_subgraph = (id, graph, node_to_subgraph, subgraph_to_node, visited) => {
    if (visited[id]) {
        return;
    }
    visited[id] = true;
    const { previous, next } = graph[id];
    let subgraph_id;
    if (node_to_subgraph[id]) {
        subgraph_id = node_to_subgraph[id];
    }
    else {
        subgraph_id = (0, id_1.randomIdNotIn)(subgraph_to_node);
        node_to_subgraph[id] = subgraph_id;
        subgraph_to_node[subgraph_id] = new Set([id]);
    }
    (0, f_1.default)(previous, (_, prev_id) => {
        node_to_subgraph[prev_id] = subgraph_id;
        subgraph_to_node[subgraph_id].add(prev_id);
        _build_subgraph(prev_id, graph, node_to_subgraph, subgraph_to_node, visited);
    });
    (0, f_1.default)(next, (_, next_id) => {
        node_to_subgraph[next_id] = subgraph_id;
        subgraph_to_node[subgraph_id].add(next_id);
        _build_subgraph(next_id, graph, node_to_subgraph, subgraph_to_node, visited);
    });
};
const makeRelated = (graph) => {
    const related = {};
    (0, f_1.default)(graph, (_, id) => {
        const relatedTo = new Set([id]);
        (0, exports.setConnectedTo)(id, graph, relatedTo);
        related[id] = relatedTo;
    });
    return related;
};
exports.makeRelated = makeRelated;
const setConnectedTo = (id, graph, related = new Set(), visited = new Set()) => {
    const node = graph[id];
    related.add(id);
    visited.add(id);
    const previous = (0, f_2.keys)(node.previous);
    (0, array_1.forEach)(previous, (p) => {
        if (!visited.has(p)) {
            (0, exports.setConnectedTo)(p, graph, related, visited);
        }
    });
    const next = (0, f_2.keys)(node.next);
    (0, array_1.forEach)(next, (p) => {
        if (!visited.has(p)) {
            (0, exports.setConnectedTo)(p, graph, related, visited);
        }
    });
};
exports.setConnectedTo = setConnectedTo;
//# sourceMappingURL=graph.js.map