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('')
}
