/**
 * @fileOverview This module provides functions to write on the standard output stream <code>stdout</code>
 * and error stream <code>stderr</code> for error logging and quick debugging.
 * It’s similar to the console object implemented in most web browsers.
 */
const {TermWriter, BOLD, ONRED, ONYELLOW, RESET} = require("ringo/term");

const writer = new TermWriter(require("system").stdout);
const errWriter = new TermWriter(require("system").stderr);
const {traceHelper, assertHelper} = org.ringojs.util.ScriptUtils;
const timers = {};

function format() {
    let msg = arguments[0] ? String(arguments[0]) : "";
    let pattern = /%[sdifo]/;
    for (let i = 1; i < arguments.length; i++) {
        msg = pattern.test(msg)
                ? msg.replace(pattern, String(arguments[i]))
                : msg + " " + arguments[i];
    }
    return msg;
}

/**
 * Logs a message to the console on <code>stdout</code>.
 *
 * The first argument to log may be a string containing printf-like placeholders.
 * Otherwise, multipel arguments will be concatenated separated by spaces.
 * @param {*...} msg... one or more message arguments
 * @example >> console.log('Hello World!');
 * Hello World!
 * >> console.log('A: %s, B: %s, C: %s', 'a', 'b', 'c');
 * A: a, B: b, C: c
 * >> console.log('Current nanoseconds: %d', java.lang.System.nanoTime());
 * Current nanoseconds: 9607196939209
 */
exports.log = function() {
    writer.writeln(format.apply(null, arguments));
};

/**
 * Logs a message with the visual "error" representation, including the file name
 * and line number of the calling code. Prints on <code>stderr</code>.
 * @param {*...} msg... one or more message arguments
 * @function
 * @example >> console.error('Hello World!');
 * [error] Hello World! (&#60;stdin&#62;:1)
 * >> console.error('A: %s, B: %s, C: %s', 'a', 'b', 'c');
 * [error] A: a, B: b, C: c (&#60;stdin&#62;:3)
 * >> console.error('Current nanoseconds: %d', java.lang.System.nanoTime());
 * [error] Current nanoseconds: 9228448561643 (&#60;stdin&#62;:5)
 */
exports.error = traceHelper.bind(null, function() {
    const msg = format.apply(null, arguments);
    const location = format("(%s:%d)", this.sourceName(), this.lineNumber());
    errWriter.writeln(ONRED, BOLD, "[error]" + RESET, BOLD, msg, RESET, location);
});

/**
 * Logs a message with the visual "warn" representation, including the file name
 * and line number of the calling code. Prints on <code>stderr</code>.
 * @param {*...} msg... one or more message arguments
 * @function
 * @example >> console.warn('Hello World!');
 * [warn] Hello World! (&#60;stdin&#62;:1)
 * >> console.warn('A: %s, B: %s, C: %s', 'a', 'b', 'c');
 * [warn] A: a, B: b, C: c (&#60;stdin&#62;:3)
 * >> console.warn('Current nanoseconds: %d', java.lang.System.nanoTime());
 * [warn] Current nanoseconds: 9294672097821 (&#60;stdin&#62;:5)
 */
exports.warn = traceHelper.bind(null, function() {
    const msg = format.apply(null, arguments);
    const location = format("(%s:%d)", this.sourceName(), this.lineNumber());
    errWriter.writeln(ONYELLOW, BOLD, "[warn]" + RESET, BOLD, msg, RESET, location);
});

/**
 * Logs a message with the visual "info" representation, including the file name
 * and line number of the calling code. Prints on <code>stdout</code>.
 * @param {*...} msg... one or more message arguments
 * @function
 * @example >> console.info('Hello World!');
 * [info] Hello World! (&#60;stdin&#62;:1)
 * >> console.info('A: %s, B: %s, C: %s', 'a', 'b', 'c');
 * [info] A: a, B: b, C: c (&#60;stdin&#62;:3)
 * >> console.info('Current nanoseconds: %d', java.lang.System.nanoTime());
 * [info] Current nanoseconds: 9677228481391 (&#60;stdin&#62;:5)
 */
exports.info = traceHelper.bind(null, function() {
    const msg = format.apply(null, arguments);
    const location = format("(%s:%d)", this.sourceName(), this.lineNumber());
    writer.writeln("[info]", BOLD, msg, RESET, location);
});

/**
 * Prints a stack trace of JavaScript execution at the point where it is called.
 * Prints on <code>stdout</code>.
 * @param {*...} msg... optional message arguments
 * @function
 */
exports.trace = traceHelper.bind(null, function() {
    const msg = format.apply(null, arguments);
    writer.writeln("Trace: " + msg);
    writer.write(this.scriptStackTrace);
});

/**
 * Tests that an expression is true and throws an <code>AssertionError</code>
 * exception if not. It uses the ECMAScript <code>toBoolean()</code> convertion.
 * @param {Boolean} expression the expression to test
 * @param {*...} msg... one or more error messages
 * @function
 * @example >> const x = 10;
 * >> console.assert(x > 0, 'failed!'); // passes
 * >> console.assert(x < 0, 'failed!'); // fails
 *    AssertionError: failed! at &#60;stdin&#62;:12
 *
 * >> console.assert(false, 'failed!'); // fails
 *    AssertionError: failed! at &#60;stdin&#62;:13
 *
 * >> // passes; any Object expression is true
 * >> console.assert(new Boolean(false), 'failed!');
 */
exports.assert = assertHelper;

/**
 * Creates a new timer under the given name. Call `console.timeEnd(name)` with
 * the same name to stop the timer and log the time elapsed.
 * @param {String} name the timer name
 * @example >> console.time('timer-1');
 * >> // Wait some time ...
 * >> console.timeEnd('timer-1');
 * timer-1: 15769ms
 */
exports.time = function(name) {
    if (name && !timers[name]) {
        timers[name] = java.lang.System.nanoTime();
    }
};

/**
 * Stops a timer created by a call to `console.time(name)` and logs the time elapsed.
 * @param {String} name the timer name
 * @example >> console.time('timer-1');
 * >> // Wait some time ...
 * >> console.timeEnd('timer-1');
 * timer-1: 15769ms
 */
exports.timeEnd = function(name) {
    const start = timers[name];
    if (start) {
        const time = Math.round((java.lang.System.nanoTime() - start) / 1000000);
        writer.writeln(name + ": " + time + "ms");
        delete timers[name];
        return time;
    }
};

/**
 * Prints a list of all properties of an object on <code>stdout</code>.
 * @param {Object} obj the object whose properties should be output
 * @example >> const obj = { foo: "bar", baz: 12345 };
 * >> console.dir(obj);
 * { foo: 'bar', baz: 12345 }
 */
exports.dir = function(obj) {
    require("ringo/shell").printResult(obj, writer);
};
