/* RequestQueueImpl.java

	Purpose:

	Description:

	History:
		Fri Jan 20 09:51:39     2006, Created by tomyeh

Copyright (C) 2006 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zk.ui.impl;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.zkoss.lang.Objects;
import org.zkoss.zk.au.AuRequest;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.ComponentNotFoundException;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.sys.RequestQueue;

/**
 * An implementation of {@link RequestQueue} behaving as
 * a queue of {@link AuRequest}.
 * There is one queue for each desktop.
 *
 * <p>Implementation Note:
 * Unlike only of desktop members, this class must be thread-safe.
 *
 * @author tomyeh
 */
public class RequestQueueImpl implements RequestQueue {
	private static final Logger log = LoggerFactory.getLogger(RequestQueueImpl.class);

	/** A list of pending {@link AuRequest}. */
	private final List<AuRequest> _requests = new LinkedList<AuRequest>();
	/** A list of request ID for performance measurement. */
	private List<String> _pfreqIds;

	//-- RequestQueue --//
	public void addPerfRequestId(String requestId) {
		if (_pfreqIds == null)
			_pfreqIds = new LinkedList<String>();
		_pfreqIds.add(requestId);
	}

	public Collection<String> clearPerfRequestIds() {
		final List<String> old = _pfreqIds;
		_pfreqIds = null;
		return old;
	}

	public boolean isEmpty() {
		while (!_requests.isEmpty()) {
			if (isObsolete(_requests.get(0)))
				_requests.remove(0);
			else
				return false;
		}
		return true;
	}

	private static boolean isObsolete(AuRequest request) {
		final Component comp = request.getComponent();
		if (comp != null) {
			final Desktop dt = comp.getDesktop();
			return dt != null && dt != request.getDesktop();
			//Since 3.5.0, we allow a component to process events even
			//if it is detached (due to implementation of EventQueue)
		}
		return false;
	}

	public AuRequest nextRequest() {
		while (!_requests.isEmpty()) {
			final AuRequest request = _requests.remove(0);
			if (!isObsolete(request))
				return request;
		}
		return null;
	}

	public void addRequests(Collection<AuRequest> requests) {
		for (AuRequest request : requests) {
			try {
				request.activate();
				if (!isObsolete(request))
					addRequest(request);
			} catch (ComponentNotFoundException ex) { //ignore it
				//ignore it since a long request might remove a timer
				//while clients already queues onTimer generated by the timer
				if (log.isDebugEnabled())
					log.debug("Ignore request: {}", ex.getMessage());
			}
		}
	}

	private void addRequest(AuRequest request) {
		//case 1, BUSY_IGNORE: Drop any existent ignorable requests
		//We don't need to iterate all because requests is added one-by-one
		//In other words, if any temporary request, it must be the last
		{
			int last = _requests.size() - 1;
			if (last < 0) { //optimize the most common case
				_requests.add(request);
				return;
			}

			final AuRequest req2 = _requests.get(last);
			if ((req2.getOptions() & AuRequest.BUSY_IGNORE) != 0) {
				_requests.remove(last); //drop it
				if (last == 0) {
					_requests.add(request);
					return;
				}
			}
		}

		final String name = request.getCommand();
		final int opts = request.getOptions();

		//Since 3.0.2, redundant CTRL_GROUP is removed at the client

		//case 2, DUPLICATE_IGNORE: drop existent request if they are the same
		//as the arrival.
		if ((opts & AuRequest.DUPLICATE_IGNORE) != 0) {
			final String uuid = getUuid(request);
			for (Iterator<AuRequest> it = _requests.iterator(); it.hasNext();) {
				final AuRequest req2 = it.next();
				if (req2.getCommand().equals(name) && Objects.equals(getUuid(req2), uuid)) {
					it.remove(); //drop req2 (the old one)
					break; //no need to iterate because impossible to have more
				}
			}

			//Case 3, REPEAT_IGNORE: drop existent if the immediate
			//following is the same
		} else if ((opts & AuRequest.REPEAT_IGNORE) != 0) {
			final int last = _requests.size() - 1;
			final AuRequest req2 = _requests.get(last);
			if (req2.getCommand().equals(name) && Objects.equals(getUuid(req2), getUuid(request))) {
				_requests.remove(last);
			}
		}

		_requests.add(request);
	}

	private String getUuid(AuRequest request) {
		final Component comp = request.getComponent();
		if (comp != null)
			return comp.getUuid();
		final Page page = request.getPage();
		if (page != null)
			return page.getUuid();
		return null;
	}
}
