# Helpers ######################################################################

if [ -n "$BASH_SOURCE" ]; then
  script_path="${BASH_SOURCE[0]}"
else
  script_path="$0"
fi

function _get_directory() {
  local file="$1"
  dirname "$(readlink --canonicalize "$file")"
}

function _get_script_directory() {
  _get_directory "$script_path"
}

# Utilities ####################################################################

# Find the root of the Bazel workspace. This is accomplished by walking up the
# directory tree from this file, checking for a `WORKSPACE` at each step.
function bazel_root() {
  local ws_root="$(_get_script_directory)"
  while [ "$(dirname "$ws_root")" != "$ws_root" ]; do
    if [ -f "$ws_root/WORKSPACE" ]; then
      echo "$ws_root"
      return 0
    fi
    ws_root="$(dirname "$ws_root")"
  done

  echo bazel_root: Workspace root could not be found! >&2
  return 1
}

# Find the full path of the specified Bazel package.
# If no package is specified, find the path to the workspace root.
function bazel_find() {
  local target="$1"
  local ws_root="$(bazel_root)"

  if [ -z "$target" ]; then
    echo "$ws_root"
    return 0
  fi

  local package="$(basename "$target")"

  build_file="$(
    find "$ws_root" -type f -wholename "*/$package/BUILD" |
    head --lines=1
  )"

  if [ -z "$build_file" ] || [ ! -f "$build_file" ]; then
    echo bazel_find: No package found \'$package\' >&2
    return 1
  fi

  _get_directory "$build_file"
  return 0
}

# Change directory to the specified bazel package.
# If no target is specified, change to the bazel workspace root.
# Valid targets are:
#  * bazel-formatted target: //path/to/package
#  * package name: package
function bazel_cd() {
  local target="$1"
  local ws_root="$(bazel_root)"

  # No target given. Just cd to the workspace root.
  if [ -z "$target" ]; then
    builtin cd "$ws_root"
    return 0
  fi

  # Bazel-formatted target given. Strip off the leading `//` and treat it as a
  # workspace-relative directory.
  if [[ "$target" =~ ^// ]]; then
    package_path="$ws_root/${target:2}"

    if [ ! -d "$package_path" ]; then
      echo bazel_cd: \'$target\' is not a valid Bazel package target >&2
      return 1
    fi

    builtin cd "$package_path"
    return 0
  fi

  # Target is a package name. Find it in the workspace.
  local package_path="$(
    bazel_find "$target" 2>/dev/null |
    head --lines=1 2>/dev/null
  )"

  if [ -z "$package_path" ] || [ ! -d "$package_path" ]; then
    echo bazel_cd: No package found \'$target\' >&2
    return 1
  else
    builtin cd "$package_path"
    return 0
  fi
}

# Find the soft-link'd path of the specified Bazel package's binary directory.
# The bin directory is useful when your debugging tool outputs a file (like
# callgrind, massif, cachegrind, etc).
function bazel_bin_find() {
  # Get the path to package source.
  local src_path="$(bazel_find "$@")"
  if [[ $? != 0 ]]; then
    return 1
  fi

  # Insert 'bazel-bin/' between ${ZOOX_WORKSPACE_ROOT} and the rest of the
  # source package path.
  echo "${ZOOX_WORKSPACE_ROOT}/bazel-bin${src_path##${ZOOX_WORKSPACE_ROOT}}"
}

# Change directory to the specified bazel package binary directory soft-link.
# If no target is specified, change to ${ZOOX_WORKSPACE_ROOT}/bazel-bin
# Valid targets are:
#  * bazel-formatted target: //path/to/package
#  * package name: package
# The soft-link is easier to transition to/from when rebuilding your package
# under a different --compilation_mode, e.g.
#   $ bazel test //vehicle/planner/decision/...
#   $ bazel_bin_cd decision
#   $ valgrind --tool=callgrind ./test_decision_planner
#   $ bazel test --compilation_mode=dbg //vehicle/planner/decision/...
#   $ bcd
#   $ cd -
#   $ valgrind --tool=massif ./test_decision_planner
function bazel_bin_cd() {
  local bin_path="$(bazel_bin_find "$@")"
  if [[ $? != 0 ]]; then
    return 1
  fi
  builtin cd "${bin_path}"
}

# Convenience wrapper so we can continue using `roscd`.
function roscd() {
  bazel_cd "$@"
}

function bazel_build_all() {
  (bazel_cd && bazel build ...)
}

function fakeros() {
  echo "fakeros is deprecated and no longer necessary for running ROS executables."
  return 0
}

function runfiles() {
  local target_name="$1"
  if [ -z "$target_name" ]; then
    echo No target name specified! >&2
    return 1
  fi

  local current_dir="$(pwd)"
  if [ ! -f "$current_dir/BUILD" ]; then
    echo Not in a package directory! >&2
    return 1
  fi

  local ws_root="$(bazel_find)"
  local relative_path="$(python -c "import os.path; print os.path.relpath('$current_dir', '$ws_root')")"
  local runfiles_path="$ws_root/bazel-bin/$relative_path/$target_name.runfiles"
  if [ ! -d "$runfiles_path" ]; then
     echo Runfiles directory \("$runfiles_path"\) does not exist! >&2
    return 1
  fi

  builtin cd "$runfiles_path"
}

# Completion ###################################################################

function _bazel_completion_get_targets_from_package() {
  local package_path="$1"
  if [ -n "$package_path" ]; then
    (
      builtin cd "$package_path"
      echo "$(bazel query :all | sed 's/.*://')"
    )
  fi
}

function _bazel_completion_get_targets_from_cwd() {
  echo "$(_bazel_completion_get_targets_from_package $(pwd))"
}

function _bazel_completion_get_packages() {
  echo "$(find "$(bazel_find)" -type f -name "BUILD")" |
    xargs --max-lines=1 dirname |
    xargs --max-lines=1 basename
}

function _bazel_completion_find_directory_children() {
  local ws_root="$(bazel_find)"
  local parent="$ws_root/$1"
  find "$parent" \
    -maxdepth 1 \
    -type d \
    -not -wholename "$parent" \
    -not -wholename "$ws_root" \
    -not -wholename "*/.git" |
  sed "s#^$ws_root/##"
}

function _bazel_completion_get_directories() {
  # Remove optional leading `//`
  local query="$(echo "$1" | sed 's#/*#/#;s#^/##')"

  if [ -z "$query" ] || [ "${query: -1}" == "/" ]; then
    # Look within a subdirectory, relative to the workspace root.
    local directories="$(_bazel_completion_find_directory_children "$query")"
  else
    # Look within a subdirectory, relative to the workspace root.
    # Exclude anything that does not start with the base of the query string.
    local directory="$(dirname "$query" | sed 's#^\.##')"
    local base="$(basename "$query")"
    if [ -z "$directory" ]; then
      local filter="$base"
    else
      local filter="^$directory/$base"
    fi

    local directories="$(
       _bazel_completion_find_directory_children "$directory" |
       grep --extended-regexp "^$filter"
    )"
  fi

  while read -r line; do
    echo "//$line/"
  done < <(echo "$directories")
}

function _bazel_completion() {
  local query="${COMP_WORDS[COMP_CWORD]}"
  local directories="$(_bazel_completion_get_directories "$query")"
  local packages="$(_bazel_completion_get_packages)"
  COMPREPLY=( $( compgen -W "$directories $packages" -- "$query" ) )

  return 0
}

function _bazel_target_completion_cwd() {
  local query="${COMP_WORDS[COMP_CWORD]}"
  local targets="$(_bazel_completion_get_targets_from_cwd)"
  COMPREPLY=( $( compgen -W "$targets" -- "$query" ) )

  return 0
}

if [ -n "${ZSH_VERSION+x}" ]; then
  # Enable bash completion in zsh
  autoload -U +X compinit && compinit
  autoload -U +X bashcompinit && bashcompinit
fi

# Alias functions
# Please do not use aliases as we are not using interactive shells.
function bb {
  bazel build "$@"
}

function bba {
  bazel_build_all "$@"
}

# Builds all vehicle targets and passes all arguments along to Bazel.
function bazel_build_vehicle {
  bazel build //vehicle:l3_deploy_package //bin/... "$@"
}
function bb_vehicle {
  bazel_build_vehicle "$@"
}
function bbv {
  bazel_build_vehicle "$@"
}

function btest {
  bazel test "$@"
}

function bcd {
  bazel_cd "$@"
}

function bfind {
  bazel_find "$@"
}

function brun {
  bazel_run "$@"
}

function bdiff {
  bazel_diff "$@"
}

function fakeros-experimental {
  fakeros experimental/fakeros "$@"
}

function pyrun {
  if [[ "$#" < 1 ]] || [[ "$1" == '--help' ]]; then
    echo "Convenience wrapper around brun to run a mypy_binary but only after"
    echo "running the type checker on it first."
    echo
    echo "The only argument should be the mypy_binary target name to run."
    return
  fi

  bazel test --test_output=errors "$1"_mypy_test && brun "$@"
}

function zfix {
  if [[ "$#" -gt 0 ]]; then
    echo "Validates modified source files (staged and unstaged) and attempts to"
    echo "fix them if possible."
    return
  fi

  "$(git rev-parse --show-toplevel)"/ci/file_validators/validate.py --changed --fix
}

complete -F _bazel_completion -o nospace bazel_cd bcd
complete -F _bazel_completion -o nospace roscd
complete -F _bazel_completion -o nospace bazel_find bfind
complete -F _bazel_completion -o nospace brun
complete -F _bazel_target_completion_cwd -o nospace runfiles
