import { getIdentifierText, nodeContainsErrors } from "./checker.js";
import { isIdentifierNode } from "./parser.js";
import { SyntaxKind, } from "./types.js";
const binder = createBinder();
export function bindSourceFile(file) {
    binder.bind(file);
}
function createBinder() {
    let parent = undefined;
    let symbolTable = undefined;
    let colorTable = undefined;
    let graphContext = 0;
    function bind(node) {
        if (!node)
            return;
        const saveParent = parent;
        const saveContext = graphContext;
        node.parent = saveParent;
        node.graphContext = saveContext;
        parent = node;
        innerBind(node);
        parent = saveParent;
        graphContext = saveContext;
    }
    function innerBind(node) {
        switch (node.kind) {
            case SyntaxKind.DirectedGraph:
            case SyntaxKind.UndirectedGraph:
                return bindGraph(node);
            case SyntaxKind.AttributeStatement:
                return bindAttributeStatement(node);
            case SyntaxKind.EdgeStatement:
                return bindEdgeStatement(node);
            case SyntaxKind.NodeStatement:
                return bindNodeStatement(node);
            case SyntaxKind.SubGraph:
                return bindSubGraph(node);
            case SyntaxKind.SubGraphStatement:
                return bindSubGraphStatement(node);
            case SyntaxKind.IdEqualsIdStatement:
                return bindIdEqualsIdStatement(node);
            case SyntaxKind.QuotedTextIdentifier:
                return bindQuotedTextIdentifier(node);
            case SyntaxKind.EdgeRhs:
                return bindEdgeRhs(node);
            case SyntaxKind.AttributeContainer:
                return bindAttributeContainer(node);
            case SyntaxKind.Assignment:
                return bindAssignment(node);
            case SyntaxKind.NormalPortDeclaration:
                return bindNormalPortDeclaration(node);
            case SyntaxKind.CompassPortDeclaration:
                return bindCompassPortDeclaration(node);
            case SyntaxKind.NodeId:
                return bindNodeId(node);
            default:
                if (node.kind >= SyntaxKind.FirstNode)
                    throw "TODO";
        }
    }
    function bindGraph(node) {
        if (node.strict) {
            graphContext |= 2;
        }
        switch (node.kind) {
            case SyntaxKind.DirectedGraph:
                graphContext |= 4;
                break;
            case SyntaxKind.UndirectedGraph:
                graphContext |= 8;
                break;
        }
        if (node.id) {
            ensureGlobalSymbol(node.id);
            bind(node.id);
        }
        if (node.strict)
            bind(node.strict);
        bindChildren(node.statements);
    }
    function bindAttributeStatement(node) {
        bind(node.subject);
        bindChildren(node.attributes);
        if (node.terminator)
            bind(node.terminator);
    }
    function bindEdgeStatement(node) {
        bindChildren(node.attributes);
        bindChildren(node.rhs);
        bind(node.source);
        if (node.terminator)
            bind(node.terminator);
    }
    function bindNodeStatement(node) {
        bind(node.id);
        bindChildren(node.attributes);
        if (node.terminator)
            bind(node.terminator);
    }
    function bindSubGraph(node) {
        if (node.id) {
            bind(node.id);
        }
        bindChildren(node.statements);
    }
    function bindSubGraphStatement(node) {
        bind(node.subgraph);
        if (node.terminator)
            bind(node.terminator);
    }
    function bindIdEqualsIdStatement(node) {
        bind(node.leftId);
        bind(node.rightId);
        if (node.rightId && !nodeContainsErrors(node.rightId)) {
            if (isAttributeName("color", node.leftId)) {
                ensureGlobalColor(node.rightId);
            }
            else if (isAttributeName("fillcolor", node.leftId)) {
                ensureGlobalColor(node.rightId);
            }
            else if (isAttributeName("bgcolor", node.leftId)) {
                ensureGlobalColor(node.rightId);
            }
            else if (isAttributeName("fontcolor", node.leftId)) {
                ensureGlobalColor(node.rightId);
            }
        }
        if (node.terminator)
            bind(node.terminator);
    }
    function bindQuotedTextIdentifier(node) {
        bindChildren(node.values);
        node.concatenation = node.values.map(v => v.text).join("");
    }
    function bindEdgeRhs(node) {
        bind(node.operation);
        bind(node.target);
    }
    function bindAttributeContainer(node) {
        bind(node.openBracket);
        bindChildren(node.assignments);
        bind(node.closeBracket);
    }
    function bindAssignment(node) {
        const attrContainer = node.parent;
        console.assert(!!attrContainer);
        const superParentStatement = attrContainer.parent;
        console.assert(!!superParentStatement);
        bind(node.leftId);
        let carrierIdentifier = undefined;
        switch (superParentStatement.kind) {
            case SyntaxKind.NodeStatement:
                carrierIdentifier = superParentStatement.id.id;
                break;
            case SyntaxKind.EdgeStatement:
                break;
            case SyntaxKind.SubGraphStatement:
                break;
            case SyntaxKind.AttributeStatement:
                break;
        }
        if (carrierIdentifier)
            ensureMemberSymbol(node.leftId, carrierIdentifier);
        bind(node.rightId);
        if (node.rightId && !nodeContainsErrors(node.rightId)) {
            if (isAttributeName("color", node.leftId)) {
                ensureGlobalColor(node.rightId);
            }
        }
        if (node.terminator)
            bind(node.terminator);
    }
    function bindNormalPortDeclaration(node) {
        bind(node.colon);
        ensureGlobalSymbol(node.id);
        bind(node.id);
        if (node.compassPt)
            bind(node.compassPt);
    }
    function bindCompassPortDeclaration(node) {
        bind(node.colon);
        if (node.compassPt)
            bind(node.compassPt);
    }
    function bindNodeId(node) {
        ensureGlobalSymbol(node.id);
        bind(node.id);
        if (node.port)
            bind(node.port);
    }
    function bindChildren(nodes) {
        for (const n of nodes)
            bind(n);
    }
    function createSymbolTable() {
        return new Map();
    }
    function createColorTable() {
        return new Map();
    }
    function ensureMemberSymbol(node, carrier) {
        if (node && carrier && isIdentifierNode(node)) {
            const name = getIdentifierText(node);
            if (name === undefined)
                return;
            const carrierSymbol = carrier.symbol;
            if (carrierSymbol === undefined)
                throw "carrierSymbol is undefined";
            let symbols = carrierSymbol.members;
            if (symbols === undefined)
                carrierSymbol.members = symbols = createSymbolTable();
            ensureSymbolOnTable(name, node, symbols);
            return;
        }
        console.warn("ensureSymbol called on non-identifier node");
        debugger;
    }
    function ensureGlobalSymbol(node) {
        if (node && isIdentifierNode(node)) {
            const symbols = symbolTable;
            const name = getIdentifierText(node);
            if (name === undefined)
                return;
            if (symbols === undefined)
                throw "symbolTable is undefined";
            ensureSymbolOnTable(name, node, symbols);
            return;
        }
        console.warn("ensureSymbol called on non-identifier node");
        debugger;
    }
    function ensureSymbolOnTable(name, node, symbols) {
        if (!name)
            return;
        let sym = symbols.get(name);
        if (sym === undefined) {
            sym = createSymbol(name, node);
            symbols.set(name, sym);
        }
        else {
            if (!sym.references)
                sym.references = [node];
            else
                sym.references.push(node);
        }
        node.symbol = sym;
    }
    function ensureGlobalColor(node) {
        if (node && isIdentifierNode(node)) {
            const colors = colorTable;
            const name = getIdentifierText(node);
            if (name === undefined)
                return;
            if (colors === undefined)
                throw "symbolTable is undefined";
            const color = createColor(node);
            colors.set(name, color);
            return;
        }
        console.warn("ensureSymbol called on non-identifier node");
        debugger;
    }
    function createSymbol(name, node) {
        if (!name)
            throw "name is falsy";
        if (!node)
            throw "node is undefined or null";
        return {
            name,
            firstMention: node,
            references: undefined,
        };
    }
    function createColor(node) {
        return {
            node,
        };
    }
    function isAttributeName(name, id) {
        return id ? getIdentifierText(id).trim().toLowerCase() === name : false;
    }
    return {
        bind: file => {
            symbolTable = createSymbolTable();
            colorTable = createColorTable();
            const { graph } = file;
            if (graph)
                bind(graph);
            file.symbols = symbolTable;
            file.colors = colorTable;
        },
    };
}
//# sourceMappingURL=binder.js.map