#!/usr/bin/env bash

reorder_path()
{
  local part="$1" extensions_path="$2" extension="$3"
  shift 3
  local output="$extensions_path/$extension/$part/$*"
  rebuilded_path="${output// /\/}"
}

rebuild_path()
{
  local part="$1" extensions_path="$2"
  shift 2
  local params="$*"
  reorder_path "${part}" "${extensions_path}" ${params//\// }
}

is_module()
{
  local _module_path=$1
  [[ -s "${_module_path}/dsl"     && ! -d "${_module_path}/dsl"     ]] ||
  [[ -s "${_module_path}/modules" && ! -d "${_module_path}/modules" ]]
}

detect_module()
{
  local extensions_path=$1 _module_path
  shift
  rebuild_path "modules/shell" $extensions_path $@
  _module_path=${rebuilded_path}

  if is_module "${_module_path}"
  then
    module_path=${_module_path}
    module_name="$*"
    module_name="${module_name// /\/}"
    return 0
  else
    return 1
  fi
}

init_extensions_search_paths()
{
  if [[ -z "${extensions_search_paths[*]}" ]]
  then
    extensions_search_paths=( "${bdsm_path}/extensions/external" )
    for extension in ${bdsm_path}/extensions/*
    do
      case ${extension} in
        (*/external|*/builtin)
          continue
          ;;
        (*)
          extensions_search_paths+=( "${extension}" )
          ;;
      esac
    done
    extensions_search_paths+=( "${bdsm_path}/extensions/builtin" )
  fi
  export extensions_search_paths
}

#
# ## extensions\_in()
#
# Lists the extensions in a given directory.
#
# ### Input Parameters
#
# First parameter is a full path a BDSM extensions directory.
#   (example: /usr/local/bdsm/extensions)
#
# ### Stream Outputs
#
# None.
#
# ### Environmental effects
#
# The names of the extensions in the given directory are set in found_extensions
#
# ### Return Codes
#
# 0 for success.
#
# ### Failure Scenarios
#
# No failure scenarios currently.
#
# ### Usage Examples
#
#     $ bdsm call extensions_in /usr/local/bdsm/extensions/external
#
extensions_in()
{
  local _path="$1" _extension
  found_extensions=()

  if [[ -d "${_path}" ]]
  then
    for _extension in ${_path}/*
    do
      if [[ -d "${_extension}/acions" || -d "${_extension}/modules" ]] #
      then
        found_extensions+=( "${_extension##${_path}\/}" )
      fi
    done
  else
    fail "Cannot list extensions in '${_path}' as the directory does not exist."
  fi
}

in_search_paths()
{
  trace_filter search
  local callback=$1
  shift
  local args=( "$@" ) path builtin_extension

  for path in ${extensions_search_paths[@]}
  do
    if "${callback}" "${path}" "${args[@]}"
    then
      return 0
    fi
  done

  if "${callback}" "${bdsm_path}/extensions/builtin" "bdsm" "${args[@]}"
  then
    return 0
  fi

  return 1
}

#
# ## module()
#
# Loads given module and it's dependencies
#
# ### Input Parameters
#
# Module name to load
#
# ### Stream Outputs
#
# None,
# except when '--debug=search' used - displays search result
#
# ### Return Codes
#
# 0 for success
# 1 for module not found
#
# ### Failure Scenarios
#
# ERROR when dependent modules could not be loaded
#
# ### Usage Examples
#
#    if module backup
#    then
#       log "module backup found, please backup your system"
#    else
#       error "could not find backup module"
#    fi
#
module()
{
  local _module=$1 _skip_path_detection=$2 _file _module_dependency _module_path

  # Check if module already loaded
  if module_is_loaded "${_module}"
  then
    return 0
  fi

  # Search for module
  if in_search_paths detect_module "${_module}"
  then
    log_search module "$module_name" "${module_path##${bdsm_path}\/extensions\/}"
    modules+=("${module_name}=${module_path##${bdsm_path}\/extensions\/}")
  else
    log_search module "${_module}" "Not found !!!"
    return 1
  fi

  # store path so dependencies do not overwrite it
  _module_path="${module_path}"

  # Load env before dependencies so they can use env settings
  if [[ -s "${_module_path}/env" ]]
  then
    #TODO: should we check for errors here ?!?!?!
    source "${_module_path}/env"
  fi

  # Load module dependencies
  if [[ -s "${module_path}/modules" ]]
  then
    for _module_dependency in $( cat ${module_path}/modules )
    do
      module_or_error "${_module_dependency}" \
        "Could not find module dependency '${_module_dependency}' from '${_module_path##${bdsm_path}\/extensions\/}/modules'"
    done
  fi

  # Load module
  for _file in dsl cli initialize
  do
    if [[ -s "${_module_path}/${_file}" ]]
    then
      #TODO: should we check for errors here ?!?!?!
      source "${_module_path}/${_file}"
    fi
  done

  return 0
}

# load module or show error msg if module not available
module_or_error()
{
  if ! module "$1"
  then
    error "$2"
  fi
}

#
# ## modules()
#
# Loads named BDSM modules into the calling environment.
#
# ### Input Parameters
#
# Positional Parameter listing and descriptions.
#
# ### Stream Outputs
#
# None, unless loading the module causes output to a stream.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if no module names were given as parameters.
#
# ### Usage Examples
#
#     user$ modules array system
#
#  modules x y z
#
#  if x,y or z do not contain a '/' then prefix core/
#  if x contains a '/' then load directly
#
#
modules()
{
  local _module _bdsm _extension _path _file _modules=("$@")

  if (( ${#_modules[@]} > 0 ))
  then
    for _module in "${_modules[@]}"
    do
      if ! module "${_module}"
      then
        fail "Could not find module path for ${_module}"
      fi
    done
  else
    fail "No modules specified to load."
  fi
}

#
# ## modules\_reinitialize()
#
# Function Description
#
# ### Input Parameters
#
# Positional Parameter listing and descriptions.
#
# ### Stream Outputs
#
# None, unless loading the module causes output to a stream.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if no extension module files names have been given.
#
# ### Usage Examples
#
#     user$ module_reinitialize package
#
modules_reinitialize()
{
  local _module _modules=("$@")

  if array_is_nonempty _modules
  then
    for _module in "${_modules[@]}"
    do
      source_files "${modules_path}/shell/${_module}/initialize"
    done
  else
    fail "Cannot load a module as no module was given."
  fi
}

#
# ## module\_is\_loaded()
#
# Checks to see whether or not the named module has been loaded or not.
#
# ### Input Parameters
#
# First parameter is a module name.
#
# ### Stream Outputs
#
# None.
#
# ### Return Codes
#
# 0 if the module has already been loaded
# 1 if the module has not already been loaded
#
# ### Failure Scenarios
#
# Fails if no module name was given as the first parameter.
#
# ### Usage Examples
#
#     user$ module_is_loaded package
#     user$ echo $?
#     1
#     user$ modules package
#     user$ module_is_loaded package
#     0
#
module_is_loaded()
{
  local _name="${1:-}"

  if [[ -n "${_name:-}" ]]
  then
    case " ${modules[*]} " in
      (*[[:space:]]${_name}=*)
        return 0
        ;;
      (*)
        return 1
        ;;
    esac
  else
    fail "Cannot determine if a module is loaded as no module was given."
  fi
}
