/*
 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package tools.javac.combo;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A template into which tags of the form {@code #\{KEY\}} or
 * {@code #\{KEY.SUBKEY\}} can be expanded.
 */
public interface Template {
    String expand(String selector);

    interface Resolver {
        public Template lookup(String key);
    }

    public static class Behavior {
        /* Looks for expandable keys.  An expandable key can take the form:
         *   #{MAJOR}
         *   #{MAJOR.}
         *   #{MAJOR.MINOR}
         * where MAJOR can be IDENTIFIER or IDENTIFIER[NUMERIC_INDEX]
         * and MINOR can be an identifier.
         *
         * The ability to have an empty minor is provided on the
         * assumption that some tests that can be written with this
         * will find it useful to make a distinction akin to
         * distinguishing F from F(), where F is a function pointer,
         * and also cases of #{FOO.#{BAR}}, where BAR expands to an
         * empty string.
         *
         * However, this being a general-purpose framework, the exact
         * use is left up to the test writers.
         */
        private static final Pattern pattern = Pattern.compile("#\\{([A-Z_][A-Z0-9_]*(?:\\[\\d+\\])?)(?:\\.([A-Z0-9_]*))?\\}");

        public static String expandTemplate(String template, final Map<String, Template> vars) {
            return expandTemplate(template, new MapResolver(vars));
        }

        public static String expandTemplate(String template, Resolver res) {
            CharSequence in = template;
            StringBuffer out = new StringBuffer();
            while (true) {
                boolean more = false;
                Matcher m = pattern.matcher(in);
                while (m.find()) {
                    String major = m.group(1);
                    String minor = m.group(2);
                    Template key = res.lookup(major);
                    if (key == null)
                        throw new IllegalStateException("Unknown major key " + major);

                    String replacement = key.expand(minor == null ? "" : minor);
                    more |= pattern.matcher(replacement).find();
                    m.appendReplacement(out, replacement);
                }
                m.appendTail(out);
                if (!more)
                    return out.toString();
                else {
                    in = out;
                    out = new StringBuffer();
                }
            }
        }

    }
}

class MapResolver implements Template.Resolver {
    private final Map<String, Template> vars;

    public MapResolver(Map<String, Template> vars) {this.vars = vars;}

    public Template lookup(String key) {
        return vars.get(key);
    }
}

class ChainedResolver implements Template.Resolver {
    private final Template.Resolver upstreamResolver, thisResolver;

    public ChainedResolver(Template.Resolver upstreamResolver, Template.Resolver thisResolver) {
        this.upstreamResolver = upstreamResolver;
        this.thisResolver = thisResolver;
    }

    public Template.Resolver getUpstreamResolver() {
        return upstreamResolver;
    }

    @Override
    public Template lookup(String key) {
        Template result = thisResolver.lookup(key);
        if (result == null)
            result = upstreamResolver.lookup(key);
        return result;
    }
}
