#!/usr/bin/env bash

detect_template()
{
  local extensions_path=$1 _module_path
  shift
  _template_path=( $(rebuild_path "templates" $extensions_path $@ ) )

  if [[ -f "${_template_path}.template" ]]
  then
    template_path="${_template_path}.template"
    return 0
  elif [[ -f "${_template_path}" ]]
  then
    template_path="${_template_path}"
    template_name=$(echo -n $@ | sed 's/ /\//g' )
    return 0
  else
    return 1
  fi
}

#
# ## template_exists()
#
# Checks if a named template file exists in the extension_templates_path
#
# ### Input Parameters
#
# First parameter must be the name of a template file, eg. a string.
#
# ### Stream Outputs
#
# None.
#
# ### Return Codes
#
# 0 if the named template file exists and is nonempty.
# 1 if the named template file does not exist or is empty.
#
# ### Failure Scenarios
#
# Fails if no template name is given.
#
# ### Usage Examples
#
#     user$ {{ setup the scenario }}
#     user$ function_name {{ parameters }}
#     user$ {{ demonstrate the results}}
#
# ### Code Walkthrough
template_exists()
{
  if in_search_paths detect_template "$@"
  then
    log_search "template" "$template_name" "${template_path##${bdsm_path}\/extensions\/}"
    return 0
  else
    return 1
  fi
}

#
# ## install_template()
#
# Installs a named template to a given location.
#
# ### Input Parameters
#
# The first parameter should be the template name.
# Remaining parameters specify the target, mode and owner:
#   owner "<<user>>[:<<group>>]"
#   mode 0755
#   to "/path/to/new/file"
#
# ### Stream Outputs
#
# None.
#
# ### Return Codes
#
# 0 if successful
# 1 otherwise
#
# ### Failure Scenarios
#
# Fails if no template name was given.
# Fails if the template name given is a directory.
#
# ### Usage Examples
#
#     user$ install_template "nginx.conf" \
#           to "${nginx_path}/nginx.conf" \
#           mode 0644 owner "${nginx_user}"
#
# ### Code Walkthrough
install_template()
{
  # Declare function local variables, also set source mode and owner defaults
  local _template _name _target _source="${extension_templates_path}" \
    _mode=0644 _owner=$USER

  # While there are still function parameters left store the first parameter
  # as the token and remove it from the parameter list.
  #
  # Then switch based on what the parameter contains.
  #
  # If the parameter is 'to' then teh next parameter contains the target,
  # store it in the target variable and remove it from the parameters list.
  #
  # If the next parameter contains the mode, store it in the mode variable
  # and remove the mode from the parameters list.
  #
  # If the next parameter contains the owner, store it in the owner variable
  # and remove the owner from the parameters list.
  #
  # If the next parameter contains 'from' then store it in the source
  # variable and remove it from the parameters list.
  #
  # If we do not know what the token is
  # If a name has not yet been set then the token must be the name,
  # store it in the name variable otherwise if the target has not yet been
  # set then the token must be the target, store it in the target variable.
  while (( $# > 0 ))
  do
    token="${1}"
    shift
    case "${token}" in
      to)
        _target="$1"
        shift
        ;;
      mode)
        _mode="$1"
        shift
        ;;
      owner)
        _owner="$1"
        shift
        ;;
      from)
        fail 'install_template from is no more valid'
        ;;
      *)
        if variable_is_empty _name
        then
          _name="$token"
        elif variable_is_empty _target
        then
          _target="$token"
        fi
        ;;
    esac
  done

  if template_exists "${_name}"
  then
    _template="${template_path}"
    _name="$(basename "${_name}")"
  else
    fail "No template was found matching '${_name}'."
  fi

  # Create the target path if it does not exist. and then copy the template file
  # to the target location and name given.
  #
  # If the mode variable is not empty then we change the files access mode
  #
  # If the owner variable is not empty then we change the owner of the file
  #
  # Otherwise if the target is not a directory, it must be a fully specified
  # template name. Copy the template directly to the target as a file.
  # TODO: Ensure that the target's path exists.
  # If mode variable is not empty then change the file mode of the target.
  # If owner variable is not empty then change the owner of the target
  ensure_paths_exist "$(dirname "${_target}")"
  if path_exists "${_target}"
  then
    if path_exists "${_target}/${_name}"
    then
      fail "Cannot install the template '${_template}' to ${_target}/${_name}"\
        " as it is a directory"
    else
      copy_file "${_template}" to "${_target}/${_name}"

      if variable_is_nonempty _mode
      then
        chmod "${_mode}" "${_target}/${_name}"
      fi

      if variable_is_nonempty _owner
      then
        chown "${_owner}" "${_target}/${_name}"
      fi
    fi
  else
    copy_file --force "${_template}" to "${_target}"

    if variable_is_nonempty _mode
    then
      chmod_files "${_mode}" "${_target}"
    fi

    if variable_is_nonempty _owner
    then
      chown_files "${_owner}" "${_target}"
    fi
  fi
}

#
# ## seed_template()
#
# Seed a template file replacing all given keys with given values.
#
# ### Input Parameters
#
# First parameter must be the template filename to seed.
# Remaining parameters must come in pairs, the first of each pair specifies
# the key to search and replace in the template file and the second is the
# value to replace {{$key}} with.
#
# ### Stream Outputs
#
# None.
#
# ### Return Codes
#
# 0 for success
#
# ### Failure Scenarios
#
# Fails if no template filename is given.
# Fails if the template given does not exist.
# Fails if no key/value pairs are given.
# Fails if for every key there is no associated value.
# Fails if the intermediate template file does not exist...
#
# ### Usage Examples
#
#    user$ seed_template "/etc/conf.d/${extension}.conf" \
#      prefix_path "${prefix_path}" \
#      init_scripts_path "${init_scripts_path}" \
#      modules_path "${modules_path}" \
#      data_path "${data_path}" \
#      confd_path "${confd_path}" \
#      extension "${extension}"
#
# Note that if you wish to eliminate all unset strings then you pass the pair
#     ".*" ""
# As the last pair of arguments to seed_template
#
# ### Code Walkthrough
seed_template()
{
  # Store the first parameter into the template variable
  local _template="${1:-}"

  # if the template variable is not empty then remove the template from the
  # parameters list and store the remaining parameters into the hash variable
  # If the template file exists on the filesystem and if the hash array is
  # nonempty and if the hash array contains an even number of elements then
  # Loop over the hash and aggregate the replacement strings into their own
  # array. Filter the template with the replacments and if the filtering
  # happened successfully the timestamped template file will exist on the
  # filesystem then move the timestamped version to the target
  #
  # Otherwise if no key/value pairs were specified. This is typically a
  # programming error so send a fail message which will also trigger a
  # backtrace allowing the developer to fix the issue quickly.
  # Also if no template was given or the template file does not exist on the
  # filesystem then fail giving an error message and backtrace for debugging.
  if variable_is_nonempty _template
  then
    shift
    local _hash=("$@") _strings=() temporary=${_template}.${timestamp}.$$

    if file_exists "${_template}"
    then
      if array_is_nonempty _hash
      then
        if array_is_even _hash
        then
          for (( index=0 ; index < ${#_hash[@]} ; index++ ))
          do
            _strings+=(-e "s#{{${_hash[${index}]}}}#${_hash[$((++index))]}#g")
          done

          if sed "${_strings[@]}" ${_template} > ${temporary}
          then
            if file_exists "${temporary}"
            then
              move_file "${temporary}" "${_template}"
            else
              fail "Something went horribly wrong, the template intermediate file "\
                "'${temporary}' does not exist."
            fi
          else
            error "There was an error (?) seeding the template '${_template}'" \
              "Most often this is due to filesystem permissions / disk full."
          fi
        else
          fail "Every replacement key must have a replacement value."
        fi
      else
        fail "Cannot seed template '${_template}'"\
          "as no replacement keys were specified"
      fi
    else
      fail "Can not seed template template file '${_template}' does not exist."
    fi
  else
    fail "Can not install template as no template was given."
  fi
}

update_seed_template()
{
  local _template="${1:-}" _target="${2:-}"
  shift 2
  local  _incoming="${1:-${_target}.incoming}"
  shift || true
  local _hash=("$@") _strings=() _variables="${_target}.var.$$"

  if template_exists "${_template}"
  then
    _template="${template_path}"
  else
    fail "No template was found matching '${_name}'."
  fi

  for (( index=0 ; index < ${#_hash[@]} ; index++ ))
  do
    _strings+=(-e "s#{{${_hash[${index}]}}}#${_hash[$((++index))]}#g")
  done

  sed "${_strings[@]}" ${_template} | grep -E '^[^#]+=' > "${_variables}"

  diff -wN --left-column \
    <( grep -Eo '(^[^#]+=)' "${_target}" 2>/dev/null | sed 's/export //' | sort ) \
    <( grep -Eo '(^[^#]+=)' "${_variables}" | sed 's/export //' | sort ) | \
    awk -F '> ' '/^>/ {print$2}' | \
    xargs -n 1 | \
    xargs -I {} grep "^[^#]*{}" "${_variables}" > "${_incoming}"

  rm "${_variables}"
}

# actions
templates_help()
{
  echo "
Usage:

  bdsm templates list                        #show available templates
  bdsm templates list  all                   #show available templates with targets
  bdsm templates list  [template name]       #show generated files for given template
  bdsm templates apply [target file name...] #apply template on the given files
  bdsm templates apply [template]            #apply template on all targets
  bdsm templates diff  [target file name...] #diff template on the given files
  bdsm templates diff  [template]            #diff template on all targets

Template name shopuld contain '_' before last '.' example:

  my_app_nginx_.conf

Settings files is template name + .settings:

  my_app_nginx_.conf.settings

The content of settings file is consists of file names and replacements for them:

  my_app_nginx_dev.conf=(
    host_name my.host.name
    port 8080
  )
  my_app_nginx_prod.conf=(
    host_name my.host.name
    port 80
  )

"
}

templates_targets()
{
  local target

  if [[ $# -lt 1 ]]
  then
    error "Please provide template name!"
  fi

  if [[ "$1" == "${1/_*./_.}" && -f "$1" ]]
  then
    for target in $(hash_keys $1.settings)
    do
      if [[ -f $target ]]
      then
        log "  $target -> exists"
      else
        log "  $target -> missing"
      fi
    done
  else
    error "Not a template name!"
  fi
}

templates_list() {
  local template="$1"
  local settings="$template.settings"
  log "Template: $template"
  if [[ -f $settings ]]
  then
    log "Template settings: $template.settings"
  else
    log "Template settings are missing: $template.settings"
  fi
  if [[ "$2" == "all" ]]
  then
    templates_targets $1
  fi
}

templates_list_all() {
  local template templates all
  case "x$1" in
    xall)
      templates=( $( ls -1d *_.* 2>/dev/null || true ) )
      all="all"
      ;;
    x)
      templates=( $( ls -1d *_.* 2>/dev/null || true ) )
      all=""
      ;;
    *)
      templates=( $( ls -1d "$@" 2>/dev/null || true ) )
      all="all"
      ;;
  esac
  if (( ${#templates[@]} == 0 ))
  then
    error "No templates found\n"
  fi
  for template in ${templates[@]}
  do
    if [[ ! "$template" =~ ".settings" ]]
    then
      templates_list "$template" "$all"
    fi
  done
}

templates_apply_one()
{
  local target="$1" target_new="$1.$$"
  local key="$1" template="${1/_*./_.}"
  local settings="${template}.settings" replacements

  cp -f $template $target_new
  hash_read $settings $key replacements
  seed_template $target_new "${replacements[@]}" | grep -v "WARNING: Template Seeding (replacing defaults) has not yet fully implemented." || true

  if [[ ! -f $target ]] || ! diff -q $target_new $target > /dev/null
  then
    mv $target_new $target
    return $?
  else
    rm $target_new
    return 1
  fi
}

templates_apply_all()
{
  local list template first="$1"

  if (( $# < 1 ))
  then
    error "please provide template or target name!"
  fi

  if [[ "$first" == "${first/_*./_.}" && -f "$first" ]]
  then
    list=( $(hash_keys ${first}.settings) )
  else
    list=( $( ls -1d $@ ) )
  fi

  for template in "${list[@]}"
  do
    if templates_apply_one $template
    then
      log "  $template -> changed"
    else
      log "  $template"
    fi
  done
}

templates_apply_diff()
{
  local target="$1" target_new="$1.new"
  local key="$1" template="${target/_*./_.}"
  local settings="${template}.settings" replacements

  cp -f $template $target_new
  hash_read $settings $key replacements
  seed_template $target_new "${replacements[@]}" | grep -v "WARNING: Template Seeding (replacing defaults) has not yet fully implemented." || true

  diff -u $target $target_new || true
  rm -rf $target_new
}

templates_apply_diffs()
{
  local list template

  if (( $# < 1 ))
  then
    error "Please provide template or target name!"
  fi

  if [[ "$1" == "${1/_*./_.}" && -f "$1" ]]
  then
    list=( $(hash_keys $1.settings) )
  else
    list=( $( ls -1d $@ ) )
  fi

  for template in "${list[@]}"
  do
    templates_apply_diff $template
  done
}
