/*******************************************************************************
 * Copyright (c) 1997, 2008 by ProSyst Software GmbH
 * http://www.prosyst.com
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    ProSyst Software GmbH - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.internal.util.impl.tpt.timer;

/**
 * @author Pavlin Dobrev
 * @version 1.0
 */

class TimerQueue {

	static final int POOL_SIZE = 20;
	QueueElement[] pool = new QueueElement[POOL_SIZE];
	int filled = 0;

	QueueElement first;
	QueueElement lastInserted;

	TimerQueue() {
	}

	/**
	 * Adds a new task to the priority queue.
	 */
	void add(TimerQueueNode task) {
		QueueElement toAdd = getQueueElement();
		toAdd.node = task;

		if (first == null) {
			first = toAdd;
		} else {
			insertElement(toAdd);
		}
	}

	/**
	 * Return the "head task" of the priority queue. (The head task is an task
	 * with the lowest nextExecutionTime.)
	 */
	TimerQueueNode getMin() {
		return (first != null) ? first.node : null;
	}

	/**
	 * Remove the head task from the priority queue.
	 */
	void removeMin() {
		if (first != null) {
			if (lastInserted == first) {
				lastInserted = null;
			}
			if (filled < POOL_SIZE) {
				QueueElement toFree = first;
				first = first.next;
				freeQueueElement(toFree);
			} else {
				first = first.next;
			}
		}
	}

	/**
	 * Sets the nextExecutionTime associated with the head task to the specified
	 * value, and adjusts priority queue accordingly.
	 */
	void rescheduleMin(long newTime) {
		first.node.runOn = newTime;
		if (first.next != null) {
			if (lastInserted == first) {
				lastInserted = null;
			}
			QueueElement el = first;
			first = first.next;
			el.next = null;
			insertElement(el);
		}
	}

	/**
	 * Returns true if the priority queue contains no elements.
	 */
	boolean isEmpty() {
		return first == null;
	}

	/**
	 * Removes all elements from the priority queue.
	 */
	void clear() {
		while (first != null) {
			first.node.returnInPool();
			first = first.next;
		}
		lastInserted = null;
	}

	void insertElement(QueueElement newElement) {
		if (first.node.runOn >= newElement.node.runOn) {
			/* private case - insert in the beginning of the queue */
			newElement.next = first;
			first = newElement;
		} else if (lastInserted != null) {
			if (lastInserted.node.runOn == newElement.node.runOn) {
				QueueElement tmp = lastInserted.next;
				lastInserted.next = newElement;
				newElement.next = tmp;
			} else if (lastInserted.node.runOn > newElement.node.runOn) {
				// System.out.println("insert 1");
				doInsertElement(first, newElement);
			} else {
				// System.out.println("insert 2");
				doInsertElement(lastInserted, newElement);
			}
		} else {
			// System.out.println("insert 3");
			doInsertElement(first, newElement);
		}
	}

	/**
	 * elToInsert should not be placed before firstEl, because firstEl may not
	 * be the first element of the queue
	 */
	void doInsertElement(QueueElement firstEl, QueueElement elToInsert) {
		QueueElement tmp = firstEl;
		QueueElement prev = firstEl;
		while (tmp != null && tmp.node.runOn < elToInsert.node.runOn) {
			prev = tmp;
			tmp = tmp.next;
		}
		if (tmp == null) {
			/* reached the end of the queue */
			prev.next = elToInsert;
		} else {
			prev.next = elToInsert;
			elToInsert.next = tmp;
		}
		lastInserted = elToInsert;
	}

	void removeTimerNode(TimerQueueNode node) {
		QueueElement tmp = first;
		QueueElement prev = null;
		while (tmp != null) {
			if (node.listener == tmp.node.listener && node.event == tmp.node.event) {
				if (prev != null) {
					if (lastInserted == tmp) {
						lastInserted = prev;
					}
					prev.next = tmp.next;
					if (filled < POOL_SIZE) {
						freeQueueElement(tmp);
					}
				} else {
					/* removing the first element */
					if (lastInserted == first) {
						lastInserted = null;
					}
					if (filled < POOL_SIZE) {
						QueueElement toFree = first;
						first = first.next;
						freeQueueElement(toFree);
					} else {
						first = first.next;
					}
				}
				break;
			}
			prev = tmp;
			tmp = tmp.next;
		}
	}

	private QueueElement getQueueElement() {
		return (filled > 0) ? pool[--filled] : new QueueElement();
	}

	private void freeQueueElement(QueueElement toFree) {
		if (filled < POOL_SIZE) {
			pool[filled] = toFree;
			filled++;
			toFree.next = null;
			toFree.node = null;
		}
	}

	private class QueueElement {
		QueueElement next;
		TimerQueueNode node;

		public QueueElement() {
		}
	}

}
