#!/bin/bash
#
# review - Review the diff between expected output and actual output in Units
#
# Copyright (C) 2016 Masatake YAMATO
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

print_help()
{
    echo Usage:
    printf "	%-20s %-30s %s\n" "$0" "help|--help|-h" "show this message"
    printf "	%-20s %-30s %s\n" "$0" "[list] [-b]" "list failed Units and Tmain"
    help_list
    printf "	%-20s %-30s %s\n" "$0" "inspect [-b]" "inspect difference interactively"
    help_inspect
    exit $1
}

is_known_bug()
{
    [[ $1 == *.b ]]
    return $?
}

do_list ()
{
    local d
    local n
    local a
    local including_known_bugs

    for a in "$@"; do
	case $a in
	    (-b)
		including_known_bugs=-b
		;;
	    (-*)
		echo "unknown option: ${a}" 1>&2
		exit 1
		;;
	    (*)
		echo "unexpected argument ${a}" 1>&2
		exit 1
		;;
	esac
    done

    for d in $(find Units -name "DIFF.tmp"); do
	n=$(dirname "$d")
	if (( ! is_known_bug $(basename "$n") ) || [[ -n ${including_known_bugs} ]] ) \
	       && [[ -e "$n"/expected.tags ]]; then
	    echo ${n}
	fi
    done

    for d in $(find Tmain -name "*-diff.txt"); do
	n=$(dirname "$d")
	echo ${n}
    done | sort -u
}

help_list()
{
    printf "	%-20s %-30s %s\n" "" "-b" "list .b (known bug) marked cases"
}

diff_post_processor()
{
    local cmd=cat

    if which colordiff > /dev/null 2>&1; then
	cmd=colordiff
    elif which pygmentize > /dev/null 2>&1; then
	cmd="pygmentize -l diff"
    fi

    $cmd
}

units_inspect_cmd_accept ()
{
    local from=./Units/$1/FILTERED.tmp
    local to=./Units/$1/expected.tags
    local r

    if [[ ! -e "${from}" ]]; then
	echo "./Units/$1/FILTERED.tmp is not found" 1>&2
	echo "Remove DIFF.tmp?"
	read -p "[Y/n] "
	if [[ "$REPLY" == Y ]]; then
	    rm ./Units/$t/DIFF.tmp
	    echo "Removed: DIFF.tmp"
	    r=1
	else
	    echo "Kept: DIFF.tmp"
	    r=0
	fi
	return $r
    fi

    echo "Do you really want do following shell command?"
    echo
    echo "   mv \"$from\"" '\'
    echo "      \"$to\" "
    echo
    read -p "[Y/n] "

    if [[ "$REPLY" == Y ]]; then
	mv "$from" "$to"
	echo "Renamed: $from => $to"
	r=0

	echo "Remove DIFF.tmp, too?"
	read -p "[Y/n] "
	if [[ "$REPLY" == Y ]]; then
	    rm ./Units/$t/DIFF.tmp
	    echo "Removed: DIFF.tmp"
	else
	    echo "Kept: DIFF.tmp"
	fi
    else
	echo "Aborted"
	r=1
    fi

    return $r
}

units_inspect_cmd_skip ()
{
    echo "SKIPPING $1" 1>&2
}

units_inspect_cmd_show_generic ()
{
    local tcase=$1
    local file=$2
    shift 2
    local f=./Units/${tcase}/${file}

    if [[ -r "$f" ]]; then
	"$@" "$f"
    else
	echo "No $f" 1>&2
    fi
    echo '---'
    echo '# ' "$f"
}

units_inspect_cmd_show_diff ()
{
    units_inspect_cmd_show_generic $1 DIFF.tmp cat | diff_post_processor
}

units_inspect_show_args_ctags ()
{
    units_inspect_cmd_show_generic $1 args.ctags cat
}

pygmentize_or_cat ()
{
    # It seems that pygmentize ignores the empty lines at the head of input.
    if [ -z "$(head -1 $1)" ]; then
	cat -n $1
    elif which pygmentize > /dev/null 2>&1; then
	pygmentize -O "style=colorful,linenos=1" $1 2>/dev/null || cat -n $1
    else
	cat -n $1
    fi
}

units_inspect_show_input ()
{
    local tcase=$1

    units_inspect_cmd_show_generic $tcase $(basename ./Units/${tcase}/input.*) pygmentize_or_cat
}

# See https://stackoverflow.com/questions/65533232/bash-extglob-pattern-matching-breaks-when-enclosed-in-a-function
shopt -s extglob
units_inspect_show_expected_tags ()
{
    local tcase=$1

    (
	shopt -s extglob
	units_inspect_cmd_show_generic $tcase $(basename ./Units/${tcase}/expected.tags?(-*)) "cat" "-n"
    )
}
shopt -u extglob

inspect_cmd_quit ()
{
    echo "BYE" 1>&2
    exit 0
}

inspect_cmd_unknown()
{
    echo "unknown command: $REPLY" 1>&2
    echo "Just return to show menu" 1>&2
}

help_inspect ()
{
    printf "	%-20s %-30s %s\n" "" "-b" "inspect .b (known bug) marked cases"
}

do_units_inspect()
{
    local next=0
    local r
    local t=$1

    select r in '<a>ccept' '<s>kip (or <n>)' '<S>hell' '<d>iff' 'show args.<c>tags' 'show <i>nput' '<e>xpected tags' '<q>uit'; do
	case $r-$REPLY in
	    ("<a>ccept"-*|-a)
		if units_inspect_cmd_accept "$t"; then
		    next=1
		fi
		;;
	    ("<s>kip (or <n>)"-*|-s|-!|-skip|-n|-next)
		units_inspect_cmd_skip "$t"
		next=1
		;;
	    ("<S>hell"-*|-S)
		inspect_cmd_shell Units "$t"
		;;
	    ("<d>iff"-*|-d|-=|-diff)
		units_inspect_cmd_show_diff "$t"
		;;
	    ("<q>uit"-*|-q|-quit)
		inspect_cmd_quit
		;;
	    ("show args.<c>tags"-*|-c|-A|-args.ctags)
		units_inspect_show_args_ctags "$t"
		;;
	    ("show <i>nput"-*|-i|-input)
		units_inspect_show_input "$t"
		;;
	    ("<e>xpected tags"-*|-e|-expected.tags)
		units_inspect_show_expected_tags "$t"
		;;
	    (*)
		inspect_cmd_unknown
		;;
	esac
	if [[ $next == 1 ]]; then
	    break
	fi
    done
}

function inspect_cmd_shell
{
    local prefix=$1
    local t=$2

    (
	cd $prefix/$t
	PS1="review> " bash
    )
}

function tmain_inspect_run
{
    local t=$1
    make tmain UNITS=$(basename $t .d)
}


tmain_inspect_cmd_show_diff_generic()
{
    local prefix=$1
    local t=$2
    local c=$3
    cat $prefix/$t/$c-diff.txt | diff_post_processor
}

tmain_inspect_cmd_show_diff_all()
{
    local prefix=$1
    local t=$2
    local c

    for c in stdout stderr exit; do
	if [[ -e "$prefix/$t/$c-diff.txt" ]]; then
	    echo '### ' "$c"
	    tmain_inspect_cmd_show_diff_generic "$prefix" "$t" "$c"
	fi
    done
}

tmain_inspect_cmd_accept()
{
    local prefix=$1
    local t=$2
    local c
    for c in stdout stderr exit; do
	if [[ -e "$prefix/$t/$c-diff.txt" ]]; then
	    mv "$prefix/$t/$c-actual.txt" "$prefix/$t/$c-expected.txt"
	    rm "$prefix/$t/$c-diff.txt"
	fi
    done
}

do_tmain_inspect()
{
    local next=0
    local r
    local t=$1

    select r in '<n>ext' '<d>iff' '<a>ccept' '<S>hell' '<R>un' '<q>uit'; do
	case $r-$REPLY in
	    ("<n>ext"-*|-n)
		next=1
		;;
	    ("<d>iff"-*|-d|-=|-diff)
		tmain_inspect_cmd_show_diff_all Tmain "$t"
		;;
	    ("<S>hell"-*|-S)
		inspect_cmd_shell Tmain "$t"
		;;
	    ("<a>ccept"-*|-a)
		if tmain_inspect_cmd_accept Tmain "$t"; then
		    next=1
		fi
		;;
	    ("<R>un"-*|-R)
		tmain_inspect_run "$t"
		;;
	    ("<q>uit"-*|-q|-quit)
		inspect_cmd_quit
		;;
	esac
	if [[ $next == 1 ]]; then
	    break
	fi
    done
}

count ()
{
    echo $#
}

do_inspect ()
{
    local a
    local t0
    local t
    local including_known_bugs

    for a in "$@"; do
	case $a in
	    (-b)
		including_known_bugs=-b
		;;
	    (-*)
		echo "unknown option: ${a}" 1>&2
		exit 1
		;;
	    (*)
		echo "unexpected argument ${a}" 1>&2
		exit 1
		;;
	esac
    done

    local T=$(do_list ${including_known_bugs});
    local total=$(count $T)
    local i=1
    for t0 in $T; do
	PS3="[$i/$total [ $t0 ]]? "
	i=$(( i + 1 ))
	t=${t0#Units/}
	t=${t#Tmain/}

	if [[ "$t0" =~ ^Units ]]; then
	    do_units_inspect "$t"
	else
	    do_tmain_inspect "$t"
	fi
    done
}

main ()
{
    local action

    while [[ $# -gt 0 ]]; do
	case $1 in
	    (help|--help|-h)
		print_help 0
		;;
	    (list)
		action=$1
		shift
		break
		;;
	    (inspect)
		action=$1
		shift
		break
		;;
	    (*)
		print_help 1 1>&2
		;;
	esac
    done

    if  [[ -z "$action" ]]; then
	action=inspect
    fi

    if ! [[ -d ./Units ]]; then
	echo "$0: cannot find ./Units directory" 1>&2
	exit 1
    fi

    if ! [[ -d ./Tmain ]]; then
	echo "$0: cannot find ./Tmain directory" 1>&2
	exit 1
    fi

    do_${action} "$@"
}

main "$@"
