import Tone from "../core/Tone";
import "./Oscillator";
import "../signal/Scale";
import "../core/AudioNode";
import "../signal/Signal";
import "../signal/AudioToGain";
import "../type/Type";
import "../signal/Zero";

/**
 *  @class  LFO stands for low frequency oscillator. Tone.LFO produces an output signal
 *          which can be attached to an AudioParam or Tone.Signal
 *          in order to modulate that parameter with an oscillator. The LFO can
 *          also be synced to the transport to start/stop and change when the tempo changes.
 *
 *  @constructor
 *  @extends {Tone.AudioNode}
 *  @param {Frequency|Object} [frequency] The frequency of the oscillation. Typically, LFOs will be
 *                               in the frequency range of 0.1 to 10 hertz.
 *  @param {number=} min The minimum output value of the LFO.
 *  @param {number=} max The maximum value of the LFO.
 *  @example
 * var lfo = new Tone.LFO("4n", 400, 4000);
 * lfo.connect(filter.frequency);
 */
Tone.LFO = function(){

	var options = Tone.defaults(arguments, ["frequency", "min", "max"], Tone.LFO);
	Tone.AudioNode.call(this);

	/**
	 *  The oscillator.
	 *  @type {Tone.Oscillator}
	 *  @private
	 */
	this._oscillator = new Tone.Oscillator({
		"frequency" : options.frequency,
		"type" : options.type,
	});

	/**
	 *  the lfo's frequency
	 *  @type {Frequency}
	 *  @signal
	 */
	this.frequency = this._oscillator.frequency;

	/**
	 * The amplitude of the LFO, which controls the output range between
	 * the min and max output. For example if the min is -10 and the max
	 * is 10, setting the amplitude to 0.5 would make the LFO modulate
	 * between -5 and 5.
	 * @type {Number}
	 * @signal
	 */
	this.amplitude = this._oscillator.volume;
	this.amplitude.units = Tone.Type.NormalRange;
	this.amplitude.value = options.amplitude;

	/**
	 *  The signal which is output when the LFO is stopped
	 *  @type  {Tone.Signal}
	 *  @private
	 */
	this._stoppedSignal = new Tone.Signal(0, Tone.Type.AudioRange);

	/**
	 *  Just outputs zeros.
	 *  @type {Tone.Zero}
	 *  @private
	 */
	this._zeros = new Tone.Zero();

	/**
	 *  The value that the LFO outputs when it's stopped
	 *  @type {AudioRange}
	 *  @private
	 */
	this._stoppedValue = 0;

	/**
	 *  @type {Tone.AudioToGain}
	 *  @private
	 */
	this._a2g = new Tone.AudioToGain();

	/**
	 *  @type {Tone.Scale}
	 *  @private
	 */
	this._scaler = this.output = new Tone.Scale(options.min, options.max);

	/**
	 *  the units of the LFO (used for converting)
	 *  @type {Tone.Type}
	 *  @private
	 */
	this._units = Tone.Type.Default;
	this.units = options.units;

	//connect it up
	this._oscillator.chain(this._a2g, this._scaler);
	this._zeros.connect(this._a2g);
	this._stoppedSignal.connect(this._a2g);
	this._readOnly(["amplitude", "frequency"]);
	this.phase = options.phase;
};

Tone.extend(Tone.LFO, Tone.AudioNode);

/**
 *  the default parameters
 *
 *  @static
 *  @const
 *  @type {Object}
 */
Tone.LFO.defaults = {
	"type" : "sine",
	"min" : 0,
	"max" : 1,
	"phase" : 0,
	"frequency" : "4n",
	"amplitude" : 1,
	"units" : Tone.Type.Default
};

/**
 *  Start the LFO.
 *  @param  {Time} [time=now] the time the LFO will start
 *  @returns {Tone.LFO} this
 */
Tone.LFO.prototype.start = function(time){
	time = this.toSeconds(time);
	this._stoppedSignal.setValueAtTime(0, time);
	this._oscillator.start(time);
	return this;
};

/**
 *  Stop the LFO.
 *  @param  {Time} [time=now] the time the LFO will stop
 *  @returns {Tone.LFO} this
 */
Tone.LFO.prototype.stop = function(time){
	time = this.toSeconds(time);
	this._stoppedSignal.setValueAtTime(this._stoppedValue, time);
	this._oscillator.stop(time);
	return this;
};

/**
 *  Sync the start/stop/pause to the transport
 *  and the frequency to the bpm of the transport
 *  @returns {Tone.LFO} this
 *  @example
 *  lfo.frequency.value = "8n";
 *  lfo.sync().start(0)
 *  //the rate of the LFO will always be an eighth note,
 *  //even as the tempo changes
 */
Tone.LFO.prototype.sync = function(){
	this._oscillator.sync();
	this._oscillator.syncFrequency();
	return this;
};

/**
 *  unsync the LFO from transport control
 *  @returns {Tone.LFO} this
 */
Tone.LFO.prototype.unsync = function(){
	this._oscillator.unsync();
	this._oscillator.unsyncFrequency();
	return this;
};

/**
 * The miniumum output of the LFO.
 * @memberOf Tone.LFO#
 * @type {number}
 * @name min
 */
Object.defineProperty(Tone.LFO.prototype, "min", {
	get : function(){
		return this._toUnits(this._scaler.min);
	},
	set : function(min){
		min = this._fromUnits(min);
		this._scaler.min = min;
	}
});

/**
 * The maximum output of the LFO.
 * @memberOf Tone.LFO#
 * @type {number}
 * @name max
 */
Object.defineProperty(Tone.LFO.prototype, "max", {
	get : function(){
		return this._toUnits(this._scaler.max);
	},
	set : function(max){
		max = this._fromUnits(max);
		this._scaler.max = max;
	}
});

/**
 * The type of the oscillator: sine, square, sawtooth, triangle.
 * @memberOf Tone.LFO#
 * @type {string}
 * @name type
 */
Object.defineProperty(Tone.LFO.prototype, "type", {
	get : function(){
		return this._oscillator.type;
	},
	set : function(type){
		this._oscillator.type = type;
		this._stoppedValue = this._oscillator._getInitialValue();
		this._stoppedSignal.value = this._stoppedValue;
	}
});

/**
 * The phase of the LFO.
 * @memberOf Tone.LFO#
 * @type {number}
 * @name phase
 */
Object.defineProperty(Tone.LFO.prototype, "phase", {
	get : function(){
		return this._oscillator.phase;
	},
	set : function(phase){
		this._oscillator.phase = phase;
		this._stoppedValue = this._oscillator._getInitialValue();
		this._stoppedSignal.value = this._stoppedValue;
	}
});

/**
 * The output units of the LFO.
 * @memberOf Tone.LFO#
 * @type {Tone.Type}
 * @name units
 */
Object.defineProperty(Tone.LFO.prototype, "units", {
	get : function(){
		return this._units;
	},
	set : function(val){
		var currentMin = this.min;
		var currentMax = this.max;
		//convert the min and the max
		this._units = val;
		this.min = currentMin;
		this.max = currentMax;
	}
});

/**
 *  Returns the playback state of the source, either "started" or "stopped".
 *  @type {Tone.State}
 *  @readOnly
 *  @memberOf Tone.LFO#
 *  @name state
 */
Object.defineProperty(Tone.LFO.prototype, "state", {
	get : function(){
		return this._oscillator.state;
	}
});

/**
 *  Connect the output of the LFO to an AudioParam, AudioNode, or Tone Node.
 *  Tone.LFO will automatically convert to the destination units of the
 *  will get the units from the connected node.
 *  @param  {Tone | AudioParam | AudioNode} node
 *  @param {number} [outputNum=0] optionally which output to connect from
 *  @param {number} [inputNum=0] optionally which input to connect to
 *  @returns {Tone.LFO} this
 *  @private
 */
Tone.LFO.prototype.connect = function(node){
	if (node.constructor === Tone.Signal || node.constructor === Tone.Param){
		this.convert = node.convert;
		this.units = node.units;
	}
	Tone.SignalBase.prototype.connect.apply(this, arguments);
	return this;
};

/**
 *  private method borrowed from Param converts
 *  units from their destination value
 *  @function
 *  @private
 */
Tone.LFO.prototype._fromUnits = Tone.Param.prototype._fromUnits;

/**
 *  private method borrowed from Param converts
 *  units to their destination value
 *  @function
 *  @private
 */
Tone.LFO.prototype._toUnits = Tone.Param.prototype._toUnits;

/**
 *  disconnect and dispose
 *  @returns {Tone.LFO} this
 */
Tone.LFO.prototype.dispose = function(){
	Tone.AudioNode.prototype.dispose.call(this);
	this._writable(["amplitude", "frequency"]);
	this._oscillator.dispose();
	this._oscillator = null;
	this._stoppedSignal.dispose();
	this._stoppedSignal = null;
	this._zeros.dispose();
	this._zeros = null;
	this._scaler.dispose();
	this._scaler = null;
	this._a2g.dispose();
	this._a2g = null;
	this.frequency = null;
	this.amplitude = null;
	return this;
};

export default Tone.LFO;