#!/usr/bin/env bash

set -u

version=0.17.0
auto_completion=
key_bindings=
update_config=2
binary_arch=
allow_legacy=

help() {
  cat << EOF
usage: $0 [OPTIONS]

    --help               Show this message
    --bin                Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
    --all                Download fzf binary and update configuration files
                         to enable key bindings and fuzzy completion
    --[no-]key-bindings  Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
    --[no-]completion    Enable/disable fuzzy completion (bash & zsh)
    --[no-]update-rc     Whether or not to update shell configuration files

    --32                 Download 32-bit binary
    --64                 Download 64-bit binary
EOF
}

for opt in "$@"; do
  case $opt in
    --help)
      help
      exit 0
      ;;
    --all)
      auto_completion=1
      key_bindings=1
      update_config=1
      allow_legacy=1
      ;;
    --key-bindings)    key_bindings=1    ;;
    --no-key-bindings) key_bindings=0    ;;
    --completion)      auto_completion=1 ;;
    --no-completion)   auto_completion=0 ;;
    --update-rc)       update_config=1   ;;
    --no-update-rc)    update_config=0   ;;
    --32)              binary_arch=386   ;;
    --64)              binary_arch=amd64 ;;
    --bin)             ;;
    *)
      echo "unknown option: $opt"
      help
      exit 1
      ;;
  esac
done

cd "$(dirname "${BASH_SOURCE[0]}")"
fzf_base="$(pwd)"

ask() {
  # If stdin is a tty, we are "interactive".
  # non-interactive shell: wait for a linefeed
  #     interactive shell: continue after a single keypress
  read_n=$([ -t 0 ] && echo "-n 1")

  read -p "$1 ([y]/n) " $read_n -r
  echo
  [[ $REPLY =~ ^[Nn]$ ]]
}

check_binary() {
  echo -n "  - Checking fzf executable ... "
  local output
  output=$("$fzf_base"/bin/fzf --version 2>&1 | awk '{print $1}')
  if [ $? -ne 0 ]; then
    echo "Error: $output"
    binary_error="Invalid binary"
  elif [ "$version" != "$output" ]; then
    echo "$output != $version"
    binary_error="Invalid version"
  else
    echo "$output"
    binary_error=""
    return 0
  fi
  rm -f "$fzf_base"/bin/fzf
  return 1
}

link_fzf_in_path() {
  if which_fzf="$(command -v fzf)"; then
    echo "  - Found in \$PATH"
    echo "  - Creating symlink: $which_fzf -> bin/fzf"
    (cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
    check_binary && return
  fi
  return 1
}

try_curl() {
  command -v curl > /dev/null &&
  if [[ $1 =~ tgz$ ]]; then
    curl -fL $1 | tar -xzf -
  else
    local temp=${TMPDIR:-/tmp}/fzf.zip
    curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
  fi
}

try_wget() {
  command -v wget > /dev/null &&
  if [[ $1 =~ tgz$ ]]; then
    wget -O - $1 | tar -xzf -
  else
    local temp=${TMPDIR:-/tmp}/fzf.zip
    wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
  fi
}

download() {
  echo "Downloading bin/fzf ..."
  if [[ ! "$version" =~ alpha ]]; then
    if [ -x "$fzf_base"/bin/fzf ]; then
      echo "  - Already exists"
      check_binary && return
    fi
    link_fzf_in_path && return
  fi
  mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
  if [ $? -ne 0 ]; then
    binary_error="Failed to create bin directory"
    return
  fi

  local url
  [[ "$version" =~ alpha ]] &&
    url=https://github.com/junegunn/fzf-bin/releases/download/alpha/${1} ||
    url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}
  set -o pipefail
  if ! (try_curl $url || try_wget $url); then
    set +o pipefail
    binary_error="Failed to download with curl and wget"
    return
  fi
  set +o pipefail

  if [ ! -f fzf ]; then
    binary_error="Failed to download ${1}"
    return
  fi

  chmod +x fzf && check_binary
}

# Try to download binary executable
archi=$(uname -sm)
binary_available=1
binary_error=""
case "$archi" in
  Darwin\ *64)   download fzf-$version-darwin_${binary_arch:-amd64}.tgz  ;;
  Darwin\ *86)   download fzf-$version-darwin_${binary_arch:-386}.tgz    ;;
  Linux\ *64)    download fzf-$version-linux_${binary_arch:-amd64}.tgz   ;;
  Linux\ *86)    download fzf-$version-linux_${binary_arch:-386}.tgz     ;;
  Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz    ;;
  Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz    ;;
  Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz    ;;
  Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz    ;;
  FreeBSD\ *64)  download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
  FreeBSD\ *86)  download fzf-$version-freebsd_${binary_arch:-386}.tgz   ;;
  OpenBSD\ *64)  download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
  OpenBSD\ *86)  download fzf-$version-openbsd_${binary_arch:-386}.tgz   ;;
  CYGWIN*\ *64)  download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
  MINGW*\ *86)   download fzf-$version-windows_${binary_arch:-386}.zip   ;;
  MINGW*\ *64)   download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
  *)             binary_available=0 binary_error=1 ;;
esac

cd "$fzf_base"
if [ -n "$binary_error" ]; then
  if [ $binary_available -eq 0 ]; then
    echo "No prebuilt binary for $archi ..."
  else
    echo "  - $binary_error !!!"
  fi
  if command -v go > /dev/null; then
    echo -n "Building binary (go get -u github.com/junegunn/fzf) ... "
    if [ -z "${GOPATH-}" ]; then
      export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
      mkdir -p "$GOPATH"
    fi
    if go get -u github.com/junegunn/fzf; then
      echo "OK"
      cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
    else
      echo "Failed to build binary. Installation failed."
      exit 1
    fi
  else
    echo "go executable not found. Installation failed."
    exit 1
  fi
fi

[[ "$*" =~ "--bin" ]] && exit 0

# Auto-completion
if [ -z "$auto_completion" ]; then
  ask "Do you want to enable fuzzy auto-completion?"
  auto_completion=$?
fi

# Key-bindings
if [ -z "$key_bindings" ]; then
  ask "Do you want to enable key bindings?"
  key_bindings=$?
fi

echo
has_zsh=$(command -v zsh > /dev/null && echo 1 || echo 0)
shells=$([ $has_zsh -eq 1 ] && echo "bash zsh" || echo "bash")
for shell in $shells; do
  echo -n "Generate ~/.fzf.$shell ... "
  src=~/.fzf.${shell}

  fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
  if [ $auto_completion -eq 0 ]; then
    fzf_completion="# $fzf_completion"
  fi

  fzf_key_bindings="source \"$fzf_base/shell/key-bindings.${shell}\""
  if [ $key_bindings -eq 0 ]; then
    fzf_key_bindings="# $fzf_key_bindings"
  fi

  cat > $src << EOF
# Setup fzf
# ---------
if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then
  export PATH="\$PATH:$fzf_base/bin"
fi

# Auto-completion
# ---------------
$fzf_completion

# Key bindings
# ------------
$fzf_key_bindings

EOF
  echo "OK"
done

# fish
has_fish=$(command -v fish > /dev/null && echo 1 || echo 0)
if [ $has_fish -eq 1 ]; then
  echo -n "Update fish_user_paths ... "
  fish << EOF
  echo \$fish_user_paths | grep $fzf_base/bin > /dev/null
  or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin
EOF
  [ $? -eq 0 ] && echo "OK" || echo "Failed"

  mkdir -p ~/.config/fish/functions
  if [ -e ~/.config/fish/functions/fzf.fish ]; then
    echo -n "Remove unnecessary ~/.config/fish/functions/fzf.fish ... "
    rm -f ~/.config/fish/functions/fzf.fish && echo "OK" || echo "Failed"
  fi

  fish_binding=~/.config/fish/functions/fzf_key_bindings.fish
  if [ $key_bindings -ne 0 ]; then
    echo -n "Symlink $fish_binding ... "
    ln -sf "$fzf_base/shell/key-bindings.fish" \
           "$fish_binding" && echo "OK" || echo "Failed"
  else
    echo -n "Removing $fish_binding ... "
    rm -f "$fish_binding"
    echo "OK"
  fi
fi

append_line() {
  set -e

  local update line file pat lno
  update="$1"
  line="$2"
  file="$3"
  pat="${4:-}"

  echo "Update $file:"
  echo "  - $line"
  [ -f "$file" ] || touch "$file"
  if [ $# -lt 4 ]; then
    lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
  else
    lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
  fi
  if [ -n "$lno" ]; then
    echo "    - Already exists: line #$lno"
  else
    if [ $update -eq 1 ]; then
      echo >> "$file"
      echo "$line" >> "$file"
      echo "    + Added"
    else
      echo "    ~ Skipped"
    fi
  fi
  echo
  set +e
}

create_file() {
  local file="$1"
  shift
  echo "Create $file:"
  for line in "$@"; do
    echo "    $line"
    echo "$line" >> "$file"
  done
  echo
}

if [ $update_config -eq 2 ]; then
  echo
  ask "Do you want to update your shell configuration files?"
  update_config=$?
fi
echo
for shell in $shells; do
  [ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
  append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
done

if [ $key_bindings -eq 1 ] && [ $has_fish -eq 1 ]; then
  bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
  if [ ! -e "$bind_file" ]; then
    create_file "$bind_file" \
      'function fish_user_key_bindings' \
      '  fzf_key_bindings' \
      'end'
  else
    append_line $update_config "fzf_key_bindings" "$bind_file"
  fi
fi

if [ $update_config -eq 1 ]; then
  echo 'Finished. Restart your shell or reload config file.'
  echo '   source ~/.bashrc  # bash'
  [ $has_zsh  -eq 1 ] && echo "   source ${ZDOTDIR:-~}/.zshrc   # zsh"
  [ $has_fish -eq 1 ] && [ $key_bindings -eq 1 ] && echo '   fzf_key_bindings  # fish'
  echo
  echo 'Use uninstall script to remove fzf.'
  echo
fi
echo 'For more information, see: https://github.com/junegunn/fzf'
