#!/usr/bin/env bash

counterfile=jobscounter
counterlock=counterlock
runninglock=runninglock
runningfile=runningcounter
basedir=$(pwd)
runmode=0755
waittime=${waittime:=3}
maxproc=${maxproc:=3}
pbcommand=$0

die() { echo "$*" >&2; exit 1; }

# We need unique jobs numbers, preferably sequentially.
# So just use a counter file.
increasejobscounter() {
	(
		flock -x 200
		if [ -e $counterfile ]; then
			jobscounter=$(cat $counterfile)
		fi
		jobscounter=$(expr $jobscounter + 1 )
		echo $jobscounter > $counterfile
		# nodig zodat aanroepende functies kunnen weten welk
		# jobnummer ze mogen gebruiken
		echo ${jobscounter}
	) 200>$counterlock
# XXX hoe haal je lockfile weer netjes weg?
# het antwoord is waarschijnlijk: niet
}

addjob() {
	# command
	jobnumber=$(increasejobscounter)
	echo "New job: ${jobnumber}"
	if [ -e "${basedir}/jobs/${jobnumber}" ]; then
		die "Job ${jobnumber} already scheduled. This should not happen."
	fi
	echo "$*" > "${basedir}/jobs/${jobnumber}"
	chmod ${runmode} "${basedir}/jobs/${jobnumber}"
}

addjobfromfile() {
	# filename
	jobnumber=$(increasejobscounter)
	echo "New job: ${jobnumber}"
	filename=$1
	if [ -f "${filename}" ]; then
		if [ -e "${basedir}/jobs/${jobnumber}" ]; then
			die "Job ${jobnumber} already scheduled. This should not happen."
		fi
		cp "${filename}" "${basedir}/jobs/${jobnumber}"
		chmod ${runmode} "${basedir}/jobs/${jobnumber}"
	else
		die "file not found: ${filename}"
	fi
}

deljob() {
	# jobnumber
	jobnumber=$1
	if [ -f "${basedir}/jobs/${jobnumber}" ]; then
		if rm "${basedir}/jobs/${jobnumber}" ; then
			echo "Job removed: ${jobnumber}"
		else
			die "Couldn't remove job: ${jobnumber}"
		fi
	else
		die "Job '${jobnumber}' not in queue"
	fi
}

parkjobs() {
# XXX probably needs locking too, so it doens't clash with starting jobs
	if [ -n "$(ls ${basedir}/jobs/*)" ]; then
		mv ${basedir}/jobs/* ${basedir}/park/
	fi
}

unparkjobs() {
	if [ -n "$(ls ${basedir}/park/*)" ]; then
		mv ${basedir}/park/* ${basedir}/jobs/
	fi
}

unfail() {
	# jobnumber
	jobnumber=$1
	if [ -d "${basedir}/failure/${jobnumber}" ]; then
		if [ -f "${basedir}/failure/${jobnumber}/job" ]; then
			mv "${basedir}/failure/${jobnumber}/job" "${basedir}/jobs/${jobnumber}"
			rm -rf "${basedir}/failure/${jobnumber}"
		fi
	fi
}

changeprio() {
	# prio
	# jobnumber
	echo unimplemented
}

status() {
	readconfig
	# show which jobs are running
	echo "Running jobs ($(ls ${basedir}/processors/|wc -w)/${maxproc}):"
	ls "${basedir}/processors/"
	# show queues
	echo "Jobs queued ($(ls ${basedir}/jobs/|wc -w)):"
	ls "${basedir}/jobs/"
	echo "Jobs parked: $(ls ${basedir}/park/|wc -w)"
	echo "Jobs finished: $(ls ${basedir}/results/|wc -w)"
	echo "Jobs failed ($(ls ${basedir}/failure/|wc -w)):"
	ls "${basedir}/failure/"
}

stop() {
	# stop a jobnumber
	echo unimplemented
}

stopall() {
	# stop all jobs and scheduler
	echo unimplemented
}

usage() {
	echo unimplemented
	cat <<ENDTXT
Usage: pb <option>

add <command>		Add the given command as a job.
addfile|af <file>	Add the given file as a job.
createconfing		Write a default configuration file.
run			Start the master scheduler process.
del <jobnr>		Delete the given job from the queue.
help|-h			Show this help.
park			Place all queued jobs in the parkinglot.
unpark			Place all jobs from the parkinglot back in the queue.
retry <jobnr>		Reschedule a failed job. Destroys all output.
prio <h|n|l> <jobnr>	Change the priority of the given job.	[unimplemented]
status|st		Show the status of the current jobs.
stop <jobnr>		Stop the given job.	[unimplemented]
stopall			Stop all running jobs.	[unimplemented]
ENDTXT
}

readconfig() {
	if [ -f ${basedir}/config ]; then
		source ${basedir}/config
	fi
}

createdirs() {
	# setup dirs that will be used
	dirs="jobs processors results failure monitor park"
	for dir in $dirs; do
		if [ ! -d "${basedir}/${dir}" ]; then
			if ! mkdir "${basedir}/${dir}"; then
				echo "couldn't create directory ${basedir}/${dir}"
				exit 1
			fi
		fi
	done
}

createconfig() {
	if [ ! -e ${basedir}/config ]; then
		echo "# how many parallel processes?" >> ${basedir}/config
		echo "maxproc=${maxproc}" >> ${basedir}/config
		echo "# take a break for how long between process end and starting a new one" >> ${basedir}/config
		echo "waittime=${waittime}" >> ${basedir}/config
	fi
}

incrementrunning() {
	(
		flock -x 210
		if [ -e $runningfile ]; then
			echo $(expr $(cat $runningfile) + 1) > $runningfile
		fi
	) 210>$runninglock
}

decrementrunning() {
	(
		flock -x 220
		if [ -e $runningfile ]; then
			runningcounter=$(cat $runningfile)
		fi
		runningcounter=$(expr $runningcounter - 1 )
		if [ $runningcounter -lt 0 ]; then
			echo 0 > $runningfile
		else
			echo $runningcounter > $runningfile
		fi
	) 220>$runninglock
}

getjob() {
	# Meh, ik ken geen handige manier om er een uit
	# het rijtje te halen. Dan maar zo.
	# lock hier omheen?
	for jl in ${basedir}/jobs/*;do
		if [ -n "${jl}" ]; then
			echo ${jl##*/}
			mkdir ${basedir}/processors/${jl##*/}
			mv $jl ${basedir}/processors/${jl##*/}/job
		fi
		break
	done
}

failjob() {
	jobnumber=$1
	mv "${basedir}/processors/${jobnumber}" "${basedir}/failure"
}

finishjob() {
	jobnumber=$1
	mv "${basedir}/processors/${jobnumber}" "${basedir}/results"
}

runprocessor() {
	incrementrunning
	sleep 1
	runjob=$(getjob)
	if [ -n "${runjob}" ]; then
		echo "Starting job: ${runjob}"
		owd=$(pwd)
		cd "${basedir}/processors/${runjob}"
		if "${basedir}/processors/${runjob}/job" 2>&1 > "${basedir}/processors/${runjob}/output" ;then
			finishjob ${runjob}
			echo "Finished job: ${runjob}"
		else
			failjob ${runjob}
			echo "Failed job: ${runjob}"
		fi
		cd $owd
	fi
	decrementrunning
}

master() {
	echo 0 >"${basedir}/$runningfile"

	while sleep $waittime; do
		if [ $(cat "${basedir}/${runningfile}") -lt $maxproc ]; then
			# via ls omdat anders de globbing klote is
			jl=$(ls ${basedir}/jobs/)
			if [ -n "$jl" ]; then
				${pbcommand} process &
			fi
			readconfig
		fi
	done
}

###################################

# test voor voldoende args
command=$1
shift

createdirs

case "$command" in
	add)		addjob "$*" ;;
	addfile|af)	addjobfromfile "$*" ;;
	createconfig)	createconfig ;;
	run)		master ;;
	del)		deljob "$*" ;;
	help)		usage ;;
	-h)		usage ;;
	park)		parkjobs ;;
	unpark)		unparkjobs ;;
	retry)		unfail "$*" ;;
	prio)		changeprio "$*" ;;
	process)	runprocessor "$*" ;;
	status|st)	status ;;
	stop)		stop "$*" ;;
	stopall)	stopall ;;
	*)		die "Usage: pb (add|addfile) <command>" ;;
esac
