define(["Tone/core/Tone", "Tone/event/Event"], function (Tone) {

	/**
	 *  @class Tone.Loop creates a looped callback at the 
	 *         specified interval. The callback can be 
	 *         started, stopped and scheduled along
	 *         the Transport's timeline. 
	 *  @example
	 * var loop = new Tone.Loop(function(time){
	 * 	//triggered every eighth note. 
	 * 	console.log(time);
	 * }, "8n").start(0);
	 * Tone.Transport.start();
	 *  @extends {Tone}
	 *  @param {Function} callback The callback to invoke with the event.
	 *  @param {Time} interval The time between successive callback calls. 
	 */
	Tone.Loop = function(){

		var options = Tone.defaults(arguments, ["callback", "interval"], Tone.Loop);
		Tone.call(this);

		/**
		 *  The event which produces the callbacks
		 */
		this._event = new Tone.Event({
			"callback" : this._tick.bind(this),
			"loop" : true,
			"loopEnd" : options.interval,
			"playbackRate" : options.playbackRate,
			"probability" : options.probability
		});

		/**
		 *  The callback to invoke with the next event in the pattern
		 *  @type {Function}
		 */
		this.callback = options.callback;

		//set the iterations
		this.iterations = options.iterations;
	};

	Tone.extend(Tone.Loop);

	/**
	 *  The defaults
	 *  @const
	 *  @type  {Object}
	 */
	Tone.Loop.defaults = {
		"interval" : "4n",
		"callback" : Tone.noOp,
		"playbackRate" : 1,
		"iterations" : Infinity,
		"probability" : true,
		"mute" : false
	};

	/**
	 *  Start the loop at the specified time along the Transport's
	 *  timeline.
	 *  @param  {TimelinePosition=}  time  When to start the Loop.
	 *  @return  {Tone.Loop}  this
	 */
	Tone.Loop.prototype.start = function(time){
		this._event.start(time);
		return this;
	};

	/**
	 *  Stop the loop at the given time.
	 *  @param  {TimelinePosition=}  time  When to stop the Arpeggio
	 *  @return  {Tone.Loop}  this
	 */
	Tone.Loop.prototype.stop = function(time){
		this._event.stop(time);
		return this;
	};

	/**
	 *  Cancel all scheduled events greater than or equal to the given time
	 *  @param  {TimelinePosition}  [time=0]  The time after which events will be cancel.
	 *  @return  {Tone.Loop}  this
	 */
	Tone.Loop.prototype.cancel = function(time){
		this._event.cancel(time);
		return this;
	};

	/**
	 *  Internal function called when the notes should be called
	 *  @param  {Number}  time  The time the event occurs
	 *  @private
	 */
	Tone.Loop.prototype._tick = function(time){
		this.callback(time);
	};

	/**
	 *  The state of the Loop, either started or stopped.
	 *  @memberOf Tone.Loop#
	 *  @type {String}
	 *  @name state
	 *  @readOnly
	 */
	Object.defineProperty(Tone.Loop.prototype, "state", {
		get : function(){
			return this._event.state;
		}
	});

	/**
	 *  The progress of the loop as a value between 0-1. 0, when
	 *  the loop is stopped or done iterating. 
	 *  @memberOf Tone.Loop#
	 *  @type {NormalRange}
	 *  @name progress
	 *  @readOnly
	 */
	Object.defineProperty(Tone.Loop.prototype, "progress", {
		get : function(){
			return this._event.progress;
		}
	});

	/**
	 *  The time between successive callbacks. 
	 *  @example
	 * loop.interval = "8n"; //loop every 8n
	 *  @memberOf Tone.Loop#
	 *  @type {Time}
	 *  @name interval
	 */
	Object.defineProperty(Tone.Loop.prototype, "interval", {
		get : function(){
			return this._event.loopEnd;
		},
		set : function(interval){
			this._event.loopEnd = interval;
		}
	});

	/**
	 *  The playback rate of the loop. The normal playback rate is 1 (no change). 
	 *  A `playbackRate` of 2 would be twice as fast. 
	 *  @memberOf Tone.Loop#
	 *  @type {Time}
	 *  @name playbackRate
	 */
	Object.defineProperty(Tone.Loop.prototype, "playbackRate", {
		get : function(){
			return this._event.playbackRate;
		},
		set : function(rate){
			this._event.playbackRate = rate;
		}
	});

	/**
	 *  Random variation +/-0.01s to the scheduled time. 
	 *  Or give it a time value which it will randomize by.
	 *  @type {Boolean|Time}
	 *  @memberOf Tone.Loop#
	 *  @name humanize
	 */
	Object.defineProperty(Tone.Loop.prototype, "humanize", {
		get : function(){
			return this._event.humanize;
		},
		set : function(variation){
			this._event.humanize = variation;
		}
	});

	/**
	 *  The probably of the callback being invoked.
	 *  @memberOf Tone.Loop#
	 *  @type {NormalRange}
	 *  @name probability
	 */
	Object.defineProperty(Tone.Loop.prototype, "probability", {
		get : function(){
			return this._event.probability;
		},
		set : function(prob){
			this._event.probability = prob;
		}
	});

	/**
	 *  Muting the Loop means that no callbacks are invoked.
	 *  @memberOf Tone.Loop#
	 *  @type {Boolean}
	 *  @name mute
	 */
	Object.defineProperty(Tone.Loop.prototype, "mute", {
		get : function(){
			return this._event.mute;
		},
		set : function(mute){
			this._event.mute = mute;
		}
	});

	/**
	 *  The number of iterations of the loop. The default
	 *  value is Infinity (loop forever).
	 *  @memberOf Tone.Loop#
	 *  @type {Positive}
	 *  @name iterations
	 */
	Object.defineProperty(Tone.Loop.prototype, "iterations", {
		get : function(){
			if (this._event.loop === true){
				return Infinity;
			} else {
				return this._event.loop;
			}
		},
		set : function(iters){
			if (iters === Infinity){
				this._event.loop = true;
			} else {
				this._event.loop = iters;
			}
		}
	});

	/**
	 *  Clean up
	 *  @return  {Tone.Loop}  this
	 */
	Tone.Loop.prototype.dispose = function(){
		this._event.dispose();
		this._event = null;
		this.callback = null;
	};

	return Tone.Loop;
});