#autoload

# This code will try to correct the string on the line based on the
# strings generated for the context. These corrected strings will be
# shown in a list and one can cycle through them as in a menucompletion
# or get the corrected prefix.
#
# Supported configuration keys:
#
#  approximate_accept
#    This should be set to a number, specifying the maximum number
#    of errors that should be accepted. If the string also contains
#    a `n' or `N', the code will use the numeric argument as the
#    maximum number of errors if a numeric argument was given. If no
#    numeric argument was given, the number from the value of this
#    key will be used. E.g. with `compconf approximate_accept=2n' two
#    errors will be accepted, but if the user gives another number
#    with the numeric argument, this will be prefered. Also, with
#    `compconf approximate_accept=0n', normally no correction will be
#    tried, but if a numeric argument is given, automatic correction
#    will be used. On the other hand, if the string contains an `!'
#    and a `n' or `N', correction is not attempted if a numeric
#    argument is given. Once the number of errors to accept is
#    determined, the code will repeatedly try to generate matches by
#    allowing one error, two errors, and so on. Independent of the
#    number of errors the user wants to accept, the code will allow
#    only fewer errors than there are characters in the string from
#    the line.
#
#  approximate_original
#    This value is used to determine if the original string should
#    be included in the list (and thus be presented to the user when
#    cycling through the corrections). If it is set to any non-empty
#    value, the original string will be offered. If it contains the
#    sub-string `last', the original string will appear as the last
#    string when cycling through the corrections, otherwise it will
#    appear as the first one (so that the command line does not
#    change immediately). Also, if the value contains the sub-string
#    `always', the original string will always be included, whereas
#    normally it is included only if more than one possible
#    correction was generated.
#
#  approximate_prompt
#    This can be set to a string that should be printed before the
#    list of corrected strings when cycling through them. This string
#    may contain the control sequences `%n', `%B', etc. known from
#    the `-X' option of `compctl'. Also, the sequence `%e' will be
#    replaced by the number of errors accepted to generate the
#    corrected strings.
#
#  approximate_insert
#    If this is set to a string starting with `unambig', the code
#    will try to insert a usable unambiguous string in the command
#    line instead of always cycling through the corrected strings.
#    If such a unambiguous string could be found, the original
#    string is not used, independent of the setting of
#    `approximate_original'. If no sensible string could be found,
#    one can cycle through the corrected strings as usual.
#
# If any of these keys is not set, but the the same key with the
# prefix `correct' instead of `approximate' is set, that value will
# be used.

local _comp_correct _correct_prompt comax
local cfgacc cfgorig cfgps cfgins

# Only if all global matchers have been tried.

[[ compstate[matcher] -ne compstate[total_matchers] ]] && return 1

# We don't try correction if the string is too short.

[[ "${#:-$PREFIX$SUFFIX}" -le 1 ]] && return 1

# Get the configuration values, using either the prefix `correct' or
# `approximate'.

if [[ "$compstate[pattern_match]" = (|\**) ]]; then
  cfgacc="${compconfig[approximate_accept]:-$compconfig[correct_accept]}"
  cfgorig="${compconfig[approximate_original]:-$compconfig[correct_original]}"
  cfgps="${compconfig[approximate_prompt]:-$compconfig[correct_prompt]}"
  cfgins="${compconfig[approximate_insert]:-$compconfig[correct_insert]}"
else
  cfgacc="$compconfig[correct_accept]"
  cfgorig="$compconfig[correct_original]"
  cfgps="$compconfig[correct_prompt]"
  cfgins="$compconfig[correct_insert]"
fi

# Get the number of errors to accept.

if [[ "$cfgacc" = *[nN]* && ${NUMERIC:-1} -ne 1 ]]; then
  # Stop if we also have a `!'.

  [[ "$cfgacc" = *\!* ]] && return 1

  # Prefer the numeric argument if that has a sensible value.

  comax="${NUMERIC:-1}"
else
  comax="${cfgacc//[^0-9]}"
fi

# If the number of errors to accept is too small, give up.

[[ "$comax" -lt 1 ]] && return 1

# Otherwise temporarily define functions to use instead of
# the builtins that add matches. This is used to be able
# to stick the `(#a...)' into the right place (after an
# ignored prefix).

compadd() {
  [[ "$*" != *-([a-zA-Z/]#|)U* &&
     "${#:-$PREFIX$SUFFIX}" -le _comp_correct ]] && return

  if [[ "$PREFIX" = \~*/* ]]; then
    PREFIX="${PREFIX%%/*}/(#a${_comp_correct})${PREFIX#*/}"
  else
    PREFIX="(#a${_comp_correct})$PREFIX"
  fi
  if [[ -n "$_correct_prompt" ]]; then
    builtin compadd -X "$_correct_prompt" -J _correct "$@"
  else
    builtin compadd -J _correct "$@"
  fi
}

compgen() {
  [[ "$*" != *-([a-zA-Z/]#|)U* &&
     "${#:-$PREFIX$SUFFIX}" -le _comp_correct ]] && return

  if [[ "$PREFIX" = \~*/* ]]; then
    PREFIX="${PREFIX%%/*}/(#a${_comp_correct})${PREFIX#*/}"
  else
    PREFIX="(#a${_comp_correct})$PREFIX"
  fi
  if [[ -n "$_correct_prompt" ]]; then
    builtin compgen "$@" -X "$_correct_prompt" -J _correct
  else
    builtin compgen "$@" -J _correct
  fi
}

# Now initialise our counter. We also set `compstate[matcher]'
# to `-1'. This allows completion functions to use the simple
# `[[ compstate[matcher] -gt 1 ]] && return' to avoid being
# called for multiple global match specs and still be called 
# again when correction is done. Also, this makes it easy to
# test if correction is attempted since `compstate[matcher]'
# will never be set to a negative value by the completion code.

_comp_correct=1
compstate[matcher]=-1

_correct_prompt="${cfgps//\\%e/1}"

# We also need to set `extendedglob' and make the completion
# code behave as if globcomplete were set.

setopt extendedglob

[[ -z "$compstate[pattern_match]" ]] && compstate[pattern_match]='*'

while [[ _comp_correct -le comax ]]; do
  if _complete; then
    if [[ "$cfgins" = unambig* &&
          "${#compstate[unambiguous]}" -ge "${#:-$PREFIX$SUFFIX}" ]]; then
      compstate[pattern_insert]=unambiguous
    elif [[ compstate[nmatches] -gt 1 || "$cfgorig" = *always* ]]; then
      if [[ "$cfgorig" = *last* ]]; then
        builtin compadd -U -V _correct_original -nQ - "$PREFIX$SUFFIX"
      elif [[ -n "$cfgorig" ]]; then
	builtin compadd -U -nQ - "$PREFIX$SUFFIX"
      fi

      # If you always want to see the list of possible corrections,
      # set `compstate[list]=list' here.

      compstate[force_list]=list
    fi
    compstate[matcher]="$compstate[total_matchers]"
    unfunction compadd compgen

    return 0
  fi

  [[ "${#:-$PREFIX$SUFFIX}" -le _comp_correct+1 ]] && break
  (( _comp_correct++ ))

  _correct_prompt="${cfgps//\\%e/$_comp_correct}"
done

compstate[matcher]="$compstate[total_matchers]"
unfunction compadd compgen

return 1
