#!/usr/bin/env bash

#
# ## first()
#
# Returns the first parameter.
#
# ### Input Parameters
#
# One or more parameters.
#
# ### Stream Outputs
#
# Prints the first positional parameter to STDOUT of the calling environment.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if no parameters are passed.
#
# ### Usage Examples
#
#     user$ first one two three
#     one
#
#     user$ array=($(ls $HOME))
#     user$ first ${array[@]}
#     Desktop
#
first()
{
  # Store the parameters in an array variable
  local _array=("$@")

  # If the array is nonempty then print the first element out
  # Otherwise no parameters were passed in, this is a programming error.
  # Send a failure message which also triggers a backtrace enabling the
  # developer to quickly pinpoint and fix their error.
  if (( ${#_array[@]} > 0 ))
  then
    printf "%s" "${_array[0]}"
  else
    fail "No parameters given, cannot return the first one."
  fi
}

#
# ## last()
#
# Returns the last parameter.
#
# ### Input Parameters
#
# One or more parameters.
#
# ### Stream Outputs
#
# Prints the last positional parameter to STDOUT of the calling environment.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if no parameters are passed.
#
# ### Usage Examples
#
#     user$ last one two three
#     one
#
#     user$ array=($(ls $HOME))
#     user$ last ${array[@]}
#     tmp
#
last()
{
  # Store the parameters in an array variable
  local _array=("$@")

  # If the parameters array is nonempty then print the last element of the array
  # Otherwise no parameters were passed in, this is a programming error.
  # Send a failure message which also triggers a backtrace enabling the
  # developer to quickly pinpoint and fix their error.
  if (( ${#_array[@]} > 0 ))
  then
    printf "%s" "${_array[$((${#_array[@]} - 1))]}"
  else
    fail "No parameters given, cannot return the last one."
  fi
}

#
# ## match()
#
# Tests to see if a value matches a given regex (shell extended glob pattern).
#
# ### Input Parameters
#
# Positional Parameter value and regexp.
#
# ### Stream Outputs
#
# None.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 if the value matches the regex (shell extended glob pattern)
# 1 if the value does not match the regex (shell extended glob pattern)
#
# ### Failure Scenarios
#
# Fails if no value is given
# Fails if no regex is given
#
# ### Usage Examples
#
#     user$ match "The quick brown fox jumped over the lazy dog." "*fox*"
#     user$ echo $?
#     0
#
#     user$ match "The quick brown fox jumped over the lazy dog." "*smurf*"
#     user$ echo $?
#     1
#
match()
{
  # Store the first parameter into the value variable.
  local _value="$1"

  # If the varable is nonempty
  if variable_is_nonempty _value
  then
    # then remove the value from the parameters list and set the regex variable
    # to the next parameter
    shift
    local _regex="$1"

    # if the regex variable is nonempty
    if variable_is_nonempty _regex
    then
      # Ensure that extended shell globbing (bash) is turned on
      shopt -s extglob
      # use a case statement for pattern matching the regex against the value
      # If the value matches the given regex pattern then return true, 0
      # otherwise the value does not match the given regex pattern so return
      # false, 1
      case "${_value}" in
        (${_regex})
          return 0
          ;;
        (*)
          return 1
          ;;
      esac
    else
      # Otherwise no regex parameter was passed in, this is a programming error.
      # Send a failure message which also triggers a backtrace enabling the
      # developer to quickly pinpoint and fix their error.
      fail "A regex must be specified in order to match ${_value} against it."
    fi
  else
    # Otherwise no parameters were passed in, this is a programming error.
    # Send a failure message which also triggers a backtrace enabling the
    # developer to quickly pinpoint and fix their error.
    fail "A value and regex must be specified in order to match."
  fi
}

#
# ## ematch()
#
# Tests to see if a value matches a given extended regex.
#
# ### Input Parameters
#
# Positional Parameter value and regexp.
#
# ### Stream Outputs
#
# None.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 if the value matches the regex (shell extended glob pattern)
# 1 if the value does not match the regex (shell extended glob pattern)
#
# ### Failure Scenarios
#
# Fails if no value is given
# Fails if no regex is given
#
# ### Usage Examples
#
#     user$ ematch "The quick brown fox jumped over the lazy dog." "fox.*jumped"
#     user$ echo $?
#     0
#
#     user$ ematch "The quick brown fox jumped over the lazy dog." "^fox"
#     user$ echo $?
#     1
#
ematch()
{
  # Store the first parameter into the value variable.
  local _value="$1"

  # If the varable is nonempty
  if variable_is_nonempty _value
  then
    # then remove the value from the parameters list and set the regex variable
    # to the next parameter
    shift
    local _regex="$1"

    # if the regex variable is nonempty
    if variable_is_nonempty _regex
    then
      # Use grep for extended regexp
      echo "${_value}" | grep -qE "${_regex}"
      local result=$?
      case $result in
        0|1)
          # 0 & 1 are correct results
          return $result
          ;;
        *)
          # any other result means error in grep
          fail "grep returned unknown code $result"
          ;;
      esac
    else
      # Otherwise no regex parameter was passed in, this is a programming error.
      # Send a failure message which also triggers a backtrace enabling the
      # developer to quickly pinpoint and fix their error.
      fail "A regex must be specified in order to match ${_value} against it."
    fi
  else
    # Otherwise no parameters were passed in, this is a programming error.
    # Send a failure message which also triggers a backtrace enabling the
    # developer to quickly pinpoint and fix their error.
    fail "A value and regex must be specified in order to match."
  fi
}

#
# ## variables_must_be_nonempty()
#
# Halts the program with a fail message if any of the named variables are empty
# or undefined.
#
# ### Input Parameters
#
# One or more parameters specifying the names of variables.
#
# ### Stream Outputs
#
# None if all are
#
# ### Environmental effects
#
# What, if any, environmental side effects this function causes. 'None.' for none.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if no varaible names were given.
# Fails if any of the named variables are empty.
#
# ### Usage Examples
#
#     user$ cat test
#     #!/usr/bin/env bash
#     source "/usr/local/bdsm/extensions/builtin/core/modules/shell/core/initialize" # Load BDSM framework core.
#     modules trace variables # Load the trace module.
#     unset HOME
#     variables_must_be_nonempty HOME
#
#     user$ $PWD/test
#     ERROR: Required variable 'HOME' is not set.
#     ERROR: A command has returned an unhandled error code (nonzero return value).
#     Backtrace:
#        Trace   Line Function             File
#           3.     37 fail()               /usr/local/bdsm/extensions/builtin/core/modules/shell/logging/dsl
#           2.    192 variables_must_be_nonempty() /usr/local/bdsm/extensions/builtin/core/modules/shell/variables/dsl
#           1.      6 main()               /Users/wayneeseguin/test
#      > modules/shell/logging/dsl fail() 38 $ exit 1
#
variables_must_be_nonempty()
{
  local _variable _variables=("$@")

  if (( ${#_variables[@]} > 0 ))
  then
    for _variable in "${_variables}"
    do
      if variable_is_nonempty "${_variable}"
      then
        continue
      else
        # One of the variables was not set, this is a programming error.
        # Send a failure message which also triggers a backtrace enabling the
        # developer to quickly pinpoint and fix their error.
        fail "Required variable '$_variable' is not set."
      fi
    done
  else
    # Otherwise no parameters were passed in, this is a programming error.
    # Send a failure message which also triggers a backtrace enabling the
    # developer to quickly pinpoint and fix their error.
    fail "Cannot ensure variables are nonempty as no variables were given."
  fi
}

#
# ## variable_is_nonempty()
#
# Test to see if a variable is empty.
#
# ### Input Parameters
#
# First parameter is a string containing a variable name.
#
# ### Stream Outputs
#
# None.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success.
#
# ### Failure Scenarios
#
# Fails if no variable name was given as the first argument.
#
# ### Usage Examples
#
#     user$ variable_is_nonempty asdf
#     user$ echo $?
#     1
#
#     user$ asdf="w00t! "
#     user$ variable_is_nonempty asdf
#     user$ echo $?
#     0
#
variable_is_nonempty()
{
  # Store the first parameter, which should be the variable name, in the
  # variable variable :)
  local _variable="${1:-}"

  # If the variable name is nonempty
  if [[ -n "${_variable}" ]]
  then
    # if the evaluation of a nonempty test with the variable name used
    eval "[[ -n \"\${${_variable}:-}\" ]]"
  else
    # Otherwise no parameters were passed in, this is a programming error.
    # Send a failure message which also triggers a backtrace enabling the
    # developer to quickly pinpoint and fix their error.
    fail "Cannot check if variable is nonempty, no variable was given."
  fi

}

#
# ## variable_is_empty()
#
# Test to see if a variable is empty.
#
# ### Input Parameters
#
# First parameter is a string containing a variable name.
#
# ### Stream Outputs
#
# None.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success.
#
# ### Failure Scenarios
#
# Fails if no variable name was given as the first argument.
#
# ### Usage Examples
#
#     user$ variable_is_empty asdf
#     user$ echo $?
#     1
#
#     user$ asdf="w00t! "
#     user$ variable_is_empty asdf
#     user$ echo $?
#     0
#
variable_is_empty()
{
  # Store the first parameter, which should be the variable name, in the
  # variable variable :)
  local _variable="${1:-}"

  # If the variable name is nonempty
  if [[ -n "${_variable}" ]]
  then
    # if the evaluation of an empty test with the variable name used
    if eval "[[ -z \"\${${_variable}:-}\" ]]"
    then
      # is true then return true, 0
      return 0
    else
      # is false then return false, 1
      return 1
    fi
  else
    # Otherwise no parameters were passed in, this is a programming error.
    # Send a failure message which also triggers a backtrace enabling the
    # developer to quickly pinpoint and fix their error.
    fail "Cannot check if variable is empty as no variable was given."
  fi
}

#
# ## variables_are_nonempty()
#
# Test to see if any of a given list of variables are empty.
#
# ### Input Parameters
#
# First parameter is a string containing a variable name.
#
# ### Stream Outputs
#
# None.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 if all variables given are nonempty.
# 1 if any variable given is nonempty.
#
# ### Failure Scenarios
#
# Fails if no variable names were given as arguments.
#
# ### Usage Examples
#
#     user$ variables_are_nonempty BASH_VERSION asdf
#     user$ echo $?
#     1
#
#     user$ asdf="w00t! "
#     user$ variables_are_nonempty BASH_VERSION asdf
#     user$ echo $?
#     0
#
variables_are_nonempty()
{
  # Store the parameters, which should be the variable names, in the
  # variables variable.
  local _variable _variables=("$@")

  # If the number of variable names passed in is nonzero
  if (( "${#_variables[@]}" > 0 ))
  then
    # then for each variable in the passed in variable names
    for _variable in "${_variables}"
    do
      # evaluate an is empty test using the variable name
      if eval "[[ -z \"\${${_variable}:-}\" ]]"
      then
        # If any single of the variables are empty, return false, 1
        return 1
      fi
    done
    # If we get to this point, then all variables were set so return true, 0
    # to the caller.
    return 0
  else
    # Otherwise no parameters were passed in, this is a programming error.
    # Send a failure message which also triggers a backtrace enabling the
    # developer to quickly pinpoint and fix their error.
    fail "Cannot check if variables are nonempty as no variables were given."
  fi
}

