#!/usr/bin/env bash

GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)

# awk script that takes 2 input files. The first one is newline deilmeted
# string of patterns to look for, second one is the file to remove them from.
# outfile has to be passed with -v upon call and is the file result is
# written to.
read -r -d '' AWK_SCRIPT <<'EOF'
# process the first "file" (newline split argument list)
NR==FNR { a[b++]=$0; next; }
# process file
{
   found=0;
   for (i=0; i != b; i++) {
      if (a[i] == $0) { found=1; }
   }
   if (found == 1) { print "... removing '"$0"'"; }
   else { print $0 > outfile }
}
EOF



show_contents() {
  local file="${2/#~/$HOME}"
  if [ -f "$file" ]; then
    echo "$1 gitignore: $2" && cat "$file"
  else
    echo "There is no $1 .gitignore yet"
  fi
}

cd_to_git_root() {
  local error_level="$1"

  if ! git rev-parse --git-dir &>/dev/null; then
    if [ "$error_level" = '--warn' ]; then
      echo "Warning: Not currently in a Git repository" >&2
    elif [ "$error_level" = '--error' ]; then
      echo "Error: Not currently in a Git repository" >&2
      exit 1
    fi
  fi

  local result=
  if result=$(git rev-parse --show-toplevel 2>/dev/null); then
    cd "$result" || exit
  fi
}

global_ignore() {
  if ! git config --global core.excludesFile 2>/dev/null; then
    if [ -f "$HOME/.gitignore" ]; then
      echo "$HOME/.gitignore"
    else
      echo "${XDG_CONFIG_HOME:-$HOME/.config}/git/ignore"
    fi
  fi
}

show_global() {
  show_contents Global "$(global_ignore)"
}

add_global() {
  local global_gitignore
  global_gitignore="$(global_ignore)"
  if [ -z "$global_gitignore" ]; then
    echo "Can't find global .gitignore."
    echo ""
    echo "Use 'git config --global --add core.excludesfile ~/.gitignore-global' to set the path to your global gitignore file to '~/.gitignore-global'."
    echo ""
  else
    add_patterns "$global_gitignore" "$@"
  fi
}

show_local() {
  cd_to_git_root --warn
  show_contents Local .gitignore
}

add_local() {
  cd_to_git_root --warn
  add_patterns .gitignore "$@"
}

show_private() {
  cd_to_git_root --error
  show_contents Private "${GIT_DIR}/info/exclude"
}

add_private() {
  cd_to_git_root --error
  test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info"
  add_patterns "${GIT_DIR}/info/exclude" "$@"
}

add_patterns() {
  echo "Adding pattern(s) to: $1"
  local file="${1/#~/$HOME}"
  dir_name=$(dirname "$file")
  if [ ! -d "$dir_name" ]; then
      mkdir -p "$dir_name"
  fi
  if [ -s "$file" ]; then
      # If the content of $file doesn't end with a newline, add one
      test "$(tail -c 1 "$file")" != "" && echo "" >> "$file"
  fi
  for pattern in "${@:2}"; do
    echo "... adding '$pattern'"
    (test -f "$file" && test "$pattern" && grep -q -F -x -- "$pattern" "$file") || echo "$pattern" >> "$file"
  done
}

# TODO: create a file if does not exist??
edit_file() {
  local file="${2/#~/$HOME}"
  if [ -f "$file" ]; then
    echo "Editing $1 gitignore: ($2)" && eval "$(git var GIT_EDITOR) $file"
    echo "Done."
  else
    echo "There is no $1 .gitignore yet." >&2
    exit 1
  fi
}

_usage() {
  # TODO
  echo "Under construction."
}

remove_patterns() {
  declare -a args
  local tmpfile
  args=( "${@:3}" )
  tmpfile="$(mktemp -q "${TMPDIR:-/tmp}"/git-extras-ignore.XXXXXX 2>/dev/null)"
  local file="${2/#~/$HOME}"
  if [ -f "$file" ]; then
    echo "Removing patterns from $1 gitignore: $2"
    awk -v outfile="$tmpfile" "${AWK_SCRIPT}"\
       <(printf '%s\n' "${args[@]}") "$file"
    cat "$tmpfile" > "$file"
    rm "$tmpfile"
  else
    echo "There is no $1 .gitignore yet"
    exit 1
  fi
}


# can have only 1 action and 1 level, if conflicting flags are passed
# latter ones overwrite former
# sanity of args not tested (e.g. there should be no args if -e is supplied)
if test $# -eq 0; then
   show_global
   echo "---------------------------------"
   show_local
   echo "---------------------------------"
   show_private
else
  # parse flags and args
  level="none"
  action="none"
  declare -a args
  while [ $# -gt 0 ]; do
    case "$1" in
      -h|--help)
        _usage
        exit 0
        ;;
      -l|--local)
        level="local"
        ;;
      -g|--global)
        level="global"
        ;;
      -p|--private)
        level="private"
        ;;
      -e|--edit)
        action="edit"
        ;;
      -r|--remove)
        action="remove"
        ;;
      --)
        shift
        args+=("$@")
        break
        ;;
      *)
        args+=("$1")
        ;;
    esac
    shift
  done
  # compatible with previous version
  if [ "$action" = "none" ]; then
    case "$level" in
      local)
        test ${#args[@]} -ne 0 && add_local "${args[@]}" && echo
        show_local
        ;;
      global)
        test ${#args[@]} -ne 0 && add_global "${args[@]}" && echo
        show_global
        ;;
      private)
        test ${#args[@]} -ne 0 && add_private "${args[@]}" && echo
        show_private
        ;;
      none)
        add_local "${args[@]}"
        # maybe show_local here too?
        ;;
    esac
    exit 0
  # open file in editor
  elif [ "$action" = "edit" ]; then
    case "$level" in
      local|none)
        cd_to_git_root --warn
        edit_file 'local' .gitignore && echo
        ;;
      global)
        global_gitignore="$(global_ignore)"
        edit_file global "$global_gitignore" && echo
        ;;
      private)
        cd_to_git_root --error
        test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info"
        edit_file private "${GIT_DIR}/info/exclude" && echo
        ;;
    esac
    exit 0
  # remove entries
  else
    if [ ${#args[@]} -eq 0 ]; then
      echo "Nothing to remove."
      exit 0
    fi
    case "$level" in
      local|none)
        cd_to_git_root --warn
        remove_patterns 'local' .gitignore "${args[@]}" && echo
        ;;
      global)
        global_gitignore="$(global_ignore)"
        remove_patterns global "$global_gitignore" "${args[@]}" && echo
        ;;
      private)
        cd_to_git_root --error
        remove_patterns private "${GIT_DIR}/info/exclude" "${args[@]}" && echo
        ;;
    esac
    exit 0

  fi
fi
