#!/bin/sh

[ -z "$BASH_VERSION" ] && (which bash > /dev/null 2>&1) && exec bash $0 $*;

# -----------------------------------------------------------------------------

root=$(cd $(dirname $(which "$0"))/.. && pwd);

. $root/scripts/tools.sh;

# -----------------------------------------------------------------------------

reports="$root/reports-$HOSTNAME";

srcdir=$1
dstdir=.;

[ -n "$srcdir" ] || srcdir=$root;
[ -d "$srcdir" ] || srcdir=$root/submissions/$1;
[ -d "$srcdir" ] || srcdir=$root/benchmarks/$1;
[ -d "$srcdir" ] || srcdir=$root;

srcdir=$(relative "$srcdir" "$PWD");

vectors=verified.test-vectors;

if [ -z "$(find "$srcdir" -name "*$vectors")" ]; then
    error 0 "no test vectors found in $srcdir";
    exit;
fi

if [ -z "$(find "$srcdir" -name "Makefile")" ]; then
    error 0 "no Makefile found in $srcdir";
    exit;
fi

cpu_speed=$(tail -n 1 "$reports/cpuinfo");

if [ -r "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" ]; then
    status 1 "warming up CPU";

    i=0;
    while [ $i -lt 10000 ]; do ((i++));
	curr_speed=$(awk '/^cpu MHz/ { print $4; exit; }' /proc/cpuinfo);
	
	[ $((${cpu_speed%.*} - ${curr_speed%.*})) -gt 10 ] \
	    || break;
    done

    status 1;
fi

[ -r "$reports/.skip-slow" ] && skip=$(<"$reports/.skip-slow");

# -----------------------------------------------------------------------------

run ()
{ 
    srcdir=$1;
    vecdir=$2;
    var=$3;
    conf=$4;

    ext=${var}_${conf};
    make="make var=$var conf=$conf";

    name=$($make name);
    [ "$var" = "1" ] || name="$name (variant $var)";

    report=$(ls *_$ext 2> /dev/null | head -1);

    if [ -n "$report" ]; then
	touch timestamp -r $report;

	if $make -q timestamp; then
	    warning 1 "nothing to be done. $report is up to date.";
	    rm -f timestamp;
	    return;
	fi

	rm -f timestamp *_$ext;
    fi

    if [ -n "$skip" -a -r "$vecdir/fastest" ]; then
	slow=$(awk '{ if ($1 > '$skip') print "true"; }' "$vecdir/fastest");

	if [ "$slow" = "true" ]; then
	    warning 1 "skipping slow implementation.";
	    return;
	fi
    fi

    status 1 "compiling $name under $conf";
	      
    $make clean &> /dev/null;

    cat > info_current <<EOF
DATE:
-------------------------------------------------------------------------------
$(date)

CPU:
-------------------------------------------------------------------------------
$(sed -n '$!p' "$reports/cpuinfo")

COMPILER:
-------------------------------------------------------------------------------
$($make version 2>&1)

COMPILATION:
-------------------------------------------------------------------------------
$make

$($make 2>&1)

EXECUTABLE:
-------------------------------------------------------------------------------
EOF

    status 1;

    if $make -q &> /dev/null; then
	hash=$($make hash);
    else
	echo "none" >> info_current;
	mv info_current errors_$ext;
	error 0 "compilation failed (see errors_$ext).";

	$make clean &> /dev/null;
	return;
    fi

    status 1 "checking for duplicates";

    matches=$(grep -l "$hash" {info,speed}_*_* 2> /dev/null);

    status 1;

    echo "$hash" >> info_current;

    if [ -n "$matches" ]; then
	info 1 "executable has been checked before (see $(echo $matches))";
	mv info_current info_$ext;

	$make clean &> /dev/null;
	return;
    fi

    run=$($make run);

    detailed=false;

    for flags in "-pak -s 128 64 64" "-s 128 64 64 -s 256 128 128"; do
	status 1 "running speed measurements on $cpu_speed MHz CPU";
    
	error=$($run -qc $cpu_speed $flags \
	    2> /dev/null > speed_current; echo "$?");
	
	cat >> speed_current <<EOF

*******************************************************************************

$(cat info_current)
EOF

	status 1;
	
	cycles=$(awk '{ sub(/\r/, ""); } /cycles\/byte/ { print $4; exit; }' \
	    speed_current);
	
	if [ -z "$cycles" -o "$error" -gt "0" ]; then
	    mv speed_current errors_$ext;
	    rm -f info_current
	    error 0 "execution failed. check errors_$ext.";
	    
	    $make clean &> /dev/null;
	    return;
	fi

	if [ -r "$vecdir/fastest" ]; then
	    comparison=$(awk '{ \
              if ('$cycles' < $1) \
                print "better"; \
              else if ('$cycles' / $1 < 1.01) \
                print "similar"; \
              else \
                print "worse"; }' "$vecdir/fastest");
	else
	    comparison="better";
	fi
	
	[ "$comparison" = "worse" ] && break;

	if [ "$detailed" = "false" ]; then
	    status 1 "generating test vectors";

	    error=$($run -qv > vectors_current 2> /dev/null; echo "$?"); 
	    mv vectors_current vectors_$ext;
    
	    status 1;

	    if [ "$error" -gt "1" ]; then
		mv vectors_$ext errors_$ext;
		mv speed_current info_$ext;
		error 0 "execution failed.";

		$make clean &> /dev/null;
		return;
	    fi

	    status 1 "verifying test vectors";

	    if diff -waq vectors_$ext "$srcdir/$vecdir/"*"$vectors" \
		> /dev/null; then
		status 1;
		rm -f vectors_$ext;
	    else
		if diff -wad vectors_$ext "$srcdir/$vecdir/"*"$vectors" \
		    | grep '<' > /dev/null; then
		    status 1;
		    mv speed_current info_$ext;
		    error 0 "vectors do not match. check vectors_$ext.";

		    $make clean &> /dev/null;
		    return;
		else
		    status 1;
		    warning 1 "vectors are incomplete but match.";
		    rm -f vectors_$ext;
		fi
	    fi

	    detailed=true;
	elif [ "$comparison" = "better" ]; then
	    echo "$cycles" > "$vecdir/fastest";
	fi
    done

    $make clean &> /dev/null;

    rm -f info_current;
    mv speed_current speed_$ext;
	
    info 1 "Current implementation of $name encrypts at $cycles cycles/byte."
    info 1 "Previous implementations reached $(<$vecdir/fastest) cycles/byte."
}

# -----------------------------------------------------------------------------

process_dir ()
{
    srcdir=$1;
    dstdir=$2;
    vecdir=$3;
    conf=$4;

    if ! mkdir -p "$dstdir"; then
	error 0 "Cannot create directory $dstdir.";
	return;
    fi

    cd "$dstdir";
    info 1 "Entering directory $(relative "$srcdir" "$root")";

    if [ -n "$vecdir" ]; then 
	vecdir="../$vecdir";
    elif [ -r "$srcdir/"*"$vectors" ]; then
	vecdir=".";
    fi

    for subdir in "$srcdir"/*; do
	if [ -d "$subdir" ]; then
	    [ -n "$(find "$subdir" -name "Makefile")" ] || continue;
	    [ -n "$vecdir" ] || [ -n "$(find "$subdir" -name "*$vectors")" ] \
		|| continue;

	    (process_dir "../$subdir" "$(basename $subdir)" "$vecdir" "$conf");
	fi
    done

    [ -n "$vecdir" -a -r "$srcdir/Makefile" ] || return;

    sed 's/srcdir = ./srcdir = '${srcdir//\//\\\/}'/' "$srcdir/Makefile" \
	> Makefile;

    success=$(ls speed_${var}_${conf%%_*}_* 2> /dev/null | wc -l);
    failure=$(ls {errors,vectors}_${var}_${conf%%_*}_* 2> /dev/null | wc -l);

    if [ "$success" = "0" -a "$failure" -ge "5" ]; then
	error 0 "This implementation seems to be buggy. Please fix it first.";
	return;
    fi

    make variants &> /dev/null;

    for var in $(<variants); do
	run $srcdir $vecdir $var $conf;
    done
}

# -----------------------------------------------------------------------------

for conf in $(cd "$reports/configs"; ls *.mk); do
    tag=$(awk '/tag = / { print $3; exit; }' "$reports/configs/$conf");
    score=$(awk '/ '$tag'$/ { print $1; exit; }' "$reports/shortlist");

    echo $((score + 0)) ${conf%.mk};
done | sort -rn | while read score conf; do
    [ "$score" = "0" ] && [ -e "$reports/.shortlist-only" ] && break;

    info 1 "Testing $conf configuration (score: $score).";
    process_dir "$srcdir" "$dstdir" "" $conf < /dev/null;
done

# -----------------------------------------------------------------------------
