#!/usr/bin/env qore

/** Used to compile Java source files to class files and then create a jar from the built classes

    Arguments not explicitly processed by this script should be in the format accepted by javac; all unrecognized
    arguments are passed directly as-is to the compiler
*/

%new-style
%require-types
%strict-args
%enable-all-warnings

# automatically set up CLASSPATH to required jars
%module-cmd(jni) add-relative-classpath ../share/qore/java/qore-jni.jar
%module-cmd(jni) add-relative-classpath ../share/qore/java/qore-jni-compiler.jar

%module-cmd(jni) import org.qore.jni.compiler.QoreJavaCompiler
%module-cmd(jni) import org.qore.jni.compiler.CompilerOutput
%module-cmd(jni) import org.qore.jni.compiler.QoreJavaCompilerException
%module-cmd(jni) import java.io.InputStream
%module-cmd(jni) import java.io.FileOutputStream

%requires Util

%exec-class QJavaCompilerCmd

class QJavaCompilerCmd {
    public {
        hash<string, string> srcs;
        hash<auto> opts;
        string output_dir;

        const ERR_COMPILATION_FAILURE = 1;
        const ERR_INVALID_SOURCE      = 2;
        const ERR_NO_SOURCES          = 3;

        const BufSize = 16384;
    }

    constructor() {
        parseOptions();
        if (!srcs) {
            usage();
            set_return_value(0);
            return;
        }

        if (!srcs) {
            if (!opts.quiet) {
                error(ERR_NO_SOURCES, "no sources to compile");
            }
            set_return_value(1);
            return;
        }

        if (!opts.quiet) {
            map printf("%s: %d bytes source\n", $1.key, $1.value.size()), srcs.pairIterator();
        }

        QoreJavaCompiler compiler(ARGV);
        DiagnosticCollector diags();
        string file_list;
        try {
            hash<string, CompilerOutput> cv = compiler.compile(srcs, diags);
            foreach hash<auto> i in (cv.pairIterator()) {
                string class_file_name = i.key;
                class_file_name =~ s/\./\//g;
                class_file_name += ".class";
                string fn = class_file_name;
                io::InputStream stream = i.value.file.openInputStream();
                on_exit stream.close();

                io::File targetFile(output_dir + DirSep + fn);
                io::FileOutputStream outStream(targetFile);
                on_exit outStream.close();

                while (binary b = stream.readNBytes(BufSize)) {
                    outStream.write(b, 0, b.size());
                }

                if (file_list) {
                    file_list += " ";
                }
                # must escape "$" chars in the name
                class_file_name = sprintf("%y", class_file_name);
                class_file_name =~ s/\$/\\$/g;
                file_list += class_file_name;
                if (!opts.quiet) {
                    printf("wrote: %y\n", fn);
                }
            }
        } catch (hash<ExceptionInfo> ex) {
            if (ex.arg instanceof QoreJavaCompilerException) {
                map stderr.printf("%s:%d: %s\n", $1.getSource().getName(), $1.getLineNumber(), $1.getMessage()),
                    diags.getDiagnostics();
                stderr.printf("%s\n", get_exception_string(ex));
                set_return_value(2);
                return;
            }
            rethrow;
        }
    }

    error(int err, string fmt) {
        if (!opts.quiet) {
            stderr.printf("ERROR: %s\n", vsprintf(fmt, argv));
        }
        exit(err);
    }

    parseOptions() {
        int l = ARGV.size();
        for (int i = 0; i < l; ) {
            string arg = ARGV[i];
            if (arg == "-h" || arg == "--help") {
                usage();
                exit(0);
            }
            if (arg == "-q" || arg == "--quiet") {
                opts.quiet = True;
                splice ARGV, i, 1;
                --l;
                continue;
            }
            if (arg =~ /^-d.+/) {
                output_dir = arg[2..];
                output_dir =~ s/^=//;
                if (!output_dir.val()) {
                    stderr.print("missing argument to %y\n", arg);
                }
                splice ARGV, i, 1;
                --l;
                continue;
            }
            if (arg =~ /^--output=.+/) {
                output_dir = arg[9..];
                splice ARGV, i, 1;
                --l;
                continue;
            }
            if (arg == "-d" || arg == "--output") {
                splice ARGV, i, 1;
                *string str = (extract ARGV, i, 1)[0];
                if (!exists str) {
                    stderr.print("missing argument to %y\n", arg);
                }
                l -= 2;
                output_dir = str;
                continue;
            }
            if (arg[0] != "-" && arg =~ /\.java$/i) {
                string name = basename(arg);
                name =~ s/\.java$//;
                string src = File::readTextFile(arg);
                if (*string package = (src =~ x/package +(.*);/)[0]) {
                    name = package + "." + name;
                }
                srcs{name} = src;

                if (!output_dir) {
                    output_dir = dirname(arg);
                }
                splice ARGV, i, 1;
                --l;
                continue;
            }
            ++i;
        }
    }

    static usage() {
        printf("%s: <jar name> <source files...>\n"
            " -d,--output=<DIR>  output file directory\n"
            " -q,--quiet         suppress output\n"
            " -h,--help          this help text\n"
            "\n*NOTE*: Arguments not explicitly listed above should be in the format accepted by javac;\n"
              "        all unrecognized arguments are passed unmodified to the compiler\n",
            get_script_name()
        );
    }
}