/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package Mini;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Vector;

import org.apache.bcel.Const;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;

public class MiniC {
    private static Vector<String> errors = null;
    private static Vector<String> warnings = null;
    private static String file = null;
    private static int pass = 0;

    final static void addError(final int line, final int column, final String err) {
        if (pass != 2) {
            errors.addElement(file + ":" + fillup(line, 3) + "," + fillup(column, 2) + ": " + err);
        }
    }

    final static void addWarning(final int line, final int column, final String err) {
        warnings.addElement("Warning: " + file + ":" + fillup(line, 3) + "," + fillup(column, 3) + ": " + err);
    }

    final static void addWarning(final String err) {
        warnings.addElement(err);
    }

    final static String fillup(final int n, final int len) {
        final String str = Integer.toString(n);
        final int diff = len - str.length();

        if (diff <= 0) {
            return str;
        }
        final char[] chs = new char[diff];

        for (int i = 0; i < diff; i++) {
            chs[i] = ' ';
        }

        return new String(chs) + str;
    }

    public static void main(final String[] argv) {
        final String[] file_name = new String[argv.length];
        int files = 0;
        MiniParser parser = null;
        String base_name = null;
        boolean byte_code = true;

        try {
            /*
             * Parse command line arguments.
             */
            for (final String element : argv) {
                if (element.charAt(0) == '-') { // command line switch
                    if (element.equals("-java")) {
                        byte_code = false;
                    } else if (element.equals("-bytecode")) {
                        byte_code = true;
                    } else {
                        throw new Exception("Unknown option: " + element);
                    }
                } else { // add file name to list
                    file_name[files++] = element;
                }
            }

            if (files == 0) {
                System.err.println("Nothing to compile.");
            }

            for (int j = 0; j < files; j++) {
                errors = new Vector<>();
                warnings = new Vector<>();
                pass = 0;

                if (j == 0) {
                    parser = new MiniParser(new FileInputStream(file_name[0]));
                } else {
                    MiniParser.ReInit(new FileInputStream(file_name[j]));
                }

                int index = file_name[j].lastIndexOf('.');
                if (index > 0) {
                    base_name = file_name[j].substring(0, index);
                } else {
                    base_name = file_name[j];
                }

                if ((index = base_name.lastIndexOf(File.separatorChar)) > 0) {
                    base_name = base_name.substring(index + 1);
                }

                file = file_name[j];

                System.out.println("Parsing ...");
                MiniParser.Program();
                ASTProgram program = (ASTProgram) MiniParser.jjtree.rootNode();

                System.out.println("Pass 1: Optimizing parse tree ...");
                pass = 1;
                program = program.traverse();
                // program.dump(">");

                if (errors.isEmpty()) {
                    System.out.println("Pass 2: Type checking (I) ...");
                    program.eval(pass = 2);
                }

                if (errors.isEmpty()) {
                    System.out.println("Pass 3: Type checking (II) ...");
                    program.eval(pass = 3);
                }

                errors.forEach(System.out::println);

                warnings.forEach(System.out::println);

                if (errors.isEmpty()) {
                    if (byte_code) {
                        System.out.println("Pass 5: Generating byte code ...");
                        final ClassGen class_gen = new ClassGen(base_name, "java.lang.Object", file_name[j],
                                Const.ACC_PUBLIC | Const.ACC_FINAL | Const.ACC_SUPER, null);
                        final ConstantPoolGen cp = class_gen.getConstantPool();

                        program.byte_code(class_gen, cp);
                        final JavaClass clazz = class_gen.getJavaClass();
                        clazz.dump(base_name + ".class");
                    } else {
                        System.out.println("Pass 5: Generating Java code ...");
                        try (final PrintWriter out = new PrintWriter(new FileOutputStream(base_name + ".java"))) {
                            program.code(out, base_name);
                        }

                        System.out.println("Pass 6: Compiling Java code ...");

                        final String[] args = { "javac", base_name + ".java" };
                        // sun.tools.javac.Main compiler = new sun.tools.javac.Main(System.err, "javac");
                        try {
                            final Process p = Runtime.getRuntime().exec(args);
                            p.waitFor();
                        } catch (final Exception e) {
                            System.out.println(e);
                        }

                        // compiler.compile(args);
                    }
                }

                if (!errors.isEmpty() || !warnings.isEmpty()) {
                    System.out.println(errors.size() + " errors and " + warnings.size() + " warnings.");
                }
            }
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }
}
