import { Autocompletion } from "./Autocompletion.js";
import { BNF } from "./BNF.js";
import { Matcher } from "./Matcher.js";
import { ParsingState } from "./ParsingState.js";
import { ParseException } from "../ParseException.js";
class RDParser {
    constructor(grammar, lexer, parsedNodeFactory) {
        this.grammar = grammar;
        this.lexer = lexer;
        this.parsedNodeFactory = parsedNodeFactory;
    }
    getLexer() {
        return this.lexer;
    }
    getGrammar() {
        return this.grammar;
    }
    getParsedNodeFactory() {
        return this.parsedNodeFactory;
    }
    parse(autocompletions) {
        let seq = new SymbolSequence(BNF.ARTIFICIAL_START_SYMBOL, undefined, undefined);
        let endOfInput = [];
        let parsedSequence = this.parseRecursive(seq, endOfInput);
        if (autocompletions !== undefined)
            this.collectAutocompletions(endOfInput, autocompletions);
        let last = [];
        let ret = this.createParsedTree(parsedSequence, last);
        ret = this.buildAst(ret);
        if (ret.getMatcher().state === ParsingState.FAILED)
            throw new ParseException(ret, last[0], this);
        return ret;
    }
    buildAst(pn) {
        let children = [];
        for (let i = 0; i < pn.numChildren(); i++) {
            children.push(this.buildAst(pn.getChild(i)));
        }
        pn.removeAllChildren();
        let production = pn.getProduction();
        if (production !== undefined)
            production.builtAST(pn, ...children);
        return pn;
    }
    collectAutocompletions(endOfInput, autocompletions) {
        if (autocompletions === null || autocompletions === undefined)
            throw new Error("autocompletions should be defined at this point");
        const autocompletingParents = [];
        for (const seq of endOfInput) {
            this.collectAutocompletingParents(seq, autocompletingParents);
        }
        const done = new Set();
        for (const autocompletingParent of autocompletingParents) {
            const prod = autocompletingParent.getProduction();
            let key;
            if (prod !== undefined) {
                key = prod.getLeft().getSymbol() + ":";
                for (const s of prod.getRight())
                    key += s.getSymbol();
            }
            else {
                key = autocompletingParent.getSymbol().getSymbol();
            }
            if (!done.has(key)) {
                const veto = this.addAutocompletions(autocompletingParent, autocompletions);
                done.add(key);
                if (veto)
                    break;
            }
        }
    }
    collectAutocompletingParents(symbolSequence, autocompletingParents) {
        let last = [];
        this.createParsedTree(symbolSequence, last);
        // get a trace to the root
        let pathToRoot = [];
        let parent = last[0];
        while (parent !== undefined) {
            pathToRoot.push(parent);
            parent = parent.getParent();
        }
        // find the node closest to root which provides autocompletion
        let autocompletingParent = undefined;
        for (let i = pathToRoot.length - 1; i >= 0; i--) {
            let tmp = pathToRoot[i];
            if (tmp.doesAutocomplete()) {
                autocompletingParent = tmp;
                break;
            }
        }
        if (autocompletingParent !== undefined)
            autocompletingParents.push(autocompletingParent);
    }
    addAutocompletions(autocompletingParent, autocompletions) {
        let autocompletingParentStart = autocompletingParent.getMatcher().pos;
        let alreadyEntered = this.lexer.substring(autocompletingParentStart);
        let completion = autocompletingParent.getAutocompletion(false);
        if (completion !== undefined) {
            for (let c of completion) {
                if (c === undefined || c.isEmptyLiteral())
                    continue;
                if (c instanceof Autocompletion.Veto) {
                    autocompletions.length = 0;
                    return true;
                }
                c.setAlreadyEntered(alreadyEntered);
                const cCompletion = c.getCompletion(Autocompletion.Purpose.FOR_MENU);
                if (!autocompletions.some(ac => (ac === null || ac === void 0 ? void 0 : ac.getCompletion(Autocompletion.Purpose.FOR_MENU)) === cCompletion))
                    autocompletions.push(c);
            }
        }
        return false;
    }
    parseRecursive(symbolSequence, endOfInput) {
        // console.log("parseRecursive");
        // console.log("  symbol sequence = " + symbolSequence);
        // console.log("  lexer           = " + this.lexer);
        let next = symbolSequence.getCurrentSymbol();
        // console.log("next = " + next);
        while (next.isTerminal()) {
            // console.log("next is a terminal node, lexer pos = " + this.lexer.getPosition());
            let matcher = next.matches(this.lexer);
            // console.log("matcher = " + matcher);
            symbolSequence.addMatcher(matcher);
            if (matcher.state.equals(ParsingState.END_OF_INPUT) && endOfInput !== undefined)
                endOfInput.push(symbolSequence);
            if (!matcher.state.equals(ParsingState.SUCCESSFUL))
                return symbolSequence;
            symbolSequence.incrementPosition();
            this.lexer.fwd(matcher.parsed.length);
            if (this.lexer.isDone())
                return symbolSequence;
            next = symbolSequence.getCurrentSymbol();
        }
        let u = next;
        let alternates = this.grammar.getProductions(u);
        let best = undefined;
        let lexerPosOfBest = this.lexer.getPosition();
        for (let alternate of alternates) {
            let lexerPos = this.lexer.getPosition();
            let nextSequence = symbolSequence.replaceCurrentSymbol(alternate);
            let parsedSequence = this.parseRecursive(nextSequence, endOfInput);
            let m = parsedSequence.getLastMatcher();
            if (m !== undefined) {
                if (m.state.equals(ParsingState.SUCCESSFUL))
                    return parsedSequence;
                if (best === undefined || m.isBetterThan(best.getLastMatcher())) {
                    best = parsedSequence;
                    lexerPosOfBest = this.lexer.getPosition();
                }
            }
            this.lexer.setPosition(lexerPos);
        }
        if (best !== undefined)
            this.lexer.setPosition(lexerPosOfBest);
        return best;
    }
    createParsedTree(leafSequence, retLast) {
        const parsedNodeSequence = [];
        const nParsedMatchers = leafSequence.parsedMatchers.length;
        for (let i = 0; i < leafSequence.sequence.length; i++) {
            let symbol = leafSequence.sequence[i];
            let matcher = i < nParsedMatchers
                ? leafSequence.parsedMatchers[i]
                : new Matcher(ParsingState.NOT_PARSED, 0, ""); // TODO maybe this should not be 0
            let pn = this.parsedNodeFactory.createNode(matcher, symbol, undefined);
            parsedNodeSequence.push(pn);
        }
        if (retLast !== undefined)
            retLast[0] = parsedNodeSequence[nParsedMatchers - 1];
        let childSequence = leafSequence;
        while (childSequence.parent !== undefined) {
            let parentSequence = childSequence.parent;
            let productionToCreateChildSequence = childSequence.production;
            if (productionToCreateChildSequence === undefined)
                throw new Error("production may not be undefined at this point");
            let pos = parentSequence.pos;
            let rhs = productionToCreateChildSequence.getRight();
            let lhs = productionToCreateChildSequence.getLeft();
            let rhsSize = rhs.length;
            let childList = parsedNodeSequence.slice(pos, pos + rhsSize);
            let matcher = RDParser.matcherFromChildSequence(childList);
            let newParent = this.parsedNodeFactory.createNode(matcher, lhs, productionToCreateChildSequence);
            newParent.addChildren(...childList);
            parsedNodeSequence.splice(pos, rhsSize, newParent);
            childSequence = childSequence.parent;
        }
        let root = parsedNodeSequence[0];
        if (!root.getSymbol().equals(BNF.ARTIFICIAL_START_SYMBOL))
            throw new Error("");
        RDParser.notifyExtensionListeners(root);
        return root;
    }
    static notifyExtensionListeners(pn) {
        const production = pn.getProduction();
        if (production !== undefined) {
            production.wasExtended(pn, ...pn.getChildren());
            for (let child of pn.getChildren())
                this.notifyExtensionListeners(child);
        }
    }
    static matcherFromChildSequence(children) {
        let pos = -1;
        let state = ParsingState.NOT_PARSED;
        let parsed = "";
        for (let child of children) {
            // already encountered EOI or FAILED before, do nothing
            if (state.equals(ParsingState.END_OF_INPUT) || state.equals(ParsingState.FAILED))
                break;
            const matcher = child.getMatcher();
            const childState = matcher.state;
            if (!childState.equals(ParsingState.NOT_PARSED)) {
                if (pos === -1)
                    pos = matcher.pos; // parent pos is the pos of the first child which is not NOT_PARSED
                if (state.equals(ParsingState.NOT_PARSED) || !childState.isBetterThan(state))
                    state = childState;
            }
            parsed = parsed + matcher.parsed;
        }
        if (pos === -1)
            pos = 0;
        return new Matcher(state, pos, parsed);
    }
}
class SymbolSequence {
    /**
     * Call it with all three parameter, with the not applicable ones set to undefined
     */
    constructor(start, parent, production) {
        this.sequence = [];
        this.pos = 0;
        this.parsedMatchers = [];
        if (start !== undefined) {
            if (parent !== undefined || production !== undefined)
                throw new Error("If 'start' is defined, parent and production should be undefined");
            this.sequence.push(start);
            this.parent = undefined;
            this.production = undefined;
        }
        else {
            if (parent === undefined || production === undefined)
                throw new Error("If 'start' is not defined, parent and production should be defined");
            this.sequence.push(...parent.sequence);
            this.pos = parent.pos;
            this.parent = parent;
            this.production = production;
        }
    }
    getLastMatcher() {
        return this.parsedMatchers.at(-1);
    }
    addMatcher(matcher) {
        this.parsedMatchers.push(matcher);
    }
    getCurrentSymbol() {
        return this.sequence[this.pos];
    }
    replaceCurrentSymbol(production) {
        let copy = new SymbolSequence(undefined, this, production);
        copy.parsedMatchers.push(...this.parsedMatchers);
        copy.sequence.splice(this.pos, 1);
        let replacement = production.getRight();
        copy.sequence.splice(this.pos, 0, ...replacement);
        return copy;
    }
    incrementPosition() {
        this.pos++;
    }
    toString() {
        let ret = "";
        let i = 0;
        for (let sym of this.sequence) {
            if (i++ === this.pos)
                ret += ".";
            ret += sym + " -- ";
        }
        return ret;
    }
}
export { RDParser, SymbolSequence };
//# sourceMappingURL=RDParser.js.map