#!/bin/bash
#
#  Detects style errors in the C++ source code. Run this from the top directory
#  (which has the subdirectories build, src and utils).
#
#  The output may include colour codes if and only if standard output is a
#  terminal.
#
#  Several check commands are used:
#  * grep with a set of regular expressions
#  * whitespace_checker (with detect_spurious_indentation.py as an inferior
#    fallback)
#
#  Results are cached (there are separate caches for colour/nocolour). The
#  check commands do not write directly to a file at the cahce file location
#  because that could leave behind an incomplete cache file that is newer than
#  the source file in case that the check commands are interrupted. Instead
#  write the output from the check commands to a temporary file and when that
#  file is complete, move it to the cache file location.
#
#  Instead of storing empty cache files for source files that do not give any
#  errors, a per-directory timestamp file is stored. This drastically reduces
#  the number of cache files. The drawback is that if the script is interrupted
#  whilst checking a sequence of files with equal mtime, it will have to start
#  over with that sequence the next time it is executed. But reducing the
#  number of cache files is worth that minor inconvenience.

CHECKER=utils/spurious_source_code/whitespace_checker
if [ ! -f $CHECKER ]; then
	echo "WARNING: $CHECKER does not exist!"
	echo "WARNING:    Go to utils/spurious_source_code and build it with make!"
	echo "WARNING:    Else many checks will be done much slower or not at all!"
	unset CHECKER
fi

#  Make sure that several instances of this script running concurrently each
#  have a separate temporary directory.
TMP_DIR=/tmp/spurious_source_code_detect.$PPID
rm -fr $TMP_DIR
mkdir $TMP_DIR || { echo "ERROR: could not create $TMP_DIR"; exit; }


REGEXPS_FILE=$TMP_DIR/regexps
for r in utils/spurious_source_code/regexps/*; do
	[[ -d $r && ! -f $r/disabled && ( ! $CHECKER || ! -f $r/redundant_with_whitespace_checker ) ]] &&
		cat $r/regexps >> $REGEXPS_FILE
done


[[ -t 1 ]] && COLOUR=.colourized || unset COLOUR


RESULT_FILE=$TMP_DIR/result

#  Runs all checks on single source file. The result is written to
#  $RESULT_FILE.
#
#  Requirements on the result:
#  * Error message lines must begin with <filename>:<linenumber> so that they
#    can be parsed by tools like KDevelop.
#  * Any colour codes produced by commands like "grep --colour" must be
#    included if and only if $COLOUR is set.
#
#  Parameters:
#    $1
#      The name of the source file to be checked.
function check {
	egrep --with-filename --line-number $([ $COLOUR ] && echo --colour=always) -f $REGEXPS_FILE $1 >  $RESULT_FILE
	([[ $CHECKER ]] && $CHECKER $1 || utils/detect_spurious_indentation.py $1)                     >> $RESULT_FILE
}


CACHE_DIR="build/stylecheck"

#  Detects errors recursively and stores the result in a cache. The cache
#  content (if any) is echoed to standard output.
#
#  If a cached entry exists and is not older than the source file, no checks
#  are done on that source file.
#
#  Calls itself recursively for each subdirectory. Then checks all the source
#  files in the current directory that are newer than the timestamp file there.
#  Files are checked in mtime order, oldest first. After checking group of
#  files that have the same mtime, the timestamp is is set to that mtime.
#
#  Parameters:
#    $1
#      The name of the directory to start the search and check in.
function detect {
	for d in $1/*; do
		[[ -d $d ]] && detect $d
	done
	TIMESTAMP=$CACHE_DIR/$1/timestamp
	PREVIOUS_FILE=$1
	FILES=$(ls -tr $1/*.h $1/*.cc 2>/dev/null)
	if [[ -n $FILES ]]; then
		mkdir -p $CACHE_DIR/$1
		for f in $FILES; do
			CACHE_BASE=$CACHE_DIR/$f.stylecheck
			CACHE_COLOUR=$CACHE_BASE.colourized
			CACHE=$CACHE_BASE$COLOUR
			if [[ $f -nt $TIMESTAMP || -s $CACHE_BASE || -s $CACHE_COLOUR ]]; then
				if [ $f -nt $CACHE ]; then
					echo "Checking for errors in $f ..."
					check $f
					if [ -s $RESULT_FILE ]; then
						mv $RESULT_FILE $CACHE
						[[ $f -nt $PREVIOUS_FILE && $PREVIOUS_FILE -nt $TIMESTAMP ]] &&
							touch --reference=$PREVIOUS_FILE $TIMESTAMP
						cat $CACHE
					else
						rm -f $CACHE_BASE $CACHE_COLOUR
					fi
				else
					echo "Showing cached errors in $f ..."
					cat $CACHE
				fi
			fi
			PREVIOUS_FILE=$f
		done
		[[ $PREVIOUS_FILE -nt $TIMESTAMP ]] &&
			touch --reference=$PREVIOUS_FILE $TIMESTAMP
	fi
}


detect src

rm -fr $TMP_DIR
