package parser;

/**
 * Factory for E grammar non-terminal objects.
 */
public class EFact extends ATVFactory {
    /**
     * Token visitor to parse E1 grammar non-terminals.
     */
    private ITokVisitor _parseE1;

    /**
     * Factory to parse E1 grammar non-terminals.
     */
    private E1Fact _e1Fact;

    /**
     * Token visitor to parse S grammar non-terminals.
     */
    private ITokVisitor _parseS;

    /**
     * Factory to parse S grammar non-terminals.
     */
    private SFact _sFact;

    /**
     * Initializer lambda for this factory.
     */
    private ILambda _initializer = new ILambda() {
        public Object apply(Object param) {
            // change state to no-op
            _initializer = NoOpLambda.Singleton;

            // initialize
            _parseE1 = _e1Fact.makeVisitor();
            _parseS = _sFact.makeVisitor();
            return null;
        }
    };

    /**
     * Constructor for the E factory,
     *
     * @param tkz    tokenizer to use
     * @param e1Fact factory for E1 non-terminals
     * @param sFact  factory for S non-terminals
     */
    public EFact(ITokenizer tkz, E1Fact e1Fact, SFact sFact) {
        super(tkz);
        _e1Fact = e1Fact;
        _sFact = sFact;
    }

    /**
     * Make the visitors.
     */
    private void initialize() {
        _initializer.apply(null);
    }

    /**
     * Make a token visitor to parse an E non-terminal.
     *
     * @return token visitor
     */
    public ITokVisitor makeVisitor() {
        initialize();
        return new ITokVisitor() {
            public Object defaultCase(AToken host, Object inp) {
                ILambda l = (ILambda) inp;
                return l.apply(new E((S) host.execute(_parseS, inp), (E1) nextToken().execute(_parseE1, inp)));
            }
        };
    }

    /**
     * Make a token visitor that delegates to the given visitor in a chain of responsibility.
     *
     * @param successor visitor to serve as successor in the chain
     */
    public ITokVisitor makeChainedVisitor(final ITokVisitor successor) {
        initialize();
        return new ITokVisitor() {
            public Object defaultCase(AToken host, Object inp) {
                // This is the lambda we should execute whenever we are successful.
                ILambda l = (ILambda) inp;

                // Execute an S visitor that is chained to the successor
                // The lambda gets executed when an S is successfully parsed.
                // It continues parsing E ::= S T1 and creates and returns an E.
                return l.apply(host.execute(_sFact.makeBacktrackChainedVisitor(successor, Identity.Singleton), new ILambda() {
                    public Object apply(Object param) {
                        // S was parsed successfully and is in param
                        // Continue parsing E ::= S E1
                        return new E((S) param, (E1) nextToken().execute(_parseE1, Identity.Singleton));
                    }
                }));
            }
        };
    }

    /**
     * Make a token visitor that delegates to the given visitor in a chain of responsibility.
     *
     * @param successor       visitor to serve as successor in the chain
     * @param successorLambda lambda to pass to the successor in the default case
     */
    public ITokVisitor makeBacktrackChainedVisitor(final ITokVisitor successor, final ILambda successorLambda) {
        initialize();
        return new ITokVisitor() {
            public Object defaultCase(AToken host, Object inp) {
                // This is the lambda we should execute whenever we are successful.
                ILambda l = (ILambda) inp;

                // Execute an S visitor that is chained to the successor
                // The lambda gets executed when an S is successfully parsed.
                // It continues parsing E ::= S T1 and creates and returns an E.
                return l.apply(host.execute(_sFact.makeBacktrackChainedVisitor(successor, successorLambda), new ILambda() {
                    public Object apply(Object param) {
                        // S was parsed successfully and is in param
                        // Continue parsing E ::= S E1
                        return new E((S) param, (E1) nextToken().execute(_parseE1, Identity.Singleton));
                    }
                }));
            }
        };
    }
}

