/**
 * @fileoverview log4js is a library to log in JavaScript in similar manner
 * than in log4j for Java (but not really).
 *
 * <h3>Example:</h3>
 * <pre>
 *  const logging = require('log4js');
 *  const log = logging.getLogger('some-category');
 *
 *  //call the log
 *  log.trace('trace me' );
 * </pre>
 *
 * NOTE: the authors below are the original browser-based log4js authors
 * don't try to contact them about bugs in this version :)
 * @author Stephan Strittmatter - http://jroller.com/page/stritti
 * @author Seth Chisamore - http://www.chisamore.com
 * @since 2005-05-20
 * Website: http://log4js.berlios.de
 */
const debug = require('debug')('log4js:main');
const fs = require('fs');
const deepClone = require('rfdc')({ proto: true });
const configuration = require('./configuration');
const layouts = require('./layouts');
const levels = require('./levels');
const appenders = require('./appenders');
const categories = require('./categories');
const Logger = require('./logger');
const clustering = require('./clustering');
const connectLogger = require('./connect-logger');
const recordingModule = require('./appenders/recording');

let enabled = false;

function sendLogEventToAppender(logEvent) {
  if (!enabled) return;
  debug('Received log event ', logEvent);
  const categoryAppenders = categories.appendersForCategory(
    logEvent.categoryName
  );
  categoryAppenders.forEach((appender) => {
    appender(logEvent);
  });
}

function loadConfigurationFile(filename) {
  debug(`Loading configuration from ${filename}`);
  try {
    return JSON.parse(fs.readFileSync(filename, 'utf8'));
  } catch (e) {
    throw new Error(
      `Problem reading config from file "${filename}". Error was ${e.message}`,
      e
    );
  }
}

function configure(configurationFileOrObject) {
  if (enabled) {
    // eslint-disable-next-line no-use-before-define
    shutdown();
  }

  let configObject = configurationFileOrObject;

  if (typeof configObject === 'string') {
    configObject = loadConfigurationFile(configurationFileOrObject);
  }
  debug(`Configuration is ${configObject}`);

  configuration.configure(deepClone(configObject));

  clustering.onMessage(sendLogEventToAppender);

  enabled = true;

  // eslint-disable-next-line no-use-before-define
  return log4js;
}

function isConfigured() {
  return enabled;
}

function recording() {
  return recordingModule;
}

/**
 * This callback type is called `shutdownCallback` and is displayed as a global symbol.
 *
 * @callback shutdownCallback
 * @param {Error} [error]
 */

/**
 * Shutdown all log appenders. This will first disable all writing to appenders
 * and then call the shutdown function each appender.
 *
 * @param {shutdownCallback} [callback] - The callback to be invoked once all appenders have
 *  shutdown. If an error occurs, the callback will be given the error object
 *  as the first argument.
 */
function shutdown(callback = () => {}) {
  if (typeof callback !== 'function') {
    throw new TypeError('Invalid callback passed to shutdown');
  }
  debug('Shutdown called. Disabling all log writing.');
  // First, disable all writing to appenders. This prevents appenders from
  // not being able to be drained because of run-away log writes.
  enabled = false;

  // Clone out to maintain a reference
  const appendersToCheck = Array.from(appenders.values());

  // Reset immediately to prevent leaks
  appenders.init();
  categories.init();

  // Count the number of shutdown functions
  const shutdownFunctions = appendersToCheck.reduce(
    (accum, next) => (next.shutdown ? accum + 1 : accum),
    0
  );
  if (shutdownFunctions === 0) {
    debug('No appenders with shutdown functions found.');
    callback();
  }

  let completed = 0;
  let error;
  debug(`Found ${shutdownFunctions} appenders with shutdown functions.`);
  function complete(err) {
    error = error || err;
    completed += 1;
    debug(`Appender shutdowns complete: ${completed} / ${shutdownFunctions}`);
    if (completed >= shutdownFunctions) {
      debug('All shutdown functions completed.');
      callback(error);
    }
  }

  // Call each of the shutdown functions
  appendersToCheck
    .filter((a) => a.shutdown)
    .forEach((a) => a.shutdown(complete));
}

/**
 * Get a logger instance.
 * @static
 * @param {string} [category=default]
 * @return {Logger} instance of logger for the category
 */
function getLogger(category) {
  if (!enabled) {
    configure(
      process.env.LOG4JS_CONFIG || {
        appenders: { out: { type: 'stdout' } },
        categories: { default: { appenders: ['out'], level: 'OFF' } },
      }
    );
  }
  return new Logger(category || 'default');
}

/**
 * @name log4js
 * @namespace Log4js
 * @property getLogger
 * @property configure
 * @property shutdown
 */
const log4js = {
  getLogger,
  configure,
  isConfigured,
  shutdown,
  connectLogger,
  levels,
  addLayout: layouts.addLayout,
  recording,
};

module.exports = log4js;
