text: var util = require('util')
  var _ = require('lodash')
  
  var loggerInstance
  
  var defaultProvider = {
    log: console.log,
    debug: console.log,    // use .log(); since console does not have .debug()
    info: console.info,
    warn: console.warn,
    error: console.error
  }
  
  // log level 'weight'
  var LEVELS = {
    debug: 10,
    info: 20,
    warn: 30,
    error: 50,
    silent: 80
  }
  
  module.exports = {
    // singleton
    getInstance: function () {
      if (!loggerInstance) {
        loggerInstance = new Logger()
      }
  
      return loggerInstance
    },
    getArrow: getArrow
  }
  
  function Logger () {
    var logLevel
    var provider
  
    var api = {
      log: log,
      debug: debug,
      info: info,
      warn: warn,
      error: error,
      setLevel: function (v) {
        if (isValidLevel(v)) {
          logLevel = v
        }
      },
      setProvider: function (fn) {
        if (fn && isValidProvider(fn)) {
          provider = fn(defaultProvider)
        }
      }
    }
  
    init()
  
    return api
  
    function init () {
      api.setLevel('info')
      api.setProvider(function () {
        return defaultProvider
      })
    }
  
    // log will log messages, regardless of logLevels
    function log () {
      provider.log(_interpolate.apply(null, arguments))
    }
  
    function debug () {
      if (_showLevel('debug')) {
        provider.debug(_interpolate.apply(null, arguments))
      }
    }
  
    function info () {
      if (_showLevel('info')) {
        provider.info(_interpolate.apply(null, arguments))
      }
    }
  
    function warn () {
      if (_showLevel('warn')) {
        provider.warn(_interpolate.apply(null, arguments))
      }
    }
  
    function error () {
      if (_showLevel('error')) {
        provider.error(_interpolate.apply(null, arguments))
      }
    }
  
    /**
     * Decide to log or not to log, based on the log levels 'weight'
     * @param  {String}  showLevel [debug, info, warn, error, silent]
     * @return {Boolean}
     */
    function _showLevel (showLevel) {
      var result = false
      var currentLogLevel = LEVELS[logLevel]
  
      if (currentLogLevel && (currentLogLevel <= LEVELS[showLevel])) {
        result = true
      }
  
      return result
    }
  
    // make sure logged messages and its data are return interpolated
    // make it possible for additional log data, such date/time or custom prefix.
    function _interpolate () {
      var fn = _.spread(util.format)
      var result = fn(_.slice(arguments))
  
      return result
    }
  
    function isValidProvider (fnProvider) {
      var result = true
  
      if (fnProvider && !_.isFunction(fnProvider)) {
        throw new Error('[HPM] Log provider config error. Expecting a function.')
      }
  
      return result
    }
  
    function isValidLevel (levelName) {
      var validLevels = _.keys(LEVELS)
      var isValid = _.includes(validLevels, levelName)
  
      if (!isValid) {
        throw new Error('[HPM] Log level error. Invalid logLevel.')
      }
  
      return isValid
    }
  }
  
  /**
   * -> normal proxy
   * => router
   * ~> pathRewrite
   * ≈> router + pathRewrite
   *
   * @param  {String} originalPath
   * @param  {String} newPath
   * @param  {String} originalTarget
   * @param  {String} newTarget
   * @return {String}
   */
  function getArrow (originalPath, newPath, originalTarget, newTarget) {
    var arrow = ['>']
    var isNewTarget = (originalTarget !== newTarget) // router
    var isNewPath = (originalPath !== newPath) // pathRewrite
  
    if (isNewPath && !isNewTarget) {
      arrow.unshift('~')
    } else if (!isNewPath && isNewTarget) {
      arrow.unshift('=')
    } else if (isNewPath && isNewTarget) {
      arrow.unshift('≈')
    } else {
      arrow.unshift('-')
    }
  
    return arrow.join('')
  }
  