*! version 1.3.0 04okt2022
* Contact jesse.wursten@kuleuven.be for bug reports/inquiries.
* Many thanks to Daniel Klein for his suggestions and code snippets.

* Changelog
** 04oct2022: Better handling of named timers
** 10jun2021: timeit now executes commands in host version rather than the version 11 specified by this particular ado
** 14jan2019: Fixed space-before-colon issue
** 27nov2018: Fixed space-in-command issue
** 16apr2018: Fixed some parsing issues, added r-results (this is a pain) and incremental timer results

program define timeit, rclass
	version 11

	** Parse syntax
	gettoken beforeColon afterColon : 0, parse(":") quotes
	gettoken theTimer theName: beforeColon, quotes
	gettoken theColon theCommand : afterColon , parse(":")
	local theName = trim("`theName'")

	** Some error checking
	*** User didn't enter a timer
	if "`theTimer'" == ":" {
		local afterColonTrimmed = trim("`afterColon'")
		noisily di as error "You have to enter a timer number.  E.g. timeit 3: `afterColonTrimmed'"
		exit 198
	}

	*** User mixed up name and timer position
	local potentialTimer : word 1 of `beforeColon'
	local potentialTimer2 : word 2 of `beforeColon'
	capture assert real("`potentialTimer'") == . & real("`potentialTimer2'") != .
	if _rc == 0 {
		**** And didn't write a colon
		if "`theColon'" != ":" {
			local potentialCommand : list beforeColon - potentialTimer
			local potentialCommand : list potentialCommand - potentialTimer2
			noisily di as error "You have to enter the timer before the name, followed by a colon.  E.g. timeit `potentialTimer2' `potentialTimer': `potentialCommand'"
			exit 198
		}

		**** ... did write a colon
		else {
			noisily di as error "You have to enter the timer before the name.  E.g. timeit `potentialTimer2' `potentialTimer': `theCommand'"
			exit 198
		}
	}

	*** User didn't enter a colon
	if "`theColon'" != ":" {
		capture assert real("`potentialTimer'") != .

		**** And no timer
		if _rc != 0 {
			noisily di as error "You have to enter a timer and a colon.  E.g. timeit 3: `beforeColon'"
			exit 198
		}

		else {
			local potentialCommand : list beforeColon - potentialTimer
			noisily di as error "You have to enter a colon.  E.g. timeit `potentialTimer': `potentialCommand'"
			exit 198
		}
	}

	*** User didn't enter a command
	if `"`theCommand'"' == "" {
		noisily di as error "You have to enter a command.  E.g. timeit `theTimer': sum someVariable"
		exit 198
	}

	** Check if timer -theName- already exists
	if "`theName'" != "" {
		if r(`theName') != . local initialValue_theName = r(`theName')
	}

	** Check whether timer is already running
	*** Store return results in case they were present
	tempname r_results
	_return hold `r_results'

	*** Check timer before stopping it
	quietly timer list `theTimer'
	local beforeStop = r(t`theTimer')
	timer off `theTimer'

	*** ... and after
	quietly timer list `theTimer'
	local afterStop = r(t`theTimer')

	*** Compare
	capture assert `beforeStop' == `afterStop'
	if _rc != 0 noisily di as error "Timer was already running (active)." _newline "You might want to check your code as it will be unclear* what is being timed exactly." _newline "Note that the timer will be stopped after this timeit command." _newline _newline "*Normally, everything from -timer on `theTimer'- until this -timeit- command."

	** Return return results
	_return restore `r_results'


	** Time and execute the command
	** The nobreak + capture ... break combination ensures the timer stops when the user manually stops the program
	** The capture noisily ... cmd combination ensures the timer stops when the cmd returns an error. exit _rc then presents the error to the user
	** The version `=_caller()' ensures that commands are executed in the original version, not the one set by timeit (thanks Daniel Klein!)
	nobreak {
		timer on `theTimer'
		capture noisily break version `=_caller()': `theCommand'
		timer off `theTimer'
	}

	** Store timer
	*** Save existing r_results
	return add

	*** Access timer
	tempname timer_value timer_increment fakeReturnHold
	qui timer list
	scalar `timer_value' = r(t`theTimer')
	_return hold `fakeReturnHold'

	*** Calculate increment
	if `afterStop' != .	scalar `timer_increment' = scalar(`timer_value') - `afterStop'
	else 				scalar `timer_increment' = scalar(`timer_value')

	*** Compile return()
	**** Numbered timers
	return scalar t`theTimer' = scalar(`timer_value')
	return scalar delta_t`theTimer' = scalar(`timer_increment')

	**** Named timers
	if "`theName'" != "" {
		if "`initialValue_theName'" != "" {
			return scalar `theName' = `initialValue_theName' + scalar(`timer_increment')
			return scalar delta_`theName' = scalar(`timer_increment')
		}
		else {
			return scalar `theName' = scalar(`timer_increment')
			return scalar delta_`theName' = scalar(`timer_increment')
		}
	}

	*** Compile r()
	return add

	exit _rc
end