package ClosureTimers
import NoWurst
import TimerDialog
import TimerUtils
import Real
import Wurstunit

/** Execute an action after a certain time.
	The callback object is automatically destroyed.
	Must be used on a timer acquired with `getTimer()`

	Example use:
	| someTimer.doAfter(10.0) ->
	|	 print("10 seconds later")
*/
public function timer.doAfter(real timeToWait, CallbackSingle cb) returns CallbackSingle
	cb.start(this, timeToWait)
	return cb

/** Execute an action after a certain time.
	The callback object is automatically destroyed.

	Example use:
	| doAfter(10.0) ->
	|	 print("10 seconds later")
*/
public function doAfter(real timeToWait, CallbackSingle cb) returns CallbackSingle
	return getTimer().doAfter(timeToWait, cb)

/** Execute an action after a certain time.
	The timer is accompanied by a visible timerdialog with the title text provided.
	The callback object is automatically destroyed.

	Example use:
	| doAfterWithDialogue(10.0, "Respawn in") ->
	|	 hero.revive()
	|	 print("10 seconds later")
*/
public function doAfterWithDialogue(real timeToWait, string titleText, Callback cb) returns CallbackSingle
	let tim = getTimer()
	let dia = tim.createTimerDialog()..setTitle(titleText)..display(true)
	return tim.doAfter(timeToWait) ->
		cb.call()
		destroy cb
		dia.destr()

/** Execute an action with a 0-second timer delay.
	The callback object is destroyed afterwards.
*/
public function nullTimer(CallbackSingle cb) returns CallbackSingle
	return doAfter(0, cb)

/** Execute an action periodically.
	The callback has to be destroyed manually.
	Must be used on a timer acquired with `getTimer()`

	Example use:
	| someTimer.doPeriodically(0.5) cb ->
	|	 if i > 10
	|		destroy cb
*/
public function timer.doPeriodically(real time, CallbackPeriodic cb) returns CallbackPeriodic
	cb.start(this, time)
	return cb

/** Execute an action periodically.
	The callback has to be destroyed manually.

	Example use:
	| doPeriodically(0.5) cb ->
	|	 if i > 10
	|		destroy cb
*/
public function doPeriodically(real time, CallbackPeriodic cb) returns CallbackPeriodic
	return getTimer().doPeriodically(time, cb)

/** execute an action periodically, with a limited amount of calls
	The callback object is destroyed after the action has been executed callAmount times.
	Must be used on a timer acquired with `getTimer()`

	Example use:
	| someTimer.doPeriodicallyCounted(0.5, 100) cb ->
	|	 doSomething()

*/
public function timer.doPeriodicallyCounted(real time, int callAmount, CallbackCounted cb) returns CallbackCounted
	cb.start(this, time, callAmount)
	return cb

/** execute an action periodically, with a limited amount of calls
	The callback object is destroyed after the action has been executed callAmount times.

	Example use:
	| doPeriodicallyCounted(0.5, 100) cb ->
	|	 doSomething()

*/
public function doPeriodicallyCounted(real time, int callAmount, CallbackCounted cb) returns CallbackCounted
	return getTimer().doPeriodicallyCounted(time, callAmount, cb)

/** execute an action periodically, with a limited duration
	The callback object is destroyed afterwards.
	Must be used on a timer acquired with `getTimer()`

	Example use:
	| someTimer.doPeriodicallyCounted(0.5, 10.) ->
	|	 doSomething()

*/
public function timer.doPeriodicallyTimed(real interval, real timerDuration, CallbackCounted cb) returns CallbackCounted
	return this.doPeriodicallyCounted(interval, (timerDuration / interval + 0.5).toInt(), cb)

/** execute an action periodically, with a limited duration
	The callback object is destroyed afterwards.

	Example use:
	| doPeriodicallyCounted(0.5, 10.) ->
	|	 doSomething()

*/
public function doPeriodicallyTimed(real interval, real timerDuration, CallbackCounted cb) returns CallbackCounted
	return getTimer().doPeriodicallyTimed(interval, timerDuration, cb)

//Timer Stuff
public interface Callback
	function call()

public abstract class CallbackSingle
	private timer t
	abstract function call()

	protected function start(timer whichTimer, real time)
		t = whichTimer
			..setData(this castTo int)
			..start(time, () -> staticCallback())


	private static function staticCallback()
		let t = GetExpiredTimer()
		let cb = t.getData() castTo thistype
		cb.call()
		destroy cb

	function getRemaining() returns real
		return t.getRemaining()

	ondestroy
		t.release()


public abstract class CallbackPeriodic
	private timer t

	protected abstract function call(thistype cb)

	protected function start(timer whichTimer, real time)
		t = whichTimer
			..setData(this castTo int)
			..startPeriodic(time, function staticCallback)

	private static function staticCallback()
		let cb = (GetExpiredTimer().getData() castTo thistype)
		cb.call(cb)

	ondestroy
		t.release()

public abstract class CallbackCounted
	private var count = 0
	private var maxCount = 0
	private timer t

	protected abstract function call(thistype cb)

	protected function start(timer whichTimer, real time, int callAmount)
		count = callAmount
		maxCount = callAmount
		t = whichTimer
			..setData(this castTo int)
			..startPeriodic(time, function staticCallback)

	function isLast() returns boolean
		return count == 0

	function getCount() returns int
		return count < maxCount ? count + 1 : count

	/** Returns a value between 0 and 1. */
	function progress() returns real
		return 1. - count / maxCount

	function stop()
		count = 0

	private static function staticCallback()
		(GetExpiredTimer().getData() castTo thistype).callAndCount()

	private function callAndCount()
		if count > 0
			count--
			call(this)
		else
			destroy this

	ondestroy
		t.release()


var x = 200
@Test function testDoAfter()

	doAfter(0.1) ->
		x += 50

	doAfter(0.2) ->
		x *= 2

	doAfter(0.3) ->
		x = x div 2
		x.assertEquals(250)

	var _count = 3

	let cb = doPeriodicallyCounted(3, 3) (CallbackCounted cb) ->
		cb.getCount().assertEquals(_count)
		if cb.isLast()
			cb.progress().assertEquals(1, 0.0001)
			cb.getCount().assertEquals(1)
		else if _count == 3
			cb.progress().assertEquals(0.33333, 0.00001)
			cb.getCount().assertEquals(3)
		else if _count == 2
			cb.progress().assertEquals(0.66666, 0.00001)
			cb.getCount().assertEquals(2)
		_count--

	cb.progress().assertEquals(0, 0.00001)
	cb.getCount().assertEquals(_count)