#!/bin/bash

_ask() {
    local A=""
    while true; do
        read -p $'\x1b\x5b1;34m'"$1 [y/n]? "$'\x1b\x5b0m' A || continue
        [ "${A,,}" = "y" ] && return 0
        [ "${A,,}" = "n" ] && return 1
    done
}

_colordiff() {
    if which colordiff &>/dev/null; then
        colordiff
    else
        sed -E 's/^([-<].*)$/'$'\x1b\x5b''0;31m\1'$'\x1b\x5b''0m/ ; s/^([\+>].*)$/'$'\x1b\x5b''0;32m\1'$'\x1b\x5b''0m/ ; s/^([@0-9].*)$/'$'\x1b\x5b''0;36m\1'$'\x1b\x5b''0m/'
    fi
}

WORKDIR=".svnstash.wip"
STASHDIR=".svnstash"

if [ ! -d '.svn' ]; then
    echo "Must be called from svn repo root." >&2
    exit 1
fi

if [ -d "$WORKDIR" -a -d "$STASHDIR" ]; then
    echo "Partial stash detected. Aborting." >&2
    exit 1
elif [ -d "$WORKDIR" ]; then
    if ! _ask "Partial stash detected. Start from scratch"; then
        exit 1
    fi
    rm -rf "$WORKDIR" || exit 1
elif [ -d "$STASHDIR" ]; then
    grep --color=never --no-filename '^+++ ' "$STASHDIR/"*.patch | cut -b 5- | sort | uniq -c
    if ! _ask "Unstash all hunks"; then
        exit 0
    fi
    for H in "$STASHDIR/"*; do
        if ! patch -p0 --forward -i "$H" --batch --fuzz=0 --unified --no-backup-if-mismatch --reject-file=-; then
            if ! _ask "Retry with merging"; then
                exit 1
            fi
            patch -p0 --forward -i "$H" --batch --merge --unified --no-backup-if-mismatch --reject-file=-
            if [ "$?" -gt 1 ]; then # TODO:
                echo "Failed." >&2
                exit 1
            fi
        fi
        rm "$H"
    done
    rm -rfv "$STASHDIR"
    echo "Done." >&2
    exit 0
fi
mkdir "$WORKDIR" || exit 1


STASH_NO=0

svn status --ignore-externals "." > "$WORKDIR/svnstatus" || exit 1
exec 3<"$WORKDIR/svnstatus"
while IFS= read -r -u 3 SVNSTATUS; do

    ST="${SVNSTATUS:0:7}"
    FN="${SVNSTATUS:8}"
    FNH=`shasum <<<"$FN" | cut -d ' ' -f 1`
    if [ "$ST" = '       ' ]; then
        continue
    elif [ "$ST" != 'M      ' ]; then
        echo "Ignoring $FN" >&2
        continue
    fi

    HUNKNO=0
    DIFFHEAD=""
    HUNKDESC=""
    HUNK=""

    _end_hunk() {
        printf '%s' "$DIFFHEAD"
        printf '%s\n%s' "$HUNKDESC" "$HUNK" | _colordiff "diff"
        if _ask "Stash this hunk"; then
            STASH_NO=$(( $STASH_NO + 1 ))
            HH=`printf "%s/%s.%s.%03d.patch" "$WORKDIR" "${FN##*/}" "$FNH" "$HUNKNO"`
            printf -- '--- /dev/null\n+++ %s\n' "$FN" > "$HH"
            printf '%s\n%s' "$HUNKDESC" "$HUNK" >> "$HH"
        fi
    }

    svn diff --internal-diff --extensions --unified "$FN" > "$WORKDIR/svndiff.$FNH" || continue
    exec 4<"$WORKDIR/svndiff.$FNH"
    while IFS= read -r -u 4 SVNDIFF; do
        DF="${SVNDIFF:0:1}"
        if [ "$DF" = '@' ]; then
            HUNKNO=$(( $HUNKNO + 1 ))
            if [ -n "$HUNKDESC" ]; then
                _end_hunk
            fi
            HUNKDESC="$SVNDIFF"
            HUNK=""
        elif [ -z "$HUNKDESC" ]; then
            DIFFHEAD="$DIFFHEAD$SVNDIFF"$'\n'
        else
            HUNK="$HUNK$SVNDIFF"$'\n'
        fi
    done
    exec 4<&-

    if [ -n "$HUNKDESC" ]; then
        HUNKNO=$(( $HUNKNO + 1 ))
        _end_hunk
    fi
done
exec 3<&-


if [ "$STASH_NO" -eq 0 ]; then
    echo "Nothing to do." >&2
    rm -rf "$WORKDIR"
    exit 0
fi
if ! _ask "Stash $STASH_NO hunks"; then
    rm -rf "$WORKDIR"
    exit 0
fi
mkdir -v "$STASHDIR" || exit 1


PREVF=""
for H in "$WORKDIR/"*.patch; do
    F=`head -n 2 "$H" | tail -n 1 | sed 's/^+++ //'`
    if [ "$F" != "$PREVF" ]; then
        PREVF="$F"
        STASH_COUNT=0
    fi

    HEAD=`head -n 3 "$H" | tail -n 1`
    HEAD="${HEAD#@@ -}"
    HEAD="${HEAD% @@}"
    FROM="${HEAD% \+*}"
    TO="${HEAD#* \+}"
    FROM_START="${FROM%,*}"
    FROM_COUNT="${FROM#*,}"
    TO_START="${TO%,*}"
    TO_COUNT="${TO#*,}"

    NEW_START=$(( $TO_START - $STASH_COUNT ))
    STASH_COUNT=$(( $STASH_COUNT + $TO_COUNT - $FROM_COUNT ))

    HH="$STASHDIR/${H##*/}"
    sed "s/^@.*/@@ -$NEW_START,$FROM_COUNT +$NEW_START,$TO_COUNT @@/" "$H" > "$HH"

    if patch -p0 --reverse -i "$HH" --batch --fuzz=0 --unified --no-backup-if-mismatch --reject-file=-; then
        sed "s/^@.*/@@ -$TO_START,$FROM_COUNT +$TO_START,$TO_COUNT @@/" "$H" > "$HH"
        rm "$H"
    else
        rm "$HH"
        exit 1
    fi
done


rm -rf "$WORKDIR"
echo "Done." >&2
exit 0