import { ToneAudioNode, ToneAudioNodeOptions } from "../../core/context/ToneAudioNode";
import { optionsFromArguments } from "../../core/util/Defaults";

type AnalyserType = "fft" | "waveform";

interface AnalyserOptions extends ToneAudioNodeOptions {
	size: PowerOfTwo;
	type: AnalyserType;
	smoothing: NormalRange;
}

/**
 *  Wrapper around the native Web Audio's [AnalyserNode](http://webaudio.github.io/web-audio-api/#idl-def-AnalyserNode).
 *  Extracts FFT or Waveform data from the incoming signal.
 *  @param type The return type of the analysis, either "fft", or "waveform".
 *  @param size The size of the FFT. This must be a power of two in the range 16 to 16384.
 */
export class Analyser extends ToneAudioNode<AnalyserOptions> {

	name = "Analyser";

	input: AnalyserNode;
	output: AnalyserNode;

	/**
	 *  The analyser node.
	 */
	private _analyser = this.context.createAnalyser();
	protected _internalChannels = [this._analyser];

	/**
	 *  The analysis type
	 */
	private _type!: AnalyserType;

	/**
	 *  The buffer that the FFT data is written to
	 */
	private _buffer!: Float32Array;

	constructor(type?: AnalyserType, size?: number);
	constructor(options?: Partial<AnalyserOptions>);
	constructor() {
		super(optionsFromArguments(Analyser.getDefaults(), arguments, ["type", "size"]));
		const options = optionsFromArguments(Analyser.getDefaults(), arguments, ["type", "size"]);
		// set the values initially
		this.size = options.size;
		this.type = options.type;
		this.input = this.output = this._analyser;
	}

	static getDefaults(): AnalyserOptions {
		return Object.assign(ToneAudioNode.getDefaults(), {
			numberOfInputs: 1,
			numberOfOutputs: 1,
			size: 1024,
			smoothing: 0.8,
			type: "fft" as AnalyserType,
		});
	}

	/**
	 *  Run the analysis given the current settings and return the
	 */
	getValue(): Float32Array {
		if (this._type === "fft") {
			this._analyser.getFloatFrequencyData(this._buffer);
		} else if (this._type === "waveform") {
			this._analyser.getFloatTimeDomainData(this._buffer);
		}
		return this._buffer;
	}

	/**
	 *  The size of analysis. This must be a power of two in the range 16 to 16384.
	 */
	get size(): PowerOfTwo {
		return this._analyser.frequencyBinCount;
	}
	set size(size: PowerOfTwo) {
		this._analyser.fftSize = size * 2;
		this._buffer = new Float32Array(size);
	}

	/**
	 *  The analysis function returned by analyser.getValue(), either "fft" or "waveform".
	 */
	get type(): AnalyserType {
		return this._type;
	}
	set type(type: AnalyserType) {
		this.assert(type === "waveform" || type === "fft", `Analyser: invalid type: ${type}`);
		this._type = type;
	}

	/**
	 *  0 represents no time averaging with the last analysis frame.
	 */
	get smoothing(): NormalRange {
		return this._analyser.smoothingTimeConstant;
	}

	set smoothing(val: NormalRange) {
		this._analyser.smoothingTimeConstant = val;
	}

	/**
	 *  Clean up.
	 */
	dispose(): this {
		super.dispose();
		this._analyser.disconnect();
		return this;
	}
}