#!/usr/bin/env bash

version=0.9.4

cd $(dirname $BASH_SOURCE)
fzf_base=$(pwd)

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

check_binary() {
  echo -n "  - Checking fzf executable ... "
  local output=$("$fzf_base"/bin/fzf --version 2>&1)
  if [ "$version" = "$output" ]; then
    echo "$output"
    binary_error=""
  else
    echo "$output != $version"
    rm -f "$fzf_base"/bin/fzf
    binary_error="Invalid binary"
    return 1
  fi
}

symlink() {
  echo "  - Creating symlink: bin/$1 -> bin/fzf"
  (cd "$fzf_base"/bin &&
   rm -f fzf &&
   ln -sf $1 fzf)
  if [ $? -ne 0 ]; then
    binary_error="Failed to create symlink"
    return 1
  fi
}

download() {
  echo "Downloading bin/fzf ..."
  if [[ ! $1 =~ dev && -x "$fzf_base"/bin/fzf ]]; then
    echo "  - Already exists"
    check_binary && return
  elif [ -x "$fzf_base"/bin/$1 ]; then
    symlink $1 && check_binary && 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=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz
  if which curl > /dev/null; then
    curl -fL $url | tar -xz
  elif which wget > /dev/null; then
    wget -O - $url | tar -xz
  else
    binary_error="curl or wget not found"
    return
  fi

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

  chmod +x $1 && symlink $1 && check_binary
}

# Try to download binary executable
archi=$(uname -sm)
binary_available=1
binary_error=""
case "$archi" in
  Darwin\ x86_64) download fzf-$version-darwin_amd64 ;;
  Darwin\ i*86)   download fzf-$version-darwin_386   ;;
  Linux\ x86_64)  download fzf-$version-linux_amd64  ;;
  Linux\ i*86)    download fzf-$version-linux_386    ;;
  *)              binary_available=0                 ;;
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
  echo "Installing legacy Ruby version ..."

  # ruby executable
  echo -n "Checking Ruby executable ... "
  ruby=`which ruby`
  if [ $? -ne 0 ]; then
    echo "ruby executable not found !!!"
    exit 1
  fi

  # System ruby is preferred
  system_ruby=/usr/bin/ruby
  if [ -x $system_ruby -a $system_ruby != "$ruby" ]; then
    $system_ruby --disable-gems -rcurses -e0 2> /dev/null
    [ $? -eq 0 ] && ruby=$system_ruby
  fi

  echo "OK ($ruby)"

  # Curses-support
  echo -n "Checking Curses support ... "
  "$ruby" -rcurses -e0 2> /dev/null
  if [ $? -eq 0 ]; then
    echo "OK"
  else
    echo "Not found"
    echo "Installing 'curses' gem ... "
    if (( EUID )); then
      /usr/bin/env gem install curses --user-install
    else
      /usr/bin/env gem install curses
    fi
    if [ $? -ne 0 ]; then
      echo
      echo "Failed to install 'curses' gem."
      if [[ $(uname -r) =~ 'ARCH' ]]; then
        echo "Make sure that base-devel package group is installed."
      fi
      exit 1
    fi
  fi

  # Ruby version
  echo -n "Checking Ruby version ... "
  "$ruby" -e 'exit RUBY_VERSION >= "1.9"'
  if [ $? -eq 0 ]; then
    echo ">= 1.9"
    "$ruby" --disable-gems -rcurses -e0 2> /dev/null
    if [ $? -eq 0 ]; then
      fzf_cmd="$ruby --disable-gems $fzf_base/fzf"
    else
      fzf_cmd="$ruby $fzf_base/fzf"
    fi
  else
    echo "< 1.9"
    fzf_cmd="$ruby $fzf_base/fzf"
  fi
fi

# Auto-completion
ask "Do you want to add auto-completion support?"
auto_completion=$?

# Key-bindings
ask "Do you want to add key bindings?"
key_bindings=$?

echo
for shell in bash zsh; do
  echo -n "Generate ~/.fzf.$shell ... "
  src=~/.fzf.${shell}

  fzf_completion="[[ \$- =~ i ]] && source \"$fzf_base/fzf-completion.${shell}\""
  if [ $auto_completion -ne 0 ]; then
    fzf_completion="# $fzf_completion"
  fi

  if [ -n "$binary_error" ]; then
    cat > $src << EOF
# Setup fzf function
# ------------------
unalias fzf 2> /dev/null
fzf() {
  $fzf_cmd "\$@"
}
export -f fzf > /dev/null

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

EOF
  else
    cat > $src << EOF
# Setup fzf
# ---------
unalias fzf 2> /dev/null
unset fzf 2> /dev/null
if [[ ! "\$PATH" =~ "$fzf_base/bin" ]]; then
  export PATH="$fzf_base/bin:\$PATH"
fi

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

EOF
  fi

  if [ $key_bindings -eq 0 ]; then
    if [ $shell = bash ]; then
      cat >> $src << "EOFZF"
# Key bindings
# ------------
__fsel() {
  command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \
    -o -type f -print \
    -o -type d -print \
    -o -type l -print 2> /dev/null | sed 1d | cut -b3- | fzf -m | while read item; do
    printf '%q ' "$item"
  done
  echo
}

if [[ $- =~ i ]]; then

__fsel_tmux() {
  local height
  height=${FZF_TMUX_HEIGHT:-40%}
  if [[ $height =~ %$ ]]; then
    height="-p ${height%\%}"
  else
    height="-l $height"
  fi
  tmux split-window $height "bash -c 'source ~/.fzf.bash; tmux send-keys -t $TMUX_PANE \"\$(__fsel)\"'"
}

__fcd() {
  local dir
  dir=$(command find -L ${1:-.} \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \
    -o -type d -print 2> /dev/null | sed 1d | cut -b3- | fzf +m) && printf 'cd %q' "$dir"
}

__use_tmux=0
[ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ] && __use_tmux=1

if [ -z "$(set -o | \grep '^vi.*on')" ]; then
  # Required to refresh the prompt after fzf
  bind '"\er": redraw-current-line'

  # CTRL-T - Paste the selected file path into the command line
  if [ $__use_tmux -eq 1 ]; then
    bind '"\C-t": " \C-u \C-a\C-k$(__fsel_tmux)\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
  else
    bind '"\C-t": " \C-u \C-a\C-k$(__fsel)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"'
  fi

  # CTRL-R - Paste the selected command from history into the command line
  bind '"\C-r": " \C-e\C-u$(HISTTIMEFORMAT= history | fzf +s --tac +m -n2..,.. | sed \"s/ *[0-9]* *//\")\e\C-e\er"'

  # ALT-C - cd into the selected directory
  bind '"\ec": " \C-e\C-u$(__fcd)\e\C-e\er\C-m"'
else
  bind '"\C-x\C-e": shell-expand-line'
  bind '"\C-x\C-r": redraw-current-line'

  # CTRL-T - Paste the selected file path into the command line
  # - FIXME: Selected items are attached to the end regardless of cursor position
  if [ $__use_tmux -eq 1 ]; then
    bind '"\C-t": "\e$a \eddi$(__fsel_tmux)\C-x\C-e\e0P$xa"'
  else
    bind '"\C-t": "\e$a \eddi$(__fsel)\C-x\C-e\e0Px$a \C-x\C-r\exa "'
  fi
  bind -m vi-command '"\C-t": "i\C-t"'

  # CTRL-R - Paste the selected command from history into the command line
  bind '"\C-r": "\eddi$(HISTTIMEFORMAT= history | fzf +s --tac +m -n2..,.. | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"'
  bind -m vi-command '"\C-r": "i\C-r"'

  # ALT-C - cd into the selected directory
  bind '"\ec": "\eddi$(__fcd)\C-x\C-e\C-x\C-r\C-m"'
  bind -m vi-command '"\ec": "i\ec"'
fi

unset __use_tmux

fi
EOFZF
    else  # zsh
      cat >> $src << "EOFZF"
# Key bindings
# ------------
# CTRL-T - Paste the selected file path(s) into the command line
__fsel() {
  command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \
    -o -type f -print \
    -o -type d -print \
    -o -type l -print 2> /dev/null | sed 1d | cut -b3- | fzf -m | while read item; do
    printf '%q ' "$item"
  done
  echo
}

if [[ $- =~ i ]]; then

if [ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ]; then
  fzf-file-widget() {
    local height
    height=${FZF_TMUX_HEIGHT:-40%}
    if [[ $height =~ %$ ]]; then
      height="-p ${height%\%}"
    else
      height="-l $height"
    fi
    tmux split-window $height "zsh -c 'source ~/.fzf.zsh; tmux send-keys -t $TMUX_PANE \"\$(__fsel)\"'"
  }
else
  fzf-file-widget() {
    LBUFFER="${LBUFFER}$(__fsel)"
    zle redisplay
  }
fi
zle     -N   fzf-file-widget
bindkey '^T' fzf-file-widget

# ALT-C - cd into the selected directory
fzf-cd-widget() {
  cd "${$(command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \
    -o -type d -print 2> /dev/null | sed 1d | cut -b3- | fzf +m):-.}"
  zle reset-prompt
}
zle     -N    fzf-cd-widget
bindkey '\ec' fzf-cd-widget

# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
  LBUFFER=$(fc -l 1 | fzf +s --tac +m -n2..,.. | sed "s/ *[0-9*]* *//")
  zle redisplay
}
zle     -N   fzf-history-widget
bindkey '^R' fzf-history-widget

fi
EOFZF
    fi
  fi

  echo "OK"
done

# fish
has_fish=0
if [ -n "$(which fish)" ]; then
  has_fish=1
  echo -n "Generate ~/.config/fish/functions/fzf.fish ... "
  mkdir -p ~/.config/fish/functions
  if [ -n "$binary_error" ]; then
    cat > ~/.config/fish/functions/fzf.fish << EOFZF
function fzf
  $fzf_cmd \$argv
end
EOFZF
  else
    cat > ~/.config/fish/functions/fzf.fish << EOFZF
function fzf
  $fzf_base/bin/fzf \$argv
end
EOFZF
  fi
  echo "OK"

  if [ $key_bindings -eq 0 ]; then
    echo -n "Generate ~/.config/fish/functions/fzf_key_bindings.fish ... "
    cat > ~/.config/fish/functions/fzf_key_bindings.fish << "EOFZF"
function fzf_key_bindings
  # Due to a bug of fish, we cannot use command substitution,
  # so we use temporary file instead
  if [ -z "$TMPDIR" ]
    set -g TMPDIR /tmp
  end

  function __fzf_list
    command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \
      -o -type f -print \
      -o -type d -print \
      -o -type l -print 2> /dev/null | sed 1d | cut -b3-
  end

  function __fzf_list_dir
    command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) \
      -prune -o -type d -print 2> /dev/null | sed 1d | cut -b3-
  end

  function __fzf_escape
    while read item
      echo -n (echo -n "$item" | sed -E 's/([ "$~'\''([{<>})])/\\\\\\1/g')' '
    end
  end

  function __fzf_ctrl_t
    if [ -n "$TMUX_PANE" -a "$FZF_TMUX" != "0" ]
      tmux split-window (__fzf_tmux_height) "fish -c 'fzf_key_bindings; __fzf_ctrl_t_tmux \\$TMUX_PANE'"
    else
      __fzf_list | fzf -m > $TMPDIR/fzf.result
      and commandline -i (cat $TMPDIR/fzf.result | __fzf_escape)
      commandline -f repaint
      rm -f $TMPDIR/fzf.result
    end
  end

  function __fzf_ctrl_t_tmux
    __fzf_list | fzf -m > $TMPDIR/fzf.result
    and tmux send-keys -t $argv[1] (cat $TMPDIR/fzf.result | __fzf_escape)
    rm -f $TMPDIR/fzf.result
  end

  function __fzf_reverse
    if which tac > /dev/null
      tac $argv
    else
      tail -r $argv
    end
  end

  function __fzf_ctrl_r
    history | __fzf_reverse | fzf +s --tac +m > $TMPDIR/fzf.result
    and commandline (cat $TMPDIR/fzf.result)
    commandline -f repaint
    rm -f $TMPDIR/fzf.result
  end

  function __fzf_alt_c
    # Fish hangs if the command before pipe redirects (2> /dev/null)
    __fzf_list_dir | fzf +m > $TMPDIR/fzf.result
    [ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ]
    and cd (cat $TMPDIR/fzf.result)
    commandline -f repaint
    rm -f $TMPDIR/fzf.result
  end

  function __fzf_tmux_height
    if set -q FZF_TMUX_HEIGHT
      set height $FZF_TMUX_HEIGHT
    else
      set height 40%
    end
    if echo $height | \grep -q -E '%$'
      echo "-p "(echo $height | sed 's/%$//')
    else
      echo "-l $height"
    end
    set -e height
  end

  bind \ct '__fzf_ctrl_t'
  bind \cr '__fzf_ctrl_r'
  bind \ec '__fzf_alt_c'
end
EOFZF
    echo "OK"
  fi
fi

append_line() {
  echo "Update $2:"
  echo "  - $1"
  [ -f "$2" ] || touch "$2"
  if [ $# -lt 3 ]; then
    line=$(\grep -nF "$1" "$2" | sed 's/:.*//' | tr '\n' ' ')
  else
    line=$(\grep -nF "$3" "$2" | sed 's/:.*//' | tr '\n' ' ')
  fi
  if [ -n "$line" ]; then
    echo "    - Already exists: line #$line"
  else
    echo "$1" >> "$2"
    echo "    + Added"
  fi
  echo
}

echo
for shell in bash zsh; do
  append_line "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" ~/.${shell}rc "~/.fzf.${shell}"
done

if [ $key_bindings -eq 0 -a $has_fish -eq 1 ]; then
  bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
  append_line "fzf_key_bindings" "$bind_file"

  echo '  * Due to a known bug of fish, you may have issues running fzf on fish.'
  echo '  * If that happens, try the following:'
  echo '    - Remove ~/.config/fish/functions/fzf.fish'
  echo '    - Place fzf executable in a directory included in $PATH'
  echo
fi

cat << EOF
Finished. Restart your shell or reload config file.
   source ~/.bashrc  # bash
   source ~/.zshrc   # zsh
EOF
[ $has_fish -eq 1 ] && echo "   fzf_key_bindings  # fish"; cat << EOF

Use uninstall script to remove fzf.

For more information, see: https://github.com/junegunn/fzf
EOF

