# nextvi patch2vi utility functions
#
# Source on demand:   . ./patch2vi.sh
# Then call:          patch2vi -d input.patch [output.sh]
#                     gitdiff2vi [flags] [output.sh]
#                     edelta file [inplace]
#                     redelta input.sh
#                     reindex [file.sh | 1]
#                     migrate_deltas
#                     run_patches [1|2]
#                     conv [1]
#
# Requires the ./patch2vi binary in the current directory.

# Convert a .patch file to a nextvi shell script.
# Usage: patch2vi <flag> input.patch [output.sh]
patch2vi() {
	if [ -z "$1" ]; then
		echo "Usage: patch2vi <flag> input.patch [output.sh]" >&2
		return 1
	fi

	input="$2"
	if [ -n "$3" ]; then
		output="$3"
	else
		# Replace .patch extension with .sh, or append .sh if no .patch ext
		case "$input" in
			*.patch) output="${input%.patch}.sh" ;;
			*) output="${input}.sh" ;;
		esac
	fi

	./patch2vi "$1" < "$input" > "$output"
	chmod +x "$output"
	echo "Generated: $output"
}

patch2vi_stashed() {
	# Revert only real modifications to pre-patch so fuzz can validate
	# against the original. Exclude $2 (just rewrote it) and new files
	# (which have no pre-patch orig). Intent-to-add (git add -N) entries
	# break both stash push and pop, so unstage them around the stash.
	ita=$(git diff --name-only --diff-filter=A -- ":!$2")
	[ -n "$ita" ] && git reset -q -- $ita
	stashed=$(git diff --name-only --diff-filter=MD -- ":!$2")
	[ -n "$stashed" ] && git stash push -q -- $stashed
	./patch2vi "$1" "$2" > "_$2"
	[ -n "$stashed" ] && git stash pop -q
	[ -n "$ita" ] && git add -N -- $ita
	mv "_$2" "$2"
	chmod +x "$2"
	echo "Generated: $2" >&2
}

# Convert the current git diff to a nextvi shell script.
# Usage: gitdiff2vi [flags] [output.sh]
# If output.sh is omitted, writes to stdout.
gitdiff2vi() {
	if [ -n "$2" ]; then
		case "$1" in "-d"*)
			git diff -- ":!$2" > /tmp/tmp.patch
			sed '/^=== PATCH2VI PATCH ===$/q' "$2" > /tmp/tmp.sh
			mv /tmp/tmp.sh "$2"
			cat /tmp/tmp.patch >> "$2"
			patch2vi_stashed "$1" "$2"
		;;
		*)
			git diff -- ":!$2" > "$2"
			patch2vi_stashed "$1" "$2"
		;;
		esac
	else
		git diff | ./patch2vi $1
	fi
}

# Edit embedded deltas in a nextvi shell script.
# Usage: edelta file [inplace]
edelta() {
	if [ ! -x ./patch2vi ]; then
		git checkout patch2vi
		./build_patch2vi.sh build
		git checkout patches
	fi

	if [ "$2" ]; then
		patch2vi -i "$1"
	else
		patch2vi -d "$1"
	fi
	mv "${1}.sh" "$1"
}

# Regenerate the embedded delta of a script from the current staged diff.
# Usage: redelta input.sh
redelta() {
	if [ -z "$1" ]; then
		echo "Usage: redelta input.sh" >&2
		return 1
	fi

	git add .
	git diff --staged > /tmp/tmp.patch
	git reset
	sed '/^=== PATCH2VI PATCH ===$/q' "$1" > /tmp/tmp.sh
	mv /tmp/tmp.sh "$1"
	cat /tmp/tmp.patch >> "$1"
	patch2vi_stashed -d "$1"
}

# Re-run patch2vi-generated script(s) and refresh their embedded delta from the
# resulting diff. With a file arg, only that script; otherwise all of them
# (pass 1 to also rebuild via ./cbuild.sh after each).
# Usage: reindex [file.sh | 1]
reindex() (
	set -e
	if [ -f "$1" ]; then
		head -2 "$1" | grep -q "Generated by patch2vi" || exit 1
		printf "%s\n" "SCRIPT: $1"
		./"$1"
		new_files=$(git ls-files --others --exclude-standard)
		if [ -n "$new_files" ]; then
			printf '%s\n' $new_files | xargs git add -N
		fi
		git diff > /tmp/tmp.patch
		git checkout . &>/dev/null
		if [ -n "$new_files" ]; then
			printf '%s\n' $new_files | xargs git reset HEAD -- &>/dev/null
			printf '%s\n' $new_files | xargs rm -f
		fi
		sed '/^=== PATCH2VI PATCH ===$/q' "$1" > /tmp/tmp.sh
		cat /tmp/tmp.patch >> /tmp/tmp.sh
		cp /tmp/tmp.sh "$1"
		chmod +x "$1"
		git add "$1"
		printf "\n"
		exit 0
	fi
	for s in *.sh
	do
		head -2 "$s" | grep -q "Generated by patch2vi" || continue
		printf "%s\n" "SCRIPT: $s"
		./"$s"
		[ "$1" = "1" ] && ./cbuild.sh build
		new_files=$(git ls-files --others --exclude-standard)
		if [ -n "$new_files" ]; then
			printf '%s\n' $new_files | xargs git add -N
		fi
		git diff > /tmp/tmp.patch
		git checkout . &>/dev/null
		if [ -n "$new_files" ]; then
			printf '%s\n' $new_files | xargs git reset HEAD -- &>/dev/null
			printf '%s\n' $new_files | xargs rm -f
		fi
		# Replace embedded patch: keep everything up to the marker, append new diff
		sed '/^=== PATCH2VI PATCH ===$/q' "$s" > /tmp/tmp.sh
		cat /tmp/tmp.patch >> /tmp/tmp.sh
		cp /tmp/tmp.sh "$s"
		chmod +x "$s"
		git add "$s"
		printf "\n"
	done
	rm -f tmp *.orig *.rej
)

# DESTRUCTIVE. Discards the stored delta in every patch2vi-generated script and
# regenerates it from scratch in the new format. Only for migrating to a format
# whose breaking changes cannot be reconciled otherwise. Rare.
# Usage: migrate_deltas
migrate_deltas() (
	set -e
	export P2VI_EX='q'
	for s in *.sh
	do
		head -2 "$s" | grep -q "Generated by patch2vi" || continue
		edelta "$s" 1
	done
)

# Run every patch2vi-generated script, optionally rebuilding or refreshing patches.
# Usage: run_patches [1|2]
#   1 = rebuild via ./cbuild.sh after each script
#   2 = regenerate each script's .patch from the resulting diff
run_patches() (
	set -e
	for s in *.sh
	do
		head -2 "$s" | grep -q "Generated by patch2vi" || continue
		printf "%s\n" "RUNNING: $s"
		./"$s"
		[ "$1" = "1" ] && ./cbuild.sh build
		new_files=$(git ls-files --others --exclude-standard | grep -v '\.patch$' || true)
		if [ "$1" = "2" ]; then
			if [ -n "$new_files" ]; then
				printf '%s\n' $new_files | xargs git add -N
			fi
			git diff > "${s%.sh}.patch"
		fi
		git checkout . &>/dev/null
		if [ -n "$new_files" ]; then
			printf '%s\n' $new_files | xargs git reset HEAD -- &>/dev/null
			printf '%s\n' $new_files | xargs rm -f
		fi
		printf "\n"
	done
)

# Run every patch2vi-generated script and commit each script's refreshed
# embedded delta. With arg 1, set P2VI_EX='q' and squash all commits back into
# a single staged change at the end.
# Usage: conv [1]
conv() (
	set -e
	n=0
	if [ -n "$1" ]; then
		export P2VI_EX='q'
	fi
	for s in *.sh
	do
		head -2 "$s" | grep -q "Generated by patch2vi" || continue
		./"$s"
		new_files=$(git ls-files --others --exclude-standard)
		if [ -n "$new_files" ]; then
			printf '%s\n' $new_files | xargs git add -N
		fi
		gitdiff2vi -d "$s"
		git add "$s"
		if ! git diff --cached --quiet; then
			git commit -m "$s: conv"
			n=$((n+1))
		fi
		git reset --hard
		if [ -n "$new_files" ]; then
			printf '%s\n' $new_files | xargs rm -f
		fi
	done

	if [ -n "$1" ] && [ $n -gt 0 ]; then
		git reset HEAD~$n
		git add .
	fi
)
