# ssm bash-completion
# System Storage Manager

readonly HELP="-h"
readonly HELP_LONG="--help"
readonly SSM=$(which ssm)
readonly AWK=$(which awk)
readonly GREP=$(which grep)
readonly CUT=$(which cut)
readonly ECHO=$(which echo)
readonly SED=$(which sed)

__get_all_fs()
{
  local fs
  fs=$(${SSM} list fs | ${GREP} ^/dev | ${CUT} -d " " -f 1)
  ${ECHO} ${fs}
}

__get_all_volumes()
{
  local volumes
  volumes=$(${SSM} list vol | ${AWK} -F " " '{if ($2 ~ "[[:alpha:]]") print ; }'  | ${GREP} ^/dev | ${CUT} -d " " -f 1)
  ${ECHO} ${volumes}
}
__get_all_devs()
{
  local devs
  devs=$(${SSM} list dev |  ${GREP} ^/dev | ${CUT} -d " " -f 1)
  ${ECHO} ${devs}
}

__get_all_pools()
{
  local pools
  pools=$(${SSM} list pool | ${GREP} -v -e "Free" -e "Used" |  ${AWK} -F " " '{if ($2 ~ "[[:alpha:]]") print ; }' | ${CUT} -d " " -f 1)
  ${ECHO} ${pools}
}

__get_possible_pvs()
{
  local non_pool_devs fs devices_possible
  # This function lists all devices capable of being a PV
  # - where no LVM / FS headers exist
  #   -- device (sd*, for instance)
  #   -- partitions
  #   -- any other?

  # First, get list of FS in the system (these are to be excluded)
  fs=$(__get_all_fs)

  # Second, get devices that do not belong to a pool
  # Attention, this contains FS as well, need to filter those out
  non_pool_devs=$(${SSM} list dev |  ${AWK} -F " " '{if ($5 == "" ) print ; }' | ${GREP} ^/dev | ${CUT} -d " " -f 1)

  # Filter
  for d in ${non_pool_devs} ; do
    if [[ ! $(${ECHO} $fs | ${GREP} $d) ]] ; then
      devices_possible+="${d} "
    fi
  done
  ${ECHO} ${devices_possible}
}

__get_unmounted_fs()
{

  local fs fs_possible r

  fs=$(__get_all_fs)
  if [[ ! -z ${fs} ]] ; then
    for f in ${fs} ; do
      ${GREP} ${f} /proc/mounts &> /dev/null
      r=$?
      if [ "${r}" -eq 1 ] ; then
        fs_possible+="${f} "
      fi
    done
    ${ECHO}  ${fs_possible}
  else
    ${ECHO} ""
  fi

}

_action_create()
{
  # usage: ssm create [-h] [-s SIZE] [-n NAME] [--fstype FSTYPE] [-r LEVEL]
  #              [-I STRIPESIZE] [-i STRIPES] [-p POOL] [-e [{luks,plain}]]
  #              [-o MNT_OPTIONS] [-v VIRTUAL_SIZE]
  #              [device [device ...]] [mount]
  #
  # Attention, running 'ssm create' is valid

  local cur prev encryption_types opts_proper devices_possible
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  opts_proper="-s -n --fstype -r -I -i -p -e -o -v " # the terminating space " " to ease parsing
  encryption_types="luks plain"

  if [[ ${VISITED_CREATE} = 0 ]] ; then CREATE_REMAINING_OPTS=${opts_proper} ; fi

  if [[ ${prev} = "${HELP}" || ${prev} = "${HELP_LONG}" ]] ; then # if help is needed, just return
    return 0

  elif [[ ${cur} =~ "-" ]]; then
    COMPREPLY=( $(compgen -W "${HELP} ${HELP_LONG} ${CREATE_REMAINING_OPTS}" -- ${cur}) )
    return 0

  elif [[ ${prev} = "-s" ||
          ${prev} = "-n" ||
          ${prev} = "--fstype" ||
          ${prev} = "-r" ||
          ${prev} = "-I" ||
          ${prev} = "-i" ||
          ${prev} = "-o" ||
          ${prev} = "-v" ||
          ${prev} = "-e"
        ]] ; then # user input needed, we'll not parse that. return

    if [[ ${prev} =~ "-e" ]] ; then
      COMPREPLY+=( $(compgen -W "${encryption_types}" -- ${cur}) )
    fi
    CREATE_REMAINING_OPTS=$(${ECHO} ${CREATE_REMAINING_OPTS} | ${SED} "s/${prev} //")
    VISITED_CREATE=$((${VISITED_CREATE}+1))
    return 0

  elif [[ ${cur} == * ]] ; then # need device
    devices_possible=$(__get_possible_pvs)
    if [[ $(${ECHO} ${devices_possible} | ${GREP} ${prev} > /dev/null ; ${ECHO} $?) = 0 ]] ; then
      devices_possible=$(${ECHO} ${devices_possible} | ${SED} "s/${prev} //")
    fi
    if [ ! -z "${devices_possible}" ] ; then
      COMPREPLY+=( $(compgen -W "${devices_possible}" -- ${cur}) )
    else
      return 0
    fi
  fi
}

_action_mount()
{
  # usage: ssm mount [-h] [-o OPTIONS] volume directory
  local opts_proper opts_all devices_possible
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  opts_proper="-o"
  opts_all="${HELP} ${HELP_LONG} ${opts_proper}"

  if [[ ${prev} = "${HELP}" || ${prev} = "${HELP_LONG}" ]] ; then # if help is needed, just return
    return 0
  fi

  if [[ ${prev} = "-o" ]] ; then # user input needed, we'll not parse that. just return
    return 0

  elif [[ ${cur} = "-" ]] ; then # all options possible
     COMPREPLY+=( $(compgen -W "${opts_all}" -- ${cur}) )

  elif [ -d ${prev} ] ; then # means cursor is after mountpoint. return
    return 0

  elif [[ ${prev} =~ "/dev" ]] ; then # we need mountpoint. user input, just return
    return 0

  else # we need device
    devices_possible=$(__get_unmounted_fs)
    if [ ! -z "${devices_possible}" ] ; then
      COMPREPLY+=( $(compgen -W "${devices_possible}" -- ${cur}) )

    else
      cur=""

    fi
    return 0
  fi

  return 0
}


_action_check()
{
  # usage: ssm check [-h] device [device ...]
  local cur prev opts_all devices_possible
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  opts_all="${HELP} ${HELP_LONG}"


  if [[ ${prev} = "${HELP}" || ${prev} = "${HELP_LONG}" ]] ; then # if help is needed, just return
    return 0

  elif [[ ${cur} = "-" ]] ; then # all options possible
    COMPREPLY+=( $(compgen -W "${opts_all}" -- ${cur}) )

  elif [[ ${prev} =~ "/dev" || ${prev} =~ "check" ]] ; then # this is first or any additional device, need device
    devices_possible=$(__get_unmounted_fs)
    if [[ $(${ECHO} ${devices_possible} | ${GREP} ${prev} > /dev/null ; ${ECHO} $?) = 0 ]] ; then devices_possible=$(${ECHO} ${devices_possible} | ${SED} "s/${prev} //") ; fi

    if [ ! -z "${devices_possible}" ] ; then
        COMPREPLY+=( $(compgen -W "${devices_possible}" -- ${cur}) )

    else
      cur=""

    fi
    return 0
  fi

  return 0
}

_action_list()
{
  # usage: ssm list [-h] [{volumes,vol,dev,devices,pool,pools,fs,filesystems,snap,snapshots}]
  local cur prev opts_all positional_args
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  opts_all="${HELP} ${HELP_LONG}"
  positional_args="volumes vol dev devices pool pools fs filesystems snap snapshots"

  if [[ ${VISITED_LIST} = 0 ]] ; then LIST_REMAINING_ARGS=${positional_args} ; fi
  if [[ ${prev} = "${HELP}" || ${prev} = "${HELP_LONG}" ]] ; then # if help is needed, just return
    return 0

  elif [[ $(${ECHO} ${positional_args} | ${GREP} ${prev} > /dev/null ; ${ECHO} $?) = 0 ]] ; then
    LIST_REMAINING_OPTS=$(${ECHO} ${LIST_REMAINING_ARGS} | ${SED} "s/${prev} //")
    VISITED_LIST=$((${VISITED_LIST}+1))


  elif [[ ${cur} =~ "-" ]] ; then # all options possible
    COMPREPLY+=( $(compgen -W "${opts_all}" -- ${cur}) )

  elif [[ ${prev} =~ "list" ]] ; then # we need all positional arguments
    COMPREPLY=( $(compgen -W "${LIST_REMAINING_ARGS}" -- ${cur}) )
    return 0
  else
    return 0

  fi
}

_action_add()
{
  # usage: ssm add [-h] [-p POOL] device [device ...]
  local cur prev opts_proper opts_all devices_possible pools_existent
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  opts_proper="-p"
  opts_all="${HELP} ${HELP_LONG} ${opts_proper}"

  pools_existent=$(__get_all_pools)

  if [[ ${prev} = "${HELP}" || ${prev} = "${HELP_LONG}" ]] ; then # if help is needed, just return
    return 0

  elif [[ ${prev} = "-p" ]] ; then # need pool, which may be: existent or non existent
    COMPREPLY+=( $(compgen -W "${pools_existent}" -- ${cur}) )

  elif [[ ${cur} = "-" ]] ; then # all options possible
    COMPREPLY+=( $(compgen -W "${opts_all}" -- ${cur}) )

  elif [[ ${prev} =~ "/dev" || ${prev} =~ "add" || $(${ECHO} ${pools_existent} | ${GREP} ${prev} > /dev/null ; ${ECHO} $?) = 0 ]] ; then # this is first or any additional device, need device
    devices_possible=$(__get_possible_pvs)

    if [[ ${prev} =~ ${devices_possible} ]] ; then devices_possible=$(${ECHO} ${devices_possible} | ${SED} "s/${prev} //") ; fi

    if [ ! -z "${devices_possible}" ] ; then
      COMPREPLY+=( $(compgen -W "${devices_possible}" -- ${cur}) )

    else
      cur=""

    fi
    return 0
  fi

  return 0
}

_action_remove()
{
  # usage: ssm remove [-h] [-a] [items [items ...]] # Item could be device, pool, or volume.
  local cur prev opts_proper opts_all items_possible fs pools devs volumes
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  opts_proper="-a"
  opts_all="${HELP} ${HELP_LONG} ${opts_proper}"

  if [[ ${prev} = "${HELP}" || ${prev} = "${HELP_LONG}" || ${prev} = "-a" ]] ; then # if help is needed or -a is specified, just return
    return 0

  elif [[ ${cur} = "-" ]] ; then # all options possible
    COMPREPLY+=( $(compgen -W "${opts_all}" -- ${cur}) )

  elif [[ ${prev} =~ "/dev" || ${prev} =~ "remove" ]] ; then # this is first or any additional item, need item
    pools=$(__get_all_pools)
    volumes=$(__get_all_volumes)
    items_possible="${pools} ${volumes}"
    COMPREPLY+=( $(compgen -W "${items_possible}" -- ${cur}) )
    return 0

  fi
}

_action_resize()
{
  # usage: ssm resize [-h] [-s SIZE] volume [device [device ...]]
  local cur prev opts_proper opts_all volumes devices_possible
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  opts_proper="-s"
  opts_all="${HELP} ${HELP_LONG} ${opts_proper}"

  if [[ ${prev} = "${HELP}"  || ${prev} = "${HELP_LONG}" || ${prev} = "-s" ]] ; then # if help is needed or -s is specified, just return (-s expects user input)
    return 0

  elif [[ ${cur} = "-" ]] ; then # all options possible
    COMPREPLY+=( $(compgen -W "${opts_all}" -- ${cur}) )
    return 0

  elif [[ ${prev} =~ "/dev" ]] ; then # this is first or any additional device the volume is to be resized to. need device
    items_possible=$(__get_possible_pvs)
    ${ECHO} ${items_possible}
    return 0

  elif [[ ${prev} = * ]] ; then  # this should be the case when we need a volume
                                # either after setting value to -s or after "resize"

    volumes=$(__get_all_volumes)
    if [ ! -z "${volumes}" ] ; then
      COMPREPLY+=( $(compgen -W "${volumes}" -- ${cur}) )
    else
      cur=""
    fi

    return 0

  else
    cur=""
  fi
}

_action_snapshot()
{
  # usage: ssm snapshot [-h] [-s SIZE] [-d DEST | -n NAME] volume
  local cur prev  opts_proper opts_all volumes
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  opts_proper="-s -d|-n"
  opts_all="${HELP} ${HELP_LONG} ${opts_proper}"

  if [[ ${prev} = "${HELP}"  || ${prev} = "${HELP_LONG}" || ${prev} = "-s" || ${prev} = "-d" ||  ${prev} = "-n" ]] ; then # just return (help/user input)
    return 0
  fi

  if [[ ${cur} = -  ]] ; then
    COMPREPLY=( $(compgen -W "${opts_all}" -- ${cur}) )
  else # we need volumes
    volumes=$(__get_all_volumes)
    if [ ! -z "${volumes}" ] ; then
      COMPREPLY=( $(compgen -W "${volumes}" -- ${cur}) )
    else
      cur=""
    fi
  fi

}

_action_info() {
  local cur prev opts_proper opts_all volumes pools devices devices_possible
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  opts_proper=""
  opts_all="${HELP} ${HELP_LONG} ${opts_proper}"

  # Possible devices for action list are
  # - volumes
  # - pools
  # - devices
  # - or none =>  Running 'ssm info' is valid
  # Running 'ssm info item1 item2 .. itemN' is not valid
  # After first item, return
  volumes=$(__get_all_volumes)
  pools=$(__get_all_pools)
  devices=$(__get_all_devs)
  devices_possible="${volumes} ${pools} ${devices}"

  if [[ ${prev} = "${HELP}"        ||
        ${prev} = "${HELP_LONG}"  ||
        $(${ECHO} ${devices_possible} | ${GREP} ${prev} > /dev/null ; ${ECHO} $?) = 0
      ]] ; then # just return (help/first item)
    return 0
  else
    COMPREPLY=( $(compgen -W "${devices_possible}" -- ${cur}) )
  fi
}

_action_migrate() {
  # usage: ssm migrate [-h] source target
  # Example of error: ssm migrate: error: argument source: 'lv1' is not valid block device
  # Source can be:
  # - device with full path
  # - volume with full path
  # - fs with full path
  # Target can be the same type like source
  local cur prev prevprev opts_proper opts_all volumes devices
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  prevprev="${COMP_WORDS[COMP_CWORD-2]}"
  opts_proper=""
  opts_all="${HELP} ${HELP_LONG} ${opts_proper}"
  if [[ ${VISITED_MIGRATE} = 0 || ${VISITED_MIGRATE} = "" ]] ; then
    volumes=$(__get_all_volumes)
    devices=$(__get_all_devs)
    MIGRATE_REMAINING_DEVS="${volumes} ${devices}"

  fi
  if [[ ${prev} =~ "/dev" && ${prevprev} =~ "/dev" ]] ; then return 0 ; fi # we don't want a third device

  if [[ ${prev} = "${HELP}" || ${prev} = "${HELP_LONG}" ]] ; then # if help is needed, just return
    return 0

  elif [[ ${cur} = "-" ]] ; then # all options possible
    COMPREPLY+=( $(compgen -W "${opts_all}" -- ${cur}) )

  elif [[ ${prev} =~ "/dev" || ${prev} =~ "migrate" ]] ; then # this is first or any additional device, need device

    if [ ! -z "${MIGRATE_REMAINING_DEVS}" ] ; then
      COMPREPLY+=( $(compgen -W "${MIGRATE_REMAINING_DEVS}" -- ${cur}) )
      if [[ $(${ECHO} ${MIGRATE_REMAINING_DEVS} | ${GREP} ${prev} > /dev/null ; ${ECHO} $?) = 0 ]] ; then
        MIGRATE_REMAINING_DEVS=$(${ECHO} ${MIGRATE_REMAINING_DEVS} | ${SED} "s;${prev} ;;g")
        VISITED_MIGRATE=$((${VISITED_MIGRATE}+1))
        return 0
      fi
    else # nothing left in ${MIGRATE_REMAINING_DEVS}
      return 0

    fi
    return 0
  fi

  return 0


}

# The main function must not bear the same name
# as the program calling this bash-completion.
# Otherwise:
# bash: COMP_WORDS: bad array subscript

_ssm()
{
  local cur prev opts actions backends
  _init_completion || return
  COMPREPLY=()
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  opts="${HELP} ${HELP_LONG} --version -v --verbose -f --force -b --backend -n --dry-run"
  actions="create list remove resize check snapshot add mount info migrate"
  backends="lvm btrfs crypt multipath"
  if [[ ${VISITED_CREATE} = '' || ! ${VISITED_CREATE} > 0 ]] ; then VISITED_CREATE=0 ; fi
  if [[ ${VISITED_LIST} = '' || ! ${VISITED_LIST} > 0 ]] ; then VISITED_LIST=0 ; fi
  if [[ ${VISITED_MIGRATE} = '' || ! ${VISITED_MIGRATE} > 0 ]] ; then VISITED_MIGRATE=0 ; fi

  # Action migrate only needs two devices
  # Don't even go there if we have them already
  if [ ${VISITED_MIGRATE} -ge 2 ] ; then return 0 ; fi

  # jtulak's suggestion, thanks. Prevents error messages if run via sudo
  if [ "root" != $(whoami) ]; then
    path=${cur#*:}
    _filedir
    return 0
  fi


  if [[ ${COMP_WORDS[*]} =~ "mount" ]]; then
    _action_mount
    return 0
  elif [[ ${COMP_WORDS[*]} =~ "create" ]]; then
    _action_create
    return 0
  elif [[ ${COMP_WORDS[*]} =~ "check" ]]; then
    _action_check
    return 0
  elif [[ ${COMP_WORDS[*]} =~ "add" ]]; then
    _action_add
    return 0
  elif [[ ${COMP_WORDS[*]} =~ "list" ]]; then
    _action_list
    return 0
  elif [[ ${COMP_WORDS[*]} =~ "remove" ]]; then
    _action_remove
    return 0
  elif [[ ${COMP_WORDS[*]} =~ "resize" ]]; then
    _action_resize
    return 0
  elif [[ ${COMP_WORDS[*]} =~ "snapshot" ]]; then
    _action_snapshot
    return 0
  elif [[ ${COMP_WORDS[*]} =~ "info" ]]; then
    _action_info
    return 0
  elif [[ ${COMP_WORDS[*]} =~ "migrate" ]]; then
    _action_migrate
    return 0
  elif [[ ${prev} = "${HELP}" || ${prev} = "${HELP_LONG}" ]]; then
    return 0
  elif [[ ${cur} =~ "-" ]]; then
    COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
    return 0
  elif [[ ${prev} =~ "-b" ||  ${prev} =~ "--backend" ]]; then
    COMPREPLY=( $(compgen -W "${backends}" -- ${cur}) )
    return 0
  elif [[ ${cur} == * ]] ; then
    COMPREPLY=( $(compgen -W "${actions}" -- ${cur}) )
    return 0
  fi
}

complete -F _ssm ssm
