#!/bin/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.

set -e

function trap_err {
  echo
  echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
  echo "!!! An error occurred while creating the release candidate !!!"
  echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
  echo
  echo "Perform the following steps:"
  echo
  echo "- Drop the staged files (if they exist) at https://repository.apache.org"
  echo "- Type 'exit', fix the issues, and start over"
  echo
}

trap 'trap_err' ERR

function usage {
  BN=$(basename $0)
  cat <<USAGE >&2
usage: $BN [OPTION]...

Options:
  -n, --dry-run [alt]   Run all commands but do not actually publish anything.
                        Can optionally provide an alternative github repo and
                        branch of the form "user/repo@branch" instead of the
                        default "apache/daffodil@main"
  -h, --help            Display this help and exit
USAGE
}

function set_java_version {
  # There are a number of standard ways to change the java version. One is to
  # set JAVA_HOME and PATH, but its possible a build tool could execute
  # /usr/bin/java* directly and use the wrong version. Another option is to use
  # alternatives, but that needs slightly different commands for java 8 vs java
  # 17, so would make this function more complicated. Instead, we just manually
  # overwrite the java* bin symlinks. This is really only fine because this is
  # always expected to be run in a container.
  JAVA_BIN_ROOT="/usr/lib/jvm/java-$1/bin"
  ln -sf $JAVA_BIN_ROOT/java    /usr/bin/java
  ln -sf $JAVA_BIN_ROOT/javac   /usr/bin/javac
  ln -sf $JAVA_BIN_ROOT/javadoc /usr/bin/javadoc
  ln -sf $JAVA_BIN_ROOT/jar     /usr/bin/jar
}

# Function to get a non-empty string as input
# $1 is the prompt to use
# $2 to $N are passed directly to the read command
get_non_empty_input() {
    local prompt="$1" # prompt the user with this string
    shift
    local input

    while true; do
        # Prompt the user for input
        read -p "$prompt: " "$@" input

        # Check if the input is non-empty
        if [[ -n $input ]]; then
            echo "$input"
            return 0
        else
            echo "Empty input. Please try again." >&2
        fi
    done
}

DRY_RUN=false

echo "Which project to release?"
select PROJECT_REPO in daffodil daffodil-sbt daffodil-vscode
do
  case $PROJECT_REPO in
    "daffodil")
      PROJECT_DIST_DIR=""
      PROJECT_NAME="Daffodil"
      break
      ;;
    "daffodil-sbt")
      PROJECT_DIST_DIR="$PROJECT_REPO"
      PROJECT_NAME="Daffodil SBT Plugin"
      break
      ;;
    "daffodil-vscode")
      PROJECT_DIST_DIR="$PROJECT_REPO"
      PROJECT_NAME="Daffodil VS Code Extension"
      break
      ;;
    *)
      echo "unknown project: $REPLY" >&2
      exit 1
      ;;
    esac
done

DAFFODIL_CODE_USER="apache"
DAFFODIL_CODE_REPO="$PROJECT_REPO"
DAFFODIL_CODE_BRANCH="main"

DAFFODIL_SITE_REPO="daffodil-site"

DAFFODIL_DIST="daffodil-dist"

while [[ $# -gt 0 ]]
do
  option=$1
  case $option in
    -n|--dry-run)
      DRY_RUN=true
      if [[ (! -z $2) && ($2 != -*) ]]
      then
        DAFFODIL_CODE_USER=`echo $2 | cut -d/ -f1`
        DAFFODIL_CODE_REPO=`echo $2 | cut -d/ -f2 | cut -d@ -f1`
        DAFFODIL_CODE_BRANCH=`echo $2 | cut -d@ -f2`
        shift
      fi
      shift
    ;;
    -h|--help)
      usage
      exit 0
    ;;
    *)
      echo "unknown option: $option" >&2
      usage
      exit 1
    ;;
  esac
done

gpg-agent --daemon --default-cache-ttl 3000 --max-cache-ttl 3000
if [ $? -ne 0 ]; then
   echo -e "\n!!! Unable to change timeout of the gpg-agent !!!\n"
   echo -e "\n!!! Try updating it manually at ~/.gnupg/gpg-agent.conf for 3000 seconds each !!!\n"
   exit
fi

export GPG_TTY=$(tty)
export WIX=/opt/wix311/
export LANG=en_US.UTF-8
export CC=clang
export AR=llvm-ar

AVAILABLE_KEYS=()
SHORT_SEC_KEY_IDS=`gpg --list-secret-keys --with-colons | grep '^sec'  | cut -d: -f 5`
for SHORT_SEC_KEY_ID in $SHORT_SEC_KEY_IDS
do
    LONG_SEC_KEY_ID=`gpg --list-secret-keys --with-colons $SHORT_SEC_KEY_ID | grep -m 1 '^fpr' | cut -d: -f10`
    SEC_KEY_UID=`gpg --list-secret-keys --with-colons $LONG_SEC_KEY_ID | grep '^uid' | cut -d: -f10`
    AVAILABLE_KEYS+=("$LONG_SEC_KEY_ID [$SEC_KEY_UID]")
done

if [ ${#AVAILABLE_KEYS[@]} -eq 0 ]
then
    echo -e "\n!!! No secret keys found !!!\n"
    echo -e "\n!!! Ensure you have created a bind mount of your gpg directory, for example with -v ~/.gnupg/:/root/.gnupg/ !!!\n"
    exit
fi

echo
echo "Which code signing key:"
select PGP_KEY_SELECTION in "${AVAILABLE_KEYS[@]}"
do
    if [ -z "$PGP_KEY_SELECTION" ]
    then
        echo -e "\n!!! Bad key selection !!!\n"
        exit
    fi
    PGP_SIGNING_KEY_ID=$(echo $PGP_KEY_SELECTION | cut -d" " -f 1)
    break
done

echo

PRE_RELEASE=$(get_non_empty_input "Pre Release label (e.g., rc1 to rc99)")
GIT_COMMIT_NAME=$(get_non_empty_input "Git Commit Name (e.g., 'John Q. Smith')")
GIT_COMMIT_EMAIL=$(get_non_empty_input "Git Commit Email")
APACHE_USERNAME=$(get_non_empty_input "Apache Username")
APACHE_PASSWD=$(get_non_empty_input "Apache Password" -s)

echo
echo

echo "test" | gpg --default-key $PGP_SIGNING_KEY_ID --detach-sign --armor --output /dev/null
if [ $? -ne 0 ]; then
   echo -e "\n!!! Unable to sign data with given key: $PGP_SIGNING_KEY !!!\n"
   echo -e "\n!!! Ensure you have created a bind mount of your gpg directory, for example with -v ~/.gnupg/:/root/.gnupg/ !!!\n"
   exit
fi


TEST_GIT_DIR=/tmp/test-git-sign
mkdir -p $TEST_GIT_DIR
pushd $TEST_GIT_DIR &> /dev/null
git init -b main .
git commit --allow-empty --allow-empty-message -m "Test Commit"
git tag -as -u $PGP_SIGNING_KEY_ID -m "Test Signed Tag"  test-tag
if [ $? -ne 0 ]; then
   echo -e "\n!!! Unable to sign git tag with given key: $PGP_SIGNING_KEY !!!\n"
   echo -e "\n!!! Ensure you have created a bind mount of your gitconfig file, for example with -v ~/.gitconfig:/root/.gitconfig !!!\n"
   exit
fi
popd &> /dev/null
rm -rf $TEST_GIT_DIR

if [ ! -d /root/.ssh/ ]; then
   echo -e "\n!!! SSH keys missing, needed to pull/push github repos !!!\n"
   echo -e "\n!!! Ensure you have created a bind mount of your ssh directory, for example with -v ~/.ssh/:/root/.ssh/ !!!\n"
   exit
fi

echo
echo

REPO_ROOT=`pwd`

echo "Cloning repos..."

echo
echo git clone -b $DAFFODIL_CODE_BRANCH ssh://git@github.com/$DAFFODIL_CODE_USER/$DAFFODIL_CODE_REPO.git
git clone -b $DAFFODIL_CODE_BRANCH ssh://git@github.com/$DAFFODIL_CODE_USER/$DAFFODIL_CODE_REPO.git
echo
echo git clone ssh://git@github.com/apache/$DAFFODIL_SITE_REPO.git
git clone ssh://git@github.com/apache/$DAFFODIL_SITE_REPO.git

echo svn checkout https://dist.apache.org/repos/dist/dev/daffodil/$PROJECT_DIST_DIR $DAFFODIL_DIST
svn checkout https://dist.apache.org/repos/dist/dev/daffodil/$PROJECT_DIST_DIR $DAFFODIL_DIST

pushd $REPO_ROOT/$DAFFODIL_CODE_REPO &> /dev/null

VERSION=$(cat VERSION)
if [[ $VERSION == *SNAPSHOT* ]]; then
  echo -e "\n!!! $PROJECT_NAME version ($VERSION) should not contain SNAPSHOT for a release !!!\n";
  if [ "$DRY_RUN" = true ]; then
    echo -e "!!! Ignoring because this is a dry run !!!\n";
  else
    exit
  fi
fi

DAFFODIL_CODE_DIR=$REPO_ROOT/$DAFFODIL_CODE_REPO
DAFFODIL_SITE_DIR=$REPO_ROOT/$DAFFODIL_SITE_REPO
DAFFODIL_DOCS_DIR=$DAFFODIL_SITE_DIR/site/docs/$VERSION
DAFFODIL_VSCODE_DOCS_DIR=$DAFFODIL_SITE_DIR/site/docs/vscode/$VERSION
DAFFODIL_TUTORIALS_DIR=$DAFFODIL_SITE_DIR/site/tutorials
DAFFODIL_DIST_DIR=$REPO_ROOT/$DAFFODIL_DIST
DAFFODIL_RELEASE_DIR=$DAFFODIL_DIST_DIR/$VERSION-$PRE_RELEASE

export SOURCE_DATE_EPOCH=$(git show --no-patch --format=%ct HEAD)

if [ -d "$DAFFODIL_RELEASE_DIR" ]; then echo -e "\n!!! $PROJECT_NAME release directory already exists: $DAFFODIL_RELEASE_DIR !!! "; exit; fi

git -C $DAFFODIL_CODE_DIR config --local user.name  "$GIT_COMMIT_NAME"
git -C $DAFFODIL_CODE_DIR config --local user.email "$GIT_COMMIT_EMAIL"
git -C $DAFFODIL_SITE_DIR config --local user.name  "$GIT_COMMIT_NAME"
git -C $DAFFODIL_SITE_DIR config --local user.email "$GIT_COMMIT_EMAIL"

echo
echo "!!! Creating $PROJECT_NAME $VERSION-$PRE_RELEASE in $DAFFODIL_RELEASE_DIR !!!"
echo

echo "Removing old release candidates..."
find $DAFFODIL_DIST_DIR -maxdepth 1 -name $VERSION-\* -exec svn delete --force {} \;

echo "Installing Source..."
mkdir -p $DAFFODIL_RELEASE_DIR/src/
git archive --format=zip --prefix=apache-$PROJECT_REPO-$VERSION-src/ HEAD > $DAFFODIL_RELEASE_DIR/src/apache-$PROJECT_REPO-$VERSION-src.zip

case $PROJECT_REPO in
  "daffodil")
    set_java_version 17

    if [ "$DRY_RUN" = true ]; then
      SBT_PUBLISH="publishLocalSigned"
    else
      SBT_PUBLISH="publishSigned"
    fi

    echo "Building Convenience Binaries and Publishing to Apache Repository..."
    sbt \
      "set ThisBuild/updateOptions := updateOptions.value.withGigahorse(false)" \
      "set ThisBuild/credentials += Credentials(\"Sonatype Nexus Repository Manager\", \"repository.apache.org\", \"$APACHE_USERNAME\", \"$APACHE_PASSWD\")" \
      "set ThisBuild/publishTo := Some(\"Apache Staging Distribution Repository\" at \"https://repository.apache.org/service/local/staging/deploy/maven2\")" \
      "set pgpSigningKey := Some(\"$PGP_SIGNING_KEY_ID\")" \
      "+compile" \
      "+$SBT_PUBLISH" \
      "daffodil-cli/Rpm/packageBin" \
      "daffodil-cli/packageWindowsBin" \
      "daffodil-cli/Universal/packageBin" \
      "daffodil-cli/Universal/packageZipTarball" \
      "unidoc" \
      "genTunablesDoc" \

    echo "Installing Convenience Binaries..."
    mkdir -p $DAFFODIL_RELEASE_DIR/bin/
    cp daffodil-cli/target/universal/apache-daffodil-*.tgz $DAFFODIL_RELEASE_DIR/bin/
    cp daffodil-cli/target/universal/apache-daffodil-*.zip $DAFFODIL_RELEASE_DIR/bin/
    cp daffodil-cli/target/rpm/RPMS/noarch/apache-daffodil-*.rpm $DAFFODIL_RELEASE_DIR/bin/
    cp daffodil-cli/target/windows/apache-daffodil-*.exe $DAFFODIL_RELEASE_DIR/bin/
    chmod -x $DAFFODIL_RELEASE_DIR/bin/*.exe

    echo "Embedding RPM Signature..."
    rpmsign --define "_gpg_name $PGP_SIGNING_KEY_ID" --define "_binary_filedigest_algorithm 10" --addsign $DAFFODIL_RELEASE_DIR/bin/*.rpm

    echo "Installing Site Docs..."
    rm -rf $DAFFODIL_DOCS_DIR
    mkdir -p $DAFFODIL_DOCS_DIR/javadoc
    cp -R target/scala-*/unidoc/* $DAFFODIL_DOC_DIR/javadoc/
    cp target/tunables.md $DAFFODIL_SITE_DIR/site/

    echo "Installing Site Tutorials..."
    rm -rf $DAFFODIL_TUTORIALS_DIR
    mkdir -p $DAFFODIL_TUTORIALS_DIR
    cp -R tutorials/src/main/resources/* $DAFFODIL_TUTORIALS_DIR

    ;;

  "daffodil-sbt")
    set_java_version 1.8.0

    if [ "$DRY_RUN" = true ]; then
      SBT_PUBLISH="publishLocalSigned"
    else
      SBT_PUBLISH="publishSigned"
    fi

    echo "Building and Publishing to Apache Repository..."
    sbt \
      "set ThisBuild/updateOptions := updateOptions.value.withGigahorse(false)" \
      "set ThisBuild/credentials += Credentials(\"Sonatype Nexus Repository Manager\", \"repository.apache.org\", \"$APACHE_USERNAME\", \"$APACHE_PASSWD\")" \
      "set ThisBuild/publishTo := Some(\"Apache Staging Distribution Repository\" at \"https://repository.apache.org/service/local/staging/deploy/maven2\")" \
      "set pgpSigningKey := Some(\"$PGP_SIGNING_KEY_ID\")" \
      "^compile" \
      "^$SBT_PUBLISH" \

    ;;

  "daffodil-vscode")
    set_java_version 1.8.0

    echo "Building Convenience Binaries..."
    mkdir -p $DAFFODIL_RELEASE_DIR/bin/
    yarn package
    cp *.vsix $DAFFODIL_RELEASE_DIR/bin/

    echo "Building Extension Docs..."
    DOC_NAME="Apache-Daffodil-Extension-for-Visual-Studio-Code-$VERSION"
    git clone https://github.com/apache/daffodil-vscode.wiki.git .wiki
    pandoc -t docx -f markdown -o .wiki/$DOC_NAME.docx .wiki/*.md 
    pandoc -t html -f markdown -o .wiki/$DOC_NAME.html .wiki/*.md 

    echo "Installing Extension Docs..."
    rm -rf $DAFFODIL_VSCODE_DOCS_DIR
    mkdir -p $DAFFODIL_VSCODE_DOCS_DIR
    mv .wiki/$DOC_NAME.* $DAFFODIL_VSCODE_DOCS_DIR
    rm -rf .wiki

    ;;

esac

echo "Calculating Checksums..."
for i in $DAFFODIL_RELEASE_DIR/*/
do
    pushd $i > /dev/null
    for file in *
    do
       sha512sum --binary $file > $file.sha512
       gpg --default-key $PGP_SIGNING_KEY_ID --detach-sign --armor --output $file.asc $file
    done
    popd &> /dev/null
done

echo "Creating Git Tag..."
git tag -as -u $PGP_SIGNING_KEY_ID -m "Release v$VERSION-$PRE_RELEASE" v$VERSION-$PRE_RELEASE

popd &> /dev/null

pushd $DAFFODIL_SITE_DIR &> /dev/null
echo "Committing Site Changes..."
git add .
git diff-index --quiet HEAD || git commit -m "Stage $PROJECT_NAME release v$VERSION-$PRE_RELEASE"
popd &> /dev/null

echo "Adding Release Files..."
svn add $DAFFODIL_RELEASE_DIR

echo
echo
echo
echo
echo "!!! Success: $PROJECT_NAME $VERSION-$PRE_RELEASE output to $DAFFODIL_RELEASE_DIR !!!"
echo
echo "Things to verify: "
echo
echo "- Files in $DAFFODIL_DIST_DIR"
echo "- Git tag created in $DAFFODIL_CODE_DIR for $PROJECT_REPO v$VERSION-$PRE_RELEASE"
if [ "$PROJECT_REPO" = "daffodil" ]
then
  echo "- Files in $DAFFODIL_DOCS_DIR"
  echo "- Files in $DAFFODIL_TUTORIALS_DIR"
  echo "- File at $DAFFODIL_SITE_DIR/site/tunables.md"
  echo "- Staged published files at https://repository.apache.org/"
fi
echo

if [ "$DRY_RUN" = true ]; then
  echo "!!! This was a dry run. Do not push/publish any changes !!!"
  echo
  echo "Type 'exit' when done with the container"
else

  cat << EOF > /root/complete-release
#!/bin/bash
set -x
cd $DAFFODIL_DIST_DIR && svn ci --username $APACHE_USERNAME -m 'Staging Apache $PROJECT_NAME $VERSION-$PRE_RELEASE'
cd $DAFFODIL_CODE_DIR && git push origin v$VERSION-$PRE_RELEASE
cd $DAFFODIL_SITE_DIR && git push origin main
EOF
  chmod +x /root/complete-release

  echo "If everything looks correct, do the following:"
  echo
  echo "- Run: /root/complete-release"
  echo "- Close the staged files at https://repository.apache.org"
  echo "- Type 'exit' and  start a VOTE!"
  echo
  echo "If anything looks incorrect, do the following:"
  echo
  echo "- Drop the staged files at https://repository.apache.org"
  echo "- Type 'exit', fix the issues, and start over"
  echo
fi

echo

gpgconf --kill gpg-agent

bash
