/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

/**
 * FIFO queue backed by arrays with a reversed dequeue buffer to avoid the cost
 * of repeated `Array#shift` operations.
 * @template T
 */
class ArrayQueue {
	/**
	 * Seeds the queue with an optional iterable of items in dequeue order.
	 * @param {Iterable<T>=} items The initial elements.
	 */
	constructor(items) {
		/**
		 * @private
		 * @type {T[]}
		 */
		this._list = items ? [...items] : [];
		/**
		 * @private
		 * @type {T[]}
		 */
		this._listReversed = [];
	}

	/**
	 * Returns the current number of items waiting in either internal buffer.
	 * @returns {number} The number of elements in this queue.
	 */
	get length() {
		return this._list.length + this._listReversed.length;
	}

	/**
	 * Removes all pending items from both internal buffers.
	 */
	clear() {
		this._list.length = 0;
		this._listReversed.length = 0;
	}

	/**
	 * Appends an item to the tail of the queue.
	 * @param {T} item The element to add.
	 * @returns {void}
	 */
	enqueue(item) {
		this._list.push(item);
	}

	/**
	 * Removes and returns the next item in FIFO order, switching to a reversed
	 * buffer when that is cheaper than shifting from the front of the array.
	 * @returns {T | undefined} The head of the queue of `undefined` if this queue is empty.
	 */
	dequeue() {
		if (this._listReversed.length === 0) {
			if (this._list.length === 0) return;
			if (this._list.length === 1) return this._list.pop();
			if (this._list.length < 16) return this._list.shift();
			const temp = this._listReversed;
			this._listReversed = this._list;
			this._listReversed.reverse();
			this._list = temp;
		}
		return this._listReversed.pop();
	}

	/**
	 * Removes the first matching item from whichever internal buffer currently
	 * contains it.
	 * @param {T} item the item
	 * @returns {void}
	 */
	delete(item) {
		const i = this._list.indexOf(item);
		if (i >= 0) {
			this._list.splice(i, 1);
		} else {
			const i = this._listReversed.indexOf(item);
			if (i >= 0) this._listReversed.splice(i, 1);
		}
	}

	[Symbol.iterator]() {
		return {
			next: () => {
				const item = this.dequeue();
				if (item) {
					return {
						done: false,
						value: item
					};
				}
				return {
					done: true,
					value: undefined
				};
			}
		};
	}
}

module.exports = ArrayQueue;
