#!/usr/bin/env bash

## Description {{{
#
# Logger for shell script.
#
# Homepage: https://github.com/rcmdnk/shell-logger
#
_LOGGER_NAME="shell-logger"
_LOGGER_VERSION="v0.2.0"
_LOGGER_DATE="04/Feb/2019"
# }}}

## License {{{
#
#MIT License
#
#Copyright (c) 2017 rcmdnk
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
# }}}

# Default variables {{{
LOGGER_DATE_FORMAT=${LOGGER_DATE_FORMAT:-'%Y/%m/%d %H:%M:%S'}
LOGGER_LEVEL=${LOGGER_LEVEL:-1} # 0: debug, 1: info, 2: notice, 3: warning, 4: error
LOGGER_STDERR_LEVEL=${LOGGER_STDERR_LEVEL:-4}
LOGGER_DEBUG_COLOR=${LOGGER_DEBUG_COLOR:-"3"}
LOGGER_INFO_COLOR=${LOGGER_INFO_COLOR:-""}
LOGGER_NOTICE_COLOR=${LOGGER_NOTICE_COLOR:-"36"}
LOGGER_WARNING_COLOR=${LOGGER_WARNING_COLOR:-"33"}
LOGGER_ERROR_COLOR=${LOGGER_ERROR_COLOR:-"31"}
LOGGER_COLOR=${LOGGER_COLOR:-auto}
LOGGER_COLORS=("$LOGGER_DEBUG_COLOR" "$LOGGER_INFO_COLOR" "$LOGGER_NOTICE_COLOR" "$LOGGER_WARNING_COLOR" "$LOGGER_ERROR_COLOR")
if [ "${LOGGER_LEVELS:-}" = "" ];then
  LOGGER_LEVELS=("DEBUG" "INFO" "NOTICE" "WARNING" "ERROR")
fi
LOGGER_SHOW_TIME=${LOGGER_SHOW_TIME:-1}
LOGGER_SHOW_FILE=${LOGGER_SHOW_FILE:-1}
LOGGER_SHOW_LEVEL=${LOGGER_SHOW_LEVEL:-1}
LOGGER_ERROR_RETURN_CODE=${LOGGER_ERROR_RETURN_CODE:-100}
LOGGER_ERROR_TRACE=${LOGGER_ERROR_TRACE:-1}
# }}}

# Other global variables {{{
_LOGGER_WRAP=0
#}}}

# Functions {{{
_logger_version () {
  printf "%s %s %s\\n" "$_LOGGER_NAME" "$_LOGGER_VERSION" "$_LOGGER_DATE"
}

_get_level () {
  if [ $# -eq 0 ];then
    local level=1
  else
    local level=$1
  fi
  if ! expr "$level" : '[0-9]*' >/dev/null;then
    [ -z "${ZSH_VERSION:-}" ] || emulate -L ksh
    local i=0
    while [ $i -lt ${#LOGGER_LEVELS[@]} ];do
      if [ "$level" = "${LOGGER_LEVELS[$i]}" ];then
        level=$i
        break
      fi
      ((i++))
    done
  fi
  echo $level
}

_logger_level () {
  [ "$LOGGER_SHOW_LEVEL" -ne 1 ] && return
  if [ $# -eq 1 ];then
    local level=$1
  else
    local level=1
  fi
  [ -z "${ZSH_VERSION:-}" ] || emulate -L ksh
  printf "[${LOGGER_LEVELS[$level]}]"
}

_logger_time () {
  [ "$LOGGER_SHOW_TIME" -ne 1 ] && return
  printf "[$(date +"$LOGGER_DATE_FORMAT")]"
}

_logger_file () {
  [ "$LOGGER_SHOW_FILE" -ne 1 ] && return
  local i=0
  if [ $# -ne 0 ];then
    i=$1
  fi
  if [ -n "$BASH_VERSION" ];then
    printf "[${BASH_SOURCE[$((i+1))]}:${BASH_LINENO[$i]}]"
  else
    emulate -L ksh
    printf "[${funcfiletrace[$i]}]"
  fi
}

_logger () {
  ((_LOGGER_WRAP++)) || true
  local wrap=${_LOGGER_WRAP}
  _LOGGER_WRAP=0
  if [ $# -eq 0 ];then
    return
  fi
  local level="$1"
  shift
  if [ "$level" -lt "$(_get_level "$LOGGER_LEVEL")" ];then
    return
  fi
  local msg_prefix="$(_logger_time)$(_logger_file "$wrap")$(_logger_level "$level")"
  local msg="${msg_prefix:+$msg_prefix }$*" # add prefix with a space only if prefix not is empty
  msg="${msg/\$/\\\$}" # escape $ is msg to be able to use eval below without trying to resolve a variable
  local _logger_printf=printf
  local out=1
  if [ "$level" -ge "$LOGGER_STDERR_LEVEL" ];then
    out=2
    _logger_printf=">&2 printf"
  fi
  if [ "$LOGGER_COLOR" = "always" ] || { test "$LOGGER_COLOR" = "auto"  && test  -t $out ; };then
    [ -z "${ZSH_VERSION:-}" ] || emulate -L ksh
    eval "$_logger_printf \"\\e[${LOGGER_COLORS[$level]}m%s\\e[m\\n\"  \"$msg\""
  else
    eval "$_logger_printf \"%s\\n\" \"$msg\""
  fi
}

debug () {
  ((_LOGGER_WRAP++)) || true
  _logger 0 "$*"
}

information () {
  ((_LOGGER_WRAP++)) || true
  _logger 1 "$*"
}
info () {
  ((_LOGGER_WRAP++)) || true
  information "$*"
}

notification () {
  ((_LOGGER_WRAP++)) || true
  _logger 2 "$*"
}
notice () {
  ((_LOGGER_WRAP++)) || true
  notification "$*"
}

warning () {
  ((_LOGGER_WRAP++)) || true
  _logger 3 "$*"
}
warn () {
  ((_LOGGER_WRAP++)) || true
  warning "$*"
}

error () {
  ((_LOGGER_WRAP++)) || true
  if [ "$LOGGER_ERROR_TRACE" -eq 1 ];then
    {
      [ -z "${ZSH_VERSION:-}" ] || emulate -L ksh
      local first=0
      if [ -n "$BASH_VERSION" ];then
        local current_source=$(echo "${BASH_SOURCE[0]##*/}"|cut -d"." -f1)
        local func="${FUNCNAME[1]}"
        local i=$((${#FUNCNAME[@]}-2))
      else
        local current_source=$(echo "${funcfiletrace[0]##*/}"|cut -d":" -f1|cut -d"." -f1)
        local func="${funcstack[1]}"
        local i=$((${#funcstack[@]}-1))
        local last_source=${funcfiletrace[$i]%:*}
        if [ "$last_source" = zsh ];then
          ((i--))
        fi
      fi
      if [ "$current_source" = "shell-logger" ] && [ "$func" = err ];then
        local first=1
      fi
      if [ $i -ge $first ];then
        echo "Traceback (most recent call last):"
      fi
      while [ $i -ge $first ];do
        if [ -n "$BASH_VERSION" ];then
          local file=${BASH_SOURCE[$((i+1))]}
          local line=${BASH_LINENO[$i]}
          local func=""
          if [ ${BASH_LINENO[$((i+1))]} -ne 0 ];then
            if [ "${FUNCNAME[$((i+1))]}" = "source" ];then
              func=", in ${BASH_SOURCE[$((i+2))]}"
            else
              func=", in ${FUNCNAME[$((i+1))]}"
            fi
          fi
          local func_call="${FUNCNAME[$i]}"
          if [ "$func_call" = "source" ];then
            func_call="${func_call} ${BASH_SOURCE[$i]}"
          else
            func_call="${func_call}()"
          fi
        else
          local file=${funcfiletrace[$i]%:*}
          local line=${funcfiletrace[$i]#*:}
          local func=""
          if [ -n "${funcstack[$((i+1))]}" ];then
            if [ "${funcstack[$((i+1))]}" = "${funcfiletrace[$i]%:*}" ];then
              func=", in ${funcfiletrace[$((i+1))]%:*}"
            else
              func=", in ${funcstack[$((i+1))]}"
            fi
          fi
          local func_call="${funcstack[$i]}"
          if [ "$func_call" = "${funcfiletrace[$((i-1))]%:*}" ];then
            func_call="source ${funcfiletrace[$((i-1))]%:*}"
          else
            func_call="${func_call}()"
          fi
        fi
        echo "  File \"${file}\", line ${line}${func}"
        if [ $i -gt $first ];then
          echo "    $func_call"
        else
          echo ""
        fi
        ((i--))
      done
    } 1>&2
  fi
  _logger 4 "$*"
  return "$LOGGER_ERROR_RETURN_CODE"
}
err () {
  ((_LOGGER_WRAP++)) || true
  error "$*"
}
# }}}