/*
 * @Author: wcy
 */

'use strict';

const oriDebug = require('debug');
const util = require('util');

const PRIORITY_V = '[V]'; //verbose---0
const PRIORITY_D = '[D]'; //debug---1
const PRIORITY_I = '[I]'; //info---2
const PRIORITY_W = '[W]'; //warning---3
const PRIORITY_E = '[E]'; //error---4
const PRIORITY_F = '[F]'; //fatal---5
const PRIORITY_S = '[S]'; //silent---6

const LOGLEVEL = ['verbose', 'debug', 'info', 'warning', 'error', 'fatal', 'silent'];

const LogDebug = 'nongfu.common.LogFactory'; //namespace

//origun debug——'formatArgs' function
const _formatArgs = oriDebug.formatArgs;

/**
 * Create a debugger with the given `opts,namespace`.
 *
 * @param {object} opts
 * @param {String} namespace
 * @return {Function}
 * @api public
 */
function createDebugWrapper(opts) {

  let logLevel = validateLogLevel(opts.logLevel);
  try {
    if (!opts || typeof opts !== 'object') {
      opts = {};
      throw new Error('Function logFactory() must need a opts as arguments which is not null or opts type must be object.');
    }
  } catch (err) {
    oriDebug(LogDebug)(PRIORITY_W, new Date().toISOString(), err);
  }

  function createDebug(namespace) {

    if (!namespace) {
      namespace = 'nongfu';
    }

    //origin debug function
    let debugWrapper = oriDebug(namespace);

    //level = 1
    function debug() {

      try {
        if (logLevel > 1) {
          return;
        }
        opts.PRIORITY = '[D]';
        common(opts, arguments, debugWrapper);
      } catch (err) {
        oriDebug(LogDebug)(PRIORITY_W, new Date().toISOString(), err);
      }

    }

    debug.isDebugEnabled = function () {
      return true;
    }

    //level = 0
    debug.verbose = function () {

      try {
        if (logLevel > 0) {
          return;
        }
        opts.PRIORITY = PRIORITY_V;
        common(opts, arguments, debugWrapper);
      } catch (err) {
        oriDebug(LogDebug)(PRIORITY_W, new Date().toISOString(), err);
      }

    }

    //level = 1
    debug.debug = function () {

      try {
        if (logLevel > 1) {
          return;
        }
        opts.PRIORITY = PRIORITY_D;
        common(opts, arguments, debugWrapper);
      } catch (err) {
        oriDebug(LogDebug)(PRIORITY_W, new Date().toISOString(), err);
      }

    }

    //level = 2
    debug.info = function () {

      try {
        if (logLevel > 2) {
          return;
        }
        opts.PRIORITY = PRIORITY_I;
        common(opts, arguments, debugWrapper);
      } catch (err) {
        oriDebug(LogDebug)(PRIORITY_W, new Date().toISOString(), err);
      }

    }

    //level = 3
    debug.warning = function () {

      try {
        if (logLevel > 3) {
          return;
        }
        opts.PRIORITY = PRIORITY_W;
        common(opts, arguments, debugWrapper);
      } catch (err) {
        oriDebug(LogDebug)(PRIORITY_W, new Date().toISOString(), err);
      }

    }

    //level = 4
    debug.error = function () {

      try {
        if (logLevel > 4) {
          return;
        }
        opts.PRIORITY = PRIORITY_E;
        common(opts, arguments, debugWrapper);
      } catch (err) {
        oriDebug(LogDebug)(PRIORITY_W, new Date().toISOString(), err);
      }

    }

    //level = 5
    debug.fatal = function () {

      try {
        if (logLevel > 5) {
          return;
        }
        opts.PRIORITY = PRIORITY_F;
        common(opts, arguments, debugWrapper);
      } catch (err) {
        oriDebug(LogDebug)(PRIORITY_W, new Date().toISOString(), err);
      }

    }

    //level = 6
    debug.silent = function () {

      try {
        if (logLevel > 6) {
          return;
        }
        opts.PRIORITY = PRIORITY_S;
        common(opts, arguments, debugWrapper);
      } catch (err) {
        oriDebug(LogDebug)(PRIORITY_W, new Date().toISOString(), err);
      }

    }

    return debug;
  }

  return createDebug;

}

/**
 * common function, this function deals with all message user input. 
 * @param  {[type]} opts         [opts includes priority,service_name,runner_id,pid]
 * @param  {[type]} args         [args is user input arguments, it uses to output user's message]
 * @param  {[type]} debugWrapper [it type is function, use it to output all message]
 * @return {[type]}              [null]
 * @attention {timestamp}        [every output timestamp must be the up-to-date time]
 * @api private
 */
function common(opts, args, debugWrapper) {
  //replace origin debug's 'formatArgs' function
  oriDebug.formatArgs = formatArgs;
  let priority = opts.PRIORITY;
  let runner_id = opts.runner_id
  let pid = opts.pid;
  let timestamp = '[' + new Date().toISOString() + ']';
  let service_qname = opts.service_qname;
  let arrs = new Array(args.length);
  for (let i = 0; i < args.length; i++) {
    if (typeof args[i] === 'object') {
      arrs[i] = util.inspect(args[i], {
        depth: 2
      });
    } else {
      arrs[i] = args[i];
    }
  }
  arrs[0] = coerce(arrs[0]);
  debugWrapper(priority, service_qname, runner_id, pid, timestamp, arrs.toString());
  //restore origin debug's 'formatArgs' function
  oriDebug.formatArgs = _formatArgs;
}

/**
 * rewrite debug origin formatArgs
 * @param  {[type]} args [user input message]
 * @return {[type]}      [null]
 * @api private
 */
function formatArgs(args) {
  try {
    let name = '[' + this.namespace + ']';

    args.splice(5, 0, name);
    args.push('+' + this.diff + 'ms');
  } catch (err) {
    oriDebug(LogDebug)(PRIORITY_W, new Date().toISOString(), err);
  }
}

/**
 * Coerce `val`.
 *
 * @param {Mixed} val
 * @return {Mixed}
 * @api private
 */
function coerce(val) {
  if (val instanceof Error) return val.stack || val.message;
  return val;
}

/**
 * [validateLogLevel description]
 * @return {[type]} [description]
 */
function validateLogLevel(logLevel) {
  if (!LOGLEVEL.some(function (value, index, arr) {
    return (value === logLevel);
  })) {
    return 1;
  }
  return LOGLEVEL.indexOf(logLevel);
}

exports = module.exports = createDebugWrapper;