import { Time } from "../../core/type/Units";
import { InputNode, OutputNode, ToneAudioNode, ToneAudioNodeOptions } from "../../core/context/ToneAudioNode";
import { optionsFromArguments } from "../../core/util/Defaults";
import { OnePoleFilter } from "../filter/OnePoleFilter";
import { Abs } from "../../signal/Abs";

export interface FollowerOptions extends ToneAudioNodeOptions {
	smoothing: Time;
}

/**
 * Follower is a simple envelope follower. 
 * It's implemented by applying a lowpass filter to the absolute value of the incoming signal. 
 * ```
 *          +-----+    +---------------+
 * Input +--> Abs +----> OnePoleFilter +--> Output
 *          +-----+    +---------------+
 * ```
 * @category Component
 */
export class Follower extends ToneAudioNode<FollowerOptions> {

	readonly name: string = "Follower";

	readonly input: InputNode;
	readonly output: OutputNode;

	/**
	 * Private reference to the smoothing parameter
	 */
	private _smoothing: Time;

	/**
	 * The lowpass filter
	 */
	private _lowpass: OnePoleFilter;

	/**
	 * The absolute value
	 */
	private _abs: Abs;

	/**
	 * @param smoothing The rate of change of the follower.
	 */
	constructor(smoothing?: Time);
	constructor(options?: Partial<FollowerOptions>);
	constructor() {
		super(optionsFromArguments(Follower.getDefaults(), arguments, ["smoothing"]));
		const options = optionsFromArguments(Follower.getDefaults(), arguments, ["smoothing"]);

		this._abs = this.input = new Abs({ context: this.context });
		this._lowpass = this.output = new OnePoleFilter({
			context: this.context,
			frequency: 1 / this.toSeconds(options.smoothing),
			type: "lowpass"
		});
		this._abs.connect(this._lowpass);
		this._smoothing = options.smoothing;
	}

	static getDefaults(): FollowerOptions {
		return Object.assign(ToneAudioNode.getDefaults(), {
			smoothing: 0.05
		});
	}

	/**
	 * The amount of time it takes a value change to arrive at the updated value. 
	 */
	get smoothing(): Time {
		return this._smoothing;
	}
	set smoothing(smoothing) {
		this._smoothing = smoothing;
		this._lowpass.frequency = 1 / this.toSeconds(this.smoothing);
	}

	dispose(): this {
		super.dispose();
		this._abs.dispose();
		this._lowpass.dispose();
		return this;
	}
}