#!/usr/bin/env bash
#
# Requires: Git, NPM and RubyGems

set -eu

opt_pre=false # option for bump gem version
opt_pkg=false # option for building gem package

MAIN_BRANCH="master"
RELEASE_BRANCH="production"

GEM_SPEC="jekyll-theme-chirpy.gemspec"
NODE_SPEC="package.json"
CHANGELOG="docs/CHANGELOG.md"
CONFIG="_config.yml"

JS_DIST="assets/js/dist"

FILES=(
  "$GEM_SPEC"
  "$NODE_SPEC"
  "$CHANGELOG"
  "$CONFIG"
)

TOOLS=(
  "git"
  "npm"
  "gem"
)

help() {
  echo -e "A tool to release new version Chirpy gem.\nThis tool will:"
  echo "  1. Build a new gem and publish it to RubyGems.org"
  echo "  2. Merge the release branch into the default branch"
  echo
  echo "Usage:"
  echo "  bash ./tools/release [options]"
  echo
  echo "Options:"
  echo "  --prepare           Preparation for release"
  echo "  -p, --package       Build a gem package only, for local packaging in case of auto-publishing failure"
  echo "  -h, --help          Display this help message"
}

_check_cli() {
  for i in "${!TOOLS[@]}"; do
    cli="${TOOLS[$i]}"
    if ! command -v "$cli" &>/dev/null; then
      echo "> Command '$cli' not found!"
      exit 1
    fi
  done
}

_check_git() {
  # ensure that changes have been committed
  if [[ -n $(git status . -s) ]]; then
    echo "> Abort: Commit the staged files first, and then run this tool again."
    exit 1
  fi

  $opt_pkg || (
    if [[ "$(git branch --show-current)" != "$RELEASE_BRANCH" ]]; then
      echo "> Abort: Please run the tool in the '$RELEASE_BRANCH' branch."
      exit 1
    fi
  )
}

_check_src() {
  for i in "${!FILES[@]}"; do
    _src="${FILES[$i]}"
    if [[ ! -f $_src && ! -d $_src ]]; then
      echo -e "> Error: Missing file \"$_src\"!\n"
      exit 1
    fi
  done
}

_check_node_packages() {
  if [[ ! -d node_modules || "$(du node_modules | awk '{print $1}')" == "0" ]]; then
    npm i
  fi
}

check() {
  _check_cli
  _check_git
  _check_src
  _check_node_packages
}

## Bump new version to gem-spec file
_bump_version() {
  _version="$(grep '"version":' "$NODE_SPEC" | sed 's/.*: "//;s/".*//')"
  sed -i "s/[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/$_version/" "$GEM_SPEC"
  echo "> Bump gem version to $_version"
}

_improve_changelog() {
  # Replace multiple empty lines with a single empty line
  sed -i '/^$/N;/^\n$/D' "$CHANGELOG"
  # Escape left angle brackets of HTML tag in the changelog as they break the markdown structure. e.g., '<hr>'
  sed -i -E 's/\s(<[a-z])/ \\\1/g' "$CHANGELOG"
}

prepare() {
  _bump_version
  _improve_changelog
}

## Build a Gem package
build_gem() {
  if $opt_pkg; then
    BACKUP_PATH="$(mktemp -d)"
    cp "$JS_DIST"/* "$BACKUP_PATH"
  fi

  # Remove unnecessary theme settings
  sed -i "s/^cdn:.*/cdn:/;s/^avatar:.*/avatar:/" $CONFIG
  rm -f ./*.gem

  npm run build
  git add "$JS_DIST" -f # add JS distribution files to gem
  gem build "$GEM_SPEC"

  # resume the settings
  git reset
  git checkout .

  if $opt_pkg; then
    # restore the dist files for future development
    mkdir -p "$JS_DIST" && cp "$BACKUP_PATH"/* "$JS_DIST"
    rm -rf "$BACKUP_PATH"
  fi
}

# Push the gem to RubyGems.org (using $GEM_HOST_API_KEY)
push_gem() {
  gem push ./*.gem
}

## Merge the release branch into the default branch
merge() {
  git fetch origin "$MAIN_BRANCH"
  git checkout -b "$MAIN_BRANCH" origin/"$MAIN_BRANCH"

  git merge --no-ff --no-edit "$RELEASE_BRANCH" || (
    git merge --abort
    echo -e "\n> Conflict detected. Aborting merge.\n"
    exit 0
  )

  git push origin "$MAIN_BRANCH"
}

main() {
  check

  if $opt_pre; then
    prepare
    exit 0
  fi

  build_gem
  $opt_pkg && exit 0
  push_gem
  merge
}

while (($#)); do
  opt="$1"
  case $opt in
  --prepare)
    opt_pre=true
    shift
    ;;
  -p | --package)
    opt_pkg=true
    shift
    ;;
  -h | --help)
    help
    exit 0
    ;;
  *)
    # unknown option
    help
    exit 1
    ;;
  esac
done

main
