#!/bin/sh

set -o errexit
PATTERN="$1"
set -o nounset

# @params [string] Error message
vendorpull_fail() {
  echo "ERROR: $1" 1>&2
  exit 1
}

# @params [string] File path
# @params [string] Error message
vendorpull_assert_defined() {
  if [ -z "$1" ]
  then
    vendorpull_fail "$2"
  fi
}

# @params [string] Command
vendorpull_assert_command() {
  if ! command -v "$1" > /dev/null
  then
    vendorpull_fail "You must install $1 in order to use this tool"
  fi
}

# @params [string] File path
vendorpull_assert_file() {
  if [ ! -f "$1" ]
  then
    vendorpull_fail "No such file: $1"
  fi
}
# Clone a git repository
# @params [string] Git URL
# @params [string] Clone location
# @params [string] Revision
vendorpull_clone_git() {
  git clone --recurse-submodules --jobs 8 "$1" "$2"
  if [ "$3" != "HEAD" ]
  then
    git -C "$2" reset --hard "$3"
  fi
}

# Un-git the repository and its dependencies (if any)
# @params [string] Repository directory
vendorpull_clean_git() {
  GIT_FILES=".git .gitignore .github .gitmodules"
  git -C "$1" submodule foreach "rm -rf $GIT_FILES"
  for file in $GIT_FILES
  do
    rm -rf "$1/${file:?}"
  done
}

# @params [string] Repository directory
# @params [string] Patch file
vendorpull_patch_git() {
  git -C "$1" apply --3way "$2"
}
# Mask a directory with a set of patterns
# @params [string] Input directory
# @params [string] Mask file
vendorpull_mask_directory() {
  if [ -f "$2" ]
  then
    while read -r pattern
    do
      echo "Applying mask on $1: $pattern" 1>&2
      rm -vrf "${1:?}/${pattern:?}"
    done < "$2"
  fi
}
# Apply a set of patches to a base directory
# @params [string] Base directory
# @params [string] Patches directory
vendorpull_patch() {
  if [ -d "$2" ]
  then
    for patch in "$2"/*.patch
    do
      echo "Applying patch $patch..."
      vendorpull_patch_git "$1" "$patch"
    done
  fi
}
TEMPORARY_DIRECTORY="$(mktemp -d -t vendorpull-clone-XXXXX)"
echo "Setting up temporary directory at $TEMPORARY_DIRECTORY..."
temporary_directory_clean() {
  rm -rf "$TEMPORARY_DIRECTORY"
}
trap temporary_directory_clean EXIT
# @params [string] Dependency definition
vendorpull_dependencies_name() {
  RESULT="$(echo "$1" | cut -d ' ' -f 1)"
  vendorpull_assert_defined "$RESULT" "Missing dependency name"
  echo "$RESULT"
}

# @params [string] Dependency definition
vendorpull_dependencies_repository() {
  RESULT="$(echo "$1" | cut -d ' ' -f 2)"
  vendorpull_assert_defined "$RESULT" "Missing dependency url"
  echo "$RESULT"
}

# @params [string] Dependency definition
vendorpull_dependencies_revision() {
  RESULT="$(echo "$1" | cut -d ' ' -f 3)"
  vendorpull_assert_defined "$RESULT" "Missing dependency revision"
  echo "$RESULT"
}

# @params [string] Path to DEPENDENCIES file
# @params [string] Pattern
vendorpull_dependencies_find() {
  if [ ! -f "$1" ]
  then
    echo ""
  else
    grep "^$2" < "$1" | head -n 1
  fi
}

# @params [string] Path to DEPENDENCIES file
# @params [string] Pattern
vendorpull_dependencies_safe_find() {
  DEFINITION="$(vendorpull_dependencies_find "$1" "$2")"
  vendorpull_assert_defined "$DEFINITION" "Could not find a dependency $2 in $1"
  echo "$DEFINITION"
}

# @params [string] Path to DEPENDENCIES file
# @params [string] Dependency name
vendorpull_dependencies_find_exact() {
  if [ ! -f "$1" ]
  then
    echo ""
  else
    grep "^$2 " < "$1" | head -n 1
  fi
}

# @params [string] Path to DEPENDENCIES file
# @params [string] Dependency name
# @params [string] Dependency url
# @params [string] Dependency revision
vendorpull_dependency_set() {
  DEPENDENCY="$(vendorpull_dependencies_find_exact "$1" "$2")"
  if [ -z "$DEPENDENCY" ]
  then
    echo "$2 $3 $4" >> "$1"
  else
    # Use a delimiter other than the slash
    # in case the dependency name contains one
    if [ "$(uname)" = "Darwin" ]
    then
      sed -i .bak "s|^$2 .*|$2 $3 $4|" "$1"
      rm "$1.bak"
    else
      sed -i "s|^$2 .*|$2 $3 $4|" "$1"
    fi
  fi
}

# @params [string] Base directory
# @params [string] Dependency definition
vendorpull_command_pull() {
  NAME="$(vendorpull_dependencies_name "$2")"
  REPOSITORY="$(vendorpull_dependencies_repository "$2")"
  REVISION="$(vendorpull_dependencies_revision "$2")"

  echo "Updating $NAME..."

  GIT_REPOSITORY_DIRECTORY="$TEMPORARY_DIRECTORY/$NAME"
  vendorpull_clone_git "$REPOSITORY" "$GIT_REPOSITORY_DIRECTORY" "$REVISION"
  vendorpull_patch "$GIT_REPOSITORY_DIRECTORY" "$1/patches/$NAME"
  vendorpull_clean_git "$GIT_REPOSITORY_DIRECTORY"
  vendorpull_mask_directory "$GIT_REPOSITORY_DIRECTORY" "$1/vendor/$NAME.mask"

  # Atomically move the new dependency into the vendor directory
  OUTPUT_DIRECTORY="$1/vendor/$NAME"
  rm -rf "$OUTPUT_DIRECTORY"
  mkdir -p "$(dirname "$OUTPUT_DIRECTORY")"
  mv "$TEMPORARY_DIRECTORY/$NAME" "$OUTPUT_DIRECTORY"
}

vendorpull_assert_command 'git'

# Get the root directory of the current git repository
BASE_DIRECTORY="$(git rev-parse --show-toplevel)"
DEPENDENCIES_FILE="$BASE_DIRECTORY/DEPENDENCIES"
vendorpull_assert_file "$DEPENDENCIES_FILE"

if [ -n "$PATTERN" ]
then
  DEFINITION="$(vendorpull_dependencies_safe_find "$DEPENDENCIES_FILE" "$PATTERN")"
  vendorpull_command_pull "$BASE_DIRECTORY" "$DEFINITION"
else
  echo "Reading DEPENDENCIES files..."
  while read -r dependency
  do
    vendorpull_command_pull "$BASE_DIRECTORY" "$dependency"
  done < "$DEPENDENCIES_FILE"
fi
