#!/usr/bin/env bash

### Configuration

## Enable extended globbing features
shopt -s extglob

# Parent directory
M_PREFIX=${M_PREFIX-$HOME/.local}

# Top level directory for files managed by `m`
M_DIR=$M_PREFIX/m

# Directory to symlink binaries into
M_BIN_DIR=${M_BIN_DIR-$M_PREFIX/bin}

# Directory for MongoDB version binaries
VERSIONS_DIR=$M_DIR/versions

# Directory for MongoDB Tools version binaries
TOOLS_DIR=$M_DIR/tools/versions

# Directory for MongoDB Shell version binaries
SHELL_DIR=$M_DIR/shell/versions

# Working directory for unpacking MongoDB tarballs
BUILD_DIR_BASE=$M_DIR/mongo-

### Dev/test/experimental settings
DEBUG=${M_DEBUG:-0}

# Use cache?
CACHE=${M_CACHE:-0}

# Confirm before downloading?
CONFIRM=${M_CONFIRM:-1}

# Location of cached source file
CACHE_SRC=${M_CACHE_SRC:-$M_DIR/cache-src.json}

# Cache source expiry (seconds)
CACHE_EXPIRY=${M_CACHE_EXPIRY:-3600}

# m version
VERSION="1.10.0"

#
# Log the given <msg ...>
#

log() {
  printf "... $@\n"
}

#
# Log the given <msg ...> if in debug mode
#

debug() {
  if [[ "$DEBUG" == 1 ]]; then
    printf "$@\n" 1>&2
  fi
}

#
# Exit with the given <msg ...>
#

abort() {
  printf "Error: $@\n" && exit 1
}

#
# Exit with advice for version that isn't available
#

abort_not_installed() {
  local version=$1

  printf "Error: requested MongoDB version is not installed\n"
  if [[ ! -z "$version" ]]; then
    printf "Try 'm $version' to download and activate.\n"
  fi
  exit 1
}

abort_shell_not_installed() {
  printf "Error: The requested MongoDB shell version is not installed.\n"
  if [[ ! -z "$version" ]]; then
    printf "Try 'm mongosh $version' to download and activate.\n"
  fi
  exit 1
}

#
# Check file age (in seconds)
#

file_age_in_seconds() {
  local FILE_TIME=`date -r "$1" +%s`;
  local TIME_NOW=`date +%s`;
  echo "$[ ${TIME_NOW} - ${FILE_TIME} ]";
}

### Setup

test -d $VERSIONS_DIR || mkdir -p $VERSIONS_DIR

if ! test -d $VERSIONS_DIR; then
  abort "Failed to create versions directory ($VERSIONS_DIR), do you have permissions to do this?"
fi

# curl / wget support

GET=

# wget support (Added --no-check-certificate for Github downloads)
which wget >/dev/null 2>&1 && GET="wget -q -O-"

# curl support
which curl >/dev/null 2>&1 && GET="curl -sSLf"

# Ensure we have curl or wget
test -z "$GET" && abort "curl or wget required"

#
# Output usage information.
#

display_help() {
  cat <<-help
  m v$VERSION (MongoDB Version Management)

  Usage: m [options] [COMMAND] [config]

  Commands:

    m                            Output versions installed
    m stable [config ...]        Install or activate the latest stable MongoDB release
    m latest [config ...]        Install or activate the latest MongoDB release (including dev & RCs)
    m X.Y                        Install or activate the latest patch release for MongoDB X.Y (eg. 7.0)
    m <version> [config ...]     Install and/or use MongoDB <version>
    m reinstall <version>        Remove and reinstall MongoDB <version>
    m <version> --legacy         Install generic Linux version (does not include SSL)
    m use <version> [args ...]   Execute mongod <version> with [args ...]
    m shard <version> [args ...] Execute mongos <version> with [args ...]
    m shell <version> [args ...] Open a mongo shell <version> with [args ...]
    m bin <version>              Output bin path for <version>
    m rm <version ...>           Remove the given version(s)
    m --stable                   Output the latest stable MongoDB version available
    m --stable X.Y                .. for release series X.Y (eg. 7.0)
    m --latest                   Output the latest MongoDB version available (including dev & RCs)
    m --latest X.Y                .. for release series X.Y (eg. 7.0)
    m ls                         Output the versions of MongoDB available
    m installed [--json]         Output installed versions available (optionally, in JSON format)
    m src <version>              Output the url for source used for the given <version>
                                   (useful if installed from source)
    m pre <event> [script]       Declare one or list scripts to execute before <event>
                                   (scripts must use absolute paths)
    m post <event> [script]      Declare one or list scripts to execute after <event>
                                   (scripts must use absolute paths)
    m pre <event> rm [script]    Remove pre <event> script
    m post <event> rm [script]   Remove post <event> script
    m tools stable               Install or activate the latest stable Database Tools release
    m tools X.Y.Z                Install or activate the Database Tools X.Y.Z
    m tools ls                   Output the versions of the Database Tools available
    m tools rm <version ...>     Remove the given Database Tools versions
    m tools installed [--json]   Output the installed versions of the Database Tools
    m mongosh stable             Install or activate the latest stable MongoDB Shell release
    m mongosh X.Y.Z              Install or activate the MongoDB Shell X.Y.Z
    m mongosh ls                 Output the versions of the MongoDB Shell available
    m mongosh rm <version ...>   Remove the given MongoDB Shell versions
    m mongosh installed [--json] Output the installed versions of the MongoDB Shell

  Events:

    change   Occurs when switching MongoDB server versions
    install  Occurs when installing a previously uninstalled MongoDB server version

  Options:

    -V, --version   Output current version of m
    -h, --help      Display help information

  Aliases:

    installed  lls
    shard      sd, mongos
    shell      s, sh, mongo
    list       ls, available, avail
    use        as, mongod
    which      bin

help
  exit 0
}

#
# Output m version.
#

display_m_version() {
  debug "$0"
  echo $VERSION && exit 0
}

#
# Internal helper to get all versions
#

get_all_versions() {
  local src_url="https://downloads.mongodb.org/full.json"

  if [ $CACHE == 1 ]; then
    # Use the cached versions if valid
    if [ -e $CACHE_SRC ]; then
      debug "$CACHE_SRC is $(file_age_in_seconds $CACHE_SRC) seconds old (expiry: $CACHE_EXPIRY)"
      if [ $(file_age_in_seconds $CACHE_SRC) -lt $CACHE_EXPIRY ]; then
        debug "Using cache: $CACHE_SRC"
        all_versions=`cat $CACHE_SRC`
      fi
    fi
  fi

  if [ -z "$all_versions" ]; then
    debug "get_all_versions(): $GET $src_url"
    all_versions=`$GET $src_url`
    if [ $CACHE ]; then
      debug "Creating cache: $CACHE_SRC"
      echo "$all_versions" > $CACHE_SRC
    fi
  fi
}

#
# Check for installed version, and populate $active
#

check_current_version() {
  which $M_BIN_DIR/mongod &> /dev/null
  if test $? -eq 0; then
    active=`$M_BIN_DIR/mongod --version | grep "version\s*v[0-9]" | grep -E -o '[0-9]+\.[0-9]+\.[0-9]+([-_\.][a-zA-Z0-9]+)?' | head -1`
    # either has "modules" set to something other than none or [], or includes the string "enterprise"
    ent=`$M_BIN_DIR/mongod --version | grep -E '(modules\"?:|\"?enterprise\"?)' | grep -v -E 'modules"?:[[:space:]]*\[.*' | grep -v -E 'modules"?:[[:space:]]*none'`
    if [[ $ent == *1 ]]; then
      active="$active-ent"
    fi
  fi
}

#
# Display current MongoDB --version
# and others installed.
#

display_versions() {
  local option=$1; shift
  local json=false

  if test "$option" = "--json"; then
    json=true
  fi

  declare -a versions
  versions=(`ls -1 $VERSIONS_DIR | sort -t. -k 1,1n -k 2,2n -k 3,3n`)
  if test -z "$versions"; then
    if $json; then
      echo "[]"
    else
      echo "No installed versions"
    fi
    return
  fi
  local last=${versions[${#versions[@]}-1]}

  if $json; then
    printf "["
  fi

  check_current_version
  for version in ${versions[@]}; do
    local dir="$VERSIONS_DIR/$version"
    local config=`test -f "$dir"/.config && cat "$dir"/.config`

    if $json; then
      printf "\n  {\n    \"name\" : \"$version\",\n    \"path\" : \"$dir/bin/\" \n  }"
      if [ "$version" != "$last" ]; then
        printf ","
      fi
    else
      if [ "$version" = "$active" ]; then
        printf "  ✔ $version $config\n"
      else
        printf "    $version $config\n"
      fi
    fi
  done

  if $json; then
    printf "\n]\n"
  fi
}

#
# Install and activate MongoDB Database Tools.
# Will install the full server for version <4.3.2 since the tools
# are included with the server for those versions.
#
#   install_tools <version>
#

install_tools() {
  local version=$1

  check_current_tools_version
  check_current_version

  if [[ $version == $active_tools ]]; then
    printf "Already Active: MongoDB Server $active, MongoDB Database Tools $active_tools\n"
    exit 0
  fi

  if [[ $version =~ "rc" ]]; then
    local rc="--rc"
  fi
  get_all_tools_versions $rc

  if [[ $version == "stable" ]]; then
    # Set version to the latest available version
    version=`echo $versions | awk 'NF>1{print $NF}'`
  fi

  version_test=`echo $version | sed 's/-ent$//'`
  if [[ ! $versions =~ $version_test ]]; then
    printf "Could not find any releases for the requested version "$version" of Database Tools\n"
    exit 1;
  fi

  if verlt $version "99.0.0"; then
    # Tools are included in the server for $version
    local dir=$VERSIONS_DIR/$version
    if test ! -d $dir; then
      prompt_install "Database Tools version $version is not installed."
      install_bin $version
    fi
    printf "\nActivating: MongoDB Server $active, MongoDB Database Tools $version\n"
    debug "DB Tools path: $dir"
    ln -fs $dir/bin/bsondump \
      $dir/bin/mongodump \
      $dir/bin/mongorestore \
      $dir/bin/mongoimport \
      $dir/bin/mongoexport \
      $dir/bin/mongofiles \
      $dir/bin/mongostat \
      $dir/bin/mongotop \
      $dir/bin/mongoreplay \
      $dir/bin/mongooplog \
      $dir/bin/mongosniff \
      $dir/bin/mongoperf \
      $M_BIN_DIR
  else
    # Separately released tools
    local dir=$TOOLS_DIR/$version
    if test ! -d $dir; then
      prompt_install "Database Tools version $version is not installed."
      install_tools_bin $version
    fi
    printf "\nActivating: MongoDB Server $active, MongoDB Database Tools $version\n"
    debug "DB Tools path: $dir"
    ln -fs $dir/bin/* $M_BIN_DIR
  fi
}

#
# Install MongoDB Database Tools binaries for provided version
#
#   install_tools_bin <version>
#

install_tools_bin() {
  local version=$1
  community=1

  log "installing binary"

  ext="tgz"
  get_distro_and_arch 'tools'
  if [[ $os = "linux" ]]; then
    # for linux fetch the rhel7 tarball by default
    local dist=rhel70
    # shadowing earlier $distro
    for distro in $distros; do
      if good "https://fastdl.mongodb.org/tools/db/mongodb-database-tools-$distro-$arch-$version.$ext"; then
        dist=$distro
        break
      fi
    done
  elif [[ $os = "osx" ]]; then
    dist="macos"
    if vergte $version "100.1.0"; then
      ext="zip"
    fi
    if [[ "$arch" = "arm64" ]]; then
      if verlte "$version" "100.7.0"; then
        echo ""
        echo "NOTE: Apple Silicon is not natively supported for this version of MongoDB Database Tools"
        echo " ==> Changing detected architecture from arm64 to x86_64 so Rosetta 2 can be used"
        echo "     More info: https://support.apple.com/en-au/HT211861"
        echo ""
        arch="x86_64"
      fi
    fi
  fi

  printf "Downloading from https://fastdl.mongodb.org/tools/db/mongodb-database-tools-$dist-$arch-$version.$ext"

  $GET https://fastdl.mongodb.org/tools/db/mongodb-database-tools-$dist-$arch-$version.$ext -o $M_DIR/tools-$version.$ext
  if test $? -gt 0; then
    printf "Error: tools installation failed\n"
    printf "  Fetch for MongoDB tools version $version failed.\n"
    printf "  Try a different version or view $logpath to view\n"
    printf "  error details.\n"
    exit 1
  fi
  mkdir -p $TOOLS_DIR/$version
  if [[ $ext = "zip" ]]; then
    unzip -q $M_DIR/tools-$version.zip -d $TOOLS_DIR/$version
  else
    tar -xzf $M_DIR/tools-$version.tgz -C $TOOLS_DIR/$version
  fi
  mv $TOOLS_DIR/$version/mongodb-database-tools-$dist-$arch-$version/* $TOOLS_DIR/$version
  rm -r $TOOLS_DIR/$version/mongodb-database-tools-$dist-$arch-$version
  log "removing source"
  rm $M_DIR/tools-$version.$ext
  log "Installation complete"
}

#
# Find the current installed version of tools.
#

check_current_tools_version() {
  which $M_BIN_DIR/mongodump &> /dev/null
  if test $? -eq 0; then
    # Note: this regex only works for mongodump 3.0+
    active_tools=`$M_BIN_DIR/mongodump --version | sed -nE "s/mongodump version\: r?(.*)$/\1/p"`
  fi
}

#
# Find all available versions of the database tools.
# Includes server versions up to 4.3.1.
#

get_all_tools_versions() {
  local tools_regex="([0-3]\.[0-9]+\.[0-9]+|4\.[0-2]\.[0-9]+|4\.3\.[01])"
  local rc_regex=""
  local all_versions=""
  if [[ $1 == "--rc" ]]; then
    local rc_regex="(-rc[0-9]+)?"
  fi

  curl_tools_versions=(curl -sSf -H "Accept: application/vnd.github.v3+json")
  if [ -n "${GH_TOKEN:-}" ]; then
    debug "Using GitHub token from environment for API requests"
    curl_tools_versions+=(-H "authorization: bearer ${GH_TOKEN:?}")
  fi
  curl_tools_versions+=("https://api.github.com/repos/mongodb/mongo-tools/git/refs/tags")

  tools_versions="$("${curl_tools_versions[@]}" \
  | sed -nE "s/^.*\"ref\"\: \"refs\/tags\/r?([[:digit:]]{2,3}\.[[:digit:]]+\.[[:digit:]]+)\",$/\1/p" \
  | sort -V)"

  get_all_versions

  server_versions=`echo $all_versions \
    | grep -E -o "$tools_regex$rc_regex" \
    | sort -u \
    | sort -s -k 2.3n -t - \
    | sort -s -k 1,1n -k 2,2n -k 3,3n -t . \
    | awk '{ if ($1 ) { print "  " $1 } }'`

  versions="$server_versions $tools_versions"
}

#
# Output all database tools versions
#

list_tools_versions() {
  check_current_tools_version

  if [[ $2 == "--rc" ]]; then
    local rc="--rc"
  fi

  get_all_tools_versions $rc

  for v in $versions; do
    if test "$active_tools" = "$v"; then
      printf "  ✔ $v\n"
    else
      if test -d $VERSIONS_DIR/$v || test -d $TOOLS_DIR/$v; then
        printf "  * $v\n"
      else
        printf "    $v\n"
      fi
    fi
  done
}

#
# Version less than or equal
#

verlte() {
    [  "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
}

#
# Version greater than or equal
#

vergte() {
    [  "$1" = "`echo -e "$1\n$2" | sort -rV | head -n1`" ]
}

#
# Version less than
#

verlt() {
    [ "$1" = "$2" ] && return 1 || verlte $1 $2
}

#
# Output all installed tools versions.
# Pass "--json" to output in JSON format.
#

display_tools_versions() {
  local option=$1; shift
  local json=false
  if test "$option" = "--json"; then
    json=true
  fi

  declare -a versions
  if [ -e $TOOLS_DIR ]; then
    versions=(`(ls -1 $VERSIONS_DIR; ls -1 $TOOLS_DIR) | sort -t. -k 1,1n -k 2,2n -k 3,3n`)
  fi


  if test -z "$versions"; then
    if $json; then
      echo "[]"
    else
      echo "No installed versions"
    fi
    return
  fi
  local last=${versions[${#versions[@]}-1]}

  if $json; then
    printf "["
  fi

  check_current_tools_version
  for version in ${versions[@]}; do
    num_version=$(numeric_version $version)
    if verlt $version "99.0.0" && vergte $version "4.3.2"; then
      continue
    fi
    local dir="$TOOLS_DIR/$version"
    local config=`test -f "$dir"/.config && cat "$dir"/.config`
    if $json; then
      printf "\n  {\n    \"name\" : \"$version\",\n    \"path\" : \"$dir/bin/\" \n  }"
      if [ "$version" != "$last" ]; then
        printf ","
      fi
    else
      if [ "$version" = "$active_tools" ]; then
        printf "  ✔ $version $config\n"
      else
        printf "    $version $config\n"
      fi
    fi
  done

  if $json; then
    printf "\n]\n"
  fi
}

remove_tools_versions() {
  test -z $1 && abort "version(s) required"
  check_current_tools_version

  while test $# -ne 0; do
    local version=${1#v}
    local rmpath="$TOOLS_DIR/$version"
    debug "=> Path: $rmpath"

    if verlte $version "4.3.2"; then
      printf "Error: Cannot remove tools version $version since it is included with the\n"
      printf "MongoDB server package. To remove this version, remove the server instead:\n"
      printf "   m rm $version\n"
      shift
      continue
    fi

    if test "$version" = "$active_tools"; then
      printf "WARNING: $version is the active version!\n"
      if [[ "$CONFIRM" == 1 ]]; then
        read -p "Are you sure you want to remove this? [y/N] " yn
        case $yn in
          [Yy]* ) ;;
          * )  printf "SKIPPING $version "; exit;;
        esac
      fi
    fi

    if [ -d $rmpath ]; then
      rm -rf $rmpath
      echo "Removed MongoDB tools version $version"
      active_tools=""
    else
      echo "MongoDB tools version $version is not installed"
    fi

    shift
  done
}

#
# Find the current installed version of shell.
#

check_current_shell_version() {
  which $M_BIN_DIR/mongosh &> /dev/null
  if test $? -eq 0; then
    active_shell=`$M_BIN_DIR/mongosh --version | sed -nE "s/^([0-9]+\.[0-9]+\.[0-9]+)$/\1/p" | head -1`
  fi
}

#
# Find all available versions of the MongoDB Shell.
#

get_all_shell_versions() {
  local rc_regex=""
  if [[ $1 == "--rc" ]]; then
    rc_regex="(-rc[0-9]+)?"
  fi

  curl_shell_versions=(curl -sSf -H "Accept: application/vnd.github.v3+json")
  if [ -n "${GH_TOKEN:-}" ]; then
    debug "Using GitHub token from environment for API requests"
    curl_shell_versions+=(-H "authorization: bearer ${GH_TOKEN:?}")
  fi
  curl_shell_versions+=("https://api.github.com/repos/mongodb-js/mongosh/git/refs/tags")

  shell_versions="$("${curl_shell_versions[@]}" \
  | sed -nE "s/^.*\"ref\"\: \"refs\/tags\/v([[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$rc_regex)\",$/\1/p" \
  | sort -V)"

  versions="$shell_versions"
}

#
# Output all MongoDB Shell versions
#

list_shell_versions() {
  check_current_shell_version

  if [[ $2 == "--rc" ]]; then
    local rc="--rc"
  fi

  get_all_shell_versions $rc

  for v in $versions; do
    if test "$active_shell" = "$v"; then
      printf "  ✔ $v\n"
    else
      if test -d $SHELL_DIR/$v; then
        printf "  * $v\n"
      else
        printf "    $v\n"
      fi
    fi
  done
}

#
# Output all installed shell versions.
# Pass "--json" to output in JSON format.
#

display_shell_versions() {
  local option=$1; shift
  local json=false
  if test "$option" = "--json"; then
    json=true
  fi

  declare -a versions
  if [ -e $SHELL_DIR ]; then
    versions=(`ls -1 $SHELL_DIR | sort -t. -k 1,1n -k 2,2n -k 3,3n`)
  fi

  if test -z "$versions"; then
    if $json; then
      echo "[]"
    else
      echo "No installed versions"
    fi
    return
  fi
  local last=${versions[${#versions[@]}-1]}

  if $json; then
    printf "["
  fi

  check_current_shell_version
  for version in ${versions[@]}; do
    local dir="$SHELL_DIR/$version"
    local config=`test -f "$dir"/.config && cat "$dir"/.config`
    if $json; then
      printf "\n  {\n    \"name\" : \"$version\",\n    \"path\" : \"$dir/bin/\" \n  }"
      if [ "$version" != "$last" ]; then
        printf ","
      fi
    else
      if [ "$version" = "$active_shell" ]; then
        printf "  ✔ $version $config\n"
      else
        printf "    $version $config\n"
      fi
    fi
  done

  if $json; then
    printf "\n]\n"
  fi
}

remove_shell_versions() {
  test -z $1 && abort "version(s) required"
  check_current_shell_version

  while test $# -ne 0; do
    local version=${1#v}
    local rmpath="$SHELL_DIR/$version"
    debug "=> Path: $rmpath"
    if test "$version" = "$active_shell"; then
      printf "WARNING: $version is the active version!\n"
      if [[ "$CONFIRM" == 1 ]]; then
        read -p "Are you sure you want to remove this? [y/N] " yn
        case $yn in
          [Yy]* ) ;;
          * )  printf "SKIPPING $version "; exit;;
        esac
      fi
    fi

    if [ -d $rmpath ]; then
      rm -rf $rmpath
      echo "Removed MongoDB shell version $version"
      active_shell=""
    else
      echo "MongoDB shell version $version is not installed"
    fi

    shift
  done
}

#
# Install MongoDB Shell binaries for provided version
#
#   install_shell_bin <version>
#

install_shell_bin() {
  local version=$1

  log "installing mongosh $version"

  ext="tgz"
  get_distro_and_arch 'shell'

  if [[ $os = "linux" ]]; then
    dist="linux"
  fi

  if [[ $os = "osx" ]]; then
    dist="darwin"
    ext="zip"
  fi

  if [[ $arch = "x86_64" ]]; then
    # For mongosh, use x64 naming, following this doc:
    # https://s3.amazonaws.com/info-mongodb-com/com-download-center/mongosh.multiversion.json
    arch="x64"
  fi

  link="https://downloads.mongodb.com/compass/mongosh-$version-$dist-$arch.$ext"

  log "downloading $link"
  $GET "$link" -o $M_DIR/mongosh-$version.$ext

  if test $? -gt 0; then
    printf "Error: mongosh installation failed\n"
    printf "  Fetch for MongoDB Shell version $version failed.\n"
    printf "  Try a different version or view $logpath to view\n"
    printf "  error details.\n"
    printf "\n"
    printf "You might also consider installing the MongoDB shell from the MongoDB Download Center:\n"
    printf "  https://www.mongodb.com/try/download/shell\n"
    exit 1
  fi

  mkdir -p $SHELL_DIR/$version

  log "unpacking source"
  if [[ $ext = "zip" ]]; then
    unzip -q $M_DIR/mongosh-$version.zip -d $SHELL_DIR/$version
  else
    tar -xzf $M_DIR/mongosh-$version.tgz -C $SHELL_DIR/$version
  fi


  log "moving files"
  mv $SHELL_DIR/$version/mongosh-$version-$dist-$arch/* $SHELL_DIR/$version

  log "cleaning up"
  rm $M_DIR/mongosh-$version.$ext
  rm -r $SHELL_DIR/$version/mongosh-$version-$dist-$arch
}

#
# Install and activate MongoDB Shell.
#
#   install_shell <version>
#

install_shell() {
  local version=$1

  check_current_shell_version
  check_current_version

  if [[ $version == $active_shell ]]; then
    printf "Already Active: MongoDB Server $active, MongoDB Shell $active_shell\n"
    exit 0
  fi

  if [[ $version =~ "rc" ]]; then
    local rc="--rc"
  fi

  if [[ $version == "stable" ]]; then
    # Need to fetch versions to find the latest
    get_all_shell_versions $rc
    if [[ -z "$versions" ]]; then
      printf "Could not fetch available versions of MongoDB Shell\n"
      exit 1
    fi
    # Set version to the latest available version
    version=`echo $versions | awk 'NF>1{print $NF}'`
  elif [[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    # Specific version provided, try to install it directly
    # (only validate if we successfully fetched versions)
    get_all_shell_versions $rc
    if [[ ! -z "$versions" ]] && [[ ! $versions =~ $version ]]; then
      printf "Could not find any releases for the requested version "$version" of MongoDB Shell\n"
      exit 1
    fi
  else
    printf "Invalid version format: $version\n"
    exit 1
  fi

  local dir=$SHELL_DIR/$version
  if test ! -d $dir; then
    prompt_install "MongoDB Shell version $version is not installed."
    install_shell_bin $version
  fi

  log "activating MongoDB Shell $version"
  debug "Shell path: $dir"
  ln -fs $dir/bin/* $M_BIN_DIR

  printf "Installation complete: MongoDB Shell $version activated!\n"
}

#
# Install MongoDB <version> [config ...]
#

install_mongo() {
  local version=$1; shift
  local config=$@
  local rc=""
  local flavour="stable"

  check_current_version
  check_current_tools_version

  # Default to stable releases, with option to include RCs
  if [[ ! -z "$config" ]]; then
    case "$config" in
      latest|--latest|rc|--rc)
        debug "Using --latest config (includes dev & RCs)"
        rc="([-_\.]rc[0-9]+)?"
        flavour="latest"
        config=""
        ;;
      legacy|--legacy)
        debug "Using generic Linux legacy config (does not include SSL)"
        _legacy_only=y
        config=""
        shift
        ;;
    esac
  fi

  if test "$version" = "$active"; then
    echo "Already Active: MongoDB Server $version, MongoDB Database Tools $active_tools"
    exit 0;
  fi

  debug "CURRENT: $version"

  # shorthand for finding latest in a release series (eg: "3.6", "3.6-ent")
  if [[ $version =~ ^([0-9])\.([0-9]+)(-ent)?$ ]]; then
    local series="${BASH_REMATCH[1]}\.${BASH_REMATCH[2]}"
    local ent="${BASH_REMATCH[3]}"
    local all_versions=""

    get_all_versions

    printf ">> Checking for $flavour release of MongoDB $version\n"

    if test "$flavour" = "latest"; then
      debug "grep -E -o \"$series\.[0-9]+$rc\""
      version=`echo $all_versions \
        | grep '"version\":' \
        | grep -E -o "$series\.[0-9]+$rc" \
        | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
        | tail -n1`
    else
      debug "grep -E -v \"[-]rc\" | grep -E -o \"$series\.[0-9]+\" "

      version=`echo $all_versions \
        | grep '"version\":' \
        | grep -E -o "$series\.[0-9]+\.zip" \
        | sed s/.zip$// \
        | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
        | tail -n1`
    fi
    version="$version$ent"
  fi

  debug "TARGET: $version"
  debug "FLAVOUR: $flavour"

  if [[ -z "$version" ]]; then
    abort "Could not find any releases for the requested version\n"
  fi


  local dir=$VERSIONS_DIR/$version
  if ! test -d $dir; then
    # install
    prompt_install "MongoDB version $version is not installed."
    install_bin $version $config
  fi

  tools_glob="bsondump|mongodump|mongorestore|mongoimport|mongoexport|mongofiles|mongostat|mongotop|mongoreplay|mongooplog|mongosniff|mongoperf"

  # activate
  pre change

  printf "\nActivating: MongoDB Server $version, MongoDB Database Tools $active_tools\n"
  debug "Symlinking binaries in $dir/bin to $M_BIN_DIR"

  mkdir -p "$M_BIN_DIR"

  cd $dir \
    && ln -fs $dir/bin/!($tools_glob) $M_BIN_DIR \
    && post change

  echo ""
  echo "Installation to $M_BIN_DIR complete!"
  if [[ $PATH != *"$M_BIN_DIR"* ]];then
    echo ""
    echo "==> WARNING: \$PATH does not include $M_BIN_DIR"
  fi
  echo ""
  if vergte $version "6.0.0"; then
    if ! command -v mongosh &> /dev/null; then
      echo ""
      echo "NOTE: the mongo shell is not included in MongoDB 6.0+ distributions."
      echo ""
      echo "Install and use the MongoDB Shell (mongosh) separately via:"
      echo "   m shell stable"
      echo ""
      echo "Or download it directly from the MongoDB website:"
      echo "   https://www.mongodb.com/try/download/shell"
      echo ""
    fi
  fi

}

# Prompt installation
#
#   prompt_install "About to install something"
#

prompt_install() {
  if [[ "$CONFIRM" == 1 ]]; then
    echo $1
    while true; do
      read -p "Installation may take a while. Would you like to proceed? [y/N] " yn
      case $yn in
        [Yy]* ) break ;;
        * ) echo "Aborted."; exit ;;
      esac
    done
  fi
}

#
# Convert version string to numeric for comparison
#

numeric_version () {
  echo "$1" | awk -F. '{ printf("%d%02d%02d\n", $1,$2,$3); }';
}

#
# Install MongoDB <version> binary
#
#   template    http://fastdl.mongodb.org/$OS/mongodb-$OS-$ARCH-$VERSION.tgz
#
#   linux32     http://fastdl.mongodb.org/linux/mongodb-linux-i686-2.2.2.tgz
#   linux64     http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.2.2.tgz
#   osx64       http://fastdl.mongodb.org/osx/mongodb-osx-x86_64-2.2.2.tgz
#   osx64 ssl   http://fastdl.mongodb.org/osx/mongodb-osx-ssl-x86_64-3.0.4.tgz
#   solaris64   http://fastdl.mongodb.org/sunos5/mongodb-sunos5-x86_64-2.2.2.tgz
#
#   not supported
#   win32       http://fastdl.mongodb.org/win32/mongodb-win32-i386-2.3.2.zip
#   win64       http://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2.0.8.zip
#   win64 2008+ http://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-2.0.3.zip
#

install_bin() {
  local version=$1
  local config=$2
  community=1

  # check if enterprise
  if [[ $version == *-ent ]]; then
    community=0
    version=${version%????}
  fi

  if ! test -z $config; then
    # config was passed, must build from source
    debug "install_bin(): unsupported config ($config) passed"
    local tarball="mongodb-src-r$version.tar.gz"
    local url="http://downloads.mongodb.org/src/$tarball"
    install_tarball $version $url $config
    exit 0
  fi

  # determine url based on os and arch
  get_distro_and_arch 'server'

  # determine the download url
  if [[ "$community" == 1 ]]; then
    local tarball="mongodb-$sslbuild-$arch-$version.tgz"
    # shadowing earlier $distro
    for distro in $distros; do
      if good "http://fastdl.mongodb.org/$os/mongodb-$sslbuild-$arch-$distro-$version.tgz"; then
        tarball="mongodb-$sslbuild-$arch-$distro-$version.tgz"
        break
      fi
    done
    local url="http://fastdl.mongodb.org/$os/$tarball"
  else # enterprise version
    case $os in
      linux* )
        # for linux fetch the rhel7 tarball by default
        local dist=rhel70
        # shadowing earlier $distro
        for distro in $distros; do
          if good "http://downloads.mongodb.com/$os/mongodb-$os-$arch-enterprise-$distro-$version.tgz"; then
            dist=$distro
            tarball="mongodb-$os-$arch-enterprise-$distro-$version.tgz"
            break
          fi
        done
        ;;
      osx* )
        local tarball="mongodb-$sslbuild-$arch-enterprise-$version.tgz"
        ;;
      * )
        local tarball="mongodb-$os-$arch-enterprise-$version.tgz" ;;
    esac
    version="$version-ent"
    local url="http://downloads.mongodb.com/$os/$tarball"
  fi

  debug "Default: $url"
  if ! good $url; then
    # Fallback to generic build if not already using this
    if [ "$sslbuild" != "$os" ]; then
      tarball="mongodb-$os-$arch-$version.tgz"
      url="http://fastdl.mongodb.org/$os/$tarball"
      debug "Fallback: $url"
      echo "Binary not available with SSL support. Checking url for generic (non-SSL) binaries."
      if ! good $url; then
        bin_not_supported $os-$arch $version $config
        exit 0;
      fi
    else
      bin_not_supported $os-$arch $version $config
      exit 0;
    fi
  fi

  pre install

  # perform the download
  local builddir="$BUILD_DIR_BASE$version"
  download $version $url $builddir

  # copy binaries to version directory
  local dir=$VERSIONS_DIR/$version
  mkdir -p $dir

  log "Moving binaries to $dir/bin"
  mv "$builddir/bin" $dir \
    && cleanup "$builddir" \
    && ln -sf $dir "$M_PREFIX/m/current" \
    && post install
}

#
# Set the os, distro, and arch variables for the current machine.
#

get_distro_and_arch() {
  local type=
  case "$1" in
    tools|server|shell)
      type="$1"
      ;;
    *)
      abort "Invalid type: '$1' (expected: tools, server, or shell)"
      ;;
  esac

  arch=`uname -m`
  local OS=`uname`
  os=`echo $OS | tr '[:upper:]' '[:lower:]'`

  case $os in
    linux* )
      os=linux ;;
    darwin* )
      os=osx
      if [[ "$type" == "server" ]] && [ "$arch" = "arm64" ]; then
        if [ $(numeric_version $version) -lt $(numeric_version "6.0.0") ]; then
          echo ""
          echo "NOTE: Apple Silicon is not natively supported for MongoDB server before 6.0"
          echo " ==> Changing detected architecture from arm64 to x86_64 so Rosetta 2 can be used"
          echo "     More info: https://support.apple.com/en-au/HT211861"
          echo ""
          arch="x86_64"
        fi
      fi

      if [ $(numeric_version $version) -lt $(numeric_version "4.1.1") ]; then
          # https://jira.mongodb.org/browse/SERVER-35211
          sslbuild="osx"
          # https://jira.mongodb.org/browse/SERVER-18421
          if test "$community" = 1; then
            sslbuild="osx-ssl"
          else
            sslbuild="osx"
          fi
      else
        sslbuild="macos"
      fi
      ;;
    sunos* )
      os=sunos5 ;;
    * )
      bin_not_supported $OS $version $config ;;
  esac

  if [ -z ${sslbuild+x} ]; then
    sslbuild=$os
  fi

  local distro_id= distro_version=
  if lsb_release >/dev/null 2>&1; then
    # If the lsb_release(1) tool is installed, use it.
    distro_id=`lsb_release -si`
    distro_version=`lsb_release -sr`
    if test "$distro_version" = testing; then
      distro_version=
    fi
  fi
  if test -z "$distro_version"; then
    if test -f /etc/lsb-release; then
      # In the case where the distro provides an /etc/lsb-release file, but the lsb_release(1) util isn't installed.
      distro_id=$(sed -ne 's/^DISTRIB_ID=//p' /etc/lsb-release | sed -e 's/^["'"'"']//' -e 's/["'"'"']$//' -e 's/\\\(.\)/\1/g')
      distro_version=$(sed -ne 's/^DISTRIB_RELEASE=//p' /etc/lsb-release | sed -e 's/^["'"'"']//' -e 's/["'"'"']$//' -e 's/\\\(.\)/\1/g')
    elif test -f /etc/debian_version; then
      # Debian generally doesn't install lsb_release(1) or a /etc/lsb-release file, so we figure it out manually.
      distro_version=`cat /etc/debian_version`
      case "$distro_version" in
        etch|etch/*)
          distro_version=4 ;;
        lenny|lenny/*)
          distro_version=5 ;;
        squeeze|squeeze/*)
          distro_version=6 ;;
        wheezy|wheezy/*)
          distro_version=7 ;;
        jessie|jessie/*)
          distro_version=8 ;;
        stretch|stretch/*)
          distro_version=9 ;;
        buster|buster/*)
          distro_version=10 ;;
        bullseye|bullseye/*)
          distro_version=11 ;;
        bookworm|bookworm/*)
          distro_version=12 ;;
      esac
      distro_id=debian
    elif test -f /etc/os-release; then
      # Suse, opensuse, amazon linux, centos (but not redhat)
      distro_id=$(sed -ne 's/^ID=//p' /etc/os-release | sed -e 's/^["'"'"']//' -e 's/["'"'"']$//' -e 's/\\\(.\)/\1/g')
      distro_version=$(sed -ne 's/^VERSION_ID=//p' /etc/os-release | sed -e 's/^["'"'"']//' -e 's/["'"'"']$//' -e 's/\\\(.\)/\1/g')
    fi
  fi

  distro_id=`echo "$distro_id" | tr '[:upper:]' '[:lower:]'`
  distro_version=`echo "$distro_version" | tr '[:upper:]' '[:lower:]'`
  case "$distro_id" in
    sles) distro_id="suse" ;;
    opensuse) distro_id="suse" ;;
    almalinux) distro_id="rhel" ;;
    fedora) distro_id="rhel" ;;
    centos) distro_id="rhel" ;;
    redhatenterpriseserver) distro_id="rhel" ;;
    rocky) distro_id="rhel" ;;
    neon) distro_id="ubuntu" ;;
    pop) distro_id="ubuntu" ;;
  esac

  distro="$distro_id-$distro_version"

  if test "$community" = 1; then
    amazon1="amazon"
  else
    amazon1="amzn64"
  fi

  debug "Detecting distro for $distro"

  # Different versions of MongoDB have builds for different distributions.
  # As m allows installing old MongoDB versions, we can look for
  # binaries for distributions that the latest MongoDB release is not
  # built for. Conversely, an old MongoDB version does not have to have
  # builds available for distributions that the latest version supports.
  #
  # The logic generally is to start with the correct distribution, then try
  # one version older and one version newer. This should handle most cases
  # reasonably well.
  case "$distro" in
    debian-6*) distros="" ;;
    debian-7*) distros="debian71" ;;
    debian-8*) distros="debian81 debian71" ;;
    debian-9*) distros="debian92 debian81" ;;
    debian-10*) distros="debian10 debian92" ;;
    debian-11*) distros="debian11 debian92" ;;
    debian-*)  distros="debian11 debian10 debian92" ;;

    ubuntu-12*) distros="ubuntu1204 debian71" ;;
    ubuntu-14*) distros="ubuntu1404 ubuntu1204" ;;
    ubuntu-16*) distros="ubuntu1604 ubuntu1404" ;;
    ubuntu-18*) distros="ubuntu1804 ubuntu1604" ;;
    ubuntu-20*) distros="ubuntu2004 ubuntu1804" ;;
    ubuntu-22*) distros="ubuntu2204 ubuntu2004 ubuntu1804" ;; # see #156
    ubuntu-24*) distros="ubuntu2404 ubuntu2204 ubuntu2004 ubuntu1804" ;;
    ubuntu-*)   distros="ubuntu2404 ubuntu2204 ubuntu2004" ;;

    linuxmint-17*) distros="ubuntu1404" ;;
    linuxmint-18*) distros="ubuntu1604" ;;
    linuxmint-19*) distros="ubuntu1804" ;;
    linuxmint-20*) distros="ubuntu2004" ;;
    linuxmint-21*) distros="ubuntu2204" ;;

    suse-10*) distros="" ;;
    suse-11*) distros="suse11" ;;
    suse-12*) distros="suse12 suse11" ;;
    suse-15*) distros="suse15 suse12" ;;
    suse-*)   distros="suse15 suse12" ;;

    amzn-1) distros="$amazon1 rhel70 rhel62" ;;
    amzn-2018.03) distros="$amazon1 rhel70 rhel62" ;;
    amzn-2) distros="amazon2 $amazon1 rhel70 rhel62" ;;
    amzn-2023) distros="amazon2023" ;;
    amzn-*) distros="amazon2 $amazon1" ;;

    rhel-5*) distros="rhel55 rhel57" ;;
    rhel-6*) distros="rhel62 rhel55 rhel57 $amazon1" ;;
    rhel-7*) distros="rhel70 rhel62 $amazon1" ;;
    rhel-8*) distros="rhel80 rhel70 $amazon1" ;;
    rhel-8*) distros="rhel90 rhel80 rhel70" ;;
    rhel-*)  distros="rhel90 rhel80 rhel70" ;;

    *) distros="" ;;
  esac

  if [ "$_legacy_only" = y ]; then
    distros=""
  fi
}


#
# Determine if $url is good
#

good() {
  local url=$1
  curl -Is $url | head -n 1 | grep 200 > /dev/null
}

#
# Prompt when we cannot install binary
#

bin_not_supported() {
  local OS=$1
  local version=$2
  echo ""
  echo "----------------------------------------------------------------------------"
  echo "Prebuilt binaries for $OS $version do not appear to be available."
  echo ""
  echo "For supported platforms, please see:"
  echo "   https://www.mongodb.com/docs/manual/administration/production-notes/"
  echo ""
  echo "To build from source, check the relevant build notes in GitHub:"
  echo "   https://github.com/mongodb/mongo/blob/master/docs/building.md"
  echo "----------------------------------------------------------------------------"
  echo ""
}

#
# Install from source
#

install_tarball() {
  local version=$1
  local url=$2
  local config=$3
  bin_not_supported "$version"
}

#
# Download and untar
#

download() {
  local version=$1
  local url=$2
  local builddir=$3

  local logpath=`mktemp /tmp/m.XXXXXX` || exit 1

  # create build directory
  mkdir -p $builddir

  if ! test -d $builddir; then
    abort "Failed to create directory ($builddir), do you have permissions to do this?"
  fi

  debug "download(): $GET $url"

  # fetch and unpack
  cd $builddir \
    && $GET $url | tar xz --strip-components=1 > $logpath 2>&1

  # see if things are alright
  if test $? -gt 0; then
    cd -
    cleanup "$builddir"
    printf "Error: installation failed\n"
    printf "  MongoDB version $version does not exist,\n"
    printf "  m failed to fetch the tarball,\n"
    printf "  or tar failed. Try a different\n"
    printf "  version or view $logpath to view\n"
    printf "  error details.\n"
    exit 1
  else
    rm $logpath
  fi
  cd -
}

#
# Cleanup after the given <version>
#

cleanup() {
  local dir=$1

  if test -d $dir; then
    log "Cleaning up temporary files"
    debug "Removing working directory: $dir"
    rm -rf $dir
  fi
}

#
# Remove <versions ...>
#

remove_versions() {
  test -z $1 && abort "version(s) required"
  check_current_version

  while test $# -ne 0; do
    local version=${1#v}
    local rmpath="$VERSIONS_DIR/$version"
    debug "=> Path: $rmpath"
    if test "$version" = "$active"; then
      printf "WARNING: $version is the active version!\n"
      if [[ "$CONFIRM" == 1 ]]; then
        read -p "Are you sure you want to remove this? [y/N] " yn
        case $yn in
          [Yy]* ) ;;
          * )  printf "SKIPPING $version "; exit;;
        esac
      fi
    fi

    if [ -d $rmpath ]; then
      rm -rf $rmpath
      echo "Removed MongoDB version $version"
      active=""
    else
      echo "MongoDB version $version is not installed"
    fi

    shift
  done
}

#
# Output bin path for <version>
#

display_bin_path_for_version() {
  test -z $1 && abort "version required"
  local version=$(get_latest_installed_version ${1#v})
  local bin=$VERSIONS_DIR/$version/bin
  debug "Path: $bin"
  if test -f "$bin/mongod"; then
    echo $bin
  else
    abort_not_installed $1
  fi
}

#
# Get the latest installed version in a MongoDB release series (eg: "3.6", "3.6-ent")
#

get_latest_installed_version() {
  local version=$1

  case $version in
    latest)
      version=`$0 --latest`
      ;;
    stable)
      version=`$0 --stable`
      ;;
  esac

  if [[ $version =~ ^([0-9)]\.([0-9]+)(-ent)?$ ]]; then
    local series="${BASH_REMATCH[1]}\.${BASH_REMATCH[2]}"
    local ent="${BASH_REMATCH[3]}"
    version=`find ${VERSIONS_DIR} -mindepth 1 -maxdepth 1 -type d \
      | grep -E -o "$series\.[0-9]+([-_\.]rc[0-9]+)?" \
      | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
      | tail -n1`
    if [[ -z "$version" ]]; then
      echo ""
    else
      # append the enterprise label if included in the original version
      version="$version$ent"
      echo "$version"
    fi
  else
    echo "$version"
  fi
}

#
# Execute the given <version> of MongoDB
# with [args ...]
#

execute_with_version() {
  test -z $1 && abort "version required"

  local version=$(get_latest_installed_version ${1#v})
  local bin=$VERSIONS_DIR/$version/bin/mongod

  shift # remove version

  if test -f $bin; then
    $bin $@
  else
    abort_not_installed $version
  fi
}

#
# Execute the given <version> of mongos
# with [args ...]
#

execute_shard_with_version() {
  test -z $1 && abort "version required"
  local version=$(get_latest_installed_version ${1#v})
  local bin=$VERSIONS_DIR/$version/bin/mongos

  shift # remove version

  if test -f $bin; then
    $bin $@
  else
    abort_not_installed $version
  fi
}

#
# Execute a script with the given shell of
# MongoDB <version> with [args...]
#

execute_shell_with_version() {
  test -z $1 && abort "version required"

  # first see if this version of mongosh is installed. fallback to legacy.
  local bin=$SHELL_DIR/${1#v}/bin/mongosh

  if ! test -f $bin; then
    log "mongosh version ${1#v} not found, falling back to legacy mongo shell"
    local version=$(get_latest_installed_version ${1#v})
    local bin=$VERSIONS_DIR/$version/bin/mongo
  fi

  debug "Shell bin: $bin"

  shift # remove version

  if test -f $bin; then
    $bin $@
  else
    abort_shell_not_installed ${1#v}
  fi
}

#
# Display the latest MongoDB release version.
#

display_latest_version() {
  local version=$1
  local all_versions=""

  get_all_versions

  if [[ $version =~ ^[0-9]\.[0-9]+$ ]]; then
    echo $all_versions \
      | grep -Eo "version\":[[:space:]]*\"$version\.[0-9]+([-_\.]rc[0-9]+)?" \
      | sed 's/version\":[[:space:]]*\"//' \
      | grep -Ev "\d-\d" \
      | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
      | uniq \
      | tail -n1
  else
    if [[ ! -z "$version" ]]; then
      abort "Version [$version] does not match the MongoDB release series format (X.Y)"
    else
      echo $all_versions \
        | grep -E -o '[0-9]\.[0-9]+\.[0-9]+([-_\.]rc[0-9]+)?' \
        | grep -E -v "\d-\d" \
        | uniq \
        | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
        | tail -n1
    fi
  fi
}

#
# Display the latest stable MongoDB release version.
#

display_latest_stable_version() {
  local version=$1
  local all_versions=""

  get_all_versions

  if [[ $version =~ ^[0-4]\.[0246]+$ ]]; then
    debug "Stable version < 5.0 ($version) is X.Y (Y is even)"
    echo $all_versions \
      | grep -Eo "version\":[[:space:]]*\"($version\.[0-9]+)" \
      | sed 's/version\":[[:space:]]*\"//' \
      | grep -E -v "\d-\d" \
      | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
      | uniq \
      | tail -n1
  elif [[ $version =~ ^[5-9]\.0$ ]]; then
    debug "Stable version >= 5.0 ($version) is X.0"
    echo $all_versions \
      | grep -Eo "version\":[[:space:]]*\"($version\.[0-9]+)" \
      | sed 's/version\":[[:space:]]*\"//' \
      | grep -E -v "\d-\d" \
      | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
      | uniq \
      | tail -n1
  else
    if [[ ! -z "$version" ]]; then
      abort "Version [$version] does not match a stable MongoDB release series (X.Y)"
    else
      echo $all_versions \
        | grep -E -o '[5-9]+\.0+\.[0-9]+\.zip' \
        | grep -E -v "\d-\d" \
        | uniq \
        | sed s/.zip$// \
        | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
        | tail -n1
      fi
  fi
}

#
# Display the available MongoDB versions.
#

list_versions() {
  local version=$1
  local stable=$2

  check_current_version
  local all_versions=""

  local series="[0-9]\.[0-9]+"
  local rc=""

  # match by release series (eg: "3.6")
  if [[ $version =~ ^([0-9])\.([0-9]+) ]]; then
    series="${BASH_REMATCH[1]}\.${BASH_REMATCH[2]}"
  fi

  # Default to stable releases, with option to include RCs
  case $stable in
    latest|--latest|rc|--rc)
      rc="([-_\.]rc[0-9]+)?"
      ;;
  esac

  get_all_versions

  versions=`echo $all_versions \
    | grep -E -o "$series\.[0-9]+$rc" \
    | sort -u \
    | sort -s -k 2.3n -t - \
    | sort -s -k 1,1n -k 2,2n -k 3,3n -t . \
    | awk '{ print "  " $1 }'`

  if [[ -z "$versions" ]]; then
      abort "Could not find any releases for the requested version\n"
  fi

  for v in $versions; do
    if test "$active" = "$v"; then
      printf "  ✔ $v\n"
    else
      if test -d $VERSIONS_DIR/$v; then
        printf "  * $v\n"
      else
        printf "    $v\n"
      fi
    fi
  done
}

#
# Display src url for given <version>.
#

list_src_url() {
  test -z $1 && abort "version required"
  local version=$1
  local tarball="mongodb-src-r$version.tar.gz"
  local url="http://downloads.mongodb.org/src/$tarball"
  echo "$url"
}

#
# store a hook
#

install_hook() {
  local hook=$1
  local event=$2
  local path=$3
  local file="$M_DIR/$1_$2"

  #log "installing $1 hook into $file"
  touch $file

  validate_event $event

  # with no path, print all hooks
  if [ "" = "$path" ]; then
    if [ "pre" = $hook ]; then
      list_pres $event
    else
      list_posts $event
    fi
    exit 0
  fi

  if [ "-" = $path ] || [ "rm" = $path ]; then
    # removing script or all scripts
    if [ "" = "$4" ]; then
      # remove all
      cat /dev/null > $file
    else
      # remove specified
      # skip sed & avoid path escaping issues
      touch tmp
      while read line
      do
        if ! [ $4 = $line ]; then
          echo $line >> tmp
        fi
      done < $file
      mv tmp $file
    fi
  else
    # add hook

    if ! test -x $path; then
      abort "not an executable file: $path"
    fi

    if ! [[ $path == /* ]]; then
      abort "not an absolute path: $path"
    fi

    # (ensure it exists only once)
    # skip sed & avoid path escaping issues
    while read line
    do
      if [ $path = $line ]; then
        exit 0
      fi
    done < $file

    echo $path >> $file
  fi
}

#
# validates hook type
# {install,change}
#

validate_event() {
  if ! ([ "$1" = "install" ] || [ "$1" = "change" ]); then
    abort "invalid hook event: '$1'. Must be 'install' or 'change'."
  fi
}

#
# executes pre hooks
#

pre() {
  local file=$M_DIR/pre_$1
  if test -f $file; then
    while read line
    do
      $line
    done < $file
  fi
}

#
# executes post hooks
#

post() {
  local file=$M_DIR/post_$1
  if test -f $file; then
    while read line
    do
      $line
    done < $file
  fi
}

#
# print all pre hooks
#

list_pres() {
  if test -f $M_DIR/pre_$1; then
    while read line
    do
      echo $line
    done < $M_DIR/pre_$1
  fi
}

#
# print all post hooks
#

list_posts() {
  if test -f $M_DIR/post_$1; then
    while read line
    do
      echo $line
    done < $M_DIR/post_$1
  fi
}

# Handle tools commands

handle_tools() {
  if test $# -eq 0; then
    display_tools_versions
  else
    case $1 in
        ls|list|available|avail) list_tools_versions $@; exit;;
        lls|installed) display_tools_versions $2; exit ;;
        stable) install_tools $@; exit ;;
        rm) shift; remove_tools_versions $@; exit ;;
        *) install_tools $@; exit ;;
      esac
  fi
}

# Handle mongosh commands

handle_mongosh() {
  if test $# -eq 0; then
    display_shell_versions
  else
    case $1 in
        ls|list|available|avail) list_shell_versions $@; exit;;
        lls|installed) display_shell_versions $2; exit ;;
        stable) install_shell $@; exit ;;
        rm) shift; remove_shell_versions $@; exit ;;
        *) install_shell $@; exit ;;
      esac
  fi
}

# Handle arguments

if test $# -eq 0; then
  display_versions
else
  while test $# -ne 0; do
    case $1 in
      -V|--version) display_m_version ;;
      -h|--help|help) display_help ;;
      lls|installed) display_versions $2; exit ;;
      --latest) display_latest_version $2; exit ;;
      --stable) display_latest_stable_version $2; exit ;;
      bin|which) display_bin_path_for_version $2; exit ;;
      as|use|mongod) shift; execute_with_version $@; exit ;;
      sd|shard|mongos) shift; execute_shard_with_version $@; exit ;;
      s|sh|shell|mongo) shift; execute_shell_with_version $@; exit ;;
      rm) shift; remove_versions $@; exit ;;
      latest) install_mongo `$0 --latest`; exit ;;
      stable) install_mongo `$0 --stable`; exit ;;
      ls|list|available|avail) shift; list_versions $@; exit ;;
      pre) shift; install_hook pre $@; exit ;;
      post) shift; install_hook post $@; exit ;;
      src) shift; list_src_url $@; exit;;
      install) shift; install_mongo $@; exit ;;
      reinstall) shift; CONFIRM=0; remove_versions $@; install_mongo $@; exit;;
      tools) shift; handle_tools $@; exit;;
      mongosh) shift; handle_mongosh $@; exit;;
      *) install_mongo $@; exit ;;
    esac
    shift
  done
fi
