importPackage(java.lang);
importPackage(java.io);

debug = true;

register = emptyFunction = function() {}; // TODO

args = {};
for (var i = 0; i < arguments.length; i++) {
    var arg = /^(.+?)=(.*)$/.exec(arguments[i]);
    args[arg[1]] = arg[2];
}

logid = 0;

load(args.src + "/util.js");

function Reader(filename) {
    this.filename = filename;
    var fis = new FileInputStream(filename);
    var isr = new InputStreamReader(fis, "UTF-8");
    this.br = new BufferedReader(isr);
    this.id = Reader.count++;
    Reader.readers[this.id] = this.br;
    this.nextLine = this.br.readLine();
    this.lineNo = 0;
}

Reader.readers = {};
Reader.count = 0;

Reader.prototype.hasNext = function() { return this.nextLine != null; };

Reader.prototype.next = function() {
    var line = String(this.nextLine);
    this.nextLine = this.br.readLine();
    this.lineNo++;
    return line;
};

Reader.prototype.close = function() {
    if (this.br) {
        delete Reader.readers[this.id];
        this.br.close();
    }
};

Reader.prototype.getPos = function() {
    return format("%s:%d", this.filename, this.lineNo);
};

Reader.closeAll = function() {
    for (var i in Reader.readers) Reader.readers[i].close();
};

function Writer(filename) {
    var parent = new File(filename).getParentFile();
    if (parent && !parent.exists()) parent.mkdirs();
    var fos = new FileOutputStream(filename);
    var osw = new OutputStreamWriter(fos, "UTF-8");
    this.bw = new BufferedWriter(osw);
    this.id = Writer.count++;
    Writer.writers[this.id] = this.bw;
}

Writer.writers = {};
Writer.count = 0;

Writer.closeAll = function() {
    for (var i in Writer.writers) Writer.writers[i].close();
};

Writer.prototype.write = function(line) {
    line = String(line);
    this.bw.write(line, 0, line.length);
};

Writer.prototype.writeln = function(line) {
    line = String(line);
    this.bw.write(line, 0, line.length);
    this.bw.newLine();
};

Writer.prototype.close = function() {
    delete Writer.writers[this.id];
    this.bw.close();
};

function read(filename) {
    var r = new Reader(filename);
    var data = [];
    while (r.hasNext()) data.push(r.next());
    r.close();
    return data.join("\n");
}

function write(filename, data) {
    var w = new Writer(filename);
    w.write(String(data));
    w.close();
}

function escapeString(data) {
    var escapes = {"\b": "\\b", "\f": "\\f", "\n": "\\n", "\r": "\\r",
                   "\t": "\\t", "\"": '\\"', "\\": "\\\\" };
    return "\"" + String(data).replace(/[\x00-\x1f\\"]/g, function(c) {
        var n = Number(c.charCodeAt(0)).toString(16);
        return escapes[c] || ("\\u00" + (n.length < 2 ? "0" + n : n));
    }) + "\"";
}

if (args.log) {
    logging = {};
    evalFile = function(name, src, meta, self) {
        var filename = format("%s/%s-%d.log.js", args.log, name, ++logid);
        write(filename,
            format("logging[%d] = function(meta){%s};", logid, src));
        load(filename);
        return logging[logid].call(self, meta);
    };
} else {
    evalFile = function(name, src, meta, self) {
        return Function("meta", src).call(self, meta);
    };
}

function process(name, meta) {
    var handlers = {
        meta: function(src) {
            return evalFile(name, src, meta, this);
        },
        both: function(src) {
            evalFile(name, src, meta, this);
            return src;
        },
        src: escapeString,
        str: function(src) {
            return escapeString(src.substring(2, src.length - 2));
        },
        include: function(src) { return process(src, meta); }
    };
    var r = new Reader(format("%s/%s.js", args.src, name));
    var buf = [];
    var stack = [];
    var re = new RegExp("\\/\\*(\\w+)\\*\\/", "g");
    while (r.hasNext()) {
        var line = r.next();
        var start = 0;
        re.lastIndex = 0;
        var match = re.exec(line);
        while (match) {
            if (match.index > start) {
                buf.push(line.substring(start, match.index));
            }
            start = match.index + match[0].length;
            if (match[1] == "end") {
                if (!stack.length) throw format(_("Unexpected /*end*/ in %s."),
                                                r.getPos());
                var top = stack.pop();
                var src = buf.join("");
                var s = top.handler(src);
                buf = top.buf;
                if (s) buf.push(s);
            } else {
                var handler = handlers[match[1]];
                if (handler) {
                    stack.push({ handler: handler, buf: buf, tag: match[1],
                                 pos: r.getPos() });
                    buf = [];
                } else {
                    buf.push(match[0]);
                }
            }
            match = re.exec(line);
        }
        buf.push(line.substring(start));
        buf.push("\n");
    }
    r.close();
    if (stack.length) {
        var top = stack.pop();
        throw format(_("Unclosed /*%s*/ in %s."), top.tag, top.pos);
    }
    return buf.join("");
}

var finalizers = [];
function addFinally(callback) { finalizers.push(callback); }

try {
    process(args.run || "main");
} finally {
    try {
        for (var i = 0; i < finalizers.length; i++) finalizers[i]();
    } finally {
        Writer.closeAll();
        Reader.closeAll();
    }
}