// Copyright Joyent, Inc. and other Node contributors.
// Modified by UnityBase core team to be compatible with SyNode

var util = require('util');

/**
 * @module console
 * @memberOf module:buildin
 */

/**
 * @classdesc
 * Console & log output functions. Puts something to log with log levels depending on method.
 * In case of command line - echo to `stdout`.
 *
 * Arguments, passed to console output functions are transformed to string using {@link util.format} call.
 *
 * Do not create this class directly - use global {@link console} already created by UB
 *
 * @example
console.log('%s is a %s usually with weight less then %dgr', 'apple', 'fruit', 100) //Will output "apple is a fruit usually with weight less than 100gr"
console.log('apple', 'fruit', 100) //will output "apple fruit 100"
console.debug('something') // will output to log only if "Debug" level is serverConfig.logging.levels
console.warn('warning message')
console.error('error message')
 * @class Console
 */
function Console(stdout, stderr, stdin) {
  if (!(this instanceof Console)) {
    return new Console(stdout, stderr, stdin);
  }
  if (!stdout || typeof stdout.write !== 'function') {
    throw new TypeError('Console expects a writable stream instance');
  }
  if (!stderr) {
    stderr = stdout;
  }
  var prop = {
    writable: true,
    enumerable: false,
    configurable: true
  };
  prop.value = stdout;
  Object.defineProperty(this, '_stdout', prop);
  prop.value = stderr;
  Object.defineProperty(this, '_stderr', prop);
  prop.value = stdin;
  Object.defineProperty(this, '_stdin', prop);
  prop.value = {};
  Object.defineProperty(this, '_times', prop);

  // bind the prototype functions to this Console instance
  Object.keys(Console.prototype).forEach(function(k) {
    this[k] = this[k].bind(this);
  }, this);
}

/**
 * Output to log with log level `Info`. Internally use util.format for create output, so
 * format chars can be used:
 *
 *  - %s - String.
 *  - %d - Number (both integer and float).
 *  - %j - JSON.
 *  - % - single percent sign ('%'). This does not consume an argument.
 *
 *      console.log('%s is a %s usually with weight less than %dgr', 'apple', 'fruit', 100);
 *      //Will output "apple is a fruit usually with weight less than 100gr"
 *
 *      console.log('apple', 'fruit', 100);
 *      //will output "apple fruit 100"
 *
 *      console.log('the object JSON is %j', {a: 12, b: {inner: 11}});
 *      // will output a JSON object instead of [object Object]
 *
 * @param {...*}
 */
Console.prototype.log = function() {
  if (process.logBits() & 0b10) //sllInfo
    this._stdout.write(util.format.apply(this, arguments));
};

/**
 * Output to log with log level `Debug`
 * @method
 * @param {...*}
 */
Console.prototype.debug = function() {
  if (process.logBits() & 0b100) //sllDebug
    this._stdout.write(util.format.apply(this, arguments), 2); //SyNode specific
}

/**
 * Output to log with log level `Info` (alias for console.log)
 * @method
 * @param {...*}
 */
Console.prototype.info = Console.prototype.log;


/**
 * Output to log with log level `Warning`. In case of OS console echo output to stderr
 * @param {...*}
 */
Console.prototype.warn = function() {
  if (process.logBits() & 0b10000) // sllWarning
  this._stderr.write(util.format.apply(this, arguments), 4); //SyNode specific
};

/**
 * Output to log with log level `Error`. In case of OS console echo output to stderr
 * @param {...*}
 */
Console.prototype.error = function() {
  if (process.logBits() & 0b100000) // sllError
    this._stderr.write(util.format.apply(this, arguments), 5); //SyNode specific
};

/**
 * Uses util.inspect on obj and prints resulting string to stdout.
 * @param {Object} object
 */
Console.prototype.dir = function(object) {
  this._stdout.write(util.inspect(object) + '\n');
};

/**
 * Mark a time.
 * @param {String} label
 */
Console.prototype.time = function(label) {
  this._times[label] = Date.now();
};

/**
 * Finish timer, record output
 * @example
 *
 *      console.time('100-elements');
 *        for (var i = 0; i < 100; i++) {
 *         ;
 *      }
 *      console.timeEnd('100-elements');
 *
 * @param {string} label
 */
Console.prototype.timeEnd = function(label) {
  var time = this._times[label];
  if (!time) {
    throw new Error('No such label: ' + label);
  }
  var duration = Date.now() - time;
  this.log('%s: %dms', label, duration);
};


Console.prototype.trace = function() {
  // TODO probably can to do this better with V8's debug object once that is
  // exposed.
  var err = new Error;
  err.name = 'Trace';
  err.message = util.format.apply(this, arguments);
  //MPV Error.captureStackTrace(err, arguments.callee);
  this.error(err.stack);
};

/**
 * Similar to {@link assert#ok}, but the error message is formatted as {@link util#format util.format(message...)}.
 * @param expression
 */
Console.prototype.assert = function(expression) {
  if (!expression) {
    var arr = Array.prototype.slice.call(arguments, 1);
    require('assert').ok(false, util.format.apply(this, arr));
  }
};

/**
 * Write to stdout
 * @since 5.23.4
 * @example
 *
 *   console.write('Hello! \n Today is', 2023)
 *
 * @param {...*} value
 */
Console.prototype.write = function () {
  this._stdout.write_out.apply(this, arguments)
}

/**
 * Read line from stdin. The end of the line is marked by any of the supported line ending styles,
 * independent of the platform on which the code is running (supported line ending styles are CRLF, LF or CR).
 *
 * The end-of-line marker is not considered part of the line and is ignored.
 *
 * @since 5.23.4
 * @example
console.write('Enter your name:')
const name = console.readLn()
console.write('Hello, ', name, '!')
 * @returns {string}
 */
Console.prototype.readLn = function() {
  return this._stdin.readLn()
}

/**
 * If `Trace` log level is enabled for application and `!f._skipEmitterTrace` - trace to log what function is called
 *
 * @since 5.23.9
 * @param {function} f
 * @param {string} eventName
 * @param {string} objName
 */
Console.prototype.traceFunc = function(f, eventName, objName) {
  if ((process.logBits() & 0b1000) && (!f._skipEmitterTrace)) { // sllTrace
    this._stdout.write(`${objName}.on(${eventName})->${f.name || 'anonymous'}`, 3) // 3 = sllTrace
  }
}

module.exports = new Console(process.stdout, process.stderr, process.stdin);
module.exports.Console = Console;