#!/bin/bash

SCRIPT_DIR=`dirname $0`
source "${SCRIPT_DIR}/common_tools.sh"

# Set and check global variables.
set_post_release_global_variables()
{
    exit_if_argc_ne $# 6
    local PARAM_CONAN="$1"; shift
    local PARAM_EXTENSION_SAMPLE="$1"; shift
    local PARAM_TUTORIAL_CPP="$1"; shift
    local PARAM_TUTORIAL_JAVA="$1"; shift
    local PARAM_TUTORIAL_PYTHON="$1"; shift
    local PARAM_STREAMLIT="$1"; shift

    if [[ ${PARAM_TUTORIAL_CPP} == 1 ]] ; then
        # CMAKE to use, defaults to "cmake" if not set
        CMAKE="${CMAKE:-cmake}"
        if [ ! -f "`which "${CMAKE}"`" ] ; then
            stderr_echo "Cannot find cmake! Set CMAKE environment variable."
            return 1
        fi
    fi

    if [[ ${PARAM_TUTORIAL_JAVA} == 1 ]] ; then
        # MVN to use, defaults to "mvn" if not set
        MVN="${MVN:-mvn}"
        if [ ! -f "`which "${MVN}"`" ] ; then
            stderr_echo "Cannot find mvn! Set MVN environment variable."
            return 1
        fi

        # check java binary
        if [ -n "${JAVA_HOME}" ] ; then
            JAVA_BIN="${JAVA_HOME}/bin/java"
        fi
        JAVA_BIN="${JAVA_BIN:-java}"
        if [ ! -f "`which "${JAVA_BIN}"`" ] ; then
            stderr_echo "Cannot find java! Set JAVA_HOME or JAVA_BIN environment variable."
            return 1
        fi
    fi

    # GIT to use, defaults to "git" if not set
    GIT="${GIT:-git}"
    if [ ! -f "`which "${GIT}"`" ] ; then
        stderr_echo "Cannot find git! Set GIT environment variable."
        return 1
    fi

    # SED and TAIL to use, default to "sed" and "tail" if not set; GNU versions are required
    # (BSD sed and tail silently do something else with the options used here)
    SED="${SED:-sed}"
    if ! "${SED}" --version 2>/dev/null | grep -q "GNU" ; then
        stderr_echo "GNU sed is required, '${SED}' is not GNU sed! Set SED environment variable" \
                "(e.g. SED=gsed on macOS)."
        return 1
    fi
    TAIL="${TAIL:-tail}"
    if ! "${TAIL}" --version 2>/dev/null | grep -q "GNU" ; then
        stderr_echo "GNU tail is required, '${TAIL}' is not GNU tail! Set TAIL environment variable" \
                "(e.g. TAIL=gtail on macOS)."
        return 1
    fi

    if [[ ${PARAM_CONAN} == 1 ]] ; then
        # SHASUM to use, defaults to "shasum" if not set
        SHASUM="${SHASUM:-shasum}"
        if [ ! -f "`which "${SHASUM}"`" ] ; then
            stderr_echo "Cannot find shasum! Set SHASUM environment variable."
            return 1
        fi
    fi

    if [[ ${PARAM_TUTORIAL_PYTHON} == 1 ]] ; then
        # python to use, defaults to "python3" if not set
        PYTHON="${PYTHON:-python3}"
        if [ ! -f "`which "${PYTHON}"`" ] ; then
            stderr_echo "Cannot find Python! Set PYTHON environment variable."
            return 1
        fi
    fi

    if [[ ${PARAM_CONAN} == 1 ]] ; then
        # Directory wiht Zserio fork of conan-center-index to use,
        # defaults to "${SCRIPT_DIR}/../../zserio-conan-center-index" if not set
        ZSERIO_CONAN_DIR="${ZSERIO_CONAN_DIR:-${SCRIPT_DIR}/../../zserio-conan-center-index}"
        if [ ! -d "${ZSERIO_CONAN_DIR}" ] ; then
            stderr_echo "Cannot find Zserio Conan Center Index directory! Set ZSERIO_CONAN_DIR environment variable."
            return 1
        fi
    fi

    if [[ ${PARAM_EXTENSION_SAMPLE} == 1 ]] ; then
        # Zserio Extension Sample directory to use, defaults to "${SCRIPT_DIR}/../../zserio-extension-sample"
        # if not set
        ZSERIO_EXTENSION_SAMPLE_DIR="${ZSERIO_EXTENSION_SAMPLE_DIR:-${SCRIPT_DIR}/../../zserio-extension-sample}"
        if [ ! -d "${ZSERIO_EXTENSION_SAMPLE_DIR}" ] ; then
            stderr_echo "Cannot find Zserio Extension Sample directory! Set ZSERIO_EXTENSION_SAMPLE_DIR" \
                    "environment variable."
            return 1
        fi
    fi

    if [[ ${PARAM_TUTORIAL_CPP} == 1 ]] ; then
        # Zserio Tutorial Cpp directory to use, defaults to "${SCRIPT_DIR}/../../zserio-tutorial-cpp" if not set
        ZSERIO_TUTORIAL_CPP_DIR="${ZSERIO_TUTORIAL_CPP_DIR:-${SCRIPT_DIR}/../../zserio-tutorial-cpp}"
        if [ ! -d "${ZSERIO_TUTORIAL_CPP_DIR}" ] ; then
            stderr_echo "Cannot find Zserio Tutorial Cpp directory! Set ZSERIO_TUTORIAL_CPP_DIR" \
                    "environment variable."
            return 1
        fi
    fi

    if [[ ${PARAM_TUTORIAL_JAVA} == 1 ]] ; then
        # Zserio Tutorial Java directory to use, defaults to "${SCRIPT_DIR}/../../zserio-tutorial-java"
        # if not set
        ZSERIO_TUTORIAL_JAVA_DIR="${ZSERIO_TUTORIAL_JAVA_DIR:-${SCRIPT_DIR}/../../zserio-tutorial-java}"
        if [ ! -d "${ZSERIO_TUTORIAL_JAVA_DIR}" ] ; then
            stderr_echo "Cannot find Zserio Tutorial Java directory! Set ZSERIO_TUTORIAL_JAVA_DIR" \
                    "environment variable."
            return 1
        fi
    fi

    if [[ ${PARAM_TUTORIAL_PYTHON} == 1 ]] ; then
        # Zserio Tutorial Python directory to use, defaults to "${SCRIPT_DIR}/../../zserio-tutorial-python"
        # if not set
        ZSERIO_TUTORIAL_PYTHON_DIR="${ZSERIO_TUTORIAL_PYTHON_DIR:-${SCRIPT_DIR}/../../zserio-tutorial-python}"
        if [ ! -d "${ZSERIO_TUTORIAL_PYTHON_DIR}" ] ; then
            stderr_echo "Cannot find Zserio Tutorial Python directory! Set ZSERIO_TUTORIAL_PYTHON_DIR" \
                    "environment variable."
            return 1
        fi
    fi

    if [[ ${PARAM_STREAMLIT} == 1 ]] ; then
        # Zserio Streamlit directory to use, defaults to "${SCRIPT_DIR}/../../zserio-streamlit" if not set
        ZSERIO_STREAMLIT_DIR="${ZSERIO_STREAMLIT_DIR:-${SCRIPT_DIR}/../../zserio-streamlit}"
        if [ ! -d "${ZSERIO_STREAMLIT_DIR}" ] ; then
            stderr_echo "Cannot find Zserio Streamlit directory! Set ZSERIO_STREAMLIT_DIR environment variable."
            return 1
        fi
    fi

    return 0
}

# Print help on the environment variables used for this post release script.
print_release_help_env()
{
    cat << EOF
Uses the following environment variables for update after release:
    CMAKE    CMake executable to use. Default is "cmake".
    MVN      Mvn executable to use. Default is "mvn".
    JAVA_BIN Java executable to use. Default is "java".
    GIT      Git executable to use. Default is "git".
    SED      GNU sed executable to use. Default is "sed" (on macOS e.g. "gsed").
    TAIL     GNU tail executable to use. Default is "tail" (on macOS e.g. "gtail").
    SHASUM   Shasum exetuable to use. Default is "shasum".
    PYTHON   Python executable to use. Default is "python3".

    GITHUB_TOKEN                GitHub token authentication to use during downloading release assets from GitHub.
                                Default is without authentication.

    ZSERIO_CONAN_DIR            Zserio Conan Center Index dirctory. Default is "../../zserio-conan-center-index".
    ZSERIO_EXTENSION_SAMPLE_DIR Zserio Extension Sample project directory. Default is "../../zserio-extension-sample".
    ZSERIO_TUTORIAL_CPP_DIR     Zserio C++ Tutorial project directory. Default is "../../zserio-tutorial-cpp".
    ZSERIO_TUTORIAL_JAVA_DIR    Zserio Java Tutorial project directory. Default is "../../zserio-tutorial-java".
    ZSERIO_TUTORIAL_PYTHON_DIR  Zserio Python Tutorial project directory. Default is "../../zserio-tutorial-python".

    Either set these directly, or create 'scripts/build-env.sh' that sets these.
    It's sourced automatically if it exists.

EOF
}

# Check that a conan yml file has the expected number of versions and the given version first.
check_conan_versions()
{
    exit_if_argc_ne $# 3
    local YML_FILE="$1"; shift
    local ZSERIO_VERSION="$1"; shift
    local NUM_EXPECTED_VERSIONS="$1"; shift

    local NUM_VERSIONS=`grep -c '^  "' "${YML_FILE}"`
    if [[ ${NUM_VERSIONS} -ne ${NUM_EXPECTED_VERSIONS} ]] ; then
        stderr_echo "'${YML_FILE}' has ${NUM_VERSIONS} versions instead of ${NUM_EXPECTED_VERSIONS}!"
        return 1
    fi
    local FIRST_VERSION=`grep -m 1 '^  "' "${YML_FILE}"`
    if [[ "${FIRST_VERSION}" != "  \"${ZSERIO_VERSION}\":" ]] ; then
        stderr_echo "'${YML_FILE}' does not start with version ${ZSERIO_VERSION}!"
        return 1
    fi

    return 0
}

# Check that all sources generated in the given directories are generated by the given Zserio version.
check_generated_version()
{
    exit_if_argc_lt $# 2
    local ZSERIO_VERSION="$1"; shift

    local GENERATED_VERSIONS=`grep -rhoE "using Zserio core [^ ]+" "$@" | sort -u`
    if [[ "${GENERATED_VERSIONS}" != "using Zserio core ${ZSERIO_VERSION}." ]] ; then
        stderr_echo "Sources in '$*' are not generated by Zserio core ${ZSERIO_VERSION}, found:" \
                "'${GENERATED_VERSIONS}'!"
        return 1
    fi

    return 0
}

# Print the exact command which pushes the current branch of the given repository to origin.
print_push_command()
{
    exit_if_argc_ne $# 1
    local REPO_DIR="$1"; shift

    local REPO_TOPLEVEL=`"${GIT}" -C "${REPO_DIR}" rev-parse --show-toplevel`
    local REPO_BRANCH=`"${GIT}" -C "${REPO_DIR}" rev-parse --abbrev-ref HEAD`
    printf "    %q -C %q push origin %q\n" "${GIT}" "${REPO_TOPLEVEL}" "${REPO_BRANCH}"
}

# Update Zserio fork of conan-center-index after new Zserio release
update_conan()
{
    exit_if_argc_ne $# 2
    local CONAN_DIR="$1"; shift
    local ZSERIO_VERSION="$1"; shift

    local RELEASE_BRANCH="zserio-${ZSERIO_VERSION}"
    local GIT_MESSAGE="zserio: Add new Zserio release ${ZSERIO_VERSION}"
    local GREP_RESULT=`"${GIT}" -C "${CONAN_DIR}" log ${RELEASE_BRANCH} 2>&1 | grep "${GIT_MESSAGE}"`
    if [ $? -ne 0 -o -z "${GREP_RESULT}" ] ; then
        echo "Adding version ${ZSERIO_VERSION} to Zserio Conan Center Index."

        local DOWNLOAD_DIR="${CONAN_DIR}/download/"
        mkdir -p "${DOWNLOAD_DIR}"

        echo -ne "Downloading release assets and calculating sha256 checksums..."

        local ZSERIO_RUNTIME_LIBS_ZIP_NAME="zserio-${ZSERIO_VERSION}-runtime-libs.zip"
        get_zserio_runtime_libs "${ZSERIO_VERSION}" "${DOWNLOAD_DIR}" "${ZSERIO_RUNTIME_LIBS_ZIP_NAME}"
        if [ $? -ne 0 ] ; then
            return 1
        fi
        local ZSERIO_RUNTIME_LIBS_SHA256=$(${SHASUM} -a 256 "${DOWNLOAD_DIR}/${ZSERIO_RUNTIME_LIBS_ZIP_NAME}" | cut -d' ' -f1)

        local ZSERIO_BIN_ZIP_NAME=zserio-${ZSERIO_VERSION}-bin.zip
        get_zserio_bin "${ZSERIO_VERSION}" "${DOWNLOAD_DIR}" "${ZSERIO_BIN_ZIP_NAME}"
        if [ $? -ne 0 ] ; then
            return 1
        fi
        local ZSERIO_BIN_SHA256=$(${SHASUM} -a 256 "${DOWNLOAD_DIR}/${ZSERIO_BIN_ZIP_NAME}" | cut -d' ' -f1)

        local ZSERIO_LICENSE_NAME="LICENSE-${ZSERIO_VERSION}"
        get_zserio_license "${ZSERIO_VERSION}" "${DOWNLOAD_DIR}" "${ZSERIO_LICENSE_NAME}"
        if [ $? -ne 0 ] ; then
            return 1
        fi
        local ZSERIO_LICENSE_SHA256=$(${SHASUM} -a 256 "${DOWNLOAD_DIR}/${ZSERIO_LICENSE_NAME}" | cut -d' ' -f1)

        local SHA256
        for SHA256 in "${ZSERIO_RUNTIME_LIBS_SHA256}" "${ZSERIO_BIN_SHA256}" "${ZSERIO_LICENSE_SHA256}" ; do
            if [[ ! "${SHA256}" =~ ^[0-9a-f]{64}$ ]] ; then
                stderr_echo "Invalid sha256 checksum '${SHA256}'!"
                return 1
            fi
        done
        echo "Done"

        if [ ! `"${GIT}" -C "${CONAN_DIR}" remote | grep "upstream"` ] ; then
            echo "Adding remote 'upstream' to 'git@github.com:conan-io/conan-center-index.git'."
            "${GIT}" -C "${CONAN_DIR}" remote add upstream git@github.com:conan-io/conan-center-index.git
            if [ $? -ne 0 ] ; then
                return 1
            fi
        fi

        echo "Syncing master with upstream."
        "${GIT}" -C "${CONAN_DIR}" fetch upstream
        local GIT_RESULT=$?
        if [ ${GIT_RESULT} -ne 0 ] ; then
            stderr_echo "Git failed with return code ${GIT_RESULT}!"
            return 1
        fi

        echo "Preparing release branch '${RELEASE_BRANCH}'."
        "${GIT}" -C "${CONAN_DIR}" branch ${RELEASE_BRANCH} upstream/master
        local GIT_RESULT=$?
        if [ ${GIT_RESULT} -ne 0 ] ; then
            stderr_echo "Git failed with return code ${GIT_RESULT}"
            return 1
        fi

        echo "Switching to the release branch '${RELEASE_BRANCH}'."
        "${GIT}" -C "${CONAN_DIR}" checkout ${RELEASE_BRANCH}
        local GIT_RESULT=$?
        if [ ${GIT_RESULT} -ne 0 ] ; then
            stderr_echo "Git failed with return code ${GIT_RESULT}"
            return 1
        fi

        local CONFIG_YML=${ZSERIO_CONAN_DIR}/recipes/zserio/config.yml
        local CONANDATA_YML=${ZSERIO_CONAN_DIR}/recipes/zserio/all/conandata.yml
        local NUM_OLD_VERSIONS=`grep -c '^  "' "${CONFIG_YML}"`
        local NUM_OLD_SOURCES=`grep -c '^  "' "${CONANDATA_YML}"`
        if [[ ${NUM_OLD_VERSIONS} -eq 0 || ${NUM_OLD_SOURCES} -eq 0 ]] ; then
            stderr_echo "No versions found in '${CONFIG_YML}' or '${CONANDATA_YML}'!"
            return 1
        fi
        if grep -qF "  \"${ZSERIO_VERSION}\":" "${CONFIG_YML}" "${CONANDATA_YML}" ; then
            stderr_echo "Version ${ZSERIO_VERSION} is already in '${CONFIG_YML}' or '${CONANDATA_YML}'!"
            return 1
        fi

        local OLD_VERSIONS=$("${TAIL}" -n +2 "${CONFIG_YML}")
        cat > ${CONFIG_YML} << EOF
versions:
  "${ZSERIO_VERSION}":
    folder: all
${OLD_VERSIONS}
EOF

        local OLD_SOURCES=$("${TAIL}" -n +2 "${CONANDATA_YML}")
        local ZSERIO_RUNTIME_LIBS_URL
        get_zserio_runtime_libs_url "${ZSERIO_VERSION}" ZSERIO_RUNTIME_LIBS_URL
        local ZSERIO_BIN_URL
        get_zserio_bin_url "${ZSERIO_VERSION}" ZSERIO_BIN_URL
        local ZSERIO_LICENSE_URL
        get_zserio_license_url "${ZSERIO_VERSION}" ZSERIO_LICENSE_URL
        cat > ${CONANDATA_YML} << EOF
sources:
  "${ZSERIO_VERSION}":
    runtime:
      url: "${ZSERIO_RUNTIME_LIBS_URL}"
      sha256: "${ZSERIO_RUNTIME_LIBS_SHA256}"
    compiler:
      url: "${ZSERIO_BIN_URL}"
      sha256: "${ZSERIO_BIN_SHA256}"
    license:
      url: "${ZSERIO_LICENSE_URL}"
      sha256: "${ZSERIO_LICENSE_SHA256}"
${OLD_SOURCES}
EOF

        check_conan_versions "${CONFIG_YML}" "${ZSERIO_VERSION}" $((NUM_OLD_VERSIONS + 1))
        if [ $? -ne 0 ] ; then
            return 1
        fi
        check_conan_versions "${CONANDATA_YML}" "${ZSERIO_VERSION}" $((NUM_OLD_SOURCES + 1))
        if [ $? -ne 0 ] ; then
            return 1
        fi

        echo "Committing update of Zserio Conan Center Index."
        "${GIT}" -C "${ZSERIO_CONAN_DIR}" commit -a -m "${GIT_MESSAGE}"
        local GIT_RESULT=$?
        if [ ${GIT_RESULT} -ne 0 ] ; then
            stderr_echo "Git failed with return code ${GIT_RESULT}"
            return 1
        fi

        echo $'\e[1;33m'"Don't forget to check the Zserio Conan Center Index repository!"$'\e[0m'
        echo $'\e[1;33m'"Run: 'conan create recipes/zserio/all/conanfile.py --version ${ZSERIO_VERSION}'"$'\e[0m'
        echo $'\e[1;33m'"If it is ok, push changes to origin and make a pull request to upstream:"$'\e[0m'
        print_push_command "${CONAN_DIR}"
        read -n 1 -s -r -p "Press any key to continue..."
        echo
    else
        echo $'\e[1;33m'"Zserio fork of Conan center index already up to date."$'\e[0m'
        echo
    fi

    return 0
}

# Update Zserio Extension Sample repository after new Zserio release.
update_extension_sample()
{
    exit_if_argc_ne $# 2
    local EXTENSION_SAMPLE_DIR="$1"; shift
    local ZSERIO_VERSION="$1"; shift

    local EXTENSION_FILE="${EXTENSION_SAMPLE_DIR}/src/zserio/extension/sample/SampleExtension.java"
    echo -ne "Updating version to ${ZSERIO_VERSION} in Zserio Extension Sample..."
    "${SED}" -i -e 's/[2-9]\+\.[0-9]\+\.[0-9]\+\(\-[A-Za-z0-9]\+\)\?/'"${ZSERIO_VERSION}"'/' "${EXTENSION_FILE}"
    local SED_RESULT=$?
    if [ ${SED_RESULT} -ne 0 ] ; then
        stderr_echo "Sed failed with return code ${SED_RESULT}!"
        return 1
    fi
    if ! grep -qF "ZSERIO_VERSION_STRING = \"${ZSERIO_VERSION}\";" "${EXTENSION_FILE}" ; then
        stderr_echo "Version in '${EXTENSION_FILE}' has not been updated to ${ZSERIO_VERSION}!"
        return 1
    fi
    echo "Done"
    echo

    "${GIT}" -C "${EXTENSION_SAMPLE_DIR}" diff --exit-code > /dev/null
    if [ $? -eq 0 ] ; then
        echo $'\e[1;33m'"Zserio Extension Sample already up to date."$'\e[0m'
    else
        echo "Committing update of Zserio Extension Sample."
        "${GIT}" -C "${EXTENSION_SAMPLE_DIR}" commit -a \
                -m "Change expected zserio core version to ${ZSERIO_VERSION}"
        local GIT_RESULT=$?
        if [ ${GIT_RESULT} -ne 0 ] ; then
            stderr_echo "Git failed with return code ${GIT_RESULT}!"
            return 1
        fi
        echo $'\e[1;33m'"Don't forget to check the Zserio Extension Sample repository!"$'\e[0m'
        echo $'\e[1;33m'"If it is ok, push changes to origin:"$'\e[0m'
        print_push_command "${EXTENSION_SAMPLE_DIR}"
        read -n 1 -s -r -p "Press any key to continue..."
        echo
    fi
    echo

    return 0
}

# Update Zserio Tutorial Cpp repository after new Zserio release.
update_tutorial_cpp()
{
    exit_if_argc_ne $# 2
    local TUTORIAL_CPP_DIR="$1"; shift
    local ZSERIO_VERSION="$1"; shift

    echo "Updating generated sources with default allocators in Zserio Tutorial Cpp."
    echo
    local TUTORIAL_CPP_BUILD_DIR="${TUTORIAL_CPP_DIR}/build"
    rm -rf "${TUTORIAL_CPP_BUILD_DIR}"
    mkdir -p "${TUTORIAL_CPP_BUILD_DIR}"
    pushd "${TUTORIAL_CPP_BUILD_DIR}" > /dev/null
    "${CMAKE}" -DREGENERATE_CPP_SOURCES=ON ..
    local CMAKE_RESULT=$?
    popd > /dev/null
    if [ ${CMAKE_RESULT} -ne 0 ] ; then
        stderr_echo "CMake failed with return code ${CMAKE_RESULT}!"
        return 1
    fi
    echo

    echo "Updating generated sources with polymorphic allocators in Zserio Tutorial Cpp."
    echo
    local TUTORIAL_CPP_BUILD_DIR="${TUTORIAL_CPP_DIR}/build"
    rm -rf "${TUTORIAL_CPP_BUILD_DIR}"
    mkdir -p "${TUTORIAL_CPP_BUILD_DIR}"
    pushd "${TUTORIAL_CPP_BUILD_DIR}" > /dev/null
    "${CMAKE}" -DREGENERATE_CPP_SOURCES=ON ../pmr
    local CMAKE_RESULT=$?
    popd > /dev/null
    if [ ${CMAKE_RESULT} -ne 0 ] ; then
        stderr_echo "CMake failed with return code ${CMAKE_RESULT}!"
        return 1
    fi
    echo

    check_generated_version "${ZSERIO_VERSION}" "${TUTORIAL_CPP_DIR}/src" "${TUTORIAL_CPP_DIR}/pmr/src"
    if [ $? -ne 0 ] ; then
        return 1
    fi

    "${GIT}" -C "${TUTORIAL_CPP_DIR}" diff --exit-code > /dev/null
    if [ $? -eq 0 ] ; then
        echo $'\e[1;33m'"Zserio Tutorial Cpp already up to date."$'\e[0m'
    else
        echo "Committing update of Zserio Tutorial Cpp."
        "${GIT}" -C "${TUTORIAL_CPP_DIR}" commit -a -m "Update generated sources to version ${ZSERIO_VERSION}"
        local GIT_RESULT=$?
        if [ ${GIT_RESULT} -ne 0 ] ; then
            stderr_echo "Git failed with return code ${GIT_RESULT}!"
            return 1
        fi
        echo $'\e[1;33m'"Don't forget to check the Zserio Tutorial Cpp repository!"$'\e[0m'
        echo $'\e[1;33m'"If it is ok, push changes to origin:"$'\e[0m'
        print_push_command "${TUTORIAL_CPP_DIR}"
        read -n 1 -s -r -p "Press any key to continue..."
        echo
    fi
    echo

    return 0
}

# Update Zserio Tutorial Java repository after new Zserio release.
update_tutorial_java()
{
    exit_if_argc_ne $# 2
    local TUTORIAL_JAVA_DIR="$1"; shift
    local ZSERIO_VERSION="$1"; shift

    echo "Updating generated sources in Zserio Tutorial Java."
    echo
    local TUTORIAL_JAVA_BUILD_DIR="${TUTORIAL_JAVA_DIR}/build"
    ${MVN} dependency:copy -Dmaven.repo.local="${TUTORIAL_JAVA_BUILD_DIR}/download" \
            -Dartifact=io.github.ndsev:zserio:${ZSERIO_VERSION} \
            -DoutputDirectory="${TUTORIAL_JAVA_BUILD_DIR}" \
            -Dmdep.stripVersion=true
    local MVN_RESULT=$?
    if [ ${MVN_RESULT} -ne 0 ] ; then
        stderr_echo "Maven download failed with return code ${MVN_RESULT}!"
        return 1
    fi
    "${JAVA_BIN}" -jar "${TUTORIAL_JAVA_BUILD_DIR}/zserio.jar" -src "${TUTORIAL_JAVA_DIR}" tutorial.zs \
            -java "${TUTORIAL_JAVA_DIR}/src"
    local ZSERIO_RESULT=$?
    if [ ${ZSERIO_RESULT} -ne 0 ] ; then
        stderr_echo "Zserio compilation failed with return code ${ZSERIO_RESULT}!"
        return 1
    fi
    echo

    check_generated_version "${ZSERIO_VERSION}" "${TUTORIAL_JAVA_DIR}/src"
    if [ $? -ne 0 ] ; then
        return 1
    fi

    "${GIT}" -C "${TUTORIAL_JAVA_DIR}" diff --exit-code > /dev/null
    if [ $? -eq 0 ] ; then
        echo $'\e[1;33m'"Zserio Tutorial Java already up to date."$'\e[0m'
    else
        echo "Committing update of Zserio Tutorial Java."
        "${GIT}" -C "${TUTORIAL_JAVA_DIR}" commit -a -m "Update generated sources to version ${ZSERIO_VERSION}"
        local GIT_RESULT=$?
        if [ ${GIT_RESULT} -ne 0 ] ; then
            stderr_echo "Git failed with return code ${GIT_RESULT}!"
            return 1
        fi
        echo $'\e[1;33m'"Don't forget to check the Zserio Tutorial Java repository!"$'\e[0m'
        echo $'\e[1;33m'"If it is ok, push changes to origin:"$'\e[0m'
        print_push_command "${TUTORIAL_JAVA_DIR}"
        read -n 1 -s -r -p "Press any key to continue..."
        echo
    fi
    echo

    return 0
}

# Update Zserio Tutorial Python repository after new Zserio release.
update_tutorial_python()
{
    exit_if_argc_ne $# 2
    local TUTORIAL_PYTHON_DIR="$1"; shift
    local ZSERIO_VERSION="$1"; shift

    echo "Updating generated sources in Zserio Tutorial Python."
    echo
    local TUTORIAL_PYTHON_BUILD_DIR="${TUTORIAL_PYTHON_DIR}/build"
    rm -rf "${TUTORIAL_PYTHON_BUILD_DIR}"
    mkdir -p "${TUTORIAL_PYTHON_BUILD_DIR}"

    "${PYTHON}" -m virtualenv "${TUTORIAL_PYTHON_BUILD_DIR}"
    local PYTHON_RESULT=$?
    if [ ${PYTHON_RESULT} -ne 0 ] ; then
        stderr_echo "Python failed with return code ${PYTHON_RESULT}!"
        return 1
    fi
    local VENV_BIN_DIR="${TUTORIAL_PYTHON_BUILD_DIR}/bin"
    if [ ! -f "${VENV_BIN_DIR}/activate" ] ; then
        VENV_BIN_DIR="${TUTORIAL_PYTHON_BUILD_DIR}/Scripts"
    fi
    . "${VENV_BIN_DIR}/activate"

    # call pip and zserio from the virtualenv explicitly, a zserio installed elsewhere must not be used
    "${VENV_BIN_DIR}/python" -m pip install zserio=="${ZSERIO_VERSION}"
    local PIP_RESULT=$?
    if [ ${PIP_RESULT} -ne 0 ] ; then
        stderr_echo "Pip install of zserio ${ZSERIO_VERSION} failed with return code ${PIP_RESULT}!"
        return 1
    fi
    local VENV_ZSERIO_VERSION
    VENV_ZSERIO_VERSION=`"${VENV_BIN_DIR}/zserio" --version | "${SED}" -n 's/^Core version //p'`
    if [[ "${VENV_ZSERIO_VERSION}" != "${ZSERIO_VERSION}" ]] ; then
        stderr_echo "Zserio in the virtualenv has version '${VENV_ZSERIO_VERSION}' instead of ${ZSERIO_VERSION}!"
        return 1
    fi
    "${VENV_BIN_DIR}/zserio" -src "${TUTORIAL_PYTHON_DIR}" tutorial.zs -python "${TUTORIAL_PYTHON_DIR}/src"
    local ZSERIO_RESULT=$?
    if [ ${ZSERIO_RESULT} -ne 0 ] ; then
        stderr_echo "Zserio failed with return code ${ZSERIO_RESULT}!"
        return 1
    fi
    echo

    check_generated_version "${ZSERIO_VERSION}" "${TUTORIAL_PYTHON_DIR}/src"
    if [ $? -ne 0 ] ; then
        return 1
    fi

    "${GIT}" -C "${TUTORIAL_PYTHON_DIR}" diff --exit-code > /dev/null
    if [ $? -eq 0 ] ; then
        echo $'\e[1;33m'"Zserio Tutorial Python already up to date."$'\e[0m'
    else
        echo "Committing update of Zserio Tutorial Python."
        "${GIT}" -C "${TUTORIAL_PYTHON_DIR}" commit -a -m "Update generated sources to version ${ZSERIO_VERSION}"
        local GIT_RESULT=$?
        if [ ${GIT_RESULT} -ne 0 ] ; then
            stderr_echo "Git failed with return code ${GIT_RESULT}!"
            return 1
        fi
        echo $'\e[1;33m'"Don't forget to check the Zserio Tutorial Python repository!"$'\e[0m'
        echo $'\e[1;33m'"If it is ok, push changes to origin:"$'\e[0m'
        print_push_command "${TUTORIAL_PYTHON_DIR}"
        read -n 1 -s -r -p "Press any key to continue..."
        echo
    fi
    echo

    return 0
}

# Update Zserio Streamlit repository after new Zserio release.
update_streamlit()
{
    exit_if_argc_ne $# 2
    local STREAMLIT_DIR="$1"; shift
    local ZSERIO_VERSION="$1"; shift

    local REQUIREMENTS_FILE="${STREAMLIT_DIR}/requirements.txt"
    echo -ne "Updating version to ${ZSERIO_VERSION} in Zserio Streamlit..."
    "${SED}" -i -e 's/zserio==[2-9]\+\.[0-9]\+\.[0-9]\+\(\-[A-Za-z0-9]\+\)\?/'"zserio==${ZSERIO_VERSION}"'/' \
            "${REQUIREMENTS_FILE}"
    local SED_RESULT=$?
    if [ ${SED_RESULT} -ne 0 ] ; then
        stderr_echo "Sed failed with return code ${SED_RESULT}!"
        return 1
    fi
    echo "Done"
    echo

    "${GIT}" -C "${STREAMLIT_DIR}" diff --exit-code > /dev/null
    if [ $? -eq 0 ] ; then
        echo $'\e[1;33m'"Zserio Streamlit already up to date."$'\e[0m'
    else
        echo "Committing update of Zserio Streamlit."
        "${GIT}" -C "${STREAMLIT_DIR}" commit -a \
                -m "Change required zserio version to ${ZSERIO_VERSION}"
        local GIT_RESULT=$?
        if [ ${GIT_RESULT} -ne 0 ] ; then
            stderr_echo "Git failed with return code ${GIT_RESULT}!"
            return 1
        fi
        echo $'\e[1;33m'"Don't forget to check the Zserio Streamlit repository!"$'\e[0m'
        echo $'\e[1;33m'"If it is ok, push changes to origin!"$'\e[0m'
        read -n 1 -s -r -p "Press any key to continue..."
        echo
    fi
    echo

    return 0
}

# Print help message.
print_help()
{
    cat << EOF
Description:
    Update all Zserio dependent repositories after Zserio release.

Usage:
    $0 [-h] [-e] [-o <dir>] -v <version> [repository]

Arguments:
    -h, --help       Show this help.
    -e, --help-env   Show help for enviroment variables.
    -o <dir>, --output-directory <dir>
                     Output directory where build and distr are located.
    -v <version>, --version <version>
                     Zserio release version to update the repositories to (e.g. 2.20.0). Required.

Repository can be empty for all repositories or arbitrary combination of
    conan            Update Zserio fork of conan-center-index after new Zserio release
    extension_sample Update Zserio Extension Sample repository after new Zserio release
    tutorial_cpp     Update Zserio Tutorial Cpp repository after new Zserio release
    tutorial_java    Update Zserio Tutorial Java repository after new Zserio release
    tutorial_python  Update Zserio Tutorial Python repository after new Zserio release
    streamlit        Update Zserio Streamlit repository after new Zserio release

Examples:
    $0 -v 2.20.0
    $0 -v 2.20.0 conan extension_sample

EOF
}

# Parse all command line arguments.
#
# Return codes:
# -------------
# 0 - Success. Arguments have been successfully parsed.
# 1 - Failure. Some arguments are wrong or missing.
# 2 - Help switch is present. Arguments after help switch have not been checked.
# 3 - Environment help switch is present. Arguments after help switch have not been checked.
parse_arguments()
{
    local NUM_OF_ARGS=8
    exit_if_argc_lt $# ${NUM_OF_ARGS}
    local PARAM_OUT_DIR_OUT="$1"; shift
    local PARAM_VERSION_OUT="$1"; shift
    local PARAM_CONAN_OUT="$1"; shift
    local PARAM_EXTENSION_SAMPLE_OUT="$1"; shift
    local PARAM_TUTORIAL_CPP_OUT="$1"; shift
    local PARAM_TUTORIAL_JAVA_OUT="$1"; shift
    local PARAM_TUTORIAL_PYTHON_OUT="$1"; shift
    local PARAM_STREAMLIT_OUT="$1"; shift

    eval ${PARAM_CONAN_OUT}=0
    eval ${PARAM_EXTENSION_SAMPLE_OUT}=0
    eval ${PARAM_TUTORIAL_CPP_OUT}=0
    eval ${PARAM_TUTORIAL_JAVA_OUT}=0
    eval ${PARAM_TUTORIAL_PYTHON_OUT}=0
    eval ${PARAM_STREAMLIT_OUT}=0

    local NUM_PARAMS=0
    local ARG="$1"
    while [ $# -ne 0 ] ; do
        case "${ARG}" in
            "-h" | "--help")
                return 2
                ;;

            "-e" | "--help-env")
                return 3
                ;;

            "-o" | "--output-directory")
                if [ $# -eq 1 ] ; then
                    stderr_echo "Missing output directory!"
                    echo
                    return 1
                fi
                eval ${PARAM_OUT_DIR_OUT}="$2"
                shift 2
                ;;

            "-v" | "--version")
                if [ $# -eq 1 ] ; then
                    stderr_echo "Missing version!"
                    echo
                    return 1
                fi
                if [[ ! "$2" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9]+)?$ ]] ; then
                    stderr_echo "Invalid version '$2'!"
                    echo
                    return 1
                fi
                eval ${PARAM_VERSION_OUT}="$2"
                shift 2
                ;;

            "-"*)
                stderr_echo "Invalid switch '${ARG}'!"
                echo
                return 1
                ;;

            "conan")
                eval ${PARAM_CONAN_OUT}=1
                NUM_PARAMS=$((NUM_PARAMS + 1))
                shift 1
                ;;

            "extension_sample")
                eval ${PARAM_EXTENSION_SAMPLE_OUT}=1
                NUM_PARAMS=$((NUM_PARAMS + 1))
                shift 1
                ;;

            "tutorial_cpp")
                eval ${PARAM_TUTORIAL_CPP_OUT}=1
                NUM_PARAMS=$((NUM_PARAMS + 1))
                shift 1
                ;;

            "tutorial_java")
                eval ${PARAM_TUTORIAL_JAVA_OUT}=1
                NUM_PARAMS=$((NUM_PARAMS + 1))
                shift 1
                ;;

            "tutorial_python")
                eval ${PARAM_TUTORIAL_PYTHON_OUT}=1
                NUM_PARAMS=$((NUM_PARAMS + 1))
                shift 1
                ;;

            "streamlit")
                eval ${PARAM_STREAMLIT_OUT}=1
                NUM_PARAMS=$((NUM_PARAMS + 1))
                shift 1
                ;;

            *)
                stderr_echo "Invalid parameter '${ARG}'!"
                echo
                return 1
                ;;
        esac
        ARG="$1"
    done

    if [[ -z "${!PARAM_VERSION_OUT}" ]] ; then
        stderr_echo "Missing release version, use -v <version>!"
        echo
        return 1
    fi

    if [[ ${NUM_PARAMS} == 0 ]] ; then
        eval ${PARAM_CONAN_OUT}=1
        eval ${PARAM_EXTENSION_SAMPLE_OUT}=1
        eval ${PARAM_TUTORIAL_CPP_OUT}=1
        eval ${PARAM_TUTORIAL_JAVA_OUT}=1
        eval ${PARAM_TUTORIAL_PYTHON_OUT}=1
        eval ${PARAM_STREAMLIT_OUT}=1
    fi

    return 0
}

# Main entry of the script to make Zserio release.
main()
{
    # get the project root
    local ZSERIO_PROJECT_ROOT="${SCRIPT_DIR}/.."

    # parse command line arguments
    local PARAM_OUT_DIR="${ZSERIO_PROJECT_ROOT}"
    local ZSERIO_VERSION=""
    local PARAM_CONAN
    local PARAM_EXTENSION_SAMPLE
    local PARAM_TUTORIAL_CPP
    local PARAM_TUTORIAL_JAVA
    local PARAM_TUTORIAL_PYTHON
    local PARAM_STREAMLIT
    parse_arguments PARAM_OUT_DIR ZSERIO_VERSION PARAM_CONAN PARAM_EXTENSION_SAMPLE \
            PARAM_TUTORIAL_CPP PARAM_TUTORIAL_JAVA PARAM_TUTORIAL_PYTHON PARAM_STREAMLIT "$@"
    local PARSE_RESULT=$?
    if [ ${PARSE_RESULT} -eq 2 ] ; then
        print_help
        return 0
    elif [ ${PARSE_RESULT} -eq 3 ] ; then
        print_release_help_env
        return 0
    elif [ ${PARSE_RESULT} -ne 0 ] ; then
        return 1
    fi

    # get the output directory (the absolute path is necessary)
    convert_to_absolute_path "${PARAM_OUT_DIR}" PARAM_OUT_DIR

    # set global variables
    set_post_release_global_variables ${PARAM_CONAN} ${PARAM_EXTENSION_SAMPLE} \
            ${PARAM_TUTORIAL_CPP} ${PARAM_TUTORIAL_JAVA} ${PARAM_TUTORIAL_PYTHON} ${PARAM_STREAMLIT}
    if [ $? -ne 0 ] ; then
        return 1
    fi

    echo "Updating dependent repositories after new Zserio release ${ZSERIO_VERSION}."
    echo

    if [[ ${PARAM_CONAN} == 1 ]] ; then
        update_conan "${ZSERIO_CONAN_DIR}" "${ZSERIO_VERSION}"
        if [ $? -ne 0 ] ; then
            return 1
        fi
    fi

    if [[ ${PARAM_EXTENSION_SAMPLE} == 1 ]] ; then
        update_extension_sample "${ZSERIO_EXTENSION_SAMPLE_DIR}" "${ZSERIO_VERSION}"
        if [ $? -ne 0 ] ; then
            return 1
        fi
    fi

    if [[ ${PARAM_TUTORIAL_CPP} == 1 ]] ; then
        update_tutorial_cpp "${ZSERIO_TUTORIAL_CPP_DIR}" "${ZSERIO_VERSION}"
        if [ $? -ne 0 ] ; then
            return 1
        fi
    fi

    if [[ ${PARAM_TUTORIAL_JAVA} == 1 ]] ; then
        update_tutorial_java "${ZSERIO_TUTORIAL_JAVA_DIR}" "${ZSERIO_VERSION}"
        if [ $? -ne 0 ] ; then
            return 1
        fi
    fi

    if [[ ${PARAM_TUTORIAL_PYTHON} == 1 ]] ; then
        update_tutorial_python "${ZSERIO_TUTORIAL_PYTHON_DIR}" "${ZSERIO_VERSION}"
        if [ $? -ne 0 ] ; then
            return 1
        fi
    fi

    if [[ ${PARAM_STREAMLIT} == 1 ]] ; then
        update_streamlit "${ZSERIO_STREAMLIT_DIR}" "${ZSERIO_VERSION}"
        if [ $? -ne 0 ] ; then
            return 1
        fi
    fi

    return 0
}

main "$@"
