#!/bin/sh

GIT_NOTARY_VERSION=2.3.1
GIT_NOTARY_NAMESPACE=${GIT_NOTARY_NAMESPACE:-'versioning'}

# notes [branch] [base] [namespace]
notes() {
    BRANCH=${1-'develop'}
    BASE=${2:-$(git describe --tags --abbrev=0)}
    NAMESPACE=${3:-${GIT_NOTARY_NAMESPACE}}

    git rev-list --topo-order ${BASE}..${BRANCH} --reverse | while read OBJECT; do
        printf "${OBJECT} "
        git notes --ref=${NAMESPACE} show ${OBJECT} 2> /dev/null || echo
    done | grep -E '(MAJOR|MINOR|PATCH)$'
}

# squash
squash() {
    while read OBJECT_CHANGE; do
        OBJECT=$(echo ${OBJECT_CHANGE} | awk '{ print $1 }')
        CHANGE=$(echo ${OBJECT_CHANGE} | awk '{ print $2 }')

        case ${CHANGE} in
            MAJOR)
                RESULT=MAJOR;;
            MINOR)
                test "${RESULT}" != MAJOR && RESULT=MINOR;;
            PATCH)
                test -z "${RESULT}" && RESULT=PATCH;;
        esac
    done

    test ! -z "${RESULT}" && echo ${OBJECT} ${RESULT}
}

# squeeze <up|down>
squeeze() {
    case ${1} in
        d|down|f|first|o|old*)
            DIRECTION=DOWN;;
        u|up|l|last|n|new*)
            DIRECTION=UP;;
        *)
            echo 'git-notary squeeze requires a direction (up or down)' >&2
            exit 23;;
    esac

    while read OBJECT_CHANGE; do
        OBJECT=$(echo ${OBJECT_CHANGE} | awk '{ print $1 }')
        CHANGE=$(echo ${OBJECT_CHANGE} | awk '{ print $2 }')

        if test "${CHANGE}" != "${LAST_CHANGE}"; then
            case ${DIRECTION} in
                DOWN)
                    echo ${OBJECT} ${CHANGE};;
                UP)
                    test ! -z "${LAST_OBJECT}" && echo ${LAST_OBJECT} ${LAST_CHANGE};;
            esac
        fi

        LAST_OBJECT=${OBJECT}
        LAST_CHANGE=${CHANGE}
    done

    test "${DIRECTION}" = UP && echo ${LAST_OBJECT} ${LAST_CHANGE}
}

# versions [initial]
versions() {
    set -o errexit
    VERSION=${1:-$(git describe --tags --abbrev=0)}

    MAJOR=$(echo ${VERSION} | awk -F . '{ print $1 }')
    MINOR=$(echo ${VERSION} | awk -F . '{ print $2 }')
    PATCH=$(echo ${VERSION} | awk -F . '{ print $3 }')

    next() {
        echo ${1} + 1 | bc
    }

    while read OBJECT_CHANGE; do
        OBJECT=$(echo ${OBJECT_CHANGE} | awk '{ print $1 }')
        CHANGE=$(echo ${OBJECT_CHANGE} | awk '{ print $2 }')

        case ${CHANGE} in
            MAJOR)
                MAJOR=$(next ${MAJOR})
                MINOR=0
                PATCH=0
                ;;
            MINOR)
                MINOR=$(next ${MINOR})
                PATCH=0
                ;;
            PATCH)
                PATCH=$(next ${PATCH})
                ;;
        esac

        VERSION=${MAJOR}.${MINOR}.${PATCH}
        echo ${OBJECT} ${VERSION}
    done
}

# tags [--apply]
tags() {
    while read OBJECT_TAG; do
        OBJECT=$(echo ${OBJECT_TAG} | awk '{ print $1 }')
        TAG=$(echo ${OBJECT_TAG} | awk '{ print $2 }')

        if test "${1}" = '--apply'; then
            git tag ${TAG} ${OBJECT}
        else
            echo git tag ${TAG} ${OBJECT}
        fi
    done
}

# fetch [remote] [namespace]
fetch() {
    FORCE=""
    if test "${1:-}" = "--force"; then
        shift
        FORCE="+"
    fi
    REMOTE=${1:-'origin'}
    NAMESPACE=${2:-${GIT_NOTARY_NAMESPACE}}

    git fetch ${REMOTE} ${FORCE}refs/notes/${NAMESPACE}:refs/notes/${NAMESPACE}
}

# pull [remote] [namespace] [strategy]
pull() {
    REMOTE=${1:-'origin'}
    NAMESPACE=${2:-${GIT_NOTARY_NAMESPACE}}
    STRATEGY=${3:-'theirs'}

    git fetch ${REMOTE} refs/notes/${NAMESPACE}
    env GIT_NOTES_REF=refs/notes/${NAMESPACE} git notes merge -s ${STRATEGY} FETCH_HEAD
}

# push [remote] [namespace]
push() {
    REMOTE=${1:-'origin'}
    NAMESPACE=${2:-${GIT_NOTARY_NAMESPACE}}

    git push --no-verify ${REMOTE} refs/notes/${NAMESPACE}
}

# new <major|minor|patch> [object] [namespace]
new() {
    CHANGE=$(echo ${1} | tr [:lower:] [:upper:])
    OBJECT=${2:-HEAD}
    NAMESPACE=${3:-${GIT_NOTARY_NAMESPACE}}

    if echo ${CHANGE} | grep -qE '^(MAJOR|MINOR|PATCH)$'; then
        git notes --ref=${NAMESPACE} add --message ${CHANGE} ${OBJECT}
    else
        echo MAJOR MINOR and PATCH are valid. ${CHANGE} is not.
        exit 23
    fi
}

# delta (--squash) [object] [base] [namespace]
delta() {
    if test "${1}" = '--squash'; then
       SQUASH=true
       shift
    fi

    OBJECT=${1:-HEAD}
    BASE=${2:-$(git describe --tags --abbrev=0)}
    NAMESPACE=${3:-${GIT_NOTARY_NAMESPACE}}

    if test "${SQUASH}" = 'true'; then
        NEW=$(git-notary notes ${OBJECT} ${BASE} ${NAMESPACE} | git-notary squash | git-notary versions | awk '{ print $2 }')
    else
        NEW=$(git-notary notes ${OBJECT} ${BASE} ${NAMESPACE} | git-notary versions | tail -1 | awk '{ print $2 }')
    fi

    LATEST_TAG_ON_BASE=$(git describe --tags --abbrev=0 ${BASE})
    OLD=${LATEST_TAG_ON_BASE:-'0.0.0'}
    echo "${OLD} -> ${NEW}"
}

# undo [object] [namespace]
undo() {
    OBJECT=${1:-HEAD}
    NAMESPACE=${2:-${GIT_NOTARY_NAMESPACE}}

    git notes --ref=${NAMESPACE} remove ${OBJECT}
}

# version
version() {
    echo "git-notary ${GIT_NOTARY_VERSION}"
}

# help
help() {
    cat <<EOF
$(version)
usage:
       git-notary new <major|minor|patch> [object] [namespace]
       git-notary undo [object] [namespace]
       git-notary delta [--squash] [object] [base] [namespace]

       git-notary fetch [remote] [namespace]
       git-notary push [remote] [namespace]

       git-notary notes [branch] [base] [namespace]
       git-notary versions [initial]
       git-notary tags [--apply]
EOF
}

# notary <command> [args]
notary() {
    COMMAND=${1}
    shift
    case ${COMMAND} in
        N|notes)
            notes ${@};;
        V|versions)
            versions ${@};;
        T|tags)
            tags ${@};;
        S|squash)
            squash ${@};;
        Z|squeeze)
            squeeze ${@};;
        n|new)
            new ${@};;
        u|undo)
            undo ${@};;
        d|delta)
            delta ${@};;
        P|push)
            push ${@};;
        f|fetch)
            fetch ${@};;
        fm|fetch-merge|pull)
            pull ${@};;
        M|major)
            new MAJOR ${@};;
        m|minor)
            new MINOR ${@};;
        p|patch)
            new PATCH ${@};;
        v|version|-v|--version)
            version;;
        h|help|-h|--help)
            help;;
        *)
            help
            exit 1
            ;;
    esac
}

notary "${@:-}"
