#!/bin/bash

: ${RCM_LIB:=$(dirname "$0")/../share/rcm}
. "$RCM_LIB/rcm.sh"

print_ln_v() {
  $PRINT "handle_file_ln \"$1\" \"$2\""
}

print_cp_v() {
  $PRINT "handle_file_cp \"$1\" \"$2\""
}

print_rm_v() {
  :
}

link_or_copy() {
  $DEBUG "link_or_copy $1"
  local sigil="$1"

  if [ "x$sigil" = "xX" ]; then
    echo "$CP"
  else
    echo "$LN"
  fi
}

print_generated_preface() {
  cat <<PREFACE
#!/bin/sh
#
# Usage:
#
#    sh install.sh
#
# Environment variables: VERBOSE, CP, LN, MKDIR, RM, DIRNAME.
#
#    env VERBOSE=1 sh install.sh
#
# DO NOT EDIT THIS FILE
# 
# This file is generated by rcm(7) as:
#
#   rcup $@
#
# To update it, re-run the above command.
#
: \${VERBOSE:=0}
: \${CP:=/bin/cp}
: \${LN:=/bin/ln}
: \${MKDIR:=/bin/mkdir}
: \${RM:=/bin/rm}
: \${DIRNAME:=/usr/bin/dirname}
verbose() {
  if [ "\$VERBOSE" -gt 0 ]; then
    echo "\$@"
  fi
}
handle_file_cp() {
  if [ -e "\$2" ]; then
    printf "%s " "overwrite \$2? [yN]"
    read overwrite
    case "\$overwrite" in
      y)
        \$RM -rf "\$2"
        ;;
      *)
        echo "skipping \$2"
        return
        ;;
    esac
  fi
  verbose "'\$1' -> '\$2'"
  \$MKDIR -p "\$(\$DIRNAME "\$2")"
  \$CP -R "\$1" "\$2"
}
handle_file_ln() {
  if [ -e "\$2" ]; then
    printf "%s " "overwrite \$2? [yN]"
    read overwrite
    case "\$overwrite" in
      y)
        \$RM -rf "\$2"
        ;;
      *)
        echo "skipping \$2"
        return
        ;;
    esac
  fi
  verbose "'\$1' -> '\$2'"
  \$MKDIR -p "\$(\$DIRNAME "\$2")"
  \$LN -sf "\$1" "\$2"
}
PREFACE
}

link_file() {
  local src="$1"
  local dest="$2"
  local sigil="$3"

  if [ -h "$dest" ]; then
    $RM -f "$dest"
  fi

  action="$(link_or_copy "$sigil")"
  $DEBUG "$action $src $dest"
  $action "$src" "$dest"
}

replace_file() {
  local src="$1"
  local dest="$2"
  local sigil="$3"

  $DEBUG replace_file "$1" "$2" $3

  $RM -rf "$dest"
  link_file "$src" "$dest" "$sigil"
}

is_identical() {
  diff -c "$1" "$2" > /dev/null 2>&1
}

is_linked() {
  local src="$1"
  local dest="$2"

  if [ -h "$dest" ]; then
    local link_dest=$(readlink $dest)
    [ "$link_dest" = "$src" ]
  else
    return 1
  fi  
}

handle_dir() {
  local dest="$1"

  $DEBUG "handle_dir $dest"

  $MKDIR -p "$(dirname "$dest")"
}

handle_file() {
  local generate="$1"
  local src="$2"
  local dest="$3"
  local sigil="$4"

  $DEBUG "handle_file $1 $2 $3 $4"

  if [ "$generate" -ne 1 -a -e "$dest" ]; then
    if is_identical "$src" "$dest"; then
      if [ "x$sigil" != "xX" ] && ! is_linked "$src" "$dest"; then
        $VERBOSE "replacing identical but unlinked $dest"
        replace_file "$src" "$dest" "$sigil"
      else
        $VERBOSE "identical $dest"
      fi
    elif [ $REPLACE_ALL -eq 1 ]; then
      replace_file "$src" "$dest" "$sigil"
    else
      $PROMPT "overwrite ${dest}? [ynaq]"
      read overwrite
      case "$overwrite" in
        a) REPLACE_ALL=1
           replace_file "$src" "$dest" "$sigil"
           ;;
        y) replace_file "$src" "$dest" "$sigil" ;;
        q) exit 1 ;;
        *) $VERBOSE "skipping $dest" ;;
      esac
    fi
  else
    link_file "$src" "$dest" "$sigil"
  fi
}

show_help() {
  local exit_code=${1:-0}

  $PRINT "Usage: rcup [-CfhiKkqVv] [-B HOSTNAME] [-d DOT_DIR] [-I EXCL_PAT] [-S EXCL_PAT] [-s EXCL_PAT] [-t TAG] [-U EXCL_PAT] [-u EXCL_PAT] [-x EXCL_PAT]"
  $PRINT "see rcup(1) and rcm(7) for more details"

  exit $exit_code
}

handle_command_line() {
  local arg_tags=
  local verbosity=0
  local version=0
  local run_hooks=1
  local dotfiles_dirs=
  local files=
  local excludes=
  local includes=
  local always_copy=0
  local symlink_dirs=
  local never_symlink_dirs=
  local hostname=
  local generate=0
  local args="$*"
  local undotted=
  local never_undotted=
  REPLACE_ALL=0
  GENERATE=

  while getopts :CVqvfghikKI:x:S:s:U:u:t:d:B: opt; do
    case "$opt" in
      B) hostname="$OPTARG" ;;
      C) always_copy=1 ;;
      d) dotfiles_dirs="$(append_variable "$dotfiles_dirs" "$OPTARG")" ;;
      f) REPLACE_ALL=1 ;;
      g) generate=1 ;;
      h) show_help ;;
      i) REPLACE_ALL=0 ;;
      I) includes="$(append_variable "$includes" "$OPTARG")" ;;
      k) run_hooks=1 ;;
      K) run_hooks=0 ;;
      q) verbosity=$(($verbosity - 1)) ;;
      t) arg_tags="$(append_variable "$arg_tags" "$OPTARG")" ;;
      S) symlink_dirs="$(append_variable "$symlink_dirs" "$OPTARG")" ;;
      s) never_symlink_dirs="$(append_variable "$never_symlink_dirs" "$OPTARG")";;
      U) undotted="$(append_variable "$undotted" "$OPTARG")" ;;
      u) never_undotted="$(append_variable "$never_undotted" "$OPTARG")" ;;
      v) verbosity=$(($verbosity + 1)) ;;
      V) version=1 ;;
      x) excludes="$(append_variable "$excludes" "$OPTARG")" ;;
      ?) show_help 64 ;;
    esac
  done
  shift $(($OPTIND-1))

  LN="ln_v"
  CP="cp_v"
  RM="rm_v"
  if [ $always_copy -eq 1 ]; then
    LN="$CP"
  fi
  if [ "$generate" -eq 1 ]; then
    LN="print_ln_v"
    CP="print_cp_v"
    RM="print_rm_v"
    print_generated_preface $args
  fi
  GENERATE="$generate"

  handle_common_flags rcup $version $verbosity
  hostname="$(determine_hostname "$hostname")"

  tags="${arg_tags:-$TAGS}"
  DOTFILES_DIRS="${dotfiles_dirs:-$DOTFILES_DIRS}"
  RUN_HOOKS=$run_hooks
  files=
  for file; do
    files="$files $(encode "$file")"
  done

  for tag in $tags; do
    LS_ARGS="$LS_ARGS -t \"$tag\""
  done
  for dotfiles_dir in "$DOTFILES_DIRS"; do
    LS_ARGS="$LS_ARGS -d \"$dotfiles_dir\""
  done
  for exclude in "$excludes"; do
    LS_ARGS="$LS_ARGS -x \"$exclude\""
  done
  for include in "$includes"; do
    LS_ARGS="$LS_ARGS -I \"$include\""
  done
  for symlink_dir in "$symlink_dirs"; do
    LS_ARGS="$LS_ARGS -S \"$symlink_dir\""
  done
  for never_symlink_dir in "$never_symlink_dirs"; do
    LS_ARGS="$LS_ARGS -s \"$never_symlink_dir\""
  done
  for undot in "$undotted"; do
    LS_ARGS="$LS_ARGS -U \"$undot\""
  done
  for never_undot in "$never_undotted"; do
    LS_ARGS="$LS_ARGS -u \"$never_undot\""
  done

  LS_ARGS="$LS_ARGS -B \"$hostname\" $files"

  $DEBUG "LS_ARGS: $LS_ARGS"
}

LS_ARGS=-F

handle_command_line "$@"
: ${DOTFILES_DIRS:=$DOTFILES_DIRS $DEFAULT_DOTFILES_DIR}

run_hooks pre up
pre_up_ret=$?

if [ "$pre_up_ret" -ne 0 ]; then
  exit "$pre_up_ret"
fi

dests_and_srcs="$(eval "lsrc $LS_ARGS")"

saved_ifs="$IFS"
IFS='
'
for dest_and_src in $dests_and_srcs; do
  IFS=:
  set -- $dest_and_src
  IFS="$saved_ifs"
  dest="$1"
  src="$2"
  sigil="$3"

  if is_nested "$dest"; then
    handle_dir "$dest"
  fi

  handle_file "$GENERATE" "$src" "$dest" "$sigil"
done

IFS="$saved_ifs"
run_hooks post up
