import { optionsFromArguments } from "../../core/util/Defaults";
import { Frequency, Hertz, NormalRange, Time } from "../../core/type/Units";
import { Envelope, EnvelopeOptions } from "./Envelope";
import { Scale } from "../../signal/Scale";
import { Pow } from "../../signal/Pow";
import { assertRange } from "../../core/util/Debug";

export interface FrequencyEnvelopeOptions extends EnvelopeOptions {
	baseFrequency: Frequency;
	octaves: number;
	exponent: number;
}
/**
 * FrequencyEnvelope is an {@link Envelope} which ramps between {@link baseFrequency}
 * and {@link octaves}. It can also have an optional {@link exponent} to adjust the curve
 * which it ramps. 
 * @example
 * const oscillator = new Tone.Oscillator().toDestination().start();
 * const freqEnv = new Tone.FrequencyEnvelope({
 * 	attack: 0.2,
 * 	baseFrequency: "C2",
 * 	octaves: 4
 * });
 * freqEnv.connect(oscillator.frequency);
 * freqEnv.triggerAttack();
 * @category Component
 */
export class FrequencyEnvelope extends Envelope {

	readonly name: string = "FrequencyEnvelope";

	/**
	 * Private reference to the base frequency as a number
	 */
	private _baseFrequency: Hertz;

	/**
	 * The number of octaves
	 */
	private _octaves: number;

	/**
	 * Internal scaler from 0-1 to the final output range
	 */
	private _scale: Scale;

	/**
	 * Apply a power curve to the output
	 */
	private _exponent: Pow;

	/**
	 * @param attack	the attack time in seconds
	 * @param decay		the decay time in seconds
	 * @param sustain 	a percentage (0-1) of the full amplitude
	 * @param release	the release time in seconds
	 */
	constructor(attack?: Time, decay?: Time, sustain?: NormalRange, release?: Time);
	constructor(options?: Partial<FrequencyEnvelopeOptions>)
	constructor() {
		super(optionsFromArguments(FrequencyEnvelope.getDefaults(), arguments, ["attack", "decay", "sustain", "release"]));
		const options = optionsFromArguments(FrequencyEnvelope.getDefaults(), arguments, ["attack", "decay", "sustain", "release"]);

		this._octaves = options.octaves;
		this._baseFrequency = this.toFrequency(options.baseFrequency);

		this._exponent = this.input = new Pow({
			context: this.context,
			value: options.exponent
		});
		this._scale = this.output = new Scale({
			context: this.context,
			min: this._baseFrequency,
			max: this._baseFrequency * Math.pow(2, this._octaves),
		});
		this._sig.chain(this._exponent, this._scale);
	}

	static getDefaults(): FrequencyEnvelopeOptions {
		return Object.assign(Envelope.getDefaults(), {
			baseFrequency: 200,
			exponent: 1,
			octaves: 4,
		});
	}

	/**
	 * The envelope's minimum output value. This is the value which it
	 * starts at.
	 */
	get baseFrequency(): Frequency {
		return this._baseFrequency;
	}
	set baseFrequency(min) {
		const freq = this.toFrequency(min);
		assertRange(freq, 0);
		this._baseFrequency = freq;
		this._scale.min = this._baseFrequency;
		// update the max value when the min changes
		this.octaves = this._octaves;
	}

	/**
	 * The number of octaves above the baseFrequency that the
	 * envelope will scale to.
	 */
	get octaves(): number {
		return this._octaves;
	}
	set octaves(octaves: number) {
		this._octaves = octaves;
		this._scale.max = this._baseFrequency * Math.pow(2, octaves);
	}

	/**
	 * The envelope's exponent value.
	 */
	get exponent(): number {
		return this._exponent.value;
	}
	set exponent(exponent) {
		this._exponent.value = exponent;
	}

	/**
	 * Clean up
	 */
	dispose(): this {
		super.dispose();
		this._exponent.dispose();
		this._scale.dispose();
		return this;
	}
}
