import Tone from "../core/Tone";
import "../event/Part";
import "../core/Transport";

/**
 *  @class A sequence is an alternate notation of a part. Instead
 *         of passing in an array of [time, event] pairs, pass
 *         in an array of events which will be spaced at the
 *         given subdivision. Sub-arrays will subdivide that beat
 *         by the number of items are in the array.
 *         Sequence notation inspiration from [Tidal](http://yaxu.org/tidal/)
 *  @param  {Function}  callback  The callback to invoke with every note
 *  @param  {Array}    events  The sequence
 *  @param  {Time} subdivision  The subdivision between which events are placed.
 *  @extends {Tone.Part}
 *  @example
 * var seq = new Tone.Sequence(function(time, note){
 * 	console.log(note);
 * //straight quater notes
 * }, ["C4", "E4", "G4", "A4"], "4n");
 *  @example
 * var seq = new Tone.Sequence(function(time, note){
 * 	console.log(note);
 * //subdivisions are given as subarrays
 * }, ["C4", ["E4", "D4", "E4"], "G4", ["A4", "G4"]]);
 */
Tone.Sequence = function(){

	var options = Tone.defaults(arguments, ["callback", "events", "subdivision"], Tone.Sequence);

	//remove the events
	var events = options.events;
	delete options.events;

	Tone.Part.call(this, options);

	/**
	 *  The subdivison of each note
	 *  @type  {Ticks}
	 *  @private
	 */
	this._subdivision = this.toTicks(options.subdivision);

	//if no time was passed in, the loop end is the end of the cycle
	if (Tone.isUndef(options.loopEnd) && Tone.isDefined(events)){
		this._loopEnd = (events.length * this._subdivision);
	}
	//defaults to looping
	this._loop = true;

	//add all of the events
	if (Tone.isDefined(events)){
		for (var i = 0; i < events.length; i++){
			this.add(i, events[i]);
		}
	}
};

Tone.extend(Tone.Sequence, Tone.Part);

/**
 *  The default values.
 *  @type  {Object}
 */
Tone.Sequence.defaults = {
	"subdivision" : "4n",
};

/**
 *  The subdivision of the sequence. This can only be
 *  set in the constructor. The subdivision is the
 *  interval between successive steps.
 *  @type {Time}
 *  @memberOf Tone.Sequence#
 *  @name subdivision
 *  @readOnly
 */
Object.defineProperty(Tone.Sequence.prototype, "subdivision", {
	get : function(){
		return Tone.Ticks(this._subdivision).toSeconds();
	}
});

/**
 *  Get/Set an index of the sequence. If the index contains a subarray,
 *  a Tone.Sequence representing that sub-array will be returned.
 *  @example
 * var sequence = new Tone.Sequence(playNote, ["E4", "C4", "F#4", ["A4", "Bb3"]])
 * sequence.at(0)// => returns "E4"
 * //set a value
 * sequence.at(0, "G3");
 * //get a nested sequence
 * sequence.at(3).at(1)// => returns "Bb3"
 * @param {Positive} index The index to get or set
 * @param {*} value Optionally pass in the value to set at the given index.
 */
Tone.Sequence.prototype.at = function(index, value){
	//if the value is an array,
	if (Tone.isArray(value)){
		//remove the current event at that index
		this.remove(index);
	}
	//call the parent's method
	return Tone.Part.prototype.at.call(this, this._indexTime(index), value);
};

/**
 *  Add an event at an index, if there's already something
 *  at that index, overwrite it. If `value` is an array,
 *  it will be parsed as a subsequence.
 *  @param {Number} index The index to add the event to
 *  @param {*} value The value to add at that index
 *  @returns {Tone.Sequence} this
 */
Tone.Sequence.prototype.add = function(index, value){
	if (value === null){
		return this;
	}
	if (Tone.isArray(value)){
		//make a subsequence and add that to the sequence
		var subSubdivision = Math.round(this._subdivision / value.length);
		value = new Tone.Sequence(this._tick.bind(this), value, Tone.Ticks(subSubdivision));
	}
	Tone.Part.prototype.add.call(this, this._indexTime(index), value);
	return this;
};

/**
 *  Remove a value from the sequence by index
 *  @param {Number} index The index of the event to remove
 *  @returns {Tone.Sequence} this
 */
Tone.Sequence.prototype.remove = function(index, value){
	Tone.Part.prototype.remove.call(this, this._indexTime(index), value);
	return this;
};

/**
 *  Get the time of the index given the Sequence's subdivision
 *  @param  {Number}  index
 *  @return  {Time}  The time of that index
 *  @private
 */
Tone.Sequence.prototype._indexTime = function(index){
	if (index instanceof Tone.TransportTime){
		return index;
	} else {
		return Tone.Ticks(index * this._subdivision + this.startOffset).toSeconds();
	}
};

/**
 *  Clean up.
 *  @return {Tone.Sequence} this
 */
Tone.Sequence.prototype.dispose = function(){
	Tone.Part.prototype.dispose.call(this);
	return this;
};

export default Tone.Sequence;