#!/bin/ksh

set -e

MY_NAME=$(basename "$0")
MY_NAME="${MY_NAME:-kde-release-helper}"

usage() {
cat >&2 <<EOF
usage: $MY_NAME prepare new-KDE-version
       $MY_NAME check-lists
       $MY_NAME deps
       $MY_NAME merge
       $MY_NAME next
       $MY_NAME new port-name [commentary ...]
       $MY_NAME run
       $MY_NAME show {built|packaged|status|unpackaged} [all|l10n|sc]
       $MY_NAME update status

typical usecases:
  $MY_NAME prepare 4.9.3
  cd ../kde493
  # start hacking
  $MY_NAME check-lists
  $MY_NAME new kdepkg some cool KDE stuff
  $MY_NAME deps
  $MY_NAME run
  # after some progress but before committing
  $MY_NAME update status
  # when new KDE release is ready
  $MY_NAME merge
EOF
	exit 1
}

# echo-and-run
ear() {
	echo "$*" >&2
	"$@"
}

CLEANUP_ITEMS=
cleanup() {
	test -n "$CLEANUP_ITEMS" && ear rm -Rf $CLEANUP_ITEMS
}
trap cleanup EXIT

# Files to ignore during prepare and megre stages in addition to git ones
IGNORE_FILES="*.port.mk $MY_NAME STATUS"

# File containing list of KDE packages in order they should be built
DEPS_LIST=kde.deps.list

# List of options for diff(1) to make it ignore files above
diff_ignore_opts() {
	for F in ${IGNORE_FILES}; do
		echo -n " -x $F"
	done
}

# Arg: KDE version in X.Y.Z format
# Output: kdeXYZ
kde_port_dir() {
	if ! echo "$1" | egrep -q '^[0-9]+\.[0-9]+\.[0-9]+$'; then
		echo "$1 is not a valid KDE version (X.Y.Z)" >&2
		exit 1
	fi
	echo "kde$1" | sed -e 's/\.//g'
}

# List all ports (subdirectories) in current directory.
# Assume we're in KDE ports root directory.
# Output does not include l10n, see list_l10n_ports() below.
list_local_ports() {
	ls | while read P; do
		# Test for being directory for safety in case of symlinks
		test X"$P" != Xl10n -a \
		     X"$P" != Xtemplate -a \
		     -d "$P" \
		     -a -e "$P"/Makefile || continue
		echo "$P"
	done
}

# Assume we're in KDE ports root directory.
# Input: list of subdirectories.
# Output: list of distribution names.
port_dir_to_distname() {
	while read D; do
		(cd $D && make show=DISTNAME)
	done | sed -Ee 's@-([0-9]|\$).*@@' | sort
}

# Assume we're in KDE ports root directory.
list_l10n_ports() {
	test -d l10n || return 0
	for L in $(cd l10n/ru && make show=ALL_LANGS); do
		echo "kde-l10n-${L}"
	done | sort
}

# Assume we're in KDE ports root directory.
list_l10n_ports_dirs() {
	test -d l10n || return 0
	for L in $(cd l10n/ru && make show=ALL_LANGS); do
		echo "l10n/$L"
	done | sort
}

# Assume we're in the apporiate port's directory
list_remote_source() {
	local LIST URL
	local V=`make show=MODKDE4_VERSION`

	for URL in $(make MASTER_SITE_BACKUP= show=MASTER_SITES); do
		echo "$URL" | grep -q '^ftp://' || continue
		LIST="$(ear curl -sl $URL |
		    sed -e s@-${V}.*@@ |
		    grep -v '^\.' |
		    sort)" || continue
		test `echo -n "$LIST" | wc -l` -gt 1 || continue
		echo "$LIST"
		return 0
	done

	echo "Could not retrieve source files list." >&2
	echo "Probably more FTP servers with NLIST command supported" >&2
	echo "are needed in MASTER_SITES." >&2
	return 1
}

# Assume the same as list_remote_source().
# Input: sorted local list of ports.
# Output: list of new ports prefixed with "+"
compare_sources_lists() {
	T="`mktemp -t srccomp.XXXXXXXX`"
	OCLEANUP_ITEMS="$CLEANUP_ITEMS"
	CLEANUP_ITEMS="$CLEANUP_ITEMS $T"
	list_remote_source >"$T"
	test -s "$T" && diff -u - "$T" || true
	rm "$T"
	CLEANUP_ITEMS="$OCLEANUP_ITEMS"
}

# Assume we're in KDE ports root directory.
kde_version() {
	(cd libs && make show=MODKDE4_VERSION)
}

# Arguments: port, status
print_status_line() {
	printf '%-32s%s\n' "$1" "$2"
}

is_started() {
	test -d "`cd $1 && make show=WRKSRC`"
}

is_configured() {
	test -e "`cd $1 && make show=_CONFIGURE_COOKIE`"
}

is_built() {
	test -e "`cd $1 && make show=_BUILD_COOKIE`"
}

is_tested() {
	test -e "`cd $1 && make show=_TEST_COOKIE`"
}

is_packaged() {
	local P F FOUND FLAVORS=`cd $1 && make show=FLAVORS`
	for P in `cd $1 && make show=BUILD_PACKAGES`; do
		FOUND=
		for F in "" ${FLAVORS}; do
			if [ -e "`cd $1 && SUBPACKAGE=$P FLAVOR=$F make show=PKGFILE`" ]; then
				FOUND=Y
				break
			fi
		done
		test -n "$FOUND" || return 1
	done
	return 0
}

is_locked() {
	# LOCKDIR could be empty, see ports(7)
	LOCKDIR="${LOCKDIR:-X`cd $1 && make show=LOCKDIR`}"
	if [ "$LOCKDIR" != X ]; then
		test -e "${LOCKDIR#X}/`cd $1 && make show=FULLPKGNAME`.lock"
	else
		# XXX Better detection mechanizm?
		false
	fi
}

# Argument: KDE version in X.Y.Z format.
is_stable_ver() {
	# KDE alpha/beta/etc. versions start from 80
	test 80 -gt `echo $1 | sed -E 's/^.*\.([0-9]+)$/\1/'`
}

expand_port_list() {
	case ${1:-all} in
	all)
		list_local_ports
		list_l10n_ports_dirs
		;;
	sc)
		list_local_ports
		;;
	l10n)
		list_l10n_ports_dirs
		;;
	*)
		usage
		;;
	esac
}

### Main actions go here

prepare() {
	test $# -eq 1 || usage

	if ! [ -f ../kde4/kde4.port.mk -a X"`cd .. && basename $(pwd)`" = Xx11 ]; then
		echo "Please run $MY_NAME from KDE 4 directory in ports tree" >&2
		exit 2
	fi

	local NEW_KDE_PORT_VER=$1; shift
	local CUR_KDE_PORT_VER=`kde_version`

	cd ..
	local NEW_KDE_PORT_DIR=`kde_port_dir "$NEW_KDE_PORT_VER"` 
	if [ -e $NEW_KDE_PORT_DIR ]; then
		echo "x11/$NEW_KDE_PORT_DIR already exists" >&2
		exit 2
	fi

	echo '==> copying ports directory' >&2
	CLEANUP_ITEMS="$CLEANUP_ITEMS $NEW_KDE_PORT_DIR"
	cp -R "$OLDPWD" $NEW_KDE_PORT_DIR
	local LOCAL_PORTS=`cd $NEW_KDE_PORT_DIR && list_local_ports`

	echo '==> removing extra files' >&2
	for F in $IGNORE_FILES; do ear rm -f $NEW_KDE_PORT_DIR/$F; done
	find $NEW_KDE_PORT_DIR -name 'PLIST*.orig' -print0 | xargs -0rt rm
	if is_stable_ver $NEW_KDE_PORT_VER; then
		if is_stable_ver $CUR_KDE_PORT_VER; then
			# just mark distinfo missing
			ear rm -f $NEW_KDE_PORT_DIR/l10n/distinfo
		else
			# need to copy l10n back from some stable directory
			ear mkdir -p $NEW_KDE_PORT_DIR/l10n
			ear cp -R kde4/l10n/* $NEW_KDE_PORT_DIR/l10n
		fi
	else
		# Unstable KDE versions usually lacks l10n.
		# And if not, we don't care either, they will not
		# hit the CVS anyway.
		ear rm -Rf $NEW_KDE_PORT_DIR/l10n
	fi
	for P in $LOCAL_PORTS; do
		ear rm -f $NEW_KDE_PORT_DIR/$P/distinfo
	done

	echo '==> removing REVISION marks' >&2
	for P in $LOCAL_PORTS; do
		local OCLEANUP_ITEMS="$CLEANUP_ITEMS"
		CLEANUP_ITEMS="$CLEANUP_ITEMS $NEW_KDE_PORT_DIR/$P/Makefile.orig"
		perl -ni.orig -e '/^REVISION/ or print' $NEW_KDE_PORT_DIR/$P/Makefile
		cmp -s $NEW_KDE_PORT_DIR/$P/Makefile{.orig,} || echo $P
		rm $NEW_KDE_PORT_DIR/$P/Makefile.orig
		CLEANUP_ITEMS="$OCLEANUP_ITEMS"
        done

	echo '==> patching Makefile.inc' >&2
	local MI=$NEW_KDE_PORT_DIR/Makefile.inc
	local T=`mktemp -u "$MI".XXXXXXXXXXX`
	OCLEANUP_ITEMS="$CLEANUP_ITEMS"
	CLEANUP_ITEMS="$CLEANUP_ITEMS $T"
	local MSPATCH OSTABLE=1 NSTABLE=1
	is_stable_ver $CUR_KDE_PORT_VER || OSTABLE=0
	is_stable_ver $NEW_KDE_PORT_VER || NSTABLE=0

	if [[ $OSTABLE -eq $NSTABLE ]]; then
		MSPATCH=cat
	elif [[ $OSTABLE -eq 0 ]]; then
		MSPATCH="sed /MASTER_SITES/s@unstable/@stable/@"
	else
		MSPATCH="sed /MASTER_SITES/s@stable/@unstable/@"
	fi
	awk "
/^MODKDE4_FLAVOR[[:space:]]*:?=/ {
	print \"MODKDE4_FLAVOR =	${NEW_KDE_PORT_DIR}\";
	VL = 1;
}

{
	if (VL) {
		VL = 0;
	} else {
		print;
	}
}
	    " <"$MI" | $MSPATCH >"$T"

	if ! grep -q ^MODKDE4_FLAVOR "$T"; then
		# MODKDE4_FLAVOR could be missing
		echo >>"$T"
		echo "MODKDE4_FLAVOR =	$NEW_KDE_PORT_DIR" >>"$T"
	fi

	mv "$T" "$MI"

	if ! is_stable_ver $NEW_KDE_PORT_VER; then
		echo '==> patching Makefile' >&2
		sed -Ee '/^SUBDIR[[:space:]]*\+=[[:space:]]*l10n/d' \
		    <$NEW_KDE_PORT_DIR/Makefile >"$T"
		mv "$T" $NEW_KDE_PORT_DIR/Makefile
	fi
	CLEANUP_ITEMS="$OCLEANUP_ITEMS"

	echo "==> creating STATUS" >&2
	for P in $LOCAL_PORTS `list_l10n_ports_dirs`; do
		print_status_line $P WAITING \
		    >>${NEW_KDE_PORT_DIR}/STATUS
	done

	# Avoid removing newly created directory
	CLEANUP_ITEMS=
}

check_lists() {
	test $# -eq 0 || usage
	echo '==> checking for new source packages' >&2
	list_local_ports |
	    port_dir_to_distname |
	    (cd libs && compare_sources_lists) |
	    egrep '^[\+-][^\+-]' |
	    grep -v '^+kde-l10n' || true

	test -d l10n || return 0
	echo '==> checking for l10n package list changes' >&2
	list_l10n_ports |
	    (cd l10n/ru && compare_sources_lists) |
	    egrep '^[\+-][^\+-]' || true
}

build_deps_list() {
	test $# -eq 0 || usage

	local KDEDIR=$(kde_port_dir `kde_version`)
	local P BDEPS DEP DEP2 T

	T=$(mktemp ${DEPS_LIST}.XXXXXXXX)
	OCLEANUP_ITEMS="$CLEANUP_ITEMS"
	CLEANUP_ITEMS="$CLEANUP_ITEMS $T"
	echo "===> Gathering lists of KDE ports packaged or being built" >&2
	for P in `list_local_ports; list_l10n_ports_dirs`; do
		# normalize build dependencies list by removing all but pkgpath
		BDEPS=$(cd $P && make show="BUILD_DEPENDS LIB_DEPENDS" |
		    sed -Ee 's@[^[:space:]]+:@@g')

		# take care of KDE SC dependencies only
		for DEP2 in $BDEPS; do
			echo "$DEP2"
		done |
		fgrep "x11/${KDEDIR}/" |
		sed -e "s%x11/${KDEDIR}/%%" -e "s%\$% ${P}%"
	done >"$T"
	mv "$T" "$DEPS_LIST"
	CLEANUP_ITEMS="$OCLEANUP_ITEMS"
}

choose_next() {
	test $# -eq 0 || usage

	local P
	tsort <"$DEPS_LIST" | while read P; do
		is_packaged $P && continue
		is_locked $P && continue

		if is_built $P; then
			echo "# $P was built"
			echo "(cd $P && make update-plist) && \\"
			echo "(cd $P && make port-lib-depends-check) && \\"
			echo "(cd $P && make package)"
		elif is_configured $P; then
			echo "# $P was configured"
			echo "(cd $P && make build)"
		elif is_started $P; then
			echo "# $P was started"
			echo "(cd $P && make configure)"
		else
			echo "# $P was not started"
			echo "(cd $P && make patch)"
		fi
		break
	done
}

new_port() {
	test $# -ge 1 || usage
	NEW_P=$1; shift
	mkdir "$NEW_P" "$NEW_P"/pkg
	sed -e "s@XYZ@${NEW_P}@g" \
	    -e "s@^COMMENT.*@COMMENT =	$*@" \
	    <template/Makefile \
	    >"$NEW_P"/Makefile
	echo "Now run:" >&2
	echo "cd $NEW_P && ${VISUAL:-${EDITOR:-vi}} Makefile" >&2
}

merge_back() {
	test $# -eq 0 || usage
	# Avoid erroring out here, we'll print a nicer message below.
	local SKDEV=`cd libs && make show=_MODKDE4_STABLE || true`
	local KDEV=`cd libs && make show=MODKDE4_VERSION || true`

	if [ -f kde4.port.mk -o X"`cd .. && basename $(pwd)`" != Xx11 -o \
	     X"$KDEV" = X ]; then
		echo "Please run $MY_NAME from directory with KDE4 ports being tested" >&2
		exit 2
	fi

	local SKPD=kde4
	local KPD=$(kde_port_dir $KDEV)
	cd ..
	T=`mktemp -t kdepatch.XXXXXXXX`
	CLEANUP_ITEMS="$CLEANUP_ITEMS $T"

	SKVERLINE="`egrep -n ^_MODKDE4_STABLE ${SKPD}/kde4.port.mk | head -1`"

	cat >$T <<EOF
Here is a summary patch to be applied, please review. Note that STATUS files
are missing in the diff to make it smaller, they will be handled automatically.

If you proceed and NOT abort, commit message editor will appear, two times:

  * First time for merging new directory into x11/kde4;
  * Second time for removing old directory.

To abort, just press ^C and then quit pager.

And do not worry, it's distributed VCS and you always can go back. Because you
have already commited all your work, didn't you?

--- ${SKPD}/kde4.port.mk
+++ ${SKPD}/kde4.port.mk
@@ -${SKVERLINE%%:*},1 +${SKVERLINE%%:*},1 @@
-${SKVERLINE#*:}
+_MODKDE4_STABLE =	${KDEV}
EOF
	diff -urNp -I '^MODKDE4_FLAVOR[[:space:]]*=' -X ../.gitignore \
	    `diff_ignore_opts` $SKPD $KPD \
	    | sed -e "s@^+++ ${KPD}/@+++ ${SKPD}/@" >>$T
	${PAGER:-more} <$T

	patch -C -E -p0 <$T
	patch -E -p0 <$T
	rm $T
	rm $SKPD/STATUS
	mv $KPD/STATUS $SKPD/
	git add $SKPD
	git commit -m "Merge KDE $KDEV from testing directory." -e -- $SKPD
	rm -R $KPD
	git commit -m "Remove KDE $KDEV testing directory." -e -- $KPD
}

run_job() {
	test $# -eq 0 || usage
	for P in `list_local_ports`; do
		if is_packaged $P || is_locked $P; then
			continue
		fi

		echo "==> Working on port $P" >&2
		if is_built $P; then
			local TARGETS=
			if ! ls $P/pkg/PLIST*.orig >/dev/null 2>&1; then
				TARGETS=update-plist
			fi
			TARGETS="$TARGETS port-lib-depends-check package"
			TARGETS="$TARGETS lib-depends-check install clean"
			(cd $P && make $TARGETS)
		elif is_configured $P; then
			(cd $P && make build)
		elif is_started $P; then
			(cd $P && make configure)
		else
			(cd $P && make makesum prepare)
		fi
	done
}

# Assume to be running in KDE4 ports directory
show_status() {
	local P T V PORTLIST=$(expand_port_list "$1")

	V="`kde_version`"
	for P in $PORTLIST; do
		if is_packaged $P; then
			print_status_line $P PACKAGED
		elif is_built $P; then
			print_status_line $P BUILT
		elif is_configured $P; then
			print_status_line $P CONFIGURED
		elif is_started $P; then
			print_status_line $P STARTED
		else
			print_status_line $P WAITING
		fi
	done
}

show() {
	test $# -ge 1 -a $# -le 2 || usage

	if ! [ -f ../kde4/kde4.port.mk -a \
               X"`cd .. && basename $(pwd)`" = Xx11 ]; then
		echo "Please run $MY_NAME from a KDE 4 directory in ports tree" >&2
		exit 2
	fi

	local P WHAT PORTLIST=$(expand_port_list "$2")
	WHAT=$1; shift
	case $WHAT in
	c|co|con|conf|confi|config|configu|configur|configure|configured)
		for P in $PORTLIST; do
			is_configured $P && echo $P
		done
		;;

	b|bu|bui|buil|built)
		for P in $PORTLIST; do
			is_built $P && echo $P
		done
		;;

	p|pa|pac|pack|packa|packag|package|packaged)
		for P in $PORTLIST; do
			is_packaged $P && echo $P
		done
		;;

	s|st|sta|stat|statu|status)
		show_status "$@"
		;;

	u|un|unp|unpa|unpac|unpack|unpacka|unpackag|unpackage|unpackaged)
		for P in $PORTLIST; do
			is_packaged $P || echo $P
		done
		;;

	*)
		usage
	esac
	true
}

update_status() {
	test $# -eq 0 || usage

	local V=`kde_version`
	local T="`mktemp -u STATUS.XXXXXXXX`"
	local OCLEANUP_ITEMS="$CLEANUP_ITEMS"
	CLEANUP_ITEMS="$T $CLEANUP_ITEMS"
	if [ -e "STATUS" ]; then
		echo "===> Updating status of KDE $V porting"
		local P S
		show_status all | awk "
BEGIN {
	while (getline L <\"STATUS\") {
		split(L, PARTS);
		STATUS[PARTS[1]] = PARTS[2];
	}

	# From most to least useful, the latest one isn't actually needed now.
	STATUSES[1] = \"PACKAGED\";
	STATUSES[2] = \"BUILT\";
	STATUSES[3] = \"CONFIGURED\";
	STATUSES[4] = \"STARTED\";
	#STATUSES[5] = \"WAITING\";

	# We cannot use 'for(... in STATUSES)' because it doesn't restart
	# on every loop, but starts cycling items from the position where
	# previous loop was interrupted.
	NSTATUSES = 4
}

/^([[:space:]]*#.*)?\$/ {
	next;
}

{
	for (I = 1; I < NSTATUSES; I++) {
		S = STATUSES[I];
		if (STATUS[\$1] == S || \$2 == S) {
			printf(\"%-32s%s\\n\", \$1, S);
			next;
		}
	}
	printf(\"%-32s%s\\n\", \$1, \"WAITING\");
}
	    " | tee "$T"
	else
		echo "===> Old status file missing, recreating"
		show_status "$WHAT" | tee "$T"
	fi
	mv "$T" "STATUS"
	CLEANUP_ITEMS="$OCLEANUP_ITEMS"
}

update() {
	test $# -ge 1 || usage

	if ! [ -f ../kde4/kde4.port.mk -a \
               X"`cd .. && basename $(pwd)`" = Xx11 ]; then
		echo "Please run $MY_NAME from a KDE 4 directory in ports tree" >&2
		exit 2
	fi

	local CMD=$1; shift
	case $CMD in
	s|st|sta|stat|statu|status)
		update_status "$@"
		;;

	*)
		usage
		;;
	esac
}

# All actions take (at least) one argument
test $# -ge 1 || usage
ACTION=$1; shift

case $ACTION in
p|pr|pre|prep|prepa|prepar|prepare)
	prepare "$@"
	;;

check-l|check-li|check-lis|check-list|check-lists)
	check_lists "$@"
	;;

d|de|dep|deps)
	build_deps_list "$@"
	;;

m|me|mer|merg|merge)
	merge_back "$@"
	;;

nex|next)
	choose_next "$@"
	;;

new)
	new_port "$@"
	;;

r|ru|run)
	run_job "$@"
	;;

s|sh|sho|show)
	show "$@"
	;;

u|up|upd|upda|updat|update)
	update "$@"
	;;
esac
