#!/bin/bash
#
# get_release_tag
#

###
### settings
###

set -e               # exit on any uncaught error
set +o histexpand    # don't expand history expressions
shopt -s nocasematch # case-insensitive regular expressions

###
### global variables
###

opt_next=''
opt_latest=''
opt_verbose=''

###
### functions
###

warn () {
    local message="$@"
    message="${message//\\t/$'\011'}"
    message="${message//\\n/$'\012'}"
    message="${message%"${message##*[![:space:]]}"}"
    printf "%s\n" "$message" 1>&2
}

die () {
    warn "$@"
    exit 1
}

cd_to_project_root () {
    local script_dir="$(/usr/bin/dirname "$0")"
    cd "$script_dir"
    local git_root="$(git rev-parse --show-toplevel)"
    if [[ -z "$git_root" ]]; then
        die "ERROR: Could not find git project root"
    fi
    cd "$git_root"
}

verify_git_object_is_new () {
    if git rev-parse --verify "$1" >/dev/null 2>&1; then
        die "\nERROR: Proposed new tag: '$1' already exists as a commit object\n\n"
    fi
}

sanity_check_parsed_version () {
    if [[ $# -ne 3 ]]; then
        die "ERROR: Could not parse version tag: wrong number of elements"
    fi
    if ! [[ $1 =~ ^v[0-9]+$ ]]; then
        die "ERROR: Could not parse version tag: does not start with v[0-9]"
    fi
}

output_proposed_tag () {
    local new_tag="$1.$2.$3"
    sanity_check_parsed_version "$@"
    verify_git_object_is_new "$new_tag"
    if [[ -n "$opt_verbose" ]]; then
        printf "Latest tag\t%s\n" "$latest_tag"
        printf "Proposed new tag\t%s\n" "$new_tag"
    else
        printf "%s\n" "$new_tag"
    fi
}

generate_next_major_tag () {
    local -a version_elts
    IFS='.' read -a version_elts <<< "$1"
    sanity_check_parsed_version "${version_elts[@]}"
    version_elts[0]="${version_elts[0]#v}"  # mangle v<digit> into number
    (( version_elts[0] += 1 ))              # increment version field
    version_elts[0]="v${version_elts[0]}"   # restore text v<digit>
    version_elts[1]='0'                     # reset minor field
    version_elts[2]='0'                     # reset patch field
    output_proposed_tag "${version_elts[@]}"
}

generate_next_minor_tag () {
    local -a version_elts
    IFS='.' read -a version_elts <<< "$1"
    sanity_check_parsed_version "${version_elts[@]}"
    (( version_elts[1] += 1 ))      # increment minor field
    version_elts[2]='0'             # reset patch field
    output_proposed_tag "${version_elts[@]}"
}

generate_next_patch_tag () {
    local -a version_elts
    IFS='.' read -a version_elts <<< "$1"
    sanity_check_parsed_version "${version_elts[@]}"
    (( version_elts[2] += 1 ))      # increment patch field
    output_proposed_tag "${version_elts[@]}"
}

process_args () {
    local arg
    for arg in "$@"; do
        if [[ $arg =~ ^-+h(elp)?$ ]]; then
            printf "get_release_tag [ -latest | -next | -patch | -major | -verbose | -help ]

Retrieve or calculate a release tag.

Arguments:

   -latest   Show the most recent tag in the repository, which is typically
             the designation of the latest release.  This is the default
             when invoked with no arguments.

   -next     Calculate the next minor-release tag bump and return it.

   -patch    Calculate the next patch-release tag bump and return it.
             Overrides -next if both are given.

   -major    Calculate the next major-release tag bump and return it.
             Overrides -next if both are given.

   -verbose  Output a table, possibly including both latest existing and
             proposed new tags.

See RELEASING.md for more information.

"
            exit
        elif [[ $arg =~ ^-+v(erbose)?$ ]]; then
            opt_verbose='true'
        elif [[ $arg =~ ^-+next$ ]]; then
            if [[ -z "$opt_next" ]]; then
                opt_next='minor'
            fi
        elif [[ $arg =~ ^-+patch$ ]]; then
            if [[ "$opt_next" == 'major' ]]; then
                die "ERROR: -patch is incompatible with -major"
            fi
            opt_next='patch'
        elif [[ $arg =~ ^-+major$ ]]; then
            if [[ "$opt_next" == 'patch' ]]; then
                die "ERROR: -patch is incompatible with -major"
            fi
            opt_next='major'
        elif [[ $arg =~ ^-+latest$ ]]; then
            opt_latest='true'
        else
            die "ERROR: Unknown argument '$arg'"
        fi
        if [[ -n "$opt_latest" && -n "$opt_next" ]]; then
            die "ERROR: -latest is incompatible with -next/-patch/-major"
        fi
    done
}

###
### main
###

_get_release_tag () {
    cd_to_project_root
    local latest_tag="$(git describe --tags --abbrev=0 2>/dev/null)"
    if [[ -z "$latest_tag" ]]; then
        die "ERROR: No recent tag found"
    elif [[ -z "$opt_next" ]]; then
        if [[ -n "$opt_verbose" ]]; then
            printf "Latest tag\t"
        fi
        printf "%s\n" "$latest_tag"
    elif [[ "$opt_next" == 'major' ]]; then
        generate_next_major_tag "$latest_tag"
    elif [[ "$opt_next" == 'minor' ]]; then
        generate_next_minor_tag "$latest_tag"
    elif [[ "$opt_next" == 'patch' ]]; then
        generate_next_patch_tag "$latest_tag"
    else
        die "ERROR: Should not happen. Unknown argument?"
    fi
}

process_args "${@}"

# dispatch main
_get_release_tag

#
