/**
 * Copyright (c) 2004-2005, Regents of the University of California
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the University of California, Los Angeles nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jintgen;

import cck.help.HelpCategory;
import cck.test.TestEngine;
import cck.text.*;
import cck.util.*;
import jintgen.gen.*;
import jintgen.gen.disassembler.DisassemblerGenerator;
import jintgen.isdl.ArchDecl;
import jintgen.isdl.parser.ISDLParser;
import jintgen.isdl.parser.ParseException;
import jintgen.isdl.verifier.Verifier;
import jintgen.isdl.verifier.VerifierTestHarness;
import java.io.*;

/**
 * This is the main entrypoint to Jintgen. It is responsible for parsing the options to the main program and
 * loading the architecture description.
 *
 * @author Ben L. Titzer
 */
public class Main {

    static final Options mainOptions = new Options();

    public static final Option.Str CODEMAP = mainOptions.newOption("codemap", "",
            "This option specifies the file to generate the codemap into. The codemap is used in a" +
            "dynamic basic block compiler and dependency analysis of instructions.");
    public static final Option.Bool INLINE = mainOptions.newOption("inline", true,
            "This option controls whether the ISDL processor will inline all subroutines marked as " +
            "\"inline\" in their declaration.");

    public static final Option.Bool COLORS = mainOptions.newOption("colors", true,
            "This option is used to enable or disable the terminal colors.");
    public static final Option.Bool BANNER = mainOptions.newOption("banner", true,
            "This option is used to enable or disable the printing of the banner.");
    public static final Option.List VERBOSE = mainOptions.newOptionList("verbose", "",
            "This option allows users to enable verbose printing of individual " +
            "subsystems within jIntGen. A list can be given with individual items separated " +
            "by commas. For example: -verbose=loader,optimizer");
    public static final Option.Bool HELP = mainOptions.newOption("help", false,
            "Displays this help message.");
    public static final Option.Bool LICENSE = mainOptions.newOption("license", false,
            "Display the detailed copyright and license text.");
    public static final Option.Bool HTML = mainOptions.newOption("html", false,
            "For terminal colors. Display terminal colors as HTML tags for " +
            "easier inclusion in webpages.");
    public static final Option.List GENERATORS = mainOptions.newOptionList("generate", "",
            "This option accepts a list of generators to be applied to the specified architecture " +
            "description file. Each generator may generate a tool, such as an assembler or disassembler " +
            "or perform an analysis on the architecture description. Each generator may support " +
            "further options that allow its operation to be customized.");
    public static final Option.Bool TEST = mainOptions.newOption("test", false,
            "When specified, this option activates the internal testing framework of jIntGen.");

    public static ClassMap generatorMap = new ClassMap("Generator", Generator.class);

    static {
        generatorMap.addClass("ir", InstrIRGenerator.class);
        generatorMap.addClass("codemap", CodemapGenerator.class);
        generatorMap.addClass("disassembler", DisassemblerGenerator.class);
        generatorMap.addClass("interpreter", InterpreterGenerator.class);
    }

    /**
     * The <code>main()</code> method is the entrypoint into jIntGen. It processes the command line options,
     * looks up the action, and prints help (if there are no arguments or the <code>-help</code> option is
     * specified.
     *
     * @param args an array of strings representing the command line arguments passed by the user
     */
    public static void main(String[] args) {
        try {
            // parse the command line options
            parseOptions(args);

            if ( TEST.get() ) {
                ClassMap harnessMap = new ClassMap("Test Harness", TestEngine.Harness.class);
                harnessMap.addClass("verifier", VerifierTestHarness.class);
                new TestEngine(harnessMap).runTests(mainOptions.getArguments());
            } else if (args.length == 0 || HELP.get()) {
                // print the help if there are no arguments or -help is specified
                printHelp(mainOptions.getArguments());
            } else {
                // otherwise run the specified action
                run(mainOptions.getArguments());
            }

        } catch (Util.Error e) {
            // report any internal errors
            e.report();
        } catch (Exception e) {
            // report any other exceptions
            e.printStackTrace();
        }
    }

    private static void run(String[] args) throws Exception {
        banner();
        if (args.length < 1)
            Util.userError("Usage: jintgen <arch.isdl>");

        ArchDecl.INLINE = INLINE.get();

        try {
            ArchDecl a = loadArchitecture(args[0]);
            runGenerators(a);
        } catch ( Util.Error t) {
            Status.error(t);
            t.report();
        } catch ( Throwable t) {
            Status.error(t);
            t.printStackTrace();
        }
    }

    private static ArchDecl loadArchitecture(String fname) throws FileNotFoundException, ParseException {
        Status.begin("Loading architecture description "+fname);
        checkFileExists(fname);
        File archfile = new File(fname);
        FileInputStream fis = new FileInputStream(archfile);
        ISDLParser parser = new ISDLParser(fis);
        ArchDecl a = parser.ArchDecl();
        Status.success();
        Status.begin("Verifying "+fname);
        new Verifier(a).verify();
        Status.success();
        return a;
    }

    private static void runGenerators(ArchDecl a) throws Exception {
        for ( Object o : GENERATORS.get() ) {
            String str = (String)o;
            Status.begin("Running "+str+" generator");
            Generator g = (Generator)generatorMap.getObjectOfClass(str);
            g.setArchitecture(a);
            g.processOptions(mainOptions);
            g.generate();
            Status.success();
        }
    }

    static HelpCategory buildHelpCategory() {
        HelpCategory hc = new HelpCategory("main", "");
        hc.addSection("OVERVIEW","jIntGen is a tool for generating interpreters, assemblers, " +
                "disassemblers, and other tools for " +
                "assembly language and machine code programs. jIntGen has a domain-specific language " +
                "for describing instruction sets from which it can generate interpreters and " +
                "other tools.");
        hc.addOptionSection("The main options to jIntGen specify the file containing the architecture description " +
                "to be processed and what tools to generate. To access help for the options " +
                "related to other subcategories, specify the name of the subcategory along with " +
                "the \"help\" option.", mainOptions);

        // TODO: add subcategory section for generators
        return hc;
    }

    static void printHelp(String[] args) {
        title();
        printUsage();

        buildAllCategories();

        if (args.length == 0) {
            buildHelpCategory().printHelp();
        } else if (args.length == 1) {
            printHelp(args[0]);
        } else {
            Util.userError("help available for only one category at a time.");
        }

        printFooter();
    }

    private static void buildAllCategories() {
        // TODO: add all subcategories
    }

    private static void printUsage() {
        int[] colors = {Terminal.COLOR_RED,
                        -1,
                        Terminal.COLOR_GREEN,
                        -1,
                        Terminal.COLOR_YELLOW,
                        -1,
                        Terminal.COLOR_YELLOW,
                        -1};

        String[] strs = {"Usage", ": ", "jintgen", " [", "options", "] ", "<architecture file>"};
        Terminal.print(colors, strs);
        Terminal.nextln();

        int[] colors2 = {Terminal.COLOR_RED,
                         -1,
                         Terminal.COLOR_GREEN,
                         -1,
                         Terminal.COLOR_YELLOW,
                         -1};

        String[] strs2 = {"Usage", ": ", "jintgen -help", " [", "category", "]"};
        Terminal.print(colors2, strs2);
        Terminal.println("\n");
    }

    private static void printFooter() {
        Terminal.println("For more information, see the online documentation at ");
        Terminal.printCyan("http://compilers.cs.ucla.edu/jintgen");
        Terminal.nextln();
        Terminal.println("To report bugs or seek help, consult the jIntGen mailing list: ");
        Terminal.printCyan("http://lists.ucla.edu/cgi-bin/mailman/listinfo/jintgen");
        Terminal.nextln();
        Terminal.print("Please include the version number [");
        Terminal.printBrightBlue(Version.TAG.toString());
        Terminal.print("] when posting to the list.");
        Terminal.nextln();
    }

    private static void printHelp(String a) {
        // TODO: get help for main categories
    }

    static void banner() {
        if (! BANNER.get() ) return;

        title();
        String notice;
        if (!LICENSE.get())
            notice =
                    "This interpreter generator tool is provided with absolutely no " +
                    "warranty, either expressed or implied. It is provided to you with the hope " +
                    "that it be useful for generating tools to work with machine code programs. " +
                    "For more information about the license " +
                    "that this software is provided to you under, specify the \"license\" " +
                    "option.\n\n";
        else
            notice =
                    "Copyright (c) 2005, Regents of the University of California \n" +
                    "All rights reserved.\n\n" +

                    "Redistribution and use in source and binary forms, with or without " +
                    "modification, are permitted provided that the following conditions " +
                    "are met:\n\n" +

                    "Redistributions of source code must retain the above copyright notice, " +
                    "this list of conditions and the following disclaimer.\n\n" +

                    "Redistributions in binary form must reproduce the above copyright " +
                    "notice, this list of conditions and the following disclaimer in the " +
                    "documentation and/or other materials provided with the distribution.\n\n" +

                    "Neither the name of the University of California, Los Angeles nor the " +
                    "names of its contributors may be used to endorse or promote products " +
                    "derived from this software without specific prior written permission.\n\n" +

                    "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " +
                    "\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT " +
                    "LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR " +
                    "A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT " +
                    "OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, " +
                    "SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT " +
                    "LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, " +
                    "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY " +
                    "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT " +
                    "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE " +
                    "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n";

        Terminal.print(StringUtil.formatParagraphs(notice, 0, 0, Terminal.MAXLINE));
    }

    static void title() {
        Terminal.printBrightBlue("jIntGen ");
        Terminal.print("[");
        Terminal.printBrightBlue(Version.TAG.toString());
        Terminal.print("]");
        Terminal.print(" - (c) 2005-2007 UCLA Compilers Group\n\n");
    }

    /**
     * The <code>parseOptions()</code> method takes an array of strings and parses it, extracting the options
     * and storing the option values in the internal state of main.
     *
     * @param args the array of strings to parse into options
     */
    public static void parseOptions(String[] args) {
        mainOptions.parseCommandLine(args);
        Terminal.useColors = COLORS.get();
        Terminal.htmlColors = HTML.get();
        for (String str : VERBOSE.get())
            Verbose.setVerbose(str, true);
    }

    public static void checkFilesExist(String[] files) {
        for ( int cntr = 0; cntr < files.length; cntr++ ) {
            checkFileExists(files[cntr]);
        }
    }

    public static void checkFileExists(String fstr) {
        File f = new File(fstr);
        if ( !f.exists() ) {
            Util.userError("File not found", fstr);
        }
    }
}
