#!/bin/bash

# **** License ****
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
# 
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# A copy of the GNU General Public License is available as
# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution
# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'.
# You can also obtain it by writing to the Free Software Foundation,
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
# MA 02110-1301, USA.
# 
# Author: An-Cheng Huang
# Description: bash completion for Vyatta configuration commands
# 
# **** End License ****

# Turn on history logging
export HISTCONTROL=
export HISTFILESIZE=10000
export HISTSIZE=10000
export HISTTIMEFORMAT='%FT%T%z ' 
export PROMPT_COMMAND='history -a'
builtin set histappend=1

# only do this if we are going into configure mode
if [ "$_OFR_CONFIGURE" != "ok" ]; then
  return 0
fi

umask 0002

if [ -r /etc/default/vyatta ]; then
  source /etc/default/vyatta
fi

declare is_set=0
declare last_idx=0
declare -a comp_words=()

# commands to unalias
declare -a unalias_cmds=( clear configure date debug edit exit load merge \
                          no run set show save terminal undebug up top )
for cmd in "${unalias_cmds[@]}"; do
  unalias $cmd >& /dev/null
done

show ()
{
  local show_all=''
  local -a args=()
  for arg in "$@"; do
    if [ "$arg" == "-all" ]; then
      show_all='-all'
    else
      args[${#args[@]}]="$arg"
    fi
  done
  ${vyatta_sbindir}/vyatta-output-config.pl ${show_all} \
    ${VYATTA_EDIT_LEVEL//\// } ${args[@]} \
  | eval "${VYATTA_PAGER:-cat}"
}

commit ()
{
    /opt/vyatta/sbin/my_commit $@
    if [ $? == 0 ]; then
       touch $VYATTA_CHANGES_ONLY_DIR/.unsaved
    fi
}

save ()
{
  eval "sudo sg vyattacfg \"umask 0002 ; ${vyatta_sbindir}/vyatta-save-config.pl $@\""
  rm -f $VYATTA_CHANGES_ONLY_DIR/.unsaved
}

discard ()
{
    local changes
    if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then
	changes=1
    else
	changes=0
    fi

    sudo umount $VYATTA_TEMP_CONFIG_DIR
    sudo rm -fr $VYATTA_CHANGES_ONLY_DIR $VYATTA_TEMP_CONFIG_DIR
    make_vyatta_config_dir $VYATTA_CHANGES_ONLY_DIR
    make_vyatta_config_dir $VYATTA_TEMP_CONFIG_DIR
    sudo mount -t $UNIONFS \
	-o dirs=${VYATTA_CHANGES_ONLY_DIR}=rw:${VYATTA_ACTIVE_CONFIGURATION_DIR}=ro \
	$UNIONFS ${VYATTA_TEMP_CONFIG_DIR}
    
    if (( changes )); then
	echo "Changes have been discarded"
    else
	echo "No changes have been discarded"
    fi

}

reboot ()
{
  echo "Exit from configure mode before rebooting."
}

shutdown ()
{
  echo "Exit from configure mode before shutting down system."
}

declare vyatta_cfg_prompt_level=''
set_config_ps1 ()
{
  local level=$1
  vyatta_unescape level level
  if [ -z "$level" ]; then
    export PS1="[edit]\n\u@\h# "
    vyatta_cfg_prompt_level=''
  else
    export PS1="[edit $level]\n\u@\h# "
    vyatta_cfg_prompt_level="$level"
  fi
}

load ()
{
  # don't load if there are uncommitted changes.
  if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then
    echo "Cannot load: configuration modified."
    echo "Commit or discard the changes before loading a config file."
    return 1
  fi
  # return to top level.
  export VYATTA_EDIT_LEVEL="/"
  export VYATTA_TEMPLATE_LEVEL="/"
  set_config_ps1 ''
  eval "${vyatta_sbindir}/vyatta-load-config.pl $@"
}

merge ()
{
  # don't load if there are uncommitted changes.
  if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then
    echo "Cannot load: configuration modified."
    echo "Commit or discard the changes before loading a config file."
    return 1
  fi
  # return to top level.
  export VYATTA_EDIT_LEVEL="/"
  export VYATTA_TEMPLATE_LEVEL="/"
  set_config_ps1 ''
  eval "${vyatta_sbindir}/vyatta-load-config.pl $@ --merge"
}

top ()
{
  if [ "$VYATTA_EDIT_LEVEL" == "/" ]; then
    echo "Already at the top level"
    return 0
  fi

  # go to the top level.
  export VYATTA_EDIT_LEVEL="/"
  export VYATTA_TEMPLATE_LEVEL="/"
  set_config_ps1 ''
}

rename()
{
  mvcp rename Rename mv "$@"
}

copy()
{
  mvcp copy Copy "cp -a" "$@"
}

mvcp ()
{
  local str=$1
  shift
  local Str=$1
  shift
  local cmd=$1
  shift
  local _otag=$1
  local _ovalu=$2
  local _to=$3
  local _ntag=$4
  local _nvalu=$5
  local _oval=''
  local _nval=''
  local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL}
  local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL}
  vyatta_escape _ovalu _oval
  vyatta_escape _nvalu _nval
  if [ "$_to" != 'to' ] || [ -z "$_ntag" ] || [ -z "$_nval" ]; then
    echo "Invalid $str command"
    return 1
  fi
  if [ "$_otag" != "$_ntag" ]; then
    echo "Cannot $str from \"$_otag\" to \"$_ntag\""
    return 1
  fi
  if [ ! -d "$_tpath/$_otag/$VYATTA_TAG_NAME" ]; then
    echo "Cannot $str under \"$_otag\""
    return 1
  fi
  if [ ! -d "$_mpath/$_otag/$_oval" ]; then
    echo "Configuration \"$_otag $_ovalu\" does not exist"
    return 1
  fi
  if [ -d "$_mpath/$_ntag/$_nval" ]; then
    echo "Configuration \"$_ntag $_nvalu\" already exists"
    return 1
  fi
  if ! /opt/vyatta/sbin/my_set $_ntag "$_nvalu"; then
    echo "$Str failed"
    return 1
  fi
  /opt/vyatta/sbin/my_delete $_ntag "$_nvalu" >&/dev/null 3>&1

  $cmd "$_mpath/$_otag/$_oval" "$_mpath/$_ntag/$_nval"

  return 0
}

edit ()
{
  local num_comp=${#@}
  local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL}
  local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL}
  local idx
  if ! /opt/vyatta/sbin/my_set $* >&/dev/null 3>&1; then
    echo "Invalid node \"$*\" for the 'edit' command"
    return 1
  fi
  for (( idx=1; idx <= num_comp; idx++ )); do
    local comp
    eval "comp=\$$idx"
    vyatta_escape comp comp
    push_path _mpath $comp
    push_path _tpath $comp
    if [ ! -d $_mpath ]; then
      # "edit" only allows existing node
      break
    fi

    # check if it's not tag value
    if [ -d $_tpath ]; then
      continue
    fi

    # check if it's tag value 
    pop_path _tpath
    push_path _tpath $VYATTA_TAG_NAME
    if [ -d $_tpath ]; then
      continue
    fi
    pop_path _tpath
    pop_path _mpath
    break
  done
  # "edit" only valid for
  # * "node.tag" level
  # * "node.def" level without "type:"
  if (( idx != ( num_comp + 1) )); then
    echo "Invalid node \"$*\" for the 'edit' command"
    return 1
  fi
  if [ "${_tpath:((-9))}" != "/node.tag" ]; then
    # we are not at "node.tag" level. look for "type:".
    if [ ! -r "$_tpath/node.def" ]; then
      vyatta_cfg_type=""
    else
      vyatta_parse_tmpl "$_tpath/node.def"
    fi
    if [ -n "$vyatta_cfg_type" ]; then
      # "type:" present
      echo "The 'edit' command cannot be issued at the \"$*\" level"
      return 1
    fi
  fi
  export VYATTA_EDIT_LEVEL="${_mpath#$VYATTA_TEMP_CONFIG_DIR}/"
  export VYATTA_TEMPLATE_LEVEL="${_tpath#$VYATTA_CONFIG_TEMPLATE}/"

  declare -a path_arr
  path_str2arr VYATTA_EDIT_LEVEL path_arr
  local path_str="${path_arr[*]}"
  set_config_ps1 "$path_str"
}

up ()
{
  if [ "$VYATTA_EDIT_LEVEL" == "/" ]; then
    echo "Already at the top level"
    return 0
  fi
  if [[ $VYATTA_TEMPLATE_LEVEL == */node.tag/ ]]; then
    export VYATTA_EDIT_LEVEL="${VYATTA_EDIT_LEVEL%/*/*/}/"
    export VYATTA_TEMPLATE_LEVEL="${VYATTA_TEMPLATE_LEVEL%/*/*/}/"
  else
    export VYATTA_EDIT_LEVEL="${VYATTA_EDIT_LEVEL%/*/}/"
    export VYATTA_TEMPLATE_LEVEL="${VYATTA_TEMPLATE_LEVEL%/*/}/"
  fi
  
  declare -a path_arr
  path_str2arr VYATTA_EDIT_LEVEL path_arr
  local path_str="${path_arr[*]}"
  set_config_ps1 "$path_str"
}

really_exit()
{
  if [ -f $VYATTA_CHANGES_ONLY_DIR/.unsaved ]; then
     echo "Warning: configuration changes have not been saved."
  fi
  sudo umount $VYATTA_TEMP_CONFIG_DIR
  sudo rm -rf $VYATTA_TEMP_CONFIG_DIR $VYATTA_CHANGES_ONLY_DIR \
              $VYATTA_CONFIG_TMP
  unset _OFR_CONFIGURE
  builtin exit 0
}

exit ()
{
  local discard
  if [ $# == 0 ]; then
    discard=0
  elif [ $# == 1 ] && [ "$1" == "discard" ]; then
    discard=1
  else
    echo "Invalid argument \"$*\" for 'exit'"
    return 1
  fi

  if [ "$VYATTA_EDIT_LEVEL" == "/" ]; then
    # we are at the root level. check if we can really exit.
    if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then
      if (( ! discard )); then
        echo "Cannot exit: configuration modified."
        echo "Use 'exit discard' to discard the changes and exit."
        return 1
      fi
    fi
    really_exit
  fi

  # "exit" to the root level.
  export VYATTA_EDIT_LEVEL="/"
  export VYATTA_TEMPLATE_LEVEL="/"
  set_config_ps1 ''
}

# run op mode commands
run ()
{
 if [ $# == 0 ]; then
  echo Incomplete command
  return 1
 fi 
  _vyatta_op_run $@
}

# do op mode completion
vyatta_run_complete ()
{
  local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; )
  shopt -s extglob nullglob

  COMP_WORDS=( "${COMP_WORDS[@]:1}" )
  (( COMP_CWORD -= 1 ))
  _vyatta_op_expand
  
  eval $restore_shopts
}

vyatta_loadsave_complete()
{
  # Generate completion help for the "load" and "save" commands

  local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; )
  shopt -s extglob nullglob

  # Only provide completions after command name has been typed, but
  # before any characters of the command argument have been entered.
  # File name completion, and completion of the various URL formats
  # is not supported yet.
  #
  if [ $COMP_CWORD -eq 1 -a -z "${COMP_WORDS[1]}" ]; then
      echo
      echo "Possible completions:"
      if [ "${COMP_WORDS[0]}" = "load" ]; then
	  echo -e "  <Enter>\t\t\t\tLoad from system config file"
	  echo -e "  <file>\t\t\t\tLoad from file on local machine"
	  echo -e "  scp://<user>:<passwd>@<host>/<file>\tLoad from file on remote machine"
	  echo -e "  ftp://<user>:<passwd>@<host>/<file>\tLoad from file on remote machine"
	  echo -e "  http://<host>/<file>\t\t\tLoad from file on remote machine"
	  echo -e "  tftp://<host>/<file>\t\t\tLoad from file on remote machine"
      elif [ "${COMP_WORDS[0]}" = "merge" ]; then
	  echo -e "  <Enter>\t\t\t\tMerge from system config file"
	  echo -e "  <file>\t\t\t\tMerge from file on local machine"
	  echo -e "  scp://<user>:<passwd>@<host>/<file>\tMerge from file on remote machine"
	  echo -e "  ftp://<user>:<passwd>@<host>/<file>\tMerge from file on remote machine"
	  echo -e "  http://<host>/<file>\t\t\tMerge from file on remote machine"
	  echo -e "  tftp://<host>/<file>\t\t\tMerge from file on remote machine"
      elif [ "${COMP_WORDS[0]}" = "save" ]; then
	  echo -e "  <Enter>\t\t\t\tSave to system config file"
	  echo -e "  <file>\t\t\t\tSave to file on local machine"
	  echo -e "  scp://<user>:<passwd>@<host>/<file>\tSave to file on remote machine"
	  echo -e "  ftp://<user>:<passwd>@<host>/<file>\tSave to file on remote machine"
	  echo -e "  tftp://<host>/<file>\t\t\tSave to file on remote machine"
      fi
      COMPREPLY=( "" " " )
  else
      COMPREPLY=()
  fi

  eval $restore_shopts
}

loadkey()
{
  # don't load if there are uncommitted changes.
  if [ -f "$VYATTA_TEMP_CONFIG_DIR/$VYATTA_MOD_NAME" ]; then
    echo "Cannot load: configuration modified."
    echo "Commit or discard the changes before loading a config file."
    return 1
  fi
  # return to top level.
  export VYATTA_EDIT_LEVEL="/"
  export VYATTA_TEMPLATE_LEVEL="/"
  set_config_ps1 ''
  eval "${vyatta_sbindir}/vyatta-load-user-key.pl $@"
}

comment()
{
    if [ "$#" -eq "0" ]; then
        return 0
    fi
    ${vyatta_sbindir}/vyatta-comment-config.pl "$@"
}

activate()
{
    #create or remove activate file
    eval "${vyatta_sbindir}/vyatta-activate-config.pl activate $@" 
}

deactivate()
{
    #create or remove activate file
    eval "${vyatta_sbindir}/vyatta-activate-config.pl deactivate $@" 
}

vyatta_loadkey_complete()
{
  case "$COMP_CWORD" in
      1) if [ -z "${COMP_WORDS[1]}" ]; then
	  COMPREPLY=( $(getent passwd | awk -F: '$7 == "/bin/vbash" { print $1}') )
	 else
	  COMPREPLY=( $(compgen -u -- ${COMP_WORDS[1]} ) )
         fi ;;
      2) if [ -z "${COMP_WORDS[2]}" ]; then
	  echo
	  echo "Possible completions:"
	  echo -e "  <file>\t\t\t\tLoad from file on local machine"
	  echo -e "  scp://<user>@<host>/<file>\tLoad from file on remote machine"
	  echo -e "  ftp://<user>@<host>/<file>\tLoad from file on remote machine"
	  echo -e "  http://<host>/<file>\t\t\tLoad from file on remote machine"
	  echo -e "  tftp://<host>/<file>\t\t\tLoad from file on remote machine"
	  COMPREPLY=()
	 else
	  COMPREPLY=( $(compgen -f -- ${COMP_WORDS[2]} ) )
	 fi ;;
  esac
}

declare v_cfg_completion_debug=0
decho ()
{
  if (( v_cfg_completion_debug )); then
    echo -n "$*"
  fi
}

push_path_arr ()
{
  # $1: \@path_arr
  # $2: component
  eval "$1=( \"\${$1[@]}\" '$2' )"
}

pop_path_arr ()
{
  # $1: \@path_arr
  eval "$1=( \"\${$1[@]:0:((\${#$1[@]}-1))}\" )"
}

path_arr2str ()
{
  # $1: \@path_arr
  # $2: \$path_str
  eval "$2=\"\${$1[*]}\""
  eval "$2=/\${$2// //}"
}

path_str2arr ()
{
  # $1: \$path_str
  # $2: \@path_arr
  local tmp
  eval "tmp=\${$1:1}"
  eval "$2=( \${tmp//\// } )"
}

push_path ()
{
  # $1: \$path_str
  # $2: component
  declare -a path_arr
  eval "path_str2arr $1 path_arr"
  eval "push_path_arr path_arr '$2'"
  eval "path_arr2str path_arr $1"
}

pop_path ()
{
  # $1: \$path_str
  declare -a path_arr
  eval "path_str2arr $1 path_arr"
  pop_path_arr path_arr
  eval "path_arr2str path_arr $1"
}

get_filtered_dir_listing ()
{
  # $1: path
  # $2: \@listing
  if [ ! -d $1 ]; then
    eval "$2=()"
    return
  fi
  local pattern='^node\.def$|^node\.tag$|^node\.val$|^\.modified$'
  patterh=$pattern'|^\.commit\.lck$|^\.wh\.'
  local cmd="ls $1 |egrep -v '$pattern'"
  declare -a listing=( $(eval $cmd) )
  for enode in "${listing[@]}"; do
    local unode
    vyatta_unescape enode unode
    eval "$2[\${#$2[@]}]=$unode"
  done
}

filter_existing_nodes ()
{
  # $1: mpath
  # $2: \@orig
  # $3: \@filtered
  declare -a orig
  eval "orig=( \"\${$2[@]}\" )"
  for node in "${orig[@]}"; do
    if [ -d "$1/$node" ]; then
      eval "$3[\${#$3[@]}]=$node"
    fi
  done
}

get_prefix_filtered_list ()
{
  # $1: prefix
  # $2: \@list
  # $3: \@filtered
  declare -a olist
  local pfx=$1
  pfx=${pfx#\"}
  eval "olist=( \"\${$2[@]}\" )"
  local idx=0
  for elem in "${olist[@]}"; do
    local sub=${elem#$pfx}
    if [ "$elem" == "$sub" ] && [ -n "$pfx" ]; then
      continue
    fi
    eval "$3[$idx]=\"$elem\""
    (( idx++ ))
  done
}

get_prefix_filtered_list2 ()
{
  # $1: prefix
  # $2: \@list
  # $3: \@filtered
  # $4: \@list2
  # $5: \@filtered2
  declare -a olist
  local pfx=$1
  pfx=${pfx#\"}
  eval "olist=( \"\${$2[@]}\" )"
  eval "local orig_len=\${#$2[@]}"
  local orig_idx=0
  local idx=0
  for (( orig_idx = 0; orig_idx < orig_len; orig_idx++ )); do
    eval "local elem=\${$2[$orig_idx]}"
    eval "local elem2=\${$4[$orig_idx]}"
    local sub=${elem#$pfx}
    if [ "$elem" == "$sub" ] && [ -n "$pfx" ]; then
      continue
    fi
    eval "$3[$idx]=\"$elem\""
    eval "$5[$idx]=\"$elem2\""
    (( idx++ ))
  done
}

vyatta_parse_tmpl_comp_fields ()
{
  # $1: tmpl
  # $2: field name
  sed -n '
  /^'"$2"':/,$ {
    s/^'"$2"':[ 	]*//
    h
    :b
    $ { x; p; q }
    n
    /^\([-_a-z]\+:\|#\)/ { x; p; q }
    H
    bb
  }
  ' $1
}

declare vyatta_cfg_help=""
declare vyatta_cfg_type=""
declare vyatta_cfg_tag=0
declare vyatta_cfg_multi=0
declare -a vyatta_cfg_allowed=()
declare vyatta_cfg_comp_help=""
declare -a vyatta_cfg_val_type=()
declare -a vyatta_cfg_val_help=()
vyatta_parse_tmpl ()
{
  # $1: tmpl
  vyatta_cfg_help=""
  vyatta_cfg_type=""
  vyatta_cfg_enum=''
  vyatta_cfg_tag=0
  vyatta_cfg_multi=0
  vyatta_cfg_allowed=()
  vyatta_cfg_comp_help=''
  vyatta_cfg_val_type=()
  vyatta_cfg_val_help=()
  if [ ! -r $1 ]; then
    return
  fi
  eval `sed -n '
  /^syntax:expression:[ 	]\+\$VAR(@)[ 	]\+in[ 	]\+/ {
    s/^syntax:expression:[ 	]\+\$VAR(@)[ 	]\+in[ 	]\+/vyatta_cfg_allowed=( /
    s/^\([^;]\+\);.*$/\1 )/
    s/[ 	]*,[ 	]*/ /gp
  }
  s/^tag:.*/vyatta_cfg_tag=1/p
  s/^multi:.*/vyatta_cfg_multi=1/p
  s/^type:[ 	]\+\([^ 	;]\+\)\(;.*\)\?/vyatta_cfg_type=\1/p
  s/^enumeration:[ 	]\+\([^ 	]\+\)/vyatta_cfg_enum=\1/p
  ' $1`
  
  vyatta_cfg_help=$(vyatta_parse_tmpl_comp_fields $1 "help")
  
  local acmd=$(vyatta_parse_tmpl_comp_fields $1 "allowed")
  if [ -n "$vyatta_cfg_enum" ]; then
    local enum_script="/opt/vyatta/share/enumeration/$vyatta_cfg_enum"
    if [ -f "$enum_script" ] && [ -e "$enum_script" ]; then
      acmd="$enum_script"
    fi
  fi
  vyatta_cfg_comp_help=$(vyatta_parse_tmpl_comp_fields $1 "comp_help")
  eval `grep '^val_help:' $1 | sed 's/^val_help:[ 	]*//;
                                     s/[ 	]*;[ 	]*/;/' \
        | while read line; do
            if [[ "$line" == *";"* ]]; then
              echo "vyatta_cfg_val_type+=( \"${line%%;*}\" )"
              echo "vyatta_cfg_val_help+=( \"${line##*;}\" )"
            else
              echo "vyatta_cfg_val_help+=( \"$line\" )"
            fi
          done`
 
  if (( ${#vyatta_cfg_allowed[@]} == 0 )); then
    astr=$(eval "$acmd")
    astr=${astr//</\\<}
    astr=${astr//>/\\>}
    eval "ares=( $astr )"
    for (( i=0 ; i<${#ares[@]} ; i++ )); do
      if [[ "${ares[i]}" != \<*\> ]]; then
        vyatta_cfg_allowed+=( "${ares[i]}" )
      else
        vyatta_cfg_allowed+=( "" )
      fi
    done
  fi
  if [ -z "$vyatta_cfg_help" ]; then
    vyatta_cfg_help='<No help text available>'
  fi
}

# this fills in $vyatta_help_text
generate_help_text ()
{
  # $1: \@items
  # $2: \@help_strs
  declare -a items
  declare -a helps
  eval "items=( \"\${$1[@]}\" )"
  eval "helps=( \"\${$2[@]}\" )"
  if [ -n "$vyatta_cfg_comp_help" ]; then
    vyatta_help_text="\\n${vyatta_cfg_comp_help}"
    return 0
  fi
  vyatta_help_text="\\nPossible completions:"
  for (( idx = 0; idx < ${#items[@]}; idx++ )); do
    vyatta_help_text="${vyatta_help_text}\\n\\x20\\x20"
    if [ ${#items[$idx]} -lt 6 ]; then
      vyatta_help_text="${vyatta_help_text}${items[$idx]}\\t\\t"
    elif [ ${#items[$idx]} -lt 14 ]; then
      vyatta_help_text="${vyatta_help_text}${items[$idx]}\\t"
    else
      vyatta_help_text="${vyatta_help_text}${items[$idx]}\\n\\x20\\x20\\t\\t"
    fi
    vyatta_help_text="${vyatta_help_text}${helps[$idx]}"
  done
}

# this fills in $vyatta_help_text
get_tmpl_subdir_help ()
{
  # $1: path
  # $2: \@subdirs
  declare -a subdirs
  eval "subdirs=( \"\${$2[@]}\" )"
  if [ ${#subdirs[@]} == 0 ]; then
    vyatta_help_text=""
    return
  fi
  declare -a hitems=()
  declare -a hstrs=()
  for subdir in "${subdirs[@]}"; do
    if [ ! -r $1/$subdir/node.def ]; then
      vyatta_cfg_help="<No help text available>"
    else
      vyatta_parse_tmpl "$1/$subdir/node.def"
      # comp_help overrides the current help, so we reset it here since
      # it is from the subdir.
      vyatta_cfg_comp_help=''
    fi
    hitems[${#hitems[@]}]=$subdir
    hstrs[${#hstrs[@]}]=$vyatta_cfg_help
  done
  generate_help_text hitems hstrs
}

# return 0 if yes. 1 if no.
item_in_list ()
{
  # $1: item
  # $2: \@list
  declare -a olist
  local item
  eval "olist=( \"\${$2[@]}\" )"
  for item in "${olist[@]}"; do
    if [ "$1" == "$item" ]; then
      return 0
    fi
  done
  return 1
}

append_allowed_values ()
{
  # $1: tmpl_path
  # $2: \@values
  if [ ! -r "$1/node.def" ]; then
    return
  fi
  vyatta_parse_tmpl "$1/node.def"
  local item
  for item in "${vyatta_cfg_allowed[@]}"; do
    if ! item_in_list "$item" $2; then
      eval "$2[\${#$2[@]}]=\"$item\""
    fi
  done
}

# return 0 if yes. 1 if no.
is_setting_new_leaf ()
{
  # $1: tmpl_path
  if [ $is_set == 0 ]; then
    return 1
  fi
  vyatta_parse_tmpl "$1/node.def"
  if [ -z "$vyatta_cfg_type" ]; then
    return 1
  fi
  return 0
}

get_value_format_string ()
{
  local vtype=$1
  case "$vtype" in
    txt)
      echo -n '<text>'
      ;;
    u32)
      echo -n '<0-4294967295>'
      ;;
    u32:*)
      echo -n "<${vtype##u32:}>"
      ;;
    ipv4)
      echo -n '<x.x.x.x>'
      ;;
    ipv6)
      echo -n '<h:h:h:h:h:h:h:h>'
      ;;
    ipv4net)
      echo -n '<x.x.x.x/x>'
      ;;
    ipv6net)
      echo -n '<h:h:h:h:h:h:h:h/x>'
      ;;
    bool)
      echo -n '<boolean>'
      ;;
    macaddr)
      echo -n '<h:h:h:h:h:h>'
      ;;
    *)
      echo -n "$vtype"
      ;;
  esac
}

# this fills in $vyatta_help_text
get_node_value_help ()
{
  # $1: path
  # $2: \@values
  declare -a vals
  eval "vals=( \"\${$2[@]}\" )"
  if [ $is_set == 0 -a ${#vals[@]} == 0 ]; then
    vyatta_help_text=""
    return
  fi
  if [ ! -r "$1/node.def" ]; then
    vyatta_cfg_help="<No help text available>"
    vyatta_cfg_type=""
  else
    vyatta_parse_tmpl "$1/node.def"
  fi
  if (( ${#vyatta_cfg_val_type[@]} == 0 )); then
    # didn't get val_type, use type (with support for multi-typed nodes)
    vyatta_cfg_val_type=( ${vyatta_cfg_type//,/ } )
  fi
  if (( ${#vyatta_cfg_val_help[@]} == 0 )); then
    # didn't get val_help, use help
    vyatta_cfg_val_help=( "$vyatta_cfg_help" )
  fi

  declare -a hitems=()
  declare -a hstrs=()
  for ((i = 0; i < ${#vyatta_cfg_val_type[@]}; i++)); do
    local t=$(get_value_format_string "${vyatta_cfg_val_type[i]}")
    hitems+=( "$t" )
    hstrs+=( "${vyatta_cfg_val_help[i]}" )
  done
  generate_help_text hitems hstrs
}

get_value_list ()
{
  # $1: path
  # $2: \@listing
  local vfile=$1/node.val
  if [ ! -r $vfile ]; then
    eval "$2=()"
    return
  fi
  declare -a listing=()
  local cmd=$(sed 's/^\(.*\)$/listing[\${#listing[@]}]='\''\1'\''/' $vfile)
  eval "$cmd"
  eval "$2=( \"\${listing[@]}\" )"
}

vyatta_escape ()
{
  # $1: \$original
  # $2: \$escaped
  eval "$2=\${$1//\%/%25}"
  eval "$2=\${$2//\*/%2A}"
  eval "$2=\${$2//\//%2F}"
}

vyatta_unescape ()
{
  # $1: \$escaped
  # $2: \$original
  eval "$2=\${$1//\%2F/\/}"
  eval "$2=\${$2//\%2A/*}"
  eval "$2=\${$2//\%25/%}"
}

declare -a vyatta_completions
declare vyatta_help_text="\\nNo help text available"
declare vyatta_do_help=0
vyatta_do_complete ()
{
  # when this function is called, it is expected that:
  # * "vyatta_help_text" is filled with the help text.
  # * "vyatta_completions" is an array of "filtered" possible completions
  #   (i.e., only those starting with the current last component).
  local do_help=$vyatta_do_help
 
  # we may not want to do the following
<<'ENDCOMMENT'
  if [ ${#vyatta_completions[@]} == 1 ]; then
    # no ambiguous completions. do completion instead of help.
    do_help=0
  fi

  # now check if we can auto-complete at least 1 more character.
  if (( do_help )); then
    local schar=""
    for comp in "${vyatta_completions[@]}"; do
      local sub=$comp
      if [ ! -z "${COMP_WORDS[COMP_CWORD]}" ]; then
        sub=${comp#${comp_words[$last_idx]}}
        if [ "$comp" == "$sub" ]; then
          # should not happen since vyatta_completions should be filtered.
          continue
        fi
      fi
      if [ -z "$schar" ]; then
        schar=${sub:0:1}
      else
        if [ "$schar" != "${sub:0:1}" ]; then
          schar=""
          break
        fi
      fi
    done
    if [ ! -z "$schar" ]; then
      do_help=0
    fi
  fi
ENDCOMMENT

  if (( do_help )); then
    printf "$vyatta_help_text"
    COMPREPLY=( "" " " )
  else
    local -a f_comps=()
    local cword=
    if (( ${#COMP_WORDS[@]} > 0 )); then
      cword=${COMP_WORDS[COMP_CWORD]}
    fi
    get_prefix_filtered_list "$cword" vyatta_completions f_comps
    local estr="COMPREPLY=( "
    for w in "${f_comps[@]}"; do
      estr="$estr\"$w\" "
    done
    estr="${estr})"
    eval "$estr"
  fi
  vyatta_help_text="\\nNo help text available"
}

generate_pipe_help ()
{
  local -a hcomps=( "${_vyatta_pipe_completions[@]}" \
                    "${_vyatta_pipe_noncompletions[@]}" )
  local -a hstrs=()
  for comp in "${hcomps[@]}"; do
    hstrs+=("$(_vyatta_pipe_help "$comp")")
  done
  generate_help_text hcomps hstrs
}

vyatta_config_complete ()
{
  local restore_shopts=$( shopt -p extglob nullglob | tr \\n \; )
  shopt -s extglob nullglob

  if [ "$COMP_LINE" == "$VYATTA_COMP_LINE" ]; then
    VYATTA_COMP_LINE=''
    vyatta_do_help=1
  else
    VYATTA_COMP_LINE=$COMP_LINE
    vyatta_do_help=0
  fi

  # handle pipe
  if _vyatta_pipe_completion "${COMP_WORDS[@]}"; then
    generate_pipe_help
    vyatta_completions=( "${_vyatta_pipe_completions[@]}" )
    vyatta_do_complete
    eval $restore_shopts
    return
  fi

  if (( ${#COMP_WORDS[@]} < 2 )); then
    declare -a hitems=( "comment" \
	                "commit" \
                        "copy" \
                        "delete" \
                        "discard" \
                        "edit" \
                        "exit" \
                        "load" \
			"loadkey" \
                        "merge" \
                        "rename" \
                        "run" \
                        "save" \
                        "set" \
                        "show" )
    declare -a hstrs=( \
      "Add comment to this configuration element" \
      "Commit the current set of changes" \
      "Copy a configuration element" \
      "Delete a configuration element" \
      "Discard uncommitted changes" \
      "Edit a sub-element" \
      "Exit from this configuration level" \
      "Load configuration from a file and replace running configuration" \
      "Load user SSH key from a file" \
      "Load configuration from a file and merge running configuration" \
      "Rename a configuration element" \
      "Run an operational-mode command" \
      "Save configuration to a file" \
      "Set the value of a parameter or create a new element" \
      "Show the configuration (default values may be suppressed)" \
    )
    if (( ${#COMP_WORDS[@]} == 1 )); then
      declare -a fitems=()
      declare -a fstrs=()
      get_prefix_filtered_list2 "${COMP_WORDS[0]}" hitems fitems hstrs fstrs
      hitems=( "${fitems[@]}" )
      hstrs=( "${fstrs[@]}" )
    fi
    generate_help_text hitems hstrs
    vyatta_completions=( "${hitems[@]}" )
    vyatta_do_complete
    eval $restore_shopts
    return
  fi

  local command=${COMP_WORDS[0]}
  # completion for "set"/"edit" is different from other commands
  is_set=0
  if [ "$command" == "set" -o "$command" == "edit" ]; then
    is_set=1
  fi
  local end_space=0
  local num_comp=$COMP_CWORD
  if [ -z "${COMP_WORDS[$COMP_CWORD]}" ]; then
    end_space=1
    (( num_comp -= 1 ))
  fi

  (( last_idx = num_comp - 1 ))
  comp_words=( ${COMP_WORDS[@]:1:$num_comp} )

  # handle "exit"
  if [ "$command" == "exit" ]; then
    if (( num_comp > 1 || ( end_space && num_comp > 0 ) )); then
      COMPREPLY=()
      eval $restore_shopts
      return
    fi
    declare -a hitems=( "discard" )
    declare -a hstrs=( "Discard any changes" )
    generate_help_text hitems hstrs
    vyatta_completions=( "discard" )
    vyatta_do_complete
    eval $restore_shopts
    return
  fi

  local start_idx=0

  # handle "copy" and "rename"
  if [ "$command" == "copy" -o "$command" == "rename" ]; then
      # Syntax of copy and rename commands are:
      #
      #     copy/rename <param1> <sub-param1> to <param2> <sub-param2>
      #
      # where <param1> and <param2> are configuration parameters 
      # in the tree at the current edit level.
      #
      # If parsing index 1 or 2 (i.e. <param1> or <sub-param1>),
      # fall through this test to the parameter parsing code below.
      #
      if (( ( end_space && num_comp == 2 ) || 
	    ( !end_space && num_comp == 3 ) )); then
	  # If parsing index 3, there's only one option.
	  declare -a hitems=( "to" )
	  declare -a hstrs=( "Set destination" )
	  generate_help_text hitems hstrs
	  vyatta_completions=( "to" )
	  vyatta_do_complete
	  eval $restore_shopts
	  return
      elif (( ( num_comp > 2 ) &&
	      ( ( num_comp < 5 ) || ( !end_space && num_comp == 5 ) ) )); then
	  # If parsing index 4 or 5, we set start_idx so that
	  # the parameter parsing code will start parsing at
	  # <param2>.  Parsing these parameters should follow
	  # the same rules as the "set" command.
	  start_idx=3
	  is_set=1
      elif (( ( end_space && num_comp == 5 ) ||
	      ( num_comp > 5 ) )); then
	  # If parsing after index 5, there are no more valid parameters
	  COMPREPLY=()
	  eval $restore_shopts
	  return
      fi
  fi

  local _mpath=${VYATTA_TEMP_CONFIG_DIR}/${VYATTA_EDIT_LEVEL}
  local _tpath=${VYATTA_CONFIG_TEMPLATE}/${VYATTA_TEMPLATE_LEVEL}
  local last_tag=0
  local idx=$start_idx
  for (( idx=$start_idx; idx < num_comp; idx++ )); do
    last_tag=0
    local comp=${comp_words[$idx]}
    vyatta_escape comp comp
    push_path _mpath $comp
    push_path _tpath $comp
    if [ -d $_tpath ]; then
      if (( ! is_set )); then
        # we are not in "set" => only allow existing node
        if [ ! -d $_mpath ]; then
          break
        fi
      fi
      continue
    fi
    pop_path _tpath
    push_path _tpath $VYATTA_TAG_NAME
    if [ -d $_tpath ]; then
      if (( ! is_set && end_space )); then
        # we are not in "set" && last component is complete.
        # => only allow existing tag value.
        if [ ! -d $_mpath ]; then
          break
        fi
      fi
      if (( idx != last_idx )); then
        # TODO validate value
        # break if not valid 
        # XXX is this validation necessary? (set will validate anyway)
        true
      fi
      last_tag=1
      continue
    fi
    pop_path _tpath
    pop_path _mpath
    break
  done
  # at the end of the loop, 3 possibilities:
  # 1. (idx < last_idx): some component before the last is invalid
  #    => invalid command
  # 2. (idx == last_idx): last component matches neither template nor node.tag
  #    => if end_space, then invalid command
  #       otherwise, may be an incomplete (non-tag) component, or incomplete
  #       "leaf value"
  #       => try matching dirs in _tpath or value(s) in _mpath/node.val
  # 3. (idx == num_comp): the whole command matches templates/tags
  if (( idx < last_idx || ( idx == last_idx && end_space ) )); then
    # TODO error message?
    COMPREPLY=()
    eval $restore_shopts
    return
  fi

  declare -a matches
  if (( idx == last_idx )); then
    # generate possibile matches (dirs in _tpath, and "help" from
    # node.def in each dir, or values in _mpath/node.val)
    declare -a fmatches
    if [ -f $_mpath/node.val ]; then
      decho {1a}
      get_value_list $_mpath matches
      get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches
      append_allowed_values $_tpath fmatches
      get_node_value_help $_tpath fmatches
    else
      decho {1b}
      # see if the last component is a new leaf node
      fmatches=()
      if is_setting_new_leaf $_tpath; then
        append_allowed_values $_tpath fmatches
        get_node_value_help $_tpath fmatches
      else
        # last component is a non-value node. look for child nodes.
        if (( ! is_set )); then
          # not "set". only complete existing nodes.
          declare -a amatches=()
          get_filtered_dir_listing $_tpath amatches
          filter_existing_nodes $_mpath amatches matches
        else
          get_filtered_dir_listing $_tpath matches
        fi
        get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches
        get_tmpl_subdir_help $_tpath fmatches
      fi
    fi
    vyatta_completions=( "${fmatches[@]}" )
    vyatta_do_complete
    eval $restore_shopts
    return
  fi

  if (( last_tag && end_space )); then
    # if not "set", check _mpath (last component is the tag) is valid
    # generate possible matches (dirs in _tpath, and "help" from node.def
    # in each dir)
    decho {2}
    if [ $is_set == 1 -o -d $_mpath ]; then
      if (( ! is_set )); then
        # not "set". only complete existing nodes.
        declare -a fmatches=()
        get_filtered_dir_listing $_tpath fmatches
        filter_existing_nodes $_mpath fmatches matches
      else
        get_filtered_dir_listing $_tpath matches
      fi
      get_tmpl_subdir_help $_tpath matches
      vyatta_completions=( "${matches[@]}" )
      vyatta_do_complete
      eval $restore_shopts
      return
    fi
    eval $restore_shopts
    return
  fi
  
  if (( last_tag && !end_space )); then
    # generate possible matches (dirs in _mpath, and "help" from node.def
    # in dirs in _tpath)
    decho {3}
    pop_path _mpath
    pop_path _tpath
    get_filtered_dir_listing $_mpath matches
    declare -a fmatches
    get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches
    append_allowed_values $_tpath fmatches
    get_node_value_help $_tpath fmatches
    vyatta_completions=( "${fmatches[@]}" )
    vyatta_do_complete
    eval $restore_shopts
    return
  fi
  
  if (( !last_tag && end_space )); then
    # generate possible matches
    # 1. dirs in _tpath, and "help" from node.def in each dir
    # 2. value(s) in _mpath/node.val (only if _tpath/node.def is "multi:")
    # 3. dirs in _mpath (only if _tpath/node.def is "tag:")
    if [ -d $_tpath/node.tag ]; then
      # last component is a "tag name". look for tag values.
      decho {4a}
      get_filtered_dir_listing $_mpath matches
      append_allowed_values $_tpath matches
      get_node_value_help $_tpath matches
    elif [ -f $_mpath/node.val ]; then
      # last component is a leaf node. look for values.
      decho {4b}
      get_value_list $_mpath matches
      append_allowed_values $_tpath matches
      get_node_value_help $_tpath matches
    else
      decho {4c}
      # see if the last component is a new leaf node
      matches=()
      if is_setting_new_leaf $_tpath; then
        append_allowed_values $_tpath matches
        get_node_value_help $_tpath matches
      else
        # last component is a non-value node. look for child nodes.
        if (( ! is_set )); then
          # not "set". only complete existing nodes.
          declare -a fmatches=()
          get_filtered_dir_listing $_tpath fmatches
          filter_existing_nodes $_mpath fmatches matches
        else
          get_filtered_dir_listing $_tpath matches
        fi
        get_tmpl_subdir_help $_tpath matches
      fi
    fi
    vyatta_completions=( "${matches[@]}" )
    vyatta_do_complete
    eval $restore_shopts
    return
  fi
  
  if (( !last_tag && !end_space )); then
    # generate possible matches (dirs in _tpath, and "help" from node.def
    # in each dir)
    decho {5}
    pop_path _tpath
    get_filtered_dir_listing $_tpath matches
    declare -a fmatches
    get_prefix_filtered_list ${comp_words[$last_idx]} matches fmatches
    get_tmpl_subdir_help $_tpath fmatches
    vyatta_completions=( "${fmatches[@]}" )
    vyatta_do_complete
    eval $restore_shopts
    return
  fi

  eval $restore_shopts
}

DEF_GROUP=vyattacfg
make_vyatta_config_dir ()
{
  sudo mkdir -m 0775 -p $1
  sudo chgrp ${DEF_GROUP} $1
}

if grep -q union=aufs /proc/cmdline || grep -q aufs /proc/filesystems ; then
    export UNIONFS=aufs
else
    export UNIONFS=unionfs
fi

make_vyatta_config_dir $VYATTA_ACTIVE_CONFIGURATION_DIR
make_vyatta_config_dir $VYATTA_CHANGES_ONLY_DIR
make_vyatta_config_dir $VYATTA_CONFIG_TMP
if [ ! -d $VYATTA_TEMP_CONFIG_DIR ]; then
  make_vyatta_config_dir $VYATTA_TEMP_CONFIG_DIR
  sudo mount -t $UNIONFS -o dirs=${VYATTA_CHANGES_ONLY_DIR}=rw:/opt/vyatta/config/active=ro $UNIONFS ${VYATTA_TEMP_CONFIG_DIR}
fi

# disallow 'Ctrl-D' exit, since we need special actions on 'exit'
set -o ignoreeof 1

set_config_ps1 ''
alias set=/opt/vyatta/sbin/my_set
alias delete=/opt/vyatta/sbin/my_delete

export VYATTA_COMP_LINE=""

# readline bindings
bind 'set show-all-if-ambiguous on'
if ! bind -p |grep -q '\\C-x\\C-t'; then
  bind '"\C-x\C-t": kill-region'
fi
if ! bind -p |grep -q '\\C-x\\C-o'; then
  bind '"\C-x\C-o": copy-region-as-kill'
fi

complete -F vyatta_config_complete ''
complete -F vyatta_config_complete set
complete -F vyatta_config_complete delete
complete -F vyatta_config_complete show
complete -F vyatta_config_complete edit
complete -F vyatta_config_complete exit
complete -F vyatta_run_complete run
complete -F vyatta_loadsave_complete save
complete -F vyatta_loadsave_complete load
complete -F vyatta_loadsave_complete merge
complete -F vyatta_loadkey_complete loadkey
complete -F vyatta_config_complete comment
complete -F vyatta_config_complete copy
complete -F vyatta_config_complete rename

# Local Variables:
# mode: shell-script
# sh-indentation: 4
# End: