/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program 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 program 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 at /legal/license.txt). 
 * 
 * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package config;

import java.io.*;
import java.util.*;
import util.*;

/**
 * The Configurator class deals with the creation of jvmconfig.h,
 * which controls the build options of the VM (e.g.,
 * ENABLE_JAVA_DEBUGGER, ENABLE_CLDC_11, etc).
 *
 * See src/vm/share/utilities/BuildFlags.hpp about how the content of
 * jvmconfig.h is specified.
 */
public class Configurator {
    Vector flags;
    Hashtable platform;
    Hashtable product;

    public Configurator() {

    }

    /**
     * Read input from platform file, which usually have contents like:
     * os_family    = linux
     * arch         = i386
     * carch        = thumb2
     * iarch        = c   
     * os_arch      = linux_i386
     * compiler     = gcc
     * cpu_variant  = 
     */
    public void readPlatformFile(String infile) throws Exception {
        platform = new Hashtable();

        readConfigFile(infile, platform);
    }

    public void readProductFile(String infile) throws Exception {
        product = new Hashtable();

        readConfigFile(infile, product);
    }

    private void readConfigFile(String infile, Hashtable properties) 
      throws Exception {
        FileInputStream in = new FileInputStream(infile);
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        String line;

        while ((line = reader.readLine()) != null) {
            StringTokenizer st = new StringTokenizer(line, "=");
            String name  = null;
            String value = null;

            try {
                name = st.nextToken();
                value = st.nextToken();
            } catch (Throwable t) {;}

            if (name == null) {
                continue;
            }
            name = name.trim();
            if (name.equals("")) {
                continue;
            }
            if (value == null) {
                value = "";
            } else {
                value = value.trim();
            }

            properties.put(name, value);
        }
    }

    /**
     * Read input from infile (usually BuildFlags.hpp)
     */
    public void readInputFile(String infile) throws Exception {
        FileInputStream in = new FileInputStream(infile);
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));

        String line;
        boolean started = false;
        EnableFlag currentFlag = null;
        flags = new Vector();

        while ((line = reader.readLine()) != null) {
            if (line.indexOf("{{ ENABLE_FLAGS_BEGIN") >= 0) {
                started = true;
            } else if (line.indexOf("ENABLE_FLAGS_END }}") >= 0) {
                break;
            } else {
                EnableFlag newFlag = tryStartFlag(line);
                if (newFlag == null) {
                    if (currentFlag != null) {
                        addComments(currentFlag, line);
                    }
                } else {
                    flags.addElement(newFlag);
                    currentFlag = newFlag;
                }
            }
        }
    }

    /**
     * Print a warning if an unknown ENABLE_XXX flag is specified in the
     * env. This is probably a typo by the user.
     */
    void checkSpuriousFlags(Hashtable env) {
        boolean warned = false;
        Vector names = getFlagNames();
        for (Enumeration e = env.keys(); e.hasMoreElements() ;) {
            String key = (String)e.nextElement();
            if (key.startsWith("ENABLE_") && !key.endsWith("__BY")
                && !key.equals("ENABLE_MAP_FILE")
                && !names.contains(key)) {
                if (!warned) {
                    System.out.println("*********");
                    warned = true;
                }
                System.out.println("Warning: unknown flag " + key);
            }
        }

        if (warned) {
            System.out.println("*********");
        }
    }

    /**
     * Write the output file. The env hashtable allows the user to specify
     * alternative values for the configuration flags.
     */
    public void write(String outfile, Hashtable env, Vector extra) 
        throws Exception
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos));
        byte data[];

        checkSpuriousFlags(env);
        writeConfigProlog(writer);
        writePlatformFlags(writer);
        writeEnableFlags(writer, env);
        writeOptionDump(writer);
        writeExtraFlags(writer, extra);
        writeConfigEpilog(writer);
        writer.flush();
        data = baos.toByteArray();
        writer.close();

        writeFileIfNecessary(data, outfile);
    }

    public void writeFileIfNecessary(byte data[], String outfile)
        throws Exception
    {
        boolean necessary = true;

        File f = new File(outfile);
        if (f.exists()) {
            FileInputStream in = new FileInputStream(outfile);
            byte savedData[] = new byte[in.available()];
            if (in.read(savedData) == savedData.length) {
                necessary = false;
                for (int i=0; i<savedData.length; i++) {
                    if (data[i] != savedData[i]) {
                        necessary = true;
                        break;
                    }
                }
            }
            in.close();
        }

        if (necessary) {
            FileOutputStream out = new FileOutputStream(outfile);
            out.write(data);
            out.close();
        } else {
            System.out.println("jvmconfig.h has not changed");
        }
    }

    public Vector getFlagNames() {
        Vector v = new Vector();
        for (int i=0; i<flags.size(); i++) {
            EnableFlag flag = (EnableFlag)flags.elementAt(i);
            v.addElement(flag.name);
        }
        return v;
    }

    public String getDefaultValue(String name) {
        Vector v = new Vector();
        for (int i=0; i<flags.size(); i++) {
            EnableFlag flag = (EnableFlag)flags.elementAt(i);
            if (flag.name.equals(name)) {
                if (flag.debug.def != flag.product.def) {
                    throw new Error("Debug and product default values aren't "+
                                    "same");
                }
                switch (flag.debug.def) {
                case ALWAYS_DISABLE:
                case DISABLE:
                    return "false";
                default:
                    return "true";
                }
            }
        }
        throw new Error("Option \"" + name + "\" not found");
    }

    public String getProductName() {
      return (String)product.get(PRODUCT_NAME_KEY);
    }

    public String getReleaseVersion() {
      return (String)product.get(RELEASE_VERSION_KEY);
    }

    static final String PRODUCT_NAME_KEY    = "PRODUCT_NAME";
    static final String RELEASE_VERSION_KEY = "RELEASE_VERSION";

    static final int ALWAYS_DISABLE = 1;
    static final int DISABLE        = 2;
    static final int ENABLE         = 3;
    static final int ALWAYS_ENABLE  = 4;

    EnableFlag tryStartFlag(String line) {
        StringTokenizer st = new StringTokenizer(line, " \t\n\r");
        int tokenNumber = 0;
        String name = null;
        int debugDefault = -1000;
        int productDefault = -1000;
        String comments = "";

        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            switch (tokenNumber) {
            case 0:
                if (token.equals("//")) {
                    continue;
                }
                else if (token.startsWith("ENABLE_")) {
                    name = token;
                    tokenNumber ++;
                    continue;
                } else {
                    return null;
                }

            case 1:
                if (token.length() != 3) {
                    return null;
                }
                if (token.charAt(1) != ',') {
                    return null;
                }
                if ((debugDefault = getDefault(token.charAt(0))) < 0) {
                    return null;
                }
                if ((productDefault = getDefault(token.charAt(2))) < 0) {
                    return null;
                }
                tokenNumber ++;
                break;

            default:
                if (token.indexOf("=====") < 0) {
                    comments = comments + " " + token;
                }
            }
        }

        if (tokenNumber <= 1) {
            return null;
        }

        EnableFlag flag = new EnableFlag();
        flag.name = name;
        flag.debug.def = debugDefault;
        flag.product.def = productDefault;
        flag.comments = comments;

        return flag;
    }
 
    void addComments(EnableFlag flag, String line) {
        StringTokenizer st = new StringTokenizer(line, " \t\n\r");
        String comments = "";

        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            if (token.equals("//") && comments.equals("")) {
                continue;
            } else if (token.indexOf("=====") >= 0) {
                continue;
            } else {
                comments = comments + " " + token;
            }
        }
        if (comments.length() > 0) {
            flag.comments += " " + comments;
        }
    }

    int getDefault(char c) {
        switch (c) {
        case '-': return ALWAYS_DISABLE;
        case '0': return DISABLE;
        case '1': return ENABLE;
        case '+': return ALWAYS_ENABLE;
        default:  return -1;
        }
    }

    void writeConfigProlog(PrintWriter writer) throws Exception {
        InputStream in =
            Configurator.class.getResourceAsStream("config_prolog.txt");
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        String line;

        while ((line = reader.readLine()) != null) {
            writer.println(line);
        }
        in.close();
    }

    void writePlatformFlags(PrintWriter writer)
        throws Exception
    {
        String arch     = (String)platform.get("arch");
        String iarch    = (String)platform.get("iarch");
        String carch    = (String)platform.get("carch");
        String compiler = (String)platform.get("compiler");

        if (iarch == null || iarch.equals("")) {
            iarch = arch;
        }
        if (carch == null || carch.equals("")) {
            carch = arch;
        }

        writer.println();
        writer.println("// BEGIN: Information derived from \"platform\" file");

        if (carch.equals("arm") || iarch.equals("arm")) {
            writer.println("#ifndef ARM");
            writer.println("#define ARM 1");
            writer.println("#endif");
        }
        else if (carch.equals("thumb2") || iarch.equals("thumb2")) {
            writer.println("#ifndef ARM");
            writer.println("#define ARM 1");
            writer.println("#endif");
        }
        else if (carch.equals("sh") || iarch.equals("sh")) {
            writer.println("#ifndef HITACHI_SH");
            writer.println("#define HITACHI_SH1");
            writer.println("#endif");
        }

        writer.println("#define INTERPRETER_ARCH_NAME \"" + iarch + "\"");
        writer.println("// END: Information derived from \"platform\" file");
        writer.println();
    }

    void writeEnableFlags(PrintWriter writer, Hashtable env)
        throws Exception
    {
        for (int i=0; i<flags.size(); i++) {
            EnableFlag flag = (EnableFlag)flags.elementAt(i);
            writeFlag(writer, env, flag);
        }

    }

    public void writeExtraFlags(PrintWriter writer, Vector extra) 
        throws Exception
    {
        for (int i=0; i<extra.size(); i++) {
            String flag = (String)extra.elementAt(i);
            flag = flag.replace('=', ' ');
            writer.println();
            writer.println("/* Extra flag for this build configuration */");
            writer.println("#define " + flag);
        }
    }

    void writeConfigEpilog(PrintWriter writer) throws Exception {
        writer.println();
        writer.println("#endif /* _JVM_CONFIG_H_ */");
        writer.println();
    }

    void writeFlag(PrintWriter writer, Hashtable env, EnableFlag flag) {
        writer.println();
        writer.println("/" + "*\n * " + flag.name);
        writer.print(" *      ");
 
        int linepos = 0;
        StringTokenizer st = new StringTokenizer(flag.comments);
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            if (linepos > 0 && token.length() + linepos > 60) {
                writer.println();
                writer.print(" *      ");
                linepos = 0;
            }
            writer.print(" ");
            writer.print(token);
            linepos += 1 + token.length();
        }
        writer.println();
        writer.println(" *" + "/");
        writer.println();
 
        getValue(env, flag.name, flag.debug);
        getValue(env, flag.name, flag.product);

        if (flag.debug.value == flag.product.value &&
            flag.debug.source.equals(flag.product.source)) {
            writeValue(writer, flag.name, flag.debug);
        } else {
            writer.println("#ifndef PRODUCT");
            writeValue(writer, flag.name, flag.debug);
            writer.println("#else");
            writeValue(writer, flag.name, flag.product);
            writer.println("#endif");
       }
    }

    void writeValue(PrintWriter writer, String name, FlagValue value) {
        int i;
        if (name.trim().equals("ENABLE_PCSL")) {
            writer.println("#ifndef " + name);
        }
        writer.print("#define " + name + " ");
        for (i=name.length() + 1; i<30; i++) {
            writer.print(" ");
        }
        writer.println((value.value ? "1" : "0") +
                       "  /* " + value.source + " */");
        if (name.trim().equals("ENABLE_PCSL")) {
            writer.println("#endif");
        }
    }

    void getValue(Hashtable env, String name, FlagValue value) {
        String envValue  = (String)env.get(name);
        String envSource = (String)env.get(name + "__BY");
        boolean envDefault = false;

        if (envValue != null) {
            if (envValue.equals("true")) {
                envDefault = true;
            } else if (envValue.equals("false")) {
                envDefault = false;
            } else {
                System.out.println("Bad value \"" + envValue + 
                                   "\" for env variable \"" + name +
                                   "\"");
                System.out.println("Must be \"true\" or \"false\".\n");
                System.exit(0);
                return;
            }
        }

        switch (value.def) {
        case ALWAYS_DISABLE:
            value.value = false;
            value.source = "Always disabled";
            break;
        case ALWAYS_ENABLE:
            value.value = true;
            value.source = "Always enabled";
            break;
        default:
            if (envValue == null) {
                value.value = (value.def == ENABLE);
                value.source = "VM default: BuildFlags.hpp";
            } else {
                value.value = envDefault;
                if ("idetool".equals(envSource)) {
                    value.source = "User setting in idetool";
                } else if (envSource != null) {
                    if (!envSource.startsWith("configurator override:")) {
                        value.source = "Platform default: " + envSource;
                    } else {
                        value.source = envSource;
                    }
                } else {
                    value.source = "User environment variable";
                }
            }
        }
    }

    /**
     * Writes the names and values of the flags into a table, so that
     * it's easy for C code to dump the values of the flags (for debugging 
     * purposes).
     */
    void writeOptionDump(PrintWriter writer) {
        writer.println();
        writer.println("/*");
        writer.println(" * The following table is for dumping the flags");
        writer.println(" * for debugging purposes.");
        writer.println(" */");
        writer.println();
        writer.println("#ifndef PRODUCT");
        writeDump(writer, flags, true);
        writer.println("#else");
        writeDump(writer, flags, false);
        writer.println("#endif");
    }

    void writeDump(PrintWriter writer, Vector flags, boolean isDebug) {
        writer.println("#define ENABLE_FLAG_VALUES { \\");
        for (int i=0; i<flags.size(); i++) {
            EnableFlag flag = (EnableFlag)flags.elementAt(i);
            FlagValue value;
            if (isDebug) {
                value = flag.debug;
            } else {
                value = flag.product;
            }
            writer.print("\t\"" + flag.name + "\", ");
            for (int j=flag.name.length(); j<30; j++) {
                writer.print(" ");
            }
            if (value.value) {
                writer.print("\"enabled\",");
            } else {
                writer.print("\"-\",");
            }
            writer.println(" \\");
        }
        writer.println("}");
    }
}

class EnableFlag {
    public String name;
    public String comments;
    public FlagValue debug;
    public FlagValue product;

    public EnableFlag() {
        debug = new FlagValue();
        product = new FlagValue();
    }
}

class FlagValue {
    public int def;
    public String source;
    public boolean value;
}
