_meson() {
  command="${COMP_WORDS[1]}"
  case "$command" in
    setup |\
    configure |\
    install |\
    introspect |\
    init |\
    test |\
    wrap |\
    subprojects |\
    help)
      _meson-$command "${COMP_WORDS[@]:1}"
      ;;
    *)
      _meson-setup "${COMP_WORDS[@]}"
      ;;
  esac
} &&
complete -F _meson meson

_meson_complete_option() {
  option_string=$1

  if [[ $# -eq 2 ]] && ! [[ "$option_string" == *=* ]]; then
    option_string="$option_string=$2"
  fi

  if [[ "$option_string" == *=* ]]; then
    _meson_complete_option_value "$option_string"
  else
    _meson_complete_option_name "$option_string"
  fi
}

_meson_complete_option_name() {
  option=$1
  options=($(python3 -c 'import sys, json
for option in json.load(sys.stdin):
  print(option["name"])
' <<< "$(_meson_get_options)"))
  compopt -o nospace
  COMPREPLY=($(compgen -W '${options[@]}' -S= -- "$option"))
}

_meson_complete_option_value() {
  cur=$1
  option_name=${cur%%=*}
  option_value=${cur#*=}

  if _meson_complete_filedir "$option_name" "$option_value"; then
    return
  fi

# TODO: support all the option types
  options=($(python3 -c 'import sys, json
for option in json.load(sys.stdin):
  if option["name"] != "'$option_name'":
    continue
  choices = []
  if option["type"] == "boolean":
    choices.append("true")
    choices.append("false")
  elif option["type"] == "combo":
    for choice in option["choices"]:
      choices.append(choice)
  for choice in choices:
    if choice.startswith("'$cur'"):
      print(choice)
' <<< "$(_meson_get_options)"))
  COMPREPLY=("${options[@]}")
}

_meson_get_options() {
  local options
  for builddir in "${COMP_WORDS[@]}"; do
    if [ -d "$builddir" ]; then
      break
    fi
    builddir=.
  done
  options=$(meson introspect "$builddir" --buildoptions 2>/dev/null) &&
  echo "$options" ||
  echo '[]'
}

_meson_complete_filedir() {
  _filedir_in() {
    pushd "$1" &>/dev/null
      local COMPREPLY=()
      _filedir
      echo "${COMPREPLY[@]}"
    popd &>/dev/null
  }

  option=$1
  cur=$2
  case $option in
    prefix |\
    libdir |\
    libexecdir |\
    bindir |\
    sbindir |\
    includedir |\
    datadir |\
    mandir |\
    infodir |\
    localedir |\
    sysconfdir |\
    localstatedir |\
    sharedstatedir)
      _filedir -d
      ;;
    cross-file)
      _filedir
      COMPREPLY+=($(_filedir_in "$XDG_DATA_DIRS"/meson/cross))
      COMPREPLY+=($(_filedir_in /usr/local/share/meson/cross))
      COMPREPLY+=($(_filedir_in /usr/share/meson/cross))
      COMPREPLY+=($(_filedir_in "$XDG_DATA_HOME"/meson/cross))
      COMPREPLY+=($(_filedir_in ~/.local/share/meson/cross))
      ;;
    *)
      return 1;;
  esac
  return 0
}

_meson-setup() {

  shortopts=(
    h
    D
    v
  )

  longopts=(
    help
    prefix
    libdir
    libexecdir
    bindir
    sbindir
    includedir
    datadir
    mandir
    infodir
    localedir
    sysconfdir
    localstatedir
    sharedstatedir
    backend
    buildtype
    strip
    unity
    werror
    layout
    default-library
    warnlevel
    stdsplit
    errorlogs
    cross-file
    version
    wrap-mode
  )

  local cur prev
  if _get_comp_words_by_ref cur prev &>/dev/null &&
    [ "${prev:0:2}" = '--' ] && _meson_complete_option "${prev:2}" "$cur"; then
    return
  elif _get_comp_words_by_ref cur prev &>/dev/null &&
    [ "${prev:0:1}" = '-' ] && [ "${prev:1:2}" != '-' ] && _meson_complete_option "${prev:1}"; then
    return
  elif _get_comp_words_by_ref -n '=' cur prev &>/dev/null; then
    if [ $prev == -D ]; then
      _meson_complete_option "$cur"
      return
    fi
  else
    cur="${COMP_WORDS[COMP_CWORD]}"
  fi

  if [[ "$cur" == "--"* ]]; then
    COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
  elif [[ "$cur" == "-"* ]]; then
    COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
    COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
  else
    _filedir -d
    if [ -z "$cur" ]; then
    COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}'))
    COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}'))
    fi

    if [ $COMP_CWORD -eq 1 ]; then
      COMPREPLY+=($(compgen -W 'setup configure test introspect' -- "$cur"))
    fi
  fi
}

_meson-configure() {

  shortopts=(
    h
    D
  )

  longopts=(
    help
    clearcache
  )

  local cur prev
  if _get_comp_words_by_ref -n '=' cur prev &>/dev/null; then
    if [ $prev == -D ]; then
      _meson_complete_option "$cur"
      return
    fi
  else
    cur="${COMP_WORDS[COMP_CWORD]}"
  fi

  if [[ "$cur" == "--"* ]]; then
    COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
  elif [[ "$cur" == "-"* ]]; then
    COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
    COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
  else
    for dir in "${COMP_WORDS[@]}"; do
      if [ -d "$dir" ]; then
        break
      fi
      dir=.
    done
    if [ ! -d "$dir/meson-private" ]; then
      _filedir -d
    fi

    if [ -z "$cur" ]; then
    COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}'))
    COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}'))
    fi
  fi
}

_meson-test() {
  shortopts=(
    q
    v
    t
    C
  )

  longopts=(
    quiet
    verbose
    timeout-multiplier
    repeat
    no-rebuild
    gdb
    list
    wrapper --wrap
    no-suite
    suite
    no-stdsplit
    print-errorlogs
    benchmark
    logbase
    num-processes
    setup
    test-args
  )

  local cur prev
  if _get_comp_words_by_ref -n ':' cur prev &>/dev/null; then
    case $prev in
      --repeat)
        # number, can't be completed
        return
        ;;
      --wrapper)
        _command_offset $COMP_CWORD
        return
        ;;
      -C)
        _filedir -d
        return
        ;;
      --suite | --no-suite)
        for i in "${!COMP_WORDS[@]}"; do
          opt="${COMP_WORDS[i]}"
          dir="${COMP_WORDS[i+1]}"
          case "$opt" in
            -C)
              break
              ;;
          esac
          dir=.
        done
        suites=($(python3 -c 'import sys, json;
for test in json.load(sys.stdin):
  for suite in test["suite"]:
    print(suite)
    ' <<< "$(meson introspect "$dir" --tests)"))
# TODO
        COMPREPLY+=($(compgen -W "${suites[*]}" -- "$cur"))
        return
        ;;
      --logbase)
        # free string, can't be completed
        return
        ;;
      --num-processes)
        # number, can't be completed
        return
        ;;
      -t | --timeout-multiplier)
        # number, can't be completed
        return
        ;;
      --setup)
        # TODO
        return
        ;;
      --test-args)
        return
        ;;
    esac
  else
    cur="${COMP_WORDS[COMP_CWORD]}"
  fi

  if [[ "$cur" == "--"* ]]; then
    COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
  elif [[ "$cur" == "-"* && ${#cur} -gt 1 ]]; then
    COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
  else
    for dir in "${COMP_WORDS[@]}"; do
      if [ -d "$dir" ]; then
        break
      fi
      dir=.
    done
    if [ ! -d "$dir/meson-private" ]; then
      _filedir -d
    fi

    for i in "${!COMP_WORDS[@]}"; do
      opt="${COMP_WORDS[i]}"
      dir="${COMP_WORDS[i+1]}"
      case "$opt" in
        -C)
          break
          ;;
      esac
      dir=.
    done
    tests=($(python3 -c 'import sys, json;
for test in json.load(sys.stdin):
  print(test["name"])
' <<< "$(meson introspect "$dir" --tests)"))
    COMPREPLY+=($(compgen -W "${tests[*]}" -- "$cur"))

    if [ -z "$cur" ]; then
      COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
      COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
    fi
  fi
}

_meson-introspect() {
  shortopts=(
    h
  )

  longopts=(
    targets
    installed
    buildsystem-files
    buildoptions
    tests
    benchmarks
    dependencies
    projectinfo
  )

  local cur prev
  if ! _get_comp_words_by_ref cur prev &>/dev/null; then
    cur="${COMP_WORDS[COMP_CWORD]}"
  fi

  if [[ "$cur" == "--"* ]]; then
    COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
  elif [[ "$cur" == "-"* ]]; then
    COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
    COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
  else
    for dir in "${COMP_WORDS[@]}"; do
      if [ -d "$dir" ]; then
        break
      fi
      dir=.
    done
    if [ ! -d "$dir/meson-private" ]; then
      _filedir -d
    fi

    if [ -z "$cur" ]; then
      COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}'))
      COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}'))
    fi
  fi
}

_meson-wrap() {
  : TODO
}
