#!/usr/bin/env bash

#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
DRY_RUN=${DRY_RUN:-0}
GPG="gpg --pinentry-mode loopback --no-tty --batch"
YETUS_VERSION=0.11.0

function error {
  echo "$*"
  exit 1
}

function read_config {
  local PROMPT="$1"
  local DEFAULT="$2"
  local REPLY=

  read -p "$PROMPT [$DEFAULT]: " REPLY
  local RETVAL="${REPLY:-$DEFAULT}"
  if [ -z "$RETVAL" ]; then
    error "$PROMPT is must be provided."
  fi
  echo "$RETVAL"
}

function parse_version {
  grep -e '<version>.*</version>' | \
    head -n 2 | tail -n 1 | cut -d'>' -f2 | cut -d '<' -f1
}

function run_silent {
  local BANNER="$1"
  local LOG_FILE="$2"
  shift 2

  echo "========================"
  echo "= $BANNER"
  echo "Command: $*"
  echo "Log file: $LOG_FILE"

  "$@" 1>"$LOG_FILE" 2>&1

  local EC=$?
  if [ $EC != 0 ]; then
    echo "Command FAILED. Check full logs for details."
    tail "$LOG_FILE"
    exit $EC
  fi
}

function fcreate_secure {
  local FPATH="$1"
  rm -f "$FPATH"
  touch "$FPATH"
  chmod 600 "$FPATH"
}

function check_for_tag {
  curl -s --head --fail "$ASF_GITHUB_REPO/releases/tag/$1" > /dev/null
}

# API compare version.
function get_api_diff_version {
  local version=$1
  local rev=$(echo "$version" | cut -d . -f 3)
  local api_diff_tag
  if [ $rev != 0 ]; then
    local short_version=$(echo "$version" | cut -d . -f 1-2)
    api_diff_tag="rel/${short_version}.$((rev - 1))"
  else
    local major=$(echo "$version" | cut -d . -f 1)
    local minor=$(echo "$version" | cut -d . -f 2)
    if [ $minor != 0 ]; then
      api_diff_tag="rel/${major}.$((minor - 1)).0)"
    else
      api_diff_tag="rel/$((major - 1)).0.0)"
    fi
  fi
  api_diff_tag=$(read_config "api_diff_tag", "$api_diff_tag")
  echo $api_diff_tag
}

# Get all branches that begin with 'branch-', the hbase convention for
# release branches, sort them and then pop off the most recent.
function get_release_info {
  if [[ -z "${ASF_REPO}" ]]; then
    ASF_REPO="https://gitbox.apache.org/repos/asf/${PROJECT}.git"
  fi
  if [[ -z "${ASF_REPO_WEBUI}" ]]; then
    ASF_REPO_WEBUI="https://gitbox.apache.org/repos/asf?p=${PROJECT}.git"
  fi
  if [[ -z "${ASF_GITHUB_REPO}" ]]; then
    ASF_GITHUB_REPO="https://github.com/apache/${PROJECT}"
  fi
  if [ -z "$GIT_BRANCH" ]; then
    # If no branch is specified, find out the latest branch from the repo.
    GIT_BRANCH=$(git ls-remote --heads "$ASF_REPO" |
      grep refs/heads/branch- |
      awk '{print $2}' |
      sort -r |
      head -n 1 |
      cut -d/ -f3)
  fi

  export GIT_BRANCH=$(read_config "GIT_BRANCH" "$GIT_BRANCH")

  # Find the current version for the branch.
  local VERSION=$(curl -s "$ASF_REPO_WEBUI;a=blob_plain;f=pom.xml;hb=refs/heads/$GIT_BRANCH" |
    parse_version)
  echo "Current branch VERSION is $VERSION."

  if [[ ! $VERSION =~ .*-SNAPSHOT ]]; then
    error "Not a SNAPSHOT version: $VERSION"
  fi

  NEXT_VERSION="$VERSION"
  RELEASE_VERSION="${VERSION/-SNAPSHOT/}"
  SHORT_VERSION=$(echo "$VERSION" | cut -d . -f 1-2)
  local REV=$(echo "$VERSION" | cut -d . -f 3)

  # Find out what RC is being prepared.
  # - If the current version is "x.y.0", then this is RC0 of the "x.y.0" release.
  # - If not, need to check whether the previous version has been already released or not.
  #   - If it has, then we're building RC0 of the current version.
  #   - If it has not, we're building the next RC of the previous version.
  local RC_COUNT
  if [ $REV != 0 ]; then
    local PREV_REL_REV=$((REV - 1))
    PREV_REL_TAG="rel/${SHORT_VERSION}.${PREV_REL_REV}"
    if check_for_tag "$PREV_REL_TAG"; then
      RC_COUNT=0
      REV=$((REV + 1))
      NEXT_VERSION="${SHORT_VERSION}.${REV}-SNAPSHOT"
    else
      RELEASE_VERSION="${SHORT_VERSION}.${PREV_REL_REV}"
      RC_COUNT=$(git ls-remote --tags "$ASF_REPO" "${RELEASE_VERSION}RC*" | wc -l)
      # This makes a 'number' of it.
      RC_COUNT=$((RC_COUNT))
    fi
  else
    REV=$((REV + 1))
    NEXT_VERSION="${SHORT_VERSION}.${REV}-SNAPSHOT"
    RC_COUNT=0
  fi

  export NEXT_VERSION
  export RELEASE_VERSION=$(read_config "RELEASE_VERSION" "$RELEASE_VERSION")


  RC_COUNT=$(read_config "RC_COUNT" "$RC_COUNT")

  # Check if the RC already exists, and if re-creating the RC, skip tag creation.
  RELEASE_TAG="${RELEASE_VERSION}RC${RC_COUNT}"
  SKIP_TAG=0
  if check_for_tag "$RELEASE_TAG"; then
    read -p "$RELEASE_TAG already exists. Continue anyway [y/n]? " ANSWER
    if [ "$ANSWER" != "y" ]; then
      error "Exiting."
    fi
    SKIP_TAG=1
  fi

  export RELEASE_TAG

  GIT_REF="$RELEASE_TAG"
  if is_dry_run; then
    echo "This is a dry run. Please confirm the ref that will be built for testing."
    GIT_REF=$(read_config "GIT_REF" "$GIT_REF")
  fi
  export GIT_REF
  export PACKAGE_VERSION="$RELEASE_TAG"

  export API_DIFF_TAG=$(get_api_diff_version $RELEASE_VERSION)

  # Gather some user information.
  export ASF_USERNAME=$(read_config "ASF_USERNAME" "$LOGNAME")

  GIT_NAME=$(git config user.name || echo "")
  export GIT_NAME=$(read_config "GIT_NAME" "$GIT_NAME")

  export GIT_EMAIL="$ASF_USERNAME@apache.org"
  export GPG_KEY=$(read_config "GPG_KEY" "$GIT_EMAIL")

  cat <<EOF
================
Release details:
GIT_BRANCH:      $GIT_BRANCH
RELEASE_VERSION: $RELEASE_VERSION
RELEASE_TAG:     $RELEASE_TAG
NEXT_VERSION:    $NEXT_VERSION
API_DIFF_TAG:    $API_DIFF_TAG
ASF_USERNAME:    $ASF_USERNAME
GPG_KEY:         $GPG_KEY
GIT_NAME:        $GIT_NAME
GIT-EMAIL:       $GIT_EMAIL
================
EOF

  read -p "Is this info correct [y/n]? " ANSWER
  if [ "$ANSWER" != "y" ]; then
    echo "Exiting."
    exit 1
  fi

  if ! is_dry_run; then
    if [ -z "$ASF_PASSWORD" ]; then
      stty -echo && printf "ASF_PASSWORD: " && read ASF_PASSWORD && printf '\n' && stty echo
    fi
  else
    ASF_PASSWORD="***INVALID***"
  fi

  if [ -z "$GPG_PASSPHRASE" ]; then
    stty -echo && printf "GPG_PASSPHRASE: " && read GPG_PASSPHRASE && printf '\n' && stty echo
    export GPG_TTY=$(tty)
  fi

  export ASF_PASSWORD
  export GPG_PASSPHRASE
}

function is_dry_run {
  [[ $DRY_RUN = 1 ]]
}

# Initializes JAVA_VERSION to the version of the JVM in use.
function init_java {
  if [ -z "$JAVA_HOME" ]; then
    error "JAVA_HOME is not set."
  fi
  JAVA_VERSION=$("${JAVA_HOME}"/bin/javac -version 2>&1 | cut -d " " -f 2)
  echo "java version: $JAVA_VERSION"
  export JAVA_VERSION
}

function init_python {
  if ! [ -x "$(command -v python2)"  ]; then
    echo 'Error: python2 needed by yetus. Install or add link? E.g: sudo ln -sf /usr/bin/python2.7 /usr/local/bin/python2' >&2
    exit 1
  fi
  echo "python version: `python2 --version`"
}

# Set MVN
function init_mvn {
  if [ -n "$MAVEN_HOME" ]; then
      MVN=${MAVEN_HOME}/bin/mvn
  elif [ $(type -P mvn) ]; then
      MVN=mvn
  else
    error "MAVEN_HOME is not set nor is mvn on the current path."
  fi
  echo "mvn version: `$MVN --version`"
  # Add timestamped logging.
  MVN="${MVN} -B"
  export MVN
}

# Writes report into cwd!
function generate_api_report {
  local project=$1
  local previous_tag="$2"
  local release_tag="$3"
  # Generate api report.
  ${project}/dev-support/checkcompatibility.py --annotation \
    org.apache.yetus.audience.InterfaceAudience.Public  \
    $previous_tag $release_tag
  local previous_version=$(echo ${previous_tag} | sed -e 's/rel\///')
  cp ${project}/target/compat-check/report.html "./api_compare_${previous_version}_to_${release_tag}.html"
}

# Update the CHANGES.md
# DOES NOT DO COMMITS! Caller should do that.
# yetus requires python2 to be on the path.
function update_releasenotes {
  local project="$1"
  local release_version="$2"
  local yetus="apache-yetus-${YETUS_VERSION}"
  wget -qO- "https://www.apache.org/dyn/mirrors/mirrors.cgi?action=download&filename=/yetus/${YETUS_VERSION}/${yetus}-bin.tar.gz" | \
    tar xvz -C .
  cd ./${yetus}
  # All repositories share the same HBASE project name
  ./bin/releasedocmaker -p HBASE --fileversions -v ${release_version} -l --sortorder=newer --skip-credits
  # First clear out the changes written by previous RCs.
  sed -i -e "/^## Release ${release_version}/,/^## Release/ {//!d; /^## Release ${release_version}/d;}" \
    ${project}/CHANGES.md || true
  # 2 spaces are required between HBASE and ${release_version} strings
  sed -i -e "/^# HBASE  ${release_version} Release Notes/,/^# HBASE/{//!d; /^# HBASE  ${release_version} Release Notes/d;}" \
    ${project}/RELEASENOTES.md || true

  # The above generates RELEASENOTES.X.X.X.md and CHANGELOG.X.X.X.md.
  # To insert into project CHANGES.me...need to cut the top off the
  # CHANGELOG.X.X.X.md file removing license and first line and then
  # insert it after the license comment closing where we have a
  # DO NOT REMOVE marker text!
  sed -i -e '/## Release/,$!d' CHANGELOG.${release_version}.md
  sed -i -e "/DO NOT REMOVE/r CHANGELOG.${release_version}.md" ${project}/CHANGES.md
  # Similar for RELEASENOTES but slightly different.
  sed -i -e '/Release Notes/,$!d' RELEASENOTES.${release_version}.md
  sed -i -e "/DO NOT REMOVE/r RELEASENOTES.${release_version}.md" ${project}/RELEASENOTES.md
  cd ..
}

# Make src release.
# Takes as arguments first the project name -- e.g. hbase or hbase-operator-tools
# -- and then the version string. Expects to find checkout adjacent to this script
# named for 'project', the first arg passed.
# Expects the following three defines in the environment:
# - GPG needs to be defined, with the path to GPG: defaults 'gpg'.
# - The passphrase in the GPG_PASSPHRASE variable: no default (we don't make .asc file).
# - GIT_REF which is the tag to create the tgz from: defaults to 'master'.
# For example:
# $ GPG_PASSPHRASE="XYZ" GIT_REF="master" make_src_release hbase-operator-tools 1.0.0
make_src_release() {
  # Tar up the src and sign and hash it.
  project="${1}"
  version="${2}"
  basename="${project}-${version}"
  rm -rf "${basename}-src*"
  cd "${project}"
  git clean -d -f -x
  tgz="${basename}-src.tar.gz"
  git archive --format=tar.gz --output="../${tgz}" --prefix="${basename}/" "${GIT_REF:-master}"
  cd ..
  echo $GPG_PASSPHRASE | $GPG --passphrase-fd 0 --armour --output "${tgz}.asc" \
    --detach-sig "${tgz}"
  echo $GPG_PASSPHRASE | $GPG --passphrase-fd 0 --print-md SHA512 "${tgz}" > "${tgz}.sha512"
}

# Make binary release.
# Takes as arguments first the project name -- e.g. hbase or hbase-operator-tools
# -- and then the version string. Expects to find checkout adjacent to this script
# named for 'project', the first arg passed.
# Expects the following three defines in the environment:
# - GPG needs to be defined, with the path to GPG: defaults 'gpg'.
# - The passphrase in the GPG_PASSPHRASE variable: no default (we don't make .asc file).
# - GIT_REF which is the tag to create the tgz from: defaults to 'master'.
# - MVN Default is 'mvn'.
# For example:
# $ GPG_PASSPHRASE="XYZ" GIT_REF="master" make_src_release hbase-operator-tools 1.0.0
make_binary_release() {
  project="${1}"
  version="${2}"
  basename="${project}-${version}"
  rm -rf "${basename}-bin*"
  cd $project

  # Get maven home set by MVN
  MVN_HOME=`$MVN -version 2>&1 | grep 'Maven home' | awk '{print $NF}'`

  git clean -d -f -x
  # Three invocations of maven. This seems to work. One to
  # populate the repo, another to build the site, and then
  # a third to assemble the binary artifact. Trying to do
  # all in the one invocation fails; a problem in our
  # assembly spec to in maven. TODO. Meantime, three invocations.
  MAVEN_OPTS="${MAVEN_OPTS}" ${MVN} --settings $tmp_settings clean install -DskipTests \
    -Dmaven.repo.local=${tmp_repo}
  MAVEN_OPTS="${MAVEN_OPTS}" ${MVN} --settings $tmp_settings site -DskipTests \
    -Dmaven.repo.local=${tmp_repo}
  MAVEN_OPTS="${MAVEN_OPTS}" ${MVN} --settings $tmp_settings install assembly:single -DskipTests \
    -Dcheckstyle.skip=true ${PUBLISH_PROFILES} -Dmaven.repo.local=${tmp_repo}

  cp ./${PROJECT}-assembly/target/${basename}*-bin.tar.gz ..
  cd ..
  for i in ${basename}*-bin.tar.gz; do
    echo $GPG_PASSPHRASE | $GPG --passphrase-fd 0 --armour --output $i.asc --detach-sig $i
    echo $GPG_PASSPHRASE | $GPG --passphrase-fd 0 --print-md SHA512 $i > $i.sha512
  done
}
