/**
 * @license
 * Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*jshint -W072*/
'use strict';

var util = require('util'),
    colors = require('colors/safe'),
    safeStringify = require('safe-json-stringify'),
    serializeErr = require('serr'),
    _ = require('lodash');

var DEFAULT_NOT_AVAILABLE = 'n/a';

var notAvailable = DEFAULT_NOT_AVAILABLE,
    templateTrace = 'time=%s | lvl=%s | corr=%s | trans=%s | op=%s | msg=';

/**
 * The exported formatters.
 *
 * @type {Object}
 */
var API = module.exports = {
  dev: formatDevTrace,
  json: formatJsonTrace,
  stacktracesWith: ['ERROR', 'FATAL'],
  pipe: formatTrace, // Deprecated
  setNotAvailable: setNotAvailable // Deprecated
};

/**
 * Sets a value for those fields that are not available in the context. This field
 * will only be used in the 'pipes' formatter.
 *
 * @param {String} na New value for not available fields.
 * @deprecated
 */
function setNotAvailable(na) {
  notAvailable = na;
}

/**
 * Formats a trace message with some nice TTY colors
 *
 * @param {String} level One of the following values
 *      ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
 * @param {Object} context Additional information to add to the trace
 * @param {String} message The main message to be added to the trace
 * @param {Array} args More arguments provided to the log function
 * @param {Error|undefined} err A cause error used to log extra information
 *
 * @return {String} The trace formatted
 */
function formatDevTrace(level, context, message, args, err) {
  var str,
      mainMessage = util.format.apply(global, [message].concat(args)),
      printStack = API.stacktracesWith.indexOf(level) > -1,
      errCommomMessage = err && (err.name + ': ' + err.message),
      isErrorLoggingWithoutMessage = mainMessage === errCommomMessage;

  switch (level) {
    case 'DEBUG':
      str = colors.grey(level);
      break;
    case 'INFO':
      str = colors.blue(level) + ' '; // Pad to 5 chars
      break;
    case 'WARN':
      str = colors.yellow(level) + ' '; // Pad to 5 chars
      break;
    case 'ERROR':
      str = colors.red(level);
      break;
    case 'FATAL':
      str = colors.red.bold(level);
      break;
  }
  str += ' ' + mainMessage;

  if (isErrorLoggingWithoutMessage) {
    str += colorize(colors.gray, serializeErr(err).toString(printStack).substr(mainMessage.length));
  } else if (err) {
    str += '\n' + colorize(colors.gray, serializeErr(err).toString(printStack));
  }

  var localContext = _.omit(context, formatDevTrace.omit);
  str += Object.keys(localContext).length ?
      ' ' + colorize(colors.gray, util.inspect(localContext)) :
      '';

  // pad all subsequent lines with as much spaces as "DEBUG " or "INFO  " have
  return str.replace(new RegExp('\r?\n','g'), '\n      ');
}

/**
 * Context properties that should be skipped from printing in dev format
 * @type {Array.<String>}
 */
formatDevTrace.omit = [];

// Damm! colors are not applied to multilines! split, apply and join!
function colorize(color, str) {
  return str
      .split('\n')
      .map(part => color(part))
      .join('\n');
}

/**
 * Formats a trace message with fields separated by pipes.
 *
 * DEPRECATED!
 *
 * @param {String} level One of the following values
 *      ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
 * @param {Object} context Additional information to add to the trace
 * @param {String} message The main message to be added to the trace
 * @param {Array} args More arguments provided to the log function
 * @param {Error|undefined} err A cause error used to log extra information
 *
 * @return {String} The trace formatted
 * @deprecated
 */
function formatTrace(level, context, message, args, err) {

  var recontext = {
    time: (new Date()).toISOString(),
    lvl: level,
    corr: context.corr || notAvailable,
    trans: context.trans || notAvailable,
    op: context.op || notAvailable
  };

  Object.keys(context)
      .filter((key) => {
        return !(context[key] && Object.prototype.toString.call(context[key]) === '[object Function]');
      })
      .forEach((key) => {
        recontext[key] = context[key] || notAvailable;
      });

  if (message instanceof Date || message instanceof Error) {
    // Node6 related hack. See https://github.com/telefonicaid/logops/issues/36
    recontext.msg = util.format(message);
  } else {
    recontext.msg = message;
  }

  var str = Object.keys(recontext)
      .map((key) => key + '=' + recontext[key])
      .join(' | ');

  args.unshift(str);
  if (err && message !== '' + err) {
    args.push(err);
  }

  return util.format.apply(global, args);
}

/**
 * Formats a trace message in JSON format
 *
 * @param {String} level One of the following values
 *      ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
 * @param {Object} context Additional information to add to the trace
 * @param {String} message The main message to be added to the trace
 * @param {Array} args More arguments provided to the log function
 * @param {Error|null} err A cause error used to log extra information
 *
 * @return {String} The trace formatted
 */
function formatJsonTrace(level, context, message, args, err) {
  return formatJsonTrace.stringify(formatJsonTrace.toObject(level, context, message, args, err));
}

formatJsonTrace.stringify = (obj) => {
  try {
    return JSON.stringify(obj);
  } catch (err) {
    return safeStringify(obj);
  }
};
formatJsonTrace.toObject = (level, context, message, args, err) => {
  let log = {};

  for (let attrname in context) {
    log[attrname] = context[attrname];
  }

  log.time = new Date();
  log.lvl = level;
  log.err = err && serializeErr(err).toObject(API.stacktracesWith.indexOf(level) > -1);
  log.msg = util.format.apply(global, [message].concat(args));

  return log;
};
