import { BaseAudioChannel } from './BaseAudioChannel';
import { IAudioChannel } from './IAudioChannel';

export class StreamingAudioChannel extends BaseAudioChannel implements IAudioChannel {
	public static maxChannels: number = 4;

	public static _channels: Array<StreamingAudioChannel> = new Array<StreamingAudioChannel>();

	private _sourceOpenDelegate: (event) => void;
	private _updateEndDelegate: (event) => void;
	private _sourceBuffer: SourceBuffer;
	private _sourceDirty: boolean;
	private _isQueuing: boolean;
	private _isOpening: boolean;
	private _buffer: ArrayBuffer;
	private _offset: number;
	private _volume: number;
	private _groupID: number = 0;
	private _groupVolume: number = 1;
	private _groupPan: number = 0;
	private _startTime: number = 0;
	private _duration: number;

	private _audio: HTMLAudioElement;

	private _mediaSource: MediaSource;
	private _urlString: string;

	public static stopAllSounds(channelGroup: number = -1) {
		const len: number = StreamingAudioChannel._channels.length;
		if (channelGroup < 0) {
			for (let j: number = 0; j < len; j++) {
				StreamingAudioChannel._channels[j].stop();
			}
			StreamingAudioChannel._channels.length = 0;
			return;
		}
		const aliveChannels: StreamingAudioChannel[] = [];
		for (let j: number = 0; j < len; j++) {
			if (StreamingAudioChannel._channels[j].groupID == channelGroup) {
				StreamingAudioChannel._channels[j].stop();
			} else {
				aliveChannels[aliveChannels.length] = StreamingAudioChannel._channels[j];
			}
		}
		StreamingAudioChannel._channels = aliveChannels;
	}

	public static setChannelGroupVolume(value: number, channelGroup: number = -1) {
		const len: number = StreamingAudioChannel._channels.length;
		if (channelGroup < 0) {
			for (let j: number = 0; j < len; j++) {
				StreamingAudioChannel._channels[j].groupVolume = value;
			}
			return;
		}
		for (let j: number = 0; j < len; j++) {
			if (StreamingAudioChannel._channels[j].groupID == channelGroup) {
				StreamingAudioChannel._channels[j].groupVolume = value;
			}
		}
	}

	public get duration(): number {
		return this._duration;
	}

	public get currentTime(): number {
		return this._audio.currentTime - this._startTime;
	}

	public get groupID(): number {
		return this._groupID;
	}

	public set groupID(value: number) {
		this._groupID = value;
	}

	public get groupVolume(): number {
		return this._groupVolume;
	}

	public set groupVolume(value: number) {
		if (this._groupVolume == value)
			return;

		this._groupVolume = value;

		this._audio.volume = this._groupVolume * this._volume;
	}

	public get groupPan(): number {
		return this._groupPan;
	}

	public set groupPan(value: number) {
	}

	public get volume(): number {
		return this._volume;
	}

	public set volume(value: number) {
		if (this._volume == value)
			return;

		this._volume = value;

		this._audio.volume = this._volume;
	}

	public isPlaying(): boolean {
		return this._isPlaying;
	}

	public isLooping(): boolean {
		return this._isLooping;
	}

	public isDecoding(): boolean {
		return false;
	}

	public get pan(): number {
		//todo
		return 0;
	}

	public set pan(value: number) {
		//todo
	}

	constructor(groupID: number = 0, groupVolume: number = 1, groupPan: number = 1) {
		super();

		this._groupID = groupID;
		this._groupVolume = groupVolume;
		this._groupPan = groupPan;

		this._sourceOpenDelegate = (event) => this._sourceOpen(event);
		this._updateEndDelegate = (event) => this._updateEnd(event);

		this._audio = new Audio();
		this._audio.ontimeupdate = (event) => this._onTimeUpdate(event);

		this._updateSource();
	}

	public restart(): boolean {
		console.warn('[StreamingAudioChannel] Restart not implemented');
		return  false;
	}

	public play(buffer: ArrayBuffer, offset: number = 0, loop: boolean | number = false): void {
		super.play(buffer, offset, loop, 0);
		this._isPlaying = true;

		if (this._isLooping || this._isLooping != this._loops > 0) {
			this._isLooping = this._loops > 0;
			this._sourceDirty = true;
		}

		if (this._sourceDirty)
			this._updateSource();

		this._buffer = buffer;
		this._offset = offset;
		this._audio.volume = this._groupVolume * this._volume;

		if (!this._isQueuing && !this._isOpening)
			this._queueBuffer();
	}

	private stopInternally(emitComplete = false) {
		this._audio.pause();
		this._isPlaying = false;
		this._isLooping = false;

		super.completeInternally(emitComplete, emitComplete);
	}

	public stop(): void {
		this.stopInternally(false);
		this.dispatchStop(false);
	}

	private _sourceOpen(event): void {
		this._isOpening = false;

		//TODO: find out how in the name of all that is holy how this can be
		//executed more than once on a MediaSource object
		if (this._mediaSource.activeSourceBuffers.length) {
			console.log('ERR: double sourceopen event called');
			this.dispatchStop(true);
			return;
		}

		this._sourceBuffer = this._mediaSource.addSourceBuffer('audio/mpeg');
		this._sourceBuffer.addEventListener('updateend', this._updateEndDelegate);

		if (this._isPlaying)
			this._queueBuffer();
	}

	private _queueBuffer(): void {
		this._isQueuing = true;

		this._startTime = this._sourceBuffer.timestampOffset;
		this._sourceBuffer.appendBuffer(this._buffer);
	}

	private _updateEnd(event): void {
		this._isQueuing = false;

		if (this._isLooping)
			this._mediaSource.endOfStream();

		this._duration = this._sourceBuffer.timestampOffset - this._startTime;

		this._audio.currentTime = this._startTime + this._offset;
		this._audio.play();
	}

	private _onTimeUpdate(event): void {
		//TODO: more accurate end detection
		if (!this._isLooping && this._duration < this._audio.currentTime - this._startTime + 0.1) {
			this.stopInternally(true);
		}
	}

	private _updateSource(): void {
		if (this._mediaSource)
			this._disposeSource();

		this._isQueuing = false;
		this._isOpening = true;

		this._mediaSource = new MediaSource();
		this._mediaSource.addEventListener('sourceopen', this._sourceOpenDelegate);
		this._urlString = URL.createObjectURL(this._mediaSource);
		this._audio.src = this._urlString;
		this._audio.loop = this._isLooping;

		this._sourceDirty = false;
	}

	private _disposeSource(): void {
		if (!this._isOpening) {
			if (this._sourceBuffer.timestampOffset)
				this._sourceBuffer.remove(0, this._sourceBuffer.timestampOffset);

			this._sourceBuffer.removeEventListener('updateend', this._updateEndDelegate);
			this._mediaSource.removeSourceBuffer(this._sourceBuffer);
			delete this._sourceBuffer;
			this._sourceBuffer = null;
		}

		this._mediaSource.removeEventListener('sourceopen', this._sourceOpenDelegate);
		URL.revokeObjectURL(this._urlString);
		delete this._mediaSource;
		this._mediaSource = null;
	}
}