# Set TAG_RELEASE to ON/TRUE/1 to increment minor, or to new version, or to
# UNDO to undo release.

# Increment version.
# Update CHANGELOG.md with git log.
# Make release commit.
# Tag release commit.

if(NOT EXISTS "${CMAKE_SOURCE_DIR}/.git")
    message(FATAL_ERROR "releases can only be done from a git clone")
endif()

find_package(Git)

if(NOT GIT_FOUND)
    message(FATAL_ERROR "git is required to make a release")
endif()

find_program(GPG_EXECUTABLE gpg)

if(NOT GPG_EXECUTABLE)
    message(FATAL_ERROR "gpg must be installed and set up with your key to make a release")
endif()

# From: https://gist.github.com/alorence/59d18896aaad5188b7b4.
macro(CURRENT_DATE RESULT)
    if(CMAKE_HOST_SYSTEM MATCHES Windows OR ((NOT DEFINED CMAKE_HOST_SYSTEM) AND WIN32))
        execute_process(COMMAND "cmd" " /C date /T" OUTPUT_VARIABLE ${RESULT})
        string(REGEX REPLACE ".*(..)/(..)/(....).*" "\\3-\\1-\\2" ${RESULT} ${${RESULT}})
    else()
        execute_process(COMMAND "date" "+%Y-%m-%d" OUTPUT_VARIABLE ${RESULT})
    endif()
endmacro()

function(make_release_commit_and_tag)

    # First make sure we are on master.

    execute_process(
        COMMAND ${GIT_EXECUTABLE} rev-parse --short --abbrev-ref=strict HEAD
        OUTPUT_VARIABLE git_branch
        OUTPUT_STRIP_TRAILING_WHITESPACE
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    )

    if(NOT git_branch STREQUAL master)
        message(FATAL_ERROR "you must be on the git master branch to release")
    endif()

    execute_process(
        COMMAND ${GIT_EXECUTABLE} status --porcelain=2
        OUTPUT_VARIABLE git_status
        OUTPUT_STRIP_TRAILING_WHITESPACE
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    )

    string(LENGTH "${git_status}" git_status_length)

    if(NOT git_status_length EQUAL 0)
        message(FATAL_ERROR "working tree must be clean to do a release")
    endif()

    execute_process(
        COMMAND ${GIT_EXECUTABLE} tag --sort=-v:refname
        OUTPUT_VARIABLE git_tags
        OUTPUT_STRIP_TRAILING_WHITESPACE
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    )

    string(REGEX REPLACE ";"     "\\\\;" git_tags_lines "${git_tags}")
    string(REGEX REPLACE "\r?\n" ";"     git_tags_lines "${git_tags_lines}")

    foreach(line ${git_tags_lines})
        if(line MATCHES "^v[0-9]")
            set(git_last_tag ${line})
            break()
        endif()
    endforeach()

    if(NOT DEFINED git_last_tag)
        message(FATAL_ERROR "cannot find last release tag")
    endif()

    execute_process(
        COMMAND ${GIT_EXECUTABLE} log ${git_last_tag}.. "--pretty=format:* %h - %s [%aL]"
        OUTPUT_VARIABLE release_log
        OUTPUT_STRIP_TRAILING_WHITESPACE
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    )

    # Calculate new release version, unless it was passed in.

    if(TAG_RELEASE STREQUAL UNDO)
        execute_process(
            COMMAND ${GIT_EXECUTABLE} tag -d ${git_last_tag}
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        )

        execute_process(
            COMMAND ${GIT_EXECUTABLE} reset HEAD~1
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        )

        execute_process(
            COMMAND ${GIT_EXECUTABLE} checkout HEAD CHANGELOG.md
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        )

        message(FATAL_ERROR [=[
**** RELEASE COMMIT AND TAG HAVE BEEN REMOVED ****"

The git and working tree state have been restored to their state before the
TAG_RELEASE operation.

Ignore the following cmake error.
]=])
    elseif(NOT (TAG_RELEASE STREQUAL TRUE OR TAG_RELEASE STREQUAL ON OR TAG_RELEASE STREQUAL 1))
        set(new_tag ${TAG_RELEASE})
    else()
        string(REGEX MATCH   "\\.[0-9]+$"    last_tag_minor_part     ${git_last_tag})
        string(REGEX REPLACE "\\.[0-9]+$" "" last_tag_minor_stripped ${git_last_tag})

        string(SUBSTRING ${last_tag_minor_part} 1 -1 last_tag_minor)

        math(EXPR last_minor_incremented "${last_tag_minor} + 1")

        string(CONCAT new_tag ${last_tag_minor_stripped} . ${last_minor_incremented})
    endif()

    # Make sure tag begins with "v".

    if(NOT new_tag MATCHES "^v")
        set(new_tag "v${new_tag}")
    endif()

    # But remove the "v" for the version string.

    string(REGEX REPLACE "^v" "" new_version ${new_tag})

    current_date(current_date)

    # Rewrite CHANGELOG.md

    # First make a copy for backing out.
    configure_file(
        ${CMAKE_SOURCE_DIR}/CHANGELOG.md
        ${CMAKE_BINARY_DIR}/CHANGELOG.md.orig
        COPYONLY
    )

    # Now read it and add the new release.

    include(FileIterator)

    fi_open_file(${CMAKE_SOURCE_DIR}/CHANGELOG.md)

    set(work_file ${CMAKE_BINARY_DIR}/CHANGELOG.md.work)

    file(REMOVE ${work_file})

    set(last_release_found FALSE)

    while(NOT fi_done)
        fi_get_next_line()

        if(NOT last_release_found AND fi_line MATCHES "^## \\[[0-9]")
            set(last_release_found TRUE)

            set(tag_line "## [${new_version}] - ${current_date}")

            string(LENGTH "${tag_line}" tag_line_length)

            unset(tag_line_under_bar)

            foreach(i RANGE 1 ${tag_line_length})
                set(tag_line_under_bar "=${tag_line_under_bar}")
            endforeach()

            file(APPEND ${work_file} ${tag_line} "\n")
            file(APPEND ${work_file} ${tag_line_under_bar} "\n")
            file(APPEND ${work_file} "${release_log}" "\n")
            file(APPEND ${work_file} "\n")
        endif()

        file(APPEND ${work_file} "${fi_line}" "\n")
    endwhile()

    # Convert to UNIX line endings on Windows, just copy the file otherwise.

    if(CMAKE_HOST_SYSTEM MATCHES Windows OR ((NOT DEFINED CMAKE_HOST_SYSTEM) AND WIN32))
        execute_process(
            COMMAND powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command [=[
                $text = [IO.File]::ReadAllText("CHANGELOG.md.work") -replace "`r`n", "`n"
                [IO.File]::WriteAllText("CHANGELOG.md", $text)
            ]=]
            WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
        )
    else()
        configure_file(
            ${CMAKE_BINARY_DIR}/CHANGELOG.md.work
            ${CMAKE_BINARY_DIR}/CHANGELOG.md
            COPYONLY
        )
    endif()

    # Copy the new file and add it to the commit.

    configure_file(
        ${CMAKE_BINARY_DIR}/CHANGELOG.md
        ${CMAKE_SOURCE_DIR}/CHANGELOG.md
        COPYONLY
    )

    execute_process(
        COMMAND ${GIT_EXECUTABLE} add CHANGELOG.md
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    )

    # Make the release commit.

    execute_process(
        COMMAND ${GIT_EXECUTABLE} commit -m "release ${new_tag}" --signoff -S
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    )

    # Make release tag.

    execute_process(
        COMMAND ${GIT_EXECUTABLE} tag -s -m${new_tag} ${new_tag}
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    )

    message(FATAL_ERROR [=[

Release commit and tag generated.

**** IF YOU ARE SURE YOU WANT TO RELEASE ****

Run the following commands to push the release commit and tag:

    git push
    git push --tags

**** TO UNDO THE RELEASE ****

To rollback these changes, run this command:

    cmake .. -DTAG_RELEASE=UNDO

Ignore the "configuration incomplete" message following, this mode does not
build anything.

]=])
endfunction()

make_release_commit_and_tag()
