#! /usr/bin/env bash

set -euo pipefail

if [ "$1" != "package" ] && [ "$1" != "bump-major" ]; then
  if (git grep -Er KJ_DBG c++/src | egrep -v '/debug(-test)?[.]' | grep -v 'See KJ_DBG\.$'); then
    echo '*** Error:  There are instances of KJ_DBG in the code.' >&2
    exit 1
  fi

  if (git grep -Er 'TODO\((now|soon)\)' *); then
    echo '*** Error:  There are release-blocking TODOs in the code.' >&2
    exit 1
  fi
fi

doit() {
  echo "@@@@ $@"
  "$@"
}

get_version() {
  local VERSION=$(grep '^AC_INIT' c++/configure.ac | sed -e 's/^[^]]*],\[\([^]]*\)].*$/\1/g')
  if [[ ! "$VERSION" =~ $1 ]]; then
    echo "Couldn't parse version: $VERSION" >&2
    exit 1
  fi
  echo "$VERSION"
}

get_release_version() {
  get_version '^[0-9]+[.][0-9]+[.][0-9]+(-rc[0-9]+|[.][0-9]+)?$'
}

update_version() {
  local OLD=$1
  local NEW=$2
  local BRANCH_DESC=$3

  local OLD_REGEX=${OLD//./[.]}
  doit sed -i -e "s/$OLD_REGEX/$NEW/g" c++/configure.ac
  doit sed -i -e "s/set(VERSION.*)/set(VERSION $NEW)/g" c++/CMakeLists.txt

  local NEW_NOTAG=${NEW%%-*}
  declare -a NEW_ARR=(${NEW_NOTAG//./ })
  doit sed -i -re "
      s/^#define CAPNP_VERSION_MAJOR [0-9]+\$/#define CAPNP_VERSION_MAJOR ${NEW_ARR[0]}/g;
      s/^#define CAPNP_VERSION_MINOR [0-9]+\$/#define CAPNP_VERSION_MINOR ${NEW_ARR[1]}/g;
      s/^#define CAPNP_VERSION_MICRO [0-9]+\$/#define CAPNP_VERSION_MICRO ${NEW_ARR[2]:-0}/g" \
      c++/src/capnp/common.h

  local NEW_COMBINED=$(( ${NEW_ARR[0]} * 1000000 + ${NEW_ARR[1]} * 1000 + ${NEW_ARR[2]:-0 }))
  doit sed -i -re "s/^#elif CAPNP_VERSION != [0-9]*\$/#elif CAPNP_VERSION != $NEW_COMBINED/g" \
      c++/src/*/*.capnp.h c++/src/*/*/*.capnp.h

  doit git commit -a -m "Set $BRANCH_DESC version to $NEW."
}

build_packages() {
  local VERSION=$1
  local VERSION_BASE=${VERSION%%-*}

  echo "========================================================================="
  echo "Building C++ package..."
  echo "========================================================================="

  # make dist tarball and move into ..
  cd c++
  doit autoreconf -i
  doit ./configure
  doit make -j$(nproc) distcheck
  doit mv capnproto-c++-$VERSION.tar.gz ..
  doit make distclean

  # build windows executables
  doit ./configure --host=i686-w64-mingw32 --with-external-capnp \
      --disable-shared CXXFLAGS='-static-libgcc -static-libstdc++'
  doit make -j$(nproc) capnp.exe capnpc-c++.exe capnpc-capnp.exe
  doit i686-w64-mingw32-strip capnp.exe capnpc-c++.exe capnpc-capnp.exe
  doit mkdir capnproto-tools-win32-$VERSION
  doit mv capnp.exe capnpc-c++.exe capnpc-capnp.exe capnproto-tools-win32-$VERSION
  doit make maintainer-clean

  # repack dist tarball and win32 tools into win32 zip, with DOS line endings
  doit tar zxf ../capnproto-c++-$VERSION.tar.gz
  find capnproto-c++-$VERSION -name '*.c++' -o -name '*.h' -o -name '*.capnp' -o -name '*.md' -o -name '*.txt' | grep -v testdata | doit xargs unix2dos
  doit zip -r ../capnproto-c++-win32-$VERSION.zip capnproto-c++-$VERSION capnproto-tools-win32-$VERSION

  rm -rf capnproto-c++-$VERSION capnproto-tools-win32-$VERSION
  cd ..
}

cherry_pick() {
  shift
  if [ $# -gt 0 ]; then
    echo "========================================================================="
    echo "Cherry-picking fixes"
    echo "========================================================================="
    doit git cherry-pick "$@"
  fi
}

done_banner() {
  local VERSION=$1
  local PUSH=$2
  local FINAL=$3
  echo "========================================================================="
  echo "Done"
  echo "========================================================================="
  echo "Ready to release:"
  echo "  capnproto-c++-$VERSION.tar.gz"
  echo "  capnproto-c++-win32-$VERSION.zip"
  echo "Don't forget to push changes:"
  echo "  git push origin $PUSH"

  read -s -n 1 -p "Shall I push to git and upload to capnproto.org now? (y/N)" YESNO

  echo
  case "$YESNO" in
    y | Y )
      doit git push origin $PUSH
      doit gce-ss copy-files capnproto-c++-$VERSION.tar.gz capnproto-c++-win32-$VERSION.zip \
          alpha2:/var/www/capnproto.org

      if [ "$FINAL" = yes ]; then
        echo "========================================================================="
        echo "Publishing docs"
        echo "========================================================================="
        cd doc
        doit ./push-site.sh
        cd ..
        echo "========================================================================="
        echo "Really done"
        echo "========================================================================="
      fi

      echo "Release is available at:"
      echo "  http://capnproto.org/capnproto-c++-$VERSION.tar.gz"
      ;;
    * )
      echo "OK, do it yourself then."
      ;;
  esac
}

BRANCH=$(git rev-parse --abbrev-ref HEAD)

case "${1-}:$BRANCH" in
  bump-major:* )
    echo "Bump major version number on HEAD."
    HEAD_VERSION=$(get_version '^[0-9]+[.][0-9]+-dev$')
    OLD_MAJOR=$(echo $HEAD_VERSION | cut -d. -f1)
    NEW_VERSION=$(( OLD_MAJOR + 1 )).0-dev
    update_version $HEAD_VERSION $NEW_VERSION "mainline"
    ;;

  bump-minor:* )
    echo "Bump minor version number on HEAD."
    HEAD_VERSION=$(get_version '^[0-9]+[.][0-9]+-dev$')
    RELEASE_VERSION=${HEAD_VERSION%%-dev}.0
    declare -a VERSION_ARR=(${RELEASE_VERSION//./ })
    NEXT_VERSION=${VERSION_ARR[0]}.$((VERSION_ARR[1] + 1))
    update_version $HEAD_VERSION $NEXT_VERSION-dev "mainline"
    ;;

  # ======================================================================================
  candidate:master )
    echo "New major release."

    if [ $# -gt 1 ]; then
      echo "Cannot cherry-pick when starting from master.  Do it yourself." >&2
      exit 1
    fi

    HEAD_VERSION=$(get_version '^[0-9]+[.][0-9]+-dev$')
    RELEASE_VERSION=${HEAD_VERSION%%-dev}.0

    echo "Version: $RELEASE_VERSION"

    echo "========================================================================="
    echo "Creating release branch..."
    echo "========================================================================="
    doit git checkout -b release-$RELEASE_VERSION

    update_version $HEAD_VERSION $RELEASE_VERSION-rc1 "release branch"

    build_packages $RELEASE_VERSION-rc1

    echo "========================================================================="
    echo "Updating version in master branch..."
    echo "========================================================================="

    doit git checkout master
    declare -a VERSION_ARR=(${RELEASE_VERSION//./ })
    NEXT_VERSION=${VERSION_ARR[0]}.$((VERSION_ARR[1] + 1))

    update_version $HEAD_VERSION $NEXT_VERSION-dev "mainline"

    done_banner $RELEASE_VERSION-rc1 "master release-$RELEASE_VERSION" no
    ;;

  # ======================================================================================
  candidate:release-* )
    echo "New release candidate."
    OLD_VERSION=$(get_release_version)

    if [[ $OLD_VERSION == *-rc* ]]; then
      # New release candidate for existing release.

      RC=${OLD_VERSION##*-rc}
      BRANCH_VERSION=${OLD_VERSION%%-rc*}
      RC_VERSION=$BRANCH_VERSION-rc$(( RC + 1 ))

      echo "Version: $RC_VERSION"
    else
      # New micro release.

      declare -a VERSION_ARR=(${OLD_VERSION//./ })
      BRANCH_VERSION=${VERSION_ARR[0]}.${VERSION_ARR[1]}.$((VERSION_ARR[2] + 1))

      RC_VERSION=$BRANCH_VERSION-rc1
      echo "Version: $RC_VERSION"

      echo "========================================================================="
      echo "Creating new release branch..."
      echo "========================================================================="

      doit git checkout -b release-$BRANCH_VERSION
    fi

    echo "========================================================================="
    echo "Updating version number to $RC_VERSION..."
    echo "========================================================================="

    update_version $OLD_VERSION $RC_VERSION "release branch"

    cherry_pick "$@"

    build_packages $RC_VERSION

    done_banner $RC_VERSION release-$BRANCH_VERSION no
    ;;

  # ======================================================================================
  final:release-* )
    echo "Final release."
    OLD_VERSION=$(get_release_version)

    if [[ $OLD_VERSION != *-rc* ]]; then
      echo "Current version is already a final release.  You need to create a new candidate first." >&2
      exit 1
    fi

    if [ $# -gt 1 ]; then
      echo "Cannot cherry-pick into final release.  Make another candidate." >&2
      exit 1
    fi

    RC=${OLD_VERSION##*-rc}
    NEW_VERSION=${OLD_VERSION%%-rc*}

    echo "Version: $NEW_VERSION"

    echo "========================================================================="
    echo "Updating version number to $NEW_VERSION..."
    echo "========================================================================="

    doit sed -i -re "s/capnproto-c[+][+]-[0-9]+[.][0-9]+[.][0-9]+([.][0-9]+)?\>/capnproto-c++-$NEW_VERSION/g" doc/install.md
    doit sed -i -re "s/capnproto-c[+][+]-win32-[0-9]+[.][0-9]+[.][0-9]+([.][0-9]+)?\>/capnproto-c++-win32-$NEW_VERSION/g" doc/install.md
    doit sed -i -re "s/capnproto-tools-win32-[0-9]+[.][0-9]+[.][0-9]+([.][0-9]+)?\>/capnproto-tools-win32-$NEW_VERSION/g" doc/install.md
    update_version $OLD_VERSION $NEW_VERSION "release branch"

    doit git tag v$NEW_VERSION

    build_packages $NEW_VERSION

    done_banner $NEW_VERSION "v$NEW_VERSION release-$NEW_VERSION" yes
    ;;

  # ======================================================================================
  security:release-* )
    echo "Security release."
    OLD_VERSION=$(get_release_version)

    if [[ $OLD_VERSION == *-rc* ]]; then
      echo "Security releases don't have candidates." >&2
      exit 1
    fi

    declare -a VERSION_ARR=(${OLD_VERSION//./ } 0)
    NEW_VERSION=${VERSION_ARR[0]}.${VERSION_ARR[1]}.${VERSION_ARR[2]}.$((VERSION_ARR[3] + 1))

    echo "Version: $NEW_VERSION"

    echo "========================================================================="
    echo "Updating version number to $NEW_VERSION..."
    echo "========================================================================="

    doit sed -i -re "s/capnproto-c[+][+]-[0-9]+[.][0-9]+[.][0-9]+([.][0-9]+)?\>/capnproto-c++-$NEW_VERSION/g" doc/install.md
    doit sed -i -re "s/capnproto-c[+][+]-win32-[0-9]+[.][0-9]+[.][0-9]+([.][0-9]+)?\>/capnproto-c++-win32-$NEW_VERSION/g" doc/install.md
    doit sed -i -re "s/capnproto-tools-win32-[0-9]+[.][0-9]+[.][0-9]+([.][0-9]+)?\>/capnproto-tools-win32-$NEW_VERSION/g" doc/install.md
    update_version $OLD_VERSION $NEW_VERSION "release branch"

    cherry_pick "$@"

    doit git tag v$NEW_VERSION

    build_packages $NEW_VERSION

    done_banner $NEW_VERSION "v$NEW_VERSION release-$NEW_VERSION" yes
    ;;

  # ======================================================================================
  retry:release-* )
    echo "Retrying release."
    OLD_VERSION=$(get_release_version)
    echo "Version: $OLD_VERSION"

    if [[ $OLD_VERSION == *-rc* ]]; then
      # We can add more cherry-picks when retrying a candidate.
      cherry_pick "$@"
    else
      if [ $# -gt 1 ]; then
        echo "Cannot cherry-pick into final release.  Make another candidate." >&2
        exit 1
      fi
    fi

    OLD_VERSION=$(get_release_version)
    build_packages $OLD_VERSION

    if [[ $OLD_VERSION == *-rc* ]]; then
      BRANCH_VERSION=${OLD_VERSION%%-rc*}
      done_banner $OLD_VERSION release-$BRANCH_VERSION no
    else
      doit git tag v$OLD_VERSION
      done_banner $OLD_VERSION "v$OLD_VERSION release-$OLD_VERSION" no
    fi
    ;;

  # ======================================================================================
  package:* )
    echo "Just building a package."
    build_packages $(get_version '.*')
    ;;

  # ======================================================================================
  *:master )
    echo "Invalid command for mainline branch.  Only command is 'candidate'." >&2
    exit 1
    ;;

  *:release-* )
    echo "Invalid command for release branch.  Commands are 'candidate', 'final', and 'retry'." >&2
    exit 1
    ;;

  * )
    echo "Not a master or release branch." >&2
    exit 1
    ;;
esac
