#!/usr/bin/env bash

#
# # General Services functions.
#
#
# ## services\_list()
#
# List installed services. These are extensions that have loaded the service
# module.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# A list of available services to install are printed to STDOUT of
# the calling environment.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# NIY
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ services_avilable
#     Available service listing has not yet been implemented.
#     This feature has not yet been implemented.
#
#
# # General Services functions.
#
#
# ## services\_list()
#
# List installed services. These are extensions that have loaded the service
# module.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# A list of available services to install are printed to STDOUT of
# the calling environment.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# NIY
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ services_avilable
#     Available service listing has not yet been implemented.
#     This feature has not yet been implemented.
#
services_avilable()
{
  # Notify that the services_available() function has not yet been implemented.
  NIY "Available service listing has not yet been implemented."
  services_installed
}

#
# ## services\_installed()
#
# List installed services. These are extensions that have loaded the service
# module.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# A list of installed extensions that load the service module are printed
# to the STDOUT of the calling environment.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# NIY
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ services_installed
#     nginx unicorn redis postgresql
#
services_installed()
{
  # Notify that the services_installed() function has not yet been implemented.
  NIY "Installed services has not yet been implemented."
}

#
# ## services\_available()
#
# List available services. These are extensions that load the service module.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# A list of available extensions that load the service module are printed to
# STDOUT of the calling environment.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# NIY
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ services_available
#     nginx redis postgresql mongodb ...
#
services_available()
{
  # Notify that the services_available() function has not yet been implemented.
  NIY "Installed services has not yet been implemented."
}

#
# # Single Service functions.
#
#
# ## service\_setup()
#
# Service setup and configuration.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# None.
#
# ### Environmental effects
#
# Creates a system service user with the same name as the service.
# Sets up the service init_d file from template, if it exists.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure scenarios currently.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_setup
#     ...
#
service_setup()
{
  local _path _file

  for _file in dsl initialize cli
  do
    source_files "${extension_path}/modules/${language:=shell}/${_file}"
  done

  # Notify the user that we are now creating paths for the service/version
  log "Ensuring paths for ${service} ${package_version} exist..."

  # If the user is root
  if user_is_root
  then
    # then create the service_user on the system if it is missing with the
    # same group as the name.
    # ##### TODO: create an 'user_exists' function and use it here.
    user_create_if_missing "${service_user}"\
      with group "${service_user}" >/dev/null 2>&1

    # Create the paths for the service_root_paths if they do not exist
    ensure_paths_exist "${service_root_paths[@]}"

    # then ensure that the service_user has ownership of the root serivce paths.
    chown_paths --recursively "${service_user}:${service_user}"\
      "${service_root_paths[@]}"
  fi

  # Create the paths for the service/version if they do not exist
  ensure_paths_exist "${service_paths[@]}" "${install_path}"

  # Ensure that the service_user has ownership of the serivce paths.
  for _path in "${service_paths[@]}"
  do
    if variable_is_nonempty _path
    then
      if path_exists "${_path}"
      then
        chown_paths --recursively "${service_user}:${service_user}" "${_path}"
      else
        warn "While chowning paths it was discovered that '${_path}' is in"\
          "fact nonexistant on the filesystem, this might indicate an error."
      fi
    fi
  done

  if command_exists "${service}_service_setup"
  then
    "${service}_service_setup"
  fi

  # Install the service init.d scripts.
  service_install_init_d "${service}"
}

#
# # Single Service functions.
#
#
# ## service\_uninstall()
#
# Service uninstall and configuration.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# None.
#
# ### Environmental effects
#
# Creates a system service user with the same name as the service.
# Sets up the service init_d file from template, if it exists.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure scenarios currently.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_uninstall
#     ...
#
service_uninstall()
{
  NIY "service_uninstall() has not yet been implemented."
  # 1. Remove init scripts
  # 2. Unload OS service hooks
  # 3. Deactivate database path.
}

#
# ## service\_start()
#
# Starts the given service using init scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Whatever stream output comes from the called init script.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if extension was not set.
# Fails if the init script is missing or not executable.
#
# ### Usage Examples
#
#     user$ extension=redis
#     user$ service_start
#     ...
#
service_start()
{
  # if the service variable is nonempty
  if variable_is_nonempty service
  then
    # and if the service extension has defined a start method prefixed with the
    # service's name
    if command_exists "${service}_start"
    then
      # then call the extension defined service start function.
      "${service}_start"
    else
      # Start the service using the configuration defined in the extension.
      service_init_start
    fi
  else
    # otherwise the service name variable was not set. This is a programming
    # error and as such we call the fail function which yields a message and
    # a backtrace thus enabling the developer to pinpoint and fix the issue
    # quickly.
    fail "In order to start a service the 'service' variable must be set."
  fi
}

#
# ## service\_stop()
#
# stops the given service using init scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Whatever stream output comes from the called init script.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if service was not set.
# Fails if the init script is missing or not executable.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_stop
#     ...
#
service_stop()
{
  # if the service variable is nonempty
  if variable_is_nonempty service
  then
    # and if the service extension has defined a start method prefixed with the
    # service's name
    if command_exists "${service}_stop"
    then
      # then call the extension defined service stop function.
      "${service}_stop"
    else
      # stop the service using the configuration defined in the extension.
      service_init_stop
    fi
  else
    # otherwise the service name variable was not set. This is a programming
    # error and as such we call the fail function which yields a message and
    # a backtrace thus enabling the developer to pinpoint and fix the issue
    # quickly.
    fail "In order to start a service the 'service' variable must be set."
  fi
}

#
# ## service\_reload()
#
# reloads the given service using init scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Whatever stream output comes from the called init script.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if service was not set.
# Fails if the init script is missing or not executable.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_reload
#     ...
#
service_reload()
{
  # if the service variable is nonempty
  if variable_is_nonempty service
  then
    # and if the service extension has defined a reload method prefixed with the
    # service's name
    if command_exists "${service}_reload"
    then
      # then call the extension defined service reload function.
      "${service}_reload"
    else
      # reload the service using the configuration defined in the extension.
      service_init_reload
    fi
  else
    # otherwise the service name variable was not set. This is a programming
    # error and as such we call the fail function which yields a message and
    # a backtrace thus enabling the developer to pinpoint and fix the issue
    # quickly.
    fail "In order to start a service the 'service' variable must be set."
  fi
}

#
# ## service\_restart()
#
# restarts the given service using init scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Whatever stream output comes from the called init script.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if service was not set.
# Fails if the init script is missing or not executable.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_restart
#     ...
#
service_restart()
{
  # if the service variable is nonempty
  if variable_is_nonempty service
  then
    # and if the service extension has defined a restart method prefixed with the
    # service's name
    if command_exists "${service}_restart"
    then
      # then call the extension defined service restart function.
      "${service}_restart"
    else
      # restart the service using the configuration defined in the extension.
      service_init_restart
    fi
  else
    # otherwise the service name variable was not set. This is a programming
    # error and as such we call the fail function which yields a message and
    # a backtrace thus enabling the developer to pinpoint and fix the issue
    # quickly.
    fail "In order to start a service the 'service' variable must be set."
  fi
}

#
# ## service\_status()
#
# statuss the given service using init scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Whatever stream output comes from the called init script.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if service was not set.
# Fails if the init script is missing or not executable.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_status
#     ...
#
service_status()
{
  # if the service variable is nonempty
  if variable_is_nonempty service
  then
    # and if the service extension has defined a status method prefixed with the
    # service's name
    if command_exists "${service}_status"
    then
      # then call the extension defined service status function.
      "${service}_status"
    else
      # status the service using the configuration defined in the extension.
      service_init_status
    fi
  else
    # otherwise the service name variable was not set. This is a programming
    # error and as such we call the fail function which yields a message and
    # a backtrace thus enabling the developer to pinpoint and fix the issue
    # quickly.
    fail "In order to start a service the 'service' variable must be set."
  fi
}

#
# ## service\_logtail()
#
# logtails the given service using init scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Whatever stream output comes from the called init script.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if service was not set.
# Fails if the init script is missing or not executable.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_logtail
#     ...
#
service_logtail()
{
  # if the service variable is nonempty
  if variable_is_nonempty service
  then
    # and if the service extension has defined a logtail method prefixed with the
    # service's name
    if command_exists "${service}_logtail"
    then
      # then call the extension defined service logtail function.
      "${service}_logtail"
    else
      # logtail the service using the configuration defined in the extension.
      service_init_logtail
    fi
  else
    # otherwise the service name variable was not set. This is a programming
    # error and as such we call the fail function which yields a message and
    # a backtrace thus enabling the developer to pinpoint and fix the issue
    # quickly.
    fail "In order to start a service the 'service' variable must be set."
  fi
}

#
# ## service\_info()
#
# infos the given service using init scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Whatever stream output comes from the called init script.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if service was not set.
# Fails if the init script is missing or not executable.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_info
#     ...
#
service_info()
{
  # if the service variable is nonempty
  if variable_is_nonempty service
  then
    # and if the service extension has defined a info method prefixed with the
    # service's name
    if command_exists "${service}_info"
    then
      # then call the extension defined service info function.
      "${service}_info"
    else
      # info the service using the configuration defined in the extension.
      service_init_info
    fi
  else
    # otherwise the service name variable was not set. This is a programming
    # error and as such we call the fail function which yields a message and
    # a backtrace thus enabling the developer to pinpoint and fix the issue
    # quickly.
    fail "In order to get info of a service the 'service' variable must be set."
  fi
}

#
# ## service\_configcheck()
#
# configchecks the given service using init scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Whatever stream output comes from the called init script.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if service was not set.
# Fails if the init script is missing or not executable.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_configcheck
#     ...
#
service_configcheck()
{
  # if the service variable is nonempty
  if variable_is_nonempty service
  then
    # and if the service extension has defined a configcheck method prefixed with the
    # service's name
    if command_exists "${service}_configcheck"
    then
      # then call the extension defined service configcheck function.
      "${service}_configcheck"
    else
      # configcheck the service using the configuration defined in the extension.
      service_init_configcheck
    fi
  else
    # otherwise the service name variable was not set. This is a programming
    # error and as such we call the fail function which yields a message and
    # a backtrace thus enabling the developer to pinpoint and fix the issue
    # quickly.
    fail "In order to configcheck a service the 'service' variable must be set."
  fi
}

#
# ## service\_usage()
#
# usages the given service using init scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Whatever stream output comes from the called init script.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if service was not set.
# Fails if the init script is missing or not executable.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_usage
#     ...
#
service_usage() {
  # TODO: Update this for service specific items.
  printf "
Usage:

  $0 [options]

Options:

  --prefix   - specify prefix path
  --src)     - specify source directory
  --user)    - specify user to install as
  --version) - specify version to install
  --licence) - view licence
  --help)    - view this usage information

  "
  return 0
}

#
# ## service\_cli()
#
# clis the given service using init scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Whatever stream output comes from the called init script.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if service was not set.
# Fails if the init script is missing or not executable.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_cli
#     ...
#
service_cli()
{
  # TODO: Update this for service specific items.
  local _ignored_args=()

  number_of_args=${#extension_args[@]}

  for (( index=0 ; index < $number_of_args ; index++ ))
  do
    token="${extension_args[$index]}"

    case "$token" in
      --prefix)
        prefix_path="${extension_args[$((++index))]}"
        ;;
      --src)
        src_path="${extension_args[$((++index))]}"
        ;;
      --user)
        service_user="${extension_args[$((++index))]}"
        ;;
      --as-root)
        service_run_as_root_flag=1
        ;;
      --version)
        service_version="${extension_args[$((++index))]}"
        ;;
      --base_url)
        service_base_url="${extension_args[$((++index))]}"
        ;;
      --file)
        service_file="${extension_args[$((++index))]}"
        ;;
      --directory)
        service_directory="${extension_args[$((++index))]}"
        ;;
      --archive_format)
        archive_format="${extension_args[$((++index))]}"
        ;;
      --activate|activate)
        # TODO: Throw error if parameter is not specified.
        if (( ${#extension_args[@]} > 0 ))
        then
          service_activate "${service}" "${extension_args[$((++index))]}"
        else
          error "Cannot activate ${service}, no version was given."
        fi
        ;;
      --deactivate|deactivate)
        service_deactivate "${service}"
        ;;
      --md5)
        service_md5="${extension_args[$((++index))]}"
        ;;
      --help)
        service_usage
        exit 0
        ;;
      --trace)
        set -o xtrace
        ;;
      *)
        _ignored_args+=("${token}")
        ;;
    esac

    if (( ${#_ignored_args[@]} > 0 ))
    then
      extension_args=( "${_ignored_args[@]}" )
    fi
  done

  service_init_path
}

#
# ## service\_pid()
#
# List installed services. These are extensions that have loaded the service
# module.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# A list of extensions that load the service module are printed to STDOUT of
# the calling environment.
#
# ### Environmental effects
#
# Sets the variable 'service_pid' to contain the pid of the process or 0.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# None currently.
#
# ### Usage Examples
#
#     root# service=redis
#     root# service_pid
#     root# echo $service_pid
#     0
#
service_pid()
{
  local _path="${run_path:="/var/run"}/${service}"
  local _pid=0

  ensure_paths_exist "${_path}"

  if file_is_nonempty "${_path}/${service}.pid"
  then
    read -r _pid < "${_path}/${service}.pid"
    if os_is_linux
    then # Sanity check.
      if ! path_exists "/proc/${pid}"
      then
        _pid=0
        rm -f "${_path}/${service}.pid"
      fi
    fi
  fi

  service_pid=${_pid}

  return 0
}

#
# ## service\_load\_conf\_d()
#
# Load the /etc/conf.d/{service}.conf configuration file into the calling
# environment.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# None, unless the configuration file contains any print statements.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_load_conf_d
#
service_load_conf_d()
{
  source_files "${confd_path}/${service}.conf"
}

#
# # Service init functions.
#
#
# ## service\_install\_init\_d()
#
# Installs the BDSM service module init.d script for the current extension.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging messages informing the end user of what is occurring.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service_install_init_d
#
service_install_init_d()
{
  local _service="${1:-}" _version="${2:-${package_version}}"

  variables_must_be_nonempty _service

  if path_exists "${init_scripts_path}"
  then
    if user_is_root
    then
      ensure_paths_exist "/etc/conf.d" "${init_scripts_path}"

      log "Installing init scripts for ${_service}"

      # TODO: Install the service init script...

      if template_exists "${_service}"
      then
        install_template "${_service}" to "${init_scripts_path}/${_service}" \
          mode 0755
      else
        # Use core srv init.d template
        install_template "init.d" \
          from "${bdsm_path}/extensions/builtin/srv/templates" \
          to "${init_scripts_path}/${_service}" mode 0755

        seed_template "${init_scripts_path}/${_service}" \
          modules_path "${modules_path}" \
          service "${service}" \
          service_user "${service_user}" \
          service_config_path "${service_config_path}"
      fi

      service_activate "${service}" "${package_version}"

      # TODO: Replace this with install_template + seed_template
      write "%s\n" "[ -d \"${prefix_path}/${_service}/active/bin\" ] && " \
        to "/etc/profile.d/${_service}.sh"

      write "%s\n" "PATH=\"\$PATH:${prefix_path}/${_service}/active/bin\" ; export PATH; " \
        append to "/etc/profile.d/${_service}.sh"

      chmod_files 0755 "/etc/profile.d/${_service}.sh"
    fi
  else
    return 0 # No bin/ dir, no need for it to be in the path.
  fi
}

#
# ## service\_init\_path()
#
# Sets the PATH explicitely for purposes of the current service extension.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# None.
#
# ### Environmental effects
#
# Alters the PATH variable.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service_init_path
#
service_init_path()
{
  PATH="${prefix_path}/bin:/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin:${PATH}"
}

#
# ## service\_init\_start()
#
# This is the generalized service init start function.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging telling the end user what is happening.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service_init_start
#
service_init_start()
{
  log "Starting ${service}..."

  service_init_path

  local _result=0 _command=( "${service_binary}" )

  if array_is_nonempty service_start_flags
  then
    _command+=("${service_start_flags[@]}")
  fi

  if array_is_nonempty service_flags
  then
    _command+=("${service_flags[@]}")
  fi

  if (( service_run_as_root_flag == 1 ))
  then
    if user_is_not_root
    then
      prefix="sudo"
    else
      prefix=""
    fi

    if $prefix ${_command[*]}
    then
      true
    else
      error "${service} failed to start."
    fi
  else
    if user_run_as "${service_user}" "${_command[*]}"
    then
      succeed "${service} successfully started."
    else
      log "${service} failed to start, check ${service_log_file} for details:"
      exec tail -n 50 "${service_log_file}"
      error
    fi
  fi
}

#
# ## service\_init\_stop()
#
# This is the generalized service init stop function.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging telling the end user what is happening.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service_init_stop
#
service_init_stop()
{
  service_init_path

  if (( service_pid > 0 ))
  then
    log "Stopping ${service}..."

    if command_exists "${service}_stop"
    then
      "${service}_stop"
    else
      if kill -QUIT "${service_pid}"
      then
        rm -f "$service_pid_file"
      else
        return $?
      fi
    fi
  else
    error "Service pid has not been set or is zero."
  fi
}

#
# ## service\_init\_status()
#
# This is the generalized service init status function.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging telling the end user what is happening.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service_init_status
#
service_init_status()
{
  log "statusing ${service}..."

  service_init_path

  local _command=( "${service_binary}" )

  if array_is_nonempty service_status_flags
  then
    _command+=("${service_status_flags[@]}")
  fi

  if array_is_nonempty service_flags
  then
    _command+=("${service_flags[@]}")
  fi

  if (( service_run_as_root_flag == 1 ))
  then
    if user_is_not_root
    then
      prefix="sudo"
    else
      prefix=""
    fi

    if $prefix ${_command[*]}
    then
      true
    else
      error "failed check status of ${service}"
    fi
  else
    if user_run_as "${service_user}" "${_command[*]}"
    then
      touch "$service_pid_file"
      succeed
    else
      error "failed check status of ${service}"
    fi
  fi
}

#
# ## service\_init\_reload()
#
# This is the generalized service init reload function.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging telling the end user what is happening.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service_init_reload
#
service_init_reload()
{
  log "reloading ${service}..."

  service_init_path

  local _command=( "${service_binary}" )

  if array_is_nonempty service_reload_flags
  then
    _command+=("${service_reload_flags[@]}")
  fi

  if array_is_nonempty service_flags
  then
    _command+=("${service_flags[@]}")
  fi

  if user_run_as "${service_user}" "${_command[*]}"
  then
    touch "$service_pid_file"
  else
    return $?
  fi
}

#
# ## service\_init\_usage()
#
# This is the generalized service init usage function.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging telling the end user what is happening.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service_init_usage
#
service_init_usage()
{
  log "Usage:\n\n  $0 {start|stop|restart|status|info|logtail}"
}

#
# ## service\_init\_logtail()
#
# This is the generalized service init logtail function.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging telling the end user what is happening.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service_init_logtail
#
service_init_logtail()
{
  # TODO: Adjust this to allow for parameter passing.

  tail -n 10 "${service_log_file}"
}

#
# ## service\_init\_info()
#
# This is the generalized service init info function.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Outputs information about the service (version, etc...).
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service_init_info
#
service_init_info()
{
  NIY "service_init_info has not yet been implemented for the service module."
}

#
# ## service\_conf\_d()
#
# Installs the conf.d template for the service, if it exists.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging updates to inform the user what is happening.
#
# ### Environmental effects
#
# Once completed a conf.d file should be in place.
# For example /etc/conf.d/redis.conf for the redis service.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_conf_d
#
service_conf_d()
{
  # TODO: Install a default conf.d template if extension does not define one.
  if template_exists "conf.d"
  then
    install_template "conf.d.template" to "/etc/conf.d/${service}.conf" \
      mode 0644 owner "${service_user}:${service_user}"

    if template_exists "/etc/conf.d/${service}.conf"
    then
      seed_template "/etc/conf.d/${service}.conf" \
        prefix_path "${prefix_path}" \
        init_scripts_path "${init_scripts_path}" \
        modules_path "${modules_path}" \
        confd_path "${confd_path}" \
        extension "${service}" \
        service "${service}"
    fi
  fi
}

#
# ## service\_init\_start()
#
# This is the generalized service init start function.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging telling the end user what is happening.
#
# ### Environmental effects
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service_init_action start
#
service_init_action()
{
  local _action="${1:-}"

  service_load_conf_d

  if command_exists "${service}_${action}"
  then
    "${service}_${action}"
  else
    if [[ "${service_actions[*]}" = *[[:space:]]${action}[[:space:]]* ]]
    then
      service_init_${action}
    else
      service_init_usage
    fi
  fi

  return $?
}

#
# ## service\_init\_d()
#
# The main logic for generalized service init.d scripts.
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging updates to inform the user what is happening.
#
# ### Environmental effects
#
# May alter the runstate of the service extension.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_init_d
#
service_init_d()
{
  service_load_conf_d # Load the configuration file.

  # Sanity checks go here.
  if file_is_executable "${service_binary}"
  then
    service_pid

    service_cli # Parse the script arguments and take action accordingly.

    service_init_action "${service_action}"
  else
    fail "Could not find ${service_binary} or ${service_binary} is not executable."
  fi
}

#
# ## service\_deactivate()
#
# Deactivates the named service or
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging updates to inform the user what is happening.
#
# ### Environmental effects
#
# Changes the current or named service's init script to be nonexecutable.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_deactivate
#
service_deactivate()
{
  local _service="${1:-"${service}"}"

  if [[ -z "${_service}" ]]
  then
    fail "a service name must be set or passed in in order to deactivate a service."
  fi

  if file_exists "${init_scripts_path}/${_service}"
  then
    chmod_files 0644 "${init_scripts_path}/${_service}"

    if user_is_root
    then # Add service to system startup
      if os_is_darwin
      then
        true # TODO: launchctl

      elif command_exists rc-update
      then
        rc-update add ${service} default

      elif command_exists chkconfig
      then
        chkconfig ${service} on

      #elif command_exists svc
      #then
      #  svc ${service} on # TODO: daemontools start on boot

      else # Fall back on rc.local to start redis at system startup.
        if file_exists "/etc/rc.local" &&
          ! file_contains "/etc/rc.local" "${service} start"
        then
          write "${init_scripts_path}/${service} start" \
            append to /etc/rc.local
        fi
      fi
    fi
  fi
  # TDOO: hook into system's paths.
}

#
# ## service\_activate()
#
# Activates the named service or
#
# ### Input Parameters
#
# None.
#
# ### Stream Outputs
#
# Logging updates to inform the user what is happening.
#
# ### Environmental effects
#
# Changes the current or named service's init script to be nonexecutable.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# No failure conditions currently.
#
# ### Usage Examples
#
#     user$ service=redis
#     user$ service_deactivate
#
service_activate()
{
  local _service="${1:-"${service}"}"

  if [[ -z "${_service}" ]]
  then
    fail "the service name must be set or passed in in order to activate a service."
  fi

  local _version="${2:-${package_version}}"

  variables_must_be_nonempty _service _version

  if symlink_exists "${service_db_path}/active"
  then
    service_deactivate "${_service}"
  fi

  if path_exists "${service_db_path}/${_version}"
  then
    log "Activating service ${_service} version ${_version}"

    link --force "${service_db_path}/${_version}" to "${service_db_path}/active"

    if file_exists "${init_scripts_path}/${service}"
    then
      chmod_files 0755 "${init_scripts_path}/${service}"

      if os_is_linux
      then
        if user_is_root
        then # Add service to system startup
          if command_exists rc-update
          then
            rc-update remove ${service} default

          elif command_exists chkconfig
          then
            chkconfig ${service} off

            #elif command_exists svc
            #then
            #  svc ${service} off
            #  TODO: daemontools remove start on boot

          else # Fall back on rc.local to start redis at system startup.
            if ! file_contains "/etc/rc.local" "${service} start"
            then
              sed -i -e "d#${init_scripts_path}/${service} start#" /etc/rc.local
            fi
          fi
        fi
      elif os_is_darwin
      then
        true # TODO: launchctl
      else # ::shrug::
        if file_exists "/etc/rc.local" &&
          ! file_contains "/etc/rc.local" "${service} start"
        then
          # TODO: change this to use replace_content
          sed -i -e "d#${init_scripts_path}/${service} start#" /etc/rc.local
        fi
      fi
    fi
  else
    log "Skipping activation of ${_service} ${_version} as '${install_path}' does not exist."
  fi
}

#
# ## service\_status()
#
#
service_status()
{
  log "${service}:"

  if [[ -s "${service_pid_file}" ]] && (( $(head -1 ${service_pid_file}) > 0 ))
  then
    local pid=$(head -1 ${service_pid_file})
    local ps=$(ps -p ${pid} -ostate,sgi_rss,vsize | tail -1)

    log "  status: running"
    log "  version: ${service_version}"
    log "  process: "
    log "    pid: ${pid}"
    if os_is_linux
    then
      log "    parent_pid: $(awk  '/^PPid:/{print $2}' /proc/${pid}/status)"
      log "    state: $(printf "$ps" | awk '{print $1}')"
      log "    rss: $(printf "$ps" | awk '{print $2}')"
      log "    vsz: $(printf "$ps" | awk '{print $3}')"
    fi

    if command_exists lsof
    then
      local cwd= binary= libraries=() tcp_ports=() udp_ports=() sockets=() \
        logs=() string
      while read -r line
      do
        case "$line" in
          (*[[:space:]]cwd[[:space:]]*)
            cwd="${line##* }"
            ;;
          (*[[:space:]]txt[[:space:]]*)
            binary="${line##* }"
            ;;
          (*/lib/*)
            string="${line%% (*}"
            libraries+=("${string##* }")
            ;;
          (*.log*)
            logs+=("${line##* }")
            ;;
          (*.sock*)
            sockets+=("${line##* }")
            ;;
          (*TCP*)
            string="${line%% (*}"
            tcp_ports+=("${string##* }")
            ;;
          (*)
            true # ignore
          ;;
        esac
      done < <(lsof -U -p ${pid})

      log "    cwd: ${cwd}"
      log "    binary: ${binary}"

      if (( ${#logs[@]} > 0 ))
      then
        log "    logs:"
        array_sort_asc logs
        array_unique logs
        for log in "${logs[@]}"
        do
          printf "      - %s\n" "${log}"
        done
      fi

      if (( ${#libraries[@]} > 0 ))
      then
        log "    libraries:"
        array_sort_asc libraries
        array_unique libraries
        for library in "${libraries[@]}"
        do
          printf "      - %s\n" "${library}"
        done
      fi

      if (( ${#tcp_ports[@]} > 0 ))
      then
        log "    tcp_ports:"
        array_sort_asc tcp_ports
        array_unique tcp_ports
        for tcp_port in "${tcp_ports[@]}"
        do
          printf "      - %s\n" "${tcp_port}"
        done
      fi

      if (( ${#sockets[@]} > 0 ))
      then
        log "    sockets:"
        array_sort_asc sockets
        array_unique sockets
        for socket in "${sockets[@]}"
        do
          printf "      - %s\n" "${socket}"
        done
      fi
    fi
  else
    log "  status: not running"
  fi
}
