#!/bin/bash

SO2_WORKSPACE=/linux/tools/labs
SO2_VM_LOG=/tmp/so2_vm_log.txt


ASSIGNMENT0_TIMEOUT=300 # 5 min
ASSIGNMENT0_MOD=list.ko
ASSIGNMENT0_DIR=${SO2_WORKSPACE}/skels/assignments/0-list
ASSIGNMENT0_CHECKER_LOCAL_DIR=checker/0-list-checker
ASSIGNMENT0_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/0-list-checker
ASSIGNMENT0_OUTPUT=${SO2_WORKSPACE}/skels/0-list-output
ASSIGNMENT0_FINISHED=${SO2_WORKSPACE}/skels/0-list-finished

ASSIGNMENT1_TIMEOUT=300 # 5 min
ASSIGNMENT1_MOD=tracer.ko
ASSIGNMENT1_DIR=${SO2_WORKSPACE}/skels/assignments/1-tracer
ASSIGNMENT1_CHECKER_LOCAL_DIR=checker/1-tracer-checker
ASSIGNMENT1_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/1-tracer-checker
ASSIGNMENT1_OUTPUT=${SO2_WORKSPACE}/skels/1-tracer-output
ASSIGNMENT1_FINISHED=${SO2_WORKSPACE}/skels/1-tracer-finished
ASSIGNMENT1_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/1-tracer/tracer.h
ASSIGNMENT1_CHECKER_AUX_LIST="${ASSIGNMENT1_CHECKER_DIR}/_helper/tracer_helper.ko"

ASSIGNMENT2_TIMEOUT=300 # 5 min
ASSIGNMENT2_MOD=uart16550.ko
ASSIGNMENT2_DIR=${SO2_WORKSPACE}/skels/assignments/2-uart
ASSIGNMENT2_CHECKER_LOCAL_DIR=checker/2-uart-checker
ASSIGNMENT2_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/2-uart-checker
ASSIGNMENT2_OUTPUT=${SO2_WORKSPACE}/skels/2-uart-output
ASSIGNMENT2_FINISHED=${SO2_WORKSPACE}/skels/2-uart-finished
ASSIGNMENT2_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/2-uart/uart16550.h
ASSIGNMENT2_CHECKER_AUX_LIST="${ASSIGNMENT2_CHECKER_DIR}/_test/solution.ko"

ASSIGNMENT3_TIMEOUT=360 # 6 min
ASSIGNMENT3_MOD=ssr.ko
ASSIGNMENT3_DIR=${SO2_WORKSPACE}/skels/assignments/3-raid
ASSIGNMENT3_CHECKER_LOCAL_DIR=checker/3-raid-checker
ASSIGNMENT3_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/3-raid-checker
ASSIGNMENT3_OUTPUT=${SO2_WORKSPACE}/skels/3-raid-output
ASSIGNMENT3_FINISHED=${SO2_WORKSPACE}/skels/3-raid-finished
ASSIGNMENT3_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/3-raid/ssr.h
ASSIGNMENT3_CHECKER_AUX_LIST="${ASSIGNMENT3_CHECKER_DIR}/_test/run-test"

ASSIGNMENT4_TIMEOUT=300 # 5 min
ASSIGNMENT4_MOD=af_stp.ko
ASSIGNMENT4_DIR=${SO2_WORKSPACE}/skels/assignments/4-stp
ASSIGNMENT4_CHECKER_LOCAL_DIR=checker/4-stp-checker
ASSIGNMENT4_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/4-stp-checker
ASSIGNMENT4_OUTPUT=${SO2_WORKSPACE}/skels/4-stp-output
ASSIGNMENT4_FINISHED=${SO2_WORKSPACE}/skels/4-stp-finished
ASSIGNMENT4_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/4-stp/stp.h
#ASSIGNMENT4_CHECKER_AUX_LIST="${ASSIGNMENT3_CHECKER_DIR}/_test/run-test"


usage()
{
	echo "Usage: $0 <assignment>"
	exit 1
}



recover_grade_from_timeout()
{
	local output=$1
	if [ ! -f $output ]; then
		echo "$output not available"
	else
		points_total=$(echo $(cat $output | grep "....passed" | egrep -o "/.*[0-9]+\.*[0-9]*.*\]" | egrep -o "[0-9]+\.*[0-9]*" | head -n 1))
		list=$(echo $(cat $output | grep "....passed" | egrep  -o "\[.*[0-9]+\.*[0-9]*.*\/" |  egrep -o "[0-9]+\.*[0-9]*") | sed -e 's/\s\+/,/g')
		recovered_points=$(python3 -c "print(sum([$list]))")
		echo "Recovered from timeout => Total: [$recovered_points/$points_total]"
		echo "Please note that this is not a DIRECT checker output! Other penalties may be applied!"
		echo "Please contact a teaching assistant"
		python3 -c "print('Total: ' + str(int ($recovered_points * 100 / $points_total)) + '/' + '100')"
	fi
}

timeout_exceeded()
{
	local output=$1
	pkill -SIGKILL qemu
	echo ""
	echo "TIMEOUT EXCEEDED !!! killing the process"
	if [[ $RECOVER_GRADE_TIMEOUT == 0 ]]; then
		if [ -f $output ]; then
			echo "$output not available"
		else
			cat $output
		fi
		echo "dumping SO2_VM_LOG=${SO2_VM_LOG} output"
		cat $SO2_VM_LOG

		echo "The Recover Grade Timeout option is not set! Please contact a teaching assistant!"
	else
		recover_grade_from_timeout $output
	fi
	echo "<VMCK_NEXT_END>"
	# exit successfully for vmchecker-next to process output
        exit 0 # TODO: fixme
}

compute_total()
{

	local output=$1
	points=$(cat $output | egrep "Total:" | egrep "\ *([0-9]+)" -o  | head -n 1)
	points_total=$(cat $output | egrep "Total:" | egrep "\ *([0-9]+)" -o  | tail -n 1)
	if [[ $points != "" ]] && [[ $points_total != "" ]]; then
		python3 -c "print('Total: ' + str(int ($points * 100 / $points_total)) + '/' + '100')"
		echo "<VMCK_NEXT_END>"
	fi
}

dump_output()
{
	local output=$1
	local timeout=$2
	echo "<VMCK_NEXT_BEGIN>"
	cat $output
	echo "Running time $timeout/$TIMEOUT"

}

error_message()
{
	local output=$1
	echo "<VMCK_NEXT_BEGIN>"
	echo "Cannot find $assignment_mod"
	echo -e "\t-Make sure you have the sources directly in the root of the archive."
	echo -e "\t-Make sure you have not changed the header that comes with the code skeleton."
	echo -e "\t-Make sure the assignment compiles in a similar environment as vmchecker-next by running './local.sh checker <assignment-name>'."
	echo "After you have solved the problems, resubmit the assignment on moodle until the score appears as feedback, otherwise, the assignment will not be graded."
	echo "<VMCK_NEXT_END>"
}

run_checker()
{
	local assignment_mod=$1
	local assignment_dir=$2
	local local_checker_dir=$3
	local checker_dir=$4
	local output=$5
	local finished=$6
	local assignment=$7
	local header_overwrite=$8
	local aux_modules=$9

	local module_path="${assignment_dir}/${assignment_mod}"

	echo "Copying the contents of src/ into $assignment_dir"
	cp src/* $assignment_dir

	echo "Copying the contents of $local_checker_dir into $checker_dir"
	cp -r $local_checker_dir/* $checker_dir

	echo "Checking if $assignment_mod exists before build"
	if [ -f $module_path ]; then
			echo "$assignment_mod shouldn't exists. Removing ${module_path}"
			rm $module_path
	fi

	pushd $assignment_dir &> /dev/null
		echo "Cleaning $assignment_dir => Will remove: *.o *.mod *.mod.c .*.cmd *.ko modules.order"
		rm *.o &> /dev/null
		rm *.mod &> /dev/null
		rm *.mod.c &> /dev/null
		rm .*.cmd &> /dev/null
		rm *.ko &> /dev/null
		rm modules.order &> /dev/null

		if [[ $header_overwrite != "" ]]; then
			echo "Overwrite from $header_overwrite"
			cp $header_overwrite  .
		fi
	popd &> /dev/null

		
	pushd $SO2_WORKSPACE &> /dev/null
		if [ -f $output ]; then
			echo "Removing $output"
			rm $output &> /dev/null
		fi
		if [ -f $finished ]; then
			echo "Removing $finished"
			rm $finished &> /dev/null
		fi

		echo "Building..."
		make build

		if [ ! -f $module_path ]; then
			error_message $assignment_mod
			# exit successfully for vmchecker-next to process output
			exit 0 # TODO: fixme
		fi
	
		# copy *.ko in checker
		echo "Copying $module_path into $checker_dir"
		cp $module_path $checker_dir
		
		# copy aux modules in checker
		if [[ $aux_modules != "" ]]; then
			for mod in $aux_modules
			do
				echo "Copying $mod in $checker_dir"
				cp $mod $checker_dir
			done
		fi

		LINUX_ADD_CMDLINE="so2=$assignment" make checker &> ${SO2_VM_LOG} &

		timeout=0
		echo -n "CHECKER IS RUNNING"
		while [ ! -f $finished ]
		do
			if ((timeout >= TIMEOUT)); then
				if [ -f $output ]; then
					echo ""
					dump_output $output
					compute_total $output
				fi
				timeout_exceeded $output
			fi
			sleep 2
			(( timeout += 2 ))
			echo -n .
		done
		echo ""
		dump_output $output $timeout
		compute_total $output
	popd &> /dev/null
}

case $1 in
	0-list)
		TIMEOUT=$ASSIGNMENT0_TIMEOUT
		RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory
		run_checker $ASSIGNMENT0_MOD $ASSIGNMENT0_DIR $ASSIGNMENT0_CHECKER_LOCAL_DIR $ASSIGNMENT0_CHECKER_DIR $ASSIGNMENT0_OUTPUT $ASSIGNMENT0_FINISHED $1
		;;
	1-tracer)
		TIMEOUT=$ASSIGNMENT1_TIMEOUT
		RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory
		run_checker $ASSIGNMENT1_MOD $ASSIGNMENT1_DIR $ASSIGNMENT1_CHECKER_LOCAL_DIR $ASSIGNMENT1_CHECKER_DIR $ASSIGNMENT1_OUTPUT $ASSIGNMENT1_FINISHED $1 $ASSIGNMENT1_HEADER_OVERWRITE $ASSIGNMENT1_CHECKER_AUX_LIST
		;;
	2-uart)
		TIMEOUT=$ASSIGNMENT2_TIMEOUT
		RECOVER_GRADE_TIMEOUT=1 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory
		run_checker $ASSIGNMENT2_MOD $ASSIGNMENT2_DIR $ASSIGNMENT2_CHECKER_LOCAL_DIR $ASSIGNMENT2_CHECKER_DIR $ASSIGNMENT2_OUTPUT $ASSIGNMENT2_FINISHED $1 $ASSIGNMENT2_HEADER_OVERWRITE $ASSIGNMENT2_CHECKER_AUX_LIST
 		;;
	3-raid)
		TIMEOUT=$ASSIGNMENT3_TIMEOUT
		RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory
		run_checker $ASSIGNMENT3_MOD $ASSIGNMENT3_DIR $ASSIGNMENT3_CHECKER_LOCAL_DIR $ASSIGNMENT3_CHECKER_DIR $ASSIGNMENT3_OUTPUT $ASSIGNMENT3_FINISHED $1 $ASSIGNMENT3_HEADER_OVERWRITE $ASSIGNMENT3_CHECKER_AUX_LIST
		;;
	4-stp)
		TIMEOUT=$ASSIGNMENT4_TIMEOUT
		RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output file
		run_checker $ASSIGNMENT4_MOD $ASSIGNMENT4_DIR $ASSIGNMENT4_CHECKER_LOCAL_DIR $ASSIGNMENT4_CHECKER_DIR $ASSIGNMENT4_OUTPUT $ASSIGNMENT4_FINISHED $1 $ASSIGNMENT4_HEADER_OVERWRITE
		;;
	
	*)
		usage
		;;
esac
