'use strict';

var f = require('util').format,
  MongoError = require('../error').MongoError;

// Filters for classes
var classFilters = {};
var filteredClasses = {};
var level = null;
// Save the process id
var pid = process.pid;
// current logger
var currentLogger = null;

/**
 * @callback Logger~loggerCallback
 * @param {string} msg message being logged
 * @param {object} state an object containing more metadata about the logging message
 */

/**
 * Creates a new Logger instance
 * @class
 * @param {string} className The Class name associated with the logging instance
 * @param {object} [options=null] Optional settings.
 * @param {Logger~loggerCallback} [options.logger=null] Custom logger function;
 * @param {string} [options.loggerLevel=error] Override default global log level.
 */
var Logger = function(className, options) {
  if (!(this instanceof Logger)) return new Logger(className, options);
  options = options || {};

  // Current reference
  this.className = className;

  // Current logger
  if (options.logger) {
    currentLogger = options.logger;
  } else if (currentLogger == null) {
    currentLogger = console.log;
  }

  // Set level of logging, default is error
  if (options.loggerLevel) {
    level = options.loggerLevel || 'error';
  }

  // Add all class names
  if (filteredClasses[this.className] == null) classFilters[this.className] = true;
};

/**
 * Log a message at the debug level
 * @method
 * @param {string} message The message to log
 * @param {object} object additional meta data to log
 * @return {null}
 */
Logger.prototype.debug = function(message, object) {
  if (
    this.isDebug() &&
    ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className]) ||
      (Object.keys(filteredClasses).length === 0 && classFilters[this.className]))
  ) {
    var dateTime = new Date().getTime();
    var msg = f('[%s-%s:%s] %s %s', 'DEBUG', this.className, pid, dateTime, message);
    var state = {
      type: 'debug',
      message: message,
      className: this.className,
      pid: pid,
      date: dateTime
    };
    if (object) state.meta = object;
    currentLogger(msg, state);
  }
};

/**
 * Log a message at the warn level
 * @method
 * @param {string} message The message to log
 * @param {object} object additional meta data to log
 * @return {null}
 */
(Logger.prototype.warn = function(message, object) {
  if (
    this.isWarn() &&
    ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className]) ||
      (Object.keys(filteredClasses).length === 0 && classFilters[this.className]))
  ) {
    var dateTime = new Date().getTime();
    var msg = f('[%s-%s:%s] %s %s', 'WARN', this.className, pid, dateTime, message);
    var state = {
      type: 'warn',
      message: message,
      className: this.className,
      pid: pid,
      date: dateTime
    };
    if (object) state.meta = object;
    currentLogger(msg, state);
  }
}),
  /**
   * Log a message at the info level
   * @method
   * @param {string} message The message to log
   * @param {object} object additional meta data to log
   * @return {null}
   */
  (Logger.prototype.info = function(message, object) {
    if (
      this.isInfo() &&
      ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className]) ||
        (Object.keys(filteredClasses).length === 0 && classFilters[this.className]))
    ) {
      var dateTime = new Date().getTime();
      var msg = f('[%s-%s:%s] %s %s', 'INFO', this.className, pid, dateTime, message);
      var state = {
        type: 'info',
        message: message,
        className: this.className,
        pid: pid,
        date: dateTime
      };
      if (object) state.meta = object;
      currentLogger(msg, state);
    }
  }),
  /**
   * Log a message at the error level
   * @method
   * @param {string} message The message to log
   * @param {object} object additional meta data to log
   * @return {null}
   */
  (Logger.prototype.error = function(message, object) {
    if (
      this.isError() &&
      ((Object.keys(filteredClasses).length > 0 && filteredClasses[this.className]) ||
        (Object.keys(filteredClasses).length === 0 && classFilters[this.className]))
    ) {
      var dateTime = new Date().getTime();
      var msg = f('[%s-%s:%s] %s %s', 'ERROR', this.className, pid, dateTime, message);
      var state = {
        type: 'error',
        message: message,
        className: this.className,
        pid: pid,
        date: dateTime
      };
      if (object) state.meta = object;
      currentLogger(msg, state);
    }
  }),
  /**
   * Is the logger set at info level
   * @method
   * @return {boolean}
   */
  (Logger.prototype.isInfo = function() {
    return level === 'info' || level === 'debug';
  }),
  /**
   * Is the logger set at error level
   * @method
   * @return {boolean}
   */
  (Logger.prototype.isError = function() {
    return level === 'error' || level === 'info' || level === 'debug';
  }),
  /**
   * Is the logger set at error level
   * @method
   * @return {boolean}
   */
  (Logger.prototype.isWarn = function() {
    return level === 'error' || level === 'warn' || level === 'info' || level === 'debug';
  }),
  /**
   * Is the logger set at debug level
   * @method
   * @return {boolean}
   */
  (Logger.prototype.isDebug = function() {
    return level === 'debug';
  });

/**
 * Resets the logger to default settings, error and no filtered classes
 * @method
 * @return {null}
 */
Logger.reset = function() {
  level = 'error';
  filteredClasses = {};
};

/**
 * Get the current logger function
 * @method
 * @return {Logger~loggerCallback}
 */
Logger.currentLogger = function() {
  return currentLogger;
};

/**
 * Set the current logger function
 * @method
 * @param {Logger~loggerCallback} logger Logger function.
 * @return {null}
 */
Logger.setCurrentLogger = function(logger) {
  if (typeof logger !== 'function') throw new MongoError('current logger must be a function');
  currentLogger = logger;
};

/**
 * Set what classes to log.
 * @method
 * @param {string} type The type of filter (currently only class)
 * @param {string[]} values The filters to apply
 * @return {null}
 */
Logger.filter = function(type, values) {
  if (type === 'class' && Array.isArray(values)) {
    filteredClasses = {};

    values.forEach(function(x) {
      filteredClasses[x] = true;
    });
  }
};

/**
 * Set the current log level
 * @method
 * @param {string} level Set current log level (debug, info, error)
 * @return {null}
 */
Logger.setLevel = function(_level) {
  if (_level !== 'info' && _level !== 'error' && _level !== 'debug' && _level !== 'warn') {
    throw new Error(f('%s is an illegal logging level', _level));
  }

  level = _level;
};

module.exports = Logger;
