#!/bin/bash

# We traverse the directories, finding test cases

# every cleaning function that does more than just call other functions 
# should have a test case.

CLEANASN=/netopt/ncbi_tools64/bin/cleanasn    
NEW_CLEANUP=/home/kornbluh/c++.opt/GCC/build/objtools/cleanup/test/test_basic_cleanup

PRETTY_PRINTER=/home/kornbluh/bin/asn_pretty_print

AUTO_REPLACE=./+easy_auto_replace

CHANGES_FILE=/tmp/kornbluh.cleanup_tests.changes.$$

SHOW_FILES=no
KILL_OLD_EMACS=no
# When set to "yes", it means that the output should be copied as the
# correct answer.  After that point, the same output will not give a diff.
DECLARE_CORRECT=no

# blank implies we do a "find" to get the files
TEST_FILES=

# set this to a higher number to skip the first
# ${MIN_NUM} files
MIN_NUM=0

DO_EXTENDED_CLEANUP=no
IGNORE_ANSWER_FILE=no
ALWAYS_CORRECT=no
SHOW_BEFORE_AND_AFTER=no
CLEANASN_TYPE=e
MUST_CHANGE=T
LIST_CHANGES=no
WRITE_CLEANASNS_OUTPUT=no

# used to be 'diff -w'
DIFF='diff'

die()
{
    echo "$@"
    exit 1
}

prepend_with()
{
    WORD=$1

    awk 'BEGIN { did_print = 0; } { if( 0 == did_print) { print "'$WORD'"; print $0; did_print = 1; } else { print $0 } }'
}

# Runs cleanasn on a file
do_cleanasn()
{
    for FILE in "$@" ; do
        if [[ $DO_EXTENDED_CLEANUP == yes ]]; then
            $CLEANASN -i $FILE -K bs -a ${CLEANASN_TYPE} -U grp
        else
            $CLEANASN -i $FILE -K b -a ${CLEANASN_TYPE} -U grp
        fi
    done
}

# Runs newcleanup on a file
do_newcleanup()
{
    ARG=

    # construct arg
    for file in "$@" ; do
        ARG="${ARG} -i ${file} -o - "
    done

    if [[ $DO_EXTENDED_CLEANUP == yes ]]; then
        NEW_CLEANASN_EXTRA_ARGS="${NEW_CLEANASN_EXTRA_ARGS} -e"
    fi

    if [[ $NEW_CLEANASN_EXTRA_ARGS != "" ]] ; then
        $NEW_CLEANUP -x $CHANGES_FILE ${NEW_CLEANASN_EXTRA_ARGS} ${ARG}
    else
        $NEW_CLEANUP -x $CHANGES_FILE ${ARG}
    fi
}

parse_args()
{
    for arg in "$@"; do
        case $arg in 
            -x)
                set -x
                ;;
            -wn)
                SHOW_FILES=yes
                KILL_OLD_EMACS=yes
                ;;
            -wno)
                SHOW_FILES=yes
                KILL_OLD_EMACS=no
                ;;
            -dc)
                DECLARE_CORRECT=yes
                ;;
            -ca=*)
                CLEANASN=${arg#*=}
                ;;
            -nca=*)
                NEW_CLEANUP=${arg#*=}
                ;;
            -skip=*)
                MIN_NUM=${arg#*=}
                ;;
            -e)
                DO_EXTENDED_CLEANUP=yes
                ;;
            -sd)
                DIFF=sdiff
                ;;
            -cg)
                export VALGRIND_OPTS=
                VALGRIND=/usr/local/valgrind/3.5.0/bin/valgrind
                NEW_CLEANUP="time $VALGRIND --tool=callgrind ${NEW_CLEANUP}"
                ;;
            -ia)
                IGNORE_ANSWER_FILE=yes
                ;;
            -pp=*)
                PRETTY_PRINTER=${arg#*=}
                ;;
            -ba)
                SHOW_BEFORE_AND_AFTER=yes
                ;;
            -a=*)
                CLEANASN_TYPE=${arg#*=}
                ;;
            -wco)
                WRITE_CLEANASNS_OUTPUT=yes
                ;;
            -n)
                # So we can just test if we crash on the input
                ALWAYS_CORRECT=yes
                ;;
            -ch=[TFX]) # "change"
                # meaning of the different values:
                # T: Must change.  Error if no changes
                # F: Must not change.  Error if changes
                # X: Don't care whether it changes or not
                MUST_CHANGE=${arg#*=}
                ;;
            -lc)
                # list changes
                LIST_CHANGES=yes
                ;;
            -d)
                NEW_CLEANASN_EXTRA_ARGS="${NEW_CLEANASN_EXTRA_ARGS} -d"
                ;;
            -is) # ignore space
                DIFF='diff -w'
                ;;
            -*)
                echo Bad arg given: "<$arg>"
                echo "See code for usage info"
                exit 1
                ;;
            *)
                if [[ -f $arg ]]; then
                    TEST_FILES="$TEST_FILES $arg"
                elif [[ -d $arg ]]; then
                    TEST_FILES="$TEST_FILES $(find $arg -name '*.template' -o -name '*.raw_test')"
                else
                    echo "File not found: <$arg>"
                    exit 1
                fi
                ;;
        esac
    done
}

get_files()
{
    if [[ -z $TEST_FILES ]]; then
        # (.test files are autogenerated by the _generate
        # script in their dir.  .raw_test files
        # were not generated by anybody )
        find -name '*.template' -o -name '*.raw_test'
    else
        echo $TEST_FILES | tr ' ' '\n'
    fi
}

kill_existing_test_emacs_sessions()
{
    PIDS=$(ps -ef | egrep -v egrep | egrep 'kornbluh.*emacs.*title.*cleanup_test ' | tr -s ' ' | cut -d' ' -f 2 )
    if [[ -n $PIDS ]]; then
        for pid in $( echo $PIDS ) ; do
            echo killing old emacs pid $pid
            kill $pid
        done
    fi
}

recompile_if_needed()
{
    FILES_TO_CHECK="/home/kornbluh/c++.opt/src/objtools/cleanup/newcleanupp.cpp"

    lib_age=$(stat -c %Y /home/kornbluh/c++.opt/GCC/build/objtools/cleanup/make_cleanup.log)
    if [[ -z $lib_age ]]; then
        lib_age=0
    fi

    need_recompile=no
    for file in $FILES_TO_CHECK ; do
        file_age=$(stat -c %Y $file)
        if (( $file_age > $lib_age )); then
            need_recompile=yes
        fi
    done

    if [[ $need_recompile == yes ]]; then
        BUILD_DIR=/home/kornbluh/c++.opt/GCC/build/objtools/cleanup/

        pushd .
          cd $BUILD_DIR || die cd failed $BUILD_DIR
          make -j 5 all_r || die make failed
        popd
    fi
}

get_num_changes()
{ 
    if [[ -f $CHANGES_FILE ]] ; then
        cat $CHANGES_FILE | awk -F ' ' 'BEGIN { total = 0 } /Number of changes:/ { total = total + $4 } END { print total } '
    else
        echo 0
    fi
}

# The extra awk and sort stuff is so we are in numerical order
# (put number first, then sort by number, then put number
# back in the middle )
# Otherwise, this function could be replaced with a simple glob pattern.
get_test_files()
{
    FILE_NAME_BASE=$1

    pushd . > /dev/null

    DIR=$(dirname ${FILE_NAME_BASE})

    cd $DIR

    ESCAPED_DIR=$( echo $DIR | sed -e 's/\//\\\//g' )

    ls $(basename ${FILE_NAME_BASE}).*.test | \
        awk -F. '{ print $2 "." $1 "." $3; }' | \
        sort -n | \
        awk -F. '{ print $2 "." $1 "." $3 }' | \
        sed -e 's/^/'${ESCAPED_DIR}'\//'

    popd > /dev/null
}

maybe_block_stdout()
{
    if [[ $SHOW_FILES == yes ]]; then
        # we're popping up emacs, so we block stdout in this case
        cat > /dev/null
    else
        # Let stdout pass through
        cat
    fi
}

main()
{
    parse_args "$@"

    # automatically detect if recompilation is necessary
    recompile_if_needed

    # fifos which will be used
    FILE_1=/tmp/kornbluh.cleanup_tests.file.old.$$
    FILE_2=/tmp/kornbluh.cleanup_tests.file.new.$$
    DIFF_FILE=/tmp/kornbluh.cleanup_tests.file.diff.$$
    PRETTIFIED_FILE=/tmp/kornbluh.cleanup_tests.file.formatted.$$

    # This file is only created if we failed at least one test.
    # Needed due to the annoying property of shell scripts
    # that subshells can't change variables in their parents
    FAIL_FILE=/tmp/kornbluh.cleanup_tests.file.fail.$$
    rm $FAIL_FILE 2> /dev/null

    # kill any previously-created emacs sessions
    if [[ $KILL_OLD_EMACS == yes ]]; then
        kill_existing_test_emacs_sessions
    fi

    FILE_NUM=1

    # test each file
    get_files | \
    while read file ; do

        if [[ $file == *.template ]]; then
            # clear old files
            rm ${file%.template}.*.test 2> /dev/null
            # make new files
            if ! $AUTO_REPLACE $file ; then
                echo failure doing auto-replace on template: $file
                touch $FAIL_FILE # failure occurred
                continue
            fi
            new_files=$( get_test_files ${file%.template} )
        else
            new_files=$file
        fi

        if (( $FILE_NUM <= $MIN_NUM )); then
            echo SKIPPING $FILE_NUM: $file
            FILE_NUM=$(( $FILE_NUM + 1 ))
            continue
        else
            echo testing $FILE_NUM: $file
        fi

        cat $file | $PRETTY_PRINTER > $PRETTIFIED_FILE

        # pretty_printer needed because C and C++ have slightly
        # different printing styles, but diff can't handle that right
        answer_file=${file%.*}.answer
        if [[ $DECLARE_CORRECT == yes ]]; then
            # do nothing yet
            IGNORED=ignored # just to be syntactically correct
        elif [[ -r $answer_file && $IGNORE_ANSWER_FILE == no ]]; then
            # Compare against set answer file instead of cleanasn
            echo '(using .answer file)'
            cat $answer_file  | $PRETTY_PRINTER > $FILE_1 &
        else
            do_cleanasn $new_files | $PRETTY_PRINTER > $FILE_1 &
        fi
        do_newcleanup $new_files | $PRETTY_PRINTER > $FILE_2 &
        wait

        # write cleanasns output, if requested
        if [[ $WRITE_CLEANASNS_OUTPUT == yes ]] ; then
            cleanasns_output_file=${file%.*}.cleanasns_output
            cp $FILE_1 $cleanasns_output_file
        fi

        # If declared correct, the output is considered automatically correct
        if [[ $DECLARE_CORRECT == yes ]]; then
            cp $FILE_2 $answer_file
            cp $FILE_2 $FILE_1
        fi

        # process changes
        if [[ $LIST_CHANGES == yes ]] ; then
            echo BEGIN  LIST OF CHANGES
            if [[ -f $CHANGES_FILE ]] ; then
                cat $CHANGES_FILE
            else
                echo NO CHANGES
            fi
            echo END    LIST OF CHANGES
        fi
        if [[ $MUST_CHANGE != X ]] ; then
            NUM_CHANGES=$(get_num_changes)
            if (( $NUM_CHANGES <= 0 )) && [[ $MUST_CHANGE == T ]] ; then
                echo error: no changes occurred even though expected
                touch $FAIL_FILE # failure occurred
            elif (( $NUM_CHANGES > 0 )) && [[ $MUST_CHANGE == F ]] ; then
                echo error: changes occurred when not expected
                touch $FAIL_FILE # failure occurred
            fi
        fi
        rm $CHANGES_FILE > /dev/null 2>&1

        if [[ $SHOW_BEFORE_AND_AFTER == yes ]] ; then
            echo BEGIN   BEFORE AND AFTER OLD
            ${DIFF} $PRETTIFIED_FILE $FILE_1
            echo END     BEFORE AND AFTER OLD
            echo BEGIN   BEFORE AND AFTER NEW
            ${DIFF} $PRETTIFIED_FILE $FILE_2
            echo END     BEFORE AND AFTER NEW
        fi

        # diff the results
        # (The awk script is just to print "DIFF:" before the first line
        # of the diff, if there was any)
        ${DIFF} $FILE_1 $FILE_2 > $DIFF_FILE 2>&1 || touch $FAIL_FILE # failure occurred

        if [[ $SHOW_FILES == yes ]]; then
            # Note that we will cleanup the files the emacs was looking
            # at when they're done
            ( emacs -title "1-$file cleanup_test" $FILE_1 $FILE_2 -geometry 80x65 ; rm $FILE_1 $FILE_2 ) &
            sleep 1
            ( emacs -title "2-$file cleanup_test" $file $DIFF_FILE -geometry 80x65 ; rm $DIFF_FILE ) &
            # exit prematurely if we launched an emacs (prevents creating a huge number of emacs sessions )
            exit 0
        else
            if [[ $ALWAYS_CORRECT != yes ]] ; then
                cat $DIFF_FILE
            fi

            # remove the files we were using
            rm $FILE_1
            rm $FILE_2
            rm $DIFF_FILE
        fi

        FILE_NUM=$(( $FILE_NUM + 1 ))
    done

    # We failed if the fail file exists
    [[ ! -f $FAIL_FILE ]]
    RETURN_VAL=$?
    rm $FAIL_FILE 2> /dev/null # clean up
    return $RETURN_VAL
}

LOG_FILE=/dev/stdout
LOG_SET=no

if [[ $1 == '-log' ]]; then
    LOG_FILE=/home/kornbluh/tmp/cleanup_tests.$(date +%Y_%m_%d_%H:%M:%S).$$
    LOG_SET=yes
    shift
fi

# perform the test
if [[ $LOG_SET == yes ]] ; then
    echo started at $(date) > $LOG_FILE
fi

main "$@" >> $LOG_FILE 2>&1
RETURN_VAL=$?

if [[ $LOG_SET == yes ]] ; then
    echo DONE at $(date) >> $LOG_FILE
fi

exit $RETURN_VAL
