/*******************************************************************************
 * Copyright (c) 2004, 2010 Tasktop Technologies and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.internal.tasks.core;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.commons.net.Policy;
import org.eclipse.mylyn.tasks.core.IRepositoryElement;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState;

/**
 * Stores and manages task list elements and their containment hierarchy.
 * 
 * @author Mik Kersten
 * @author Rob Elves
 * @since 3.0
 */
public class TaskList implements ITaskList, ITransferList {

	private static String DEFAULT_HANDLE_PREFIX = "handle-"; //$NON-NLS-1$

	private static ILock lock = Job.getJobManager().newLock();

	private Map<String, AbstractTaskCategory> categories;

	private final Set<ITaskListChangeListener> changeListeners = new CopyOnWriteArraySet<ITaskListChangeListener>();

	private UncategorizedTaskContainer defaultCategory;

	private int maxLocalTaskId;

	private Map<String, RepositoryQuery> queries;

	private Map<String, UnmatchedTaskContainer> unmatchedMap;

	private Map<String, UnsubmittedTaskContainer> unsubmittedTasksMap;

	private Map<String, AbstractTask> tasks;

	private Set<TaskContainerDelta> delta;

	private int nextHandle = 1;

	public TaskList() {
		reset();
	}

	public void addCategory(TaskCategory category) {
		Assert.isNotNull(category);
		try {
			lock();
			if (categories.containsKey(category.getHandleIdentifier())) {
				throw new IllegalArgumentException("Handle " + category.getHandleIdentifier() //$NON-NLS-1$
						+ " already exists in task list"); //$NON-NLS-1$
			}
			categories.put(category.getHandleIdentifier(), category);
			delta.add(new TaskContainerDelta(category, TaskContainerDelta.Kind.ADDED));
		} finally {
			unlock();
		}
	}

	public void addChangeListener(ITaskListChangeListener listener) {
		changeListeners.add(listener);
	}

	/**
	 * precondition: task must not be null and must exist in the task list
	 */
	private void addToUnmatched(AbstractTask task, Set<TaskContainerDelta> delta) {
		if (!task.getParentContainers().isEmpty()) {
			// Current policy is not to archive/orphan if the task exists in some other container
			return;
		}

		AbstractTaskContainer orphans = getUnmatchedContainer(task.getRepositoryUrl());
		if (orphans != null) {
			task.addParentContainer(orphans);
			orphans.internalAddChild(task);
			delta.add(new TaskContainerDelta(task, orphans, TaskContainerDelta.Kind.ADDED));
		}
	}

	public void addQuery(RepositoryQuery query) throws IllegalArgumentException {
		Assert.isNotNull(query);
		try {
			lock();
			if (queries.containsKey(query.getHandleIdentifier())) {
				throw new IllegalArgumentException("Handle " + query.getHandleIdentifier() //$NON-NLS-1$
						+ " already exists in task list"); //$NON-NLS-1$
			}
			queries.put(query.getHandleIdentifier(), query);
			delta.add(new TaskContainerDelta(query, TaskContainerDelta.Kind.ADDED));
		} finally {
			unlock();
		}
	}

	/**
	 * Add task to default category if it's not in the task list.
	 */
	public boolean addTaskIfAbsent(ITask task) {
		if (getTask(task.getRepositoryUrl(), task.getTaskId()) == null) {
			addTask(task, getDefaultCategory());
			return true;
		}
		return false;
	}

	/**
	 * Add orphaned task to the task list
	 */
	public void addTask(ITask task) {
		addTask(task, null);
	}

	public boolean addTask(ITask itask, AbstractTaskContainer container) {
		AbstractTask task = (AbstractTask) itask;
		Assert.isNotNull(task);
		Assert.isLegal(!(container instanceof UnmatchedTaskContainer));

		try {
			lock();
			task = getOrCreateTask(task);
			if (task.getSynchronizationState() == SynchronizationState.OUTGOING_NEW) {
				String repositoryUrl = task.getAttribute(ITasksCoreConstants.ATTRIBUTE_OUTGOING_NEW_REPOSITORY_URL);
				if (repositoryUrl != null) {
					container = getUnsubmittedContainer(repositoryUrl);
				}
			}
			if (container == null) {
				container = getUnmatchedContainer(task.getRepositoryUrl());
			} else {
				container = getValidElement(container);
			}

			if (container instanceof UnsubmittedTaskContainer && container.isEmpty()) {
				delta.add(new TaskContainerDelta(container, TaskContainerDelta.Kind.ROOT));
			}

			// ensure parent is valid and does not contain task already
			if (container == null || task.equals(container) || task.getParentContainers().contains(container)) {
				return false;
			}

			// ensure that we don't create cycles
			if ((task).contains(container.getHandleIdentifier())) {
				return false;
			}

			if (task instanceof LocalTask && task.getParentContainers().size() > 0) {
				// local tasks should only have 1 parent
				for (AbstractTaskContainer parent : task.getParentContainers()) {
					removeFromContainerInternal(parent, task, delta);
				}
			} else if (container instanceof AbstractTaskCategory) {
				// tasks can only be in one task category at a time
				AbstractTaskCategory tempCat = TaskCategory.getParentTaskCategory(task);
				if (tempCat != null) {
					removeFromContainerInternal(tempCat, task, delta);
				}
			}

			removeFromUnmatched(task, delta);

			(task).addParentContainer(container);
			container.internalAddChild(task);
			delta.add(new TaskContainerDelta(task, container, TaskContainerDelta.Kind.ADDED));
		} finally {
			unlock();
		}

		return true;
	}

	public void addUnmatchedContainer(UnmatchedTaskContainer orphanedTasksContainer) {
		unmatchedMap.put(orphanedTasksContainer.getRepositoryUrl(), orphanedTasksContainer);
		unsubmittedTasksMap.put(orphanedTasksContainer.getRepositoryUrl(), new UnsubmittedTaskContainer(
				orphanedTasksContainer.getConnectorKind(), orphanedTasksContainer.getRepositoryUrl()));
	}

	public void deleteCategory(AbstractTaskCategory category) {
		try {
			lock();
			categories.remove(category.getHandleIdentifier());
			for (ITask task : category.getChildren()) {
				((AbstractTask) task).removeParentContainer(category);
				addToUnmatched((AbstractTask) task, delta);
			}
			delta.add(new TaskContainerDelta(category, TaskContainerDelta.Kind.REMOVED));
			delta.add(new TaskContainerDelta(category, TaskContainerDelta.Kind.DELETED));
		} finally {
			unlock();
		}
	}

	public void deleteQuery(RepositoryQuery query) {
		try {
			lock();
			queries.remove(query.getHandleIdentifier());
			for (ITask task : query.getChildren()) {
				((AbstractTask) task).removeParentContainer(query);
				addToUnmatched((AbstractTask) task, delta);
			}
			delta.add(new TaskContainerDelta(query, TaskContainerDelta.Kind.REMOVED));
			delta.add(new TaskContainerDelta(query, TaskContainerDelta.Kind.DELETED));
		} finally {
			unlock();
		}
	}

	/**
	 * Task is removed from all containers. Currently subtasks are not deleted but rather are rather potentially
	 * orphaned.
	 */
	public void deleteTask(ITask itask) {
		Assert.isNotNull(itask);
		AbstractTask task = (AbstractTask) itask;
		try {
			lock();

			// remove task from all parent containers
			for (AbstractTaskContainer container : task.getParentContainers()) {
				removeFromContainerInternal(container, task, delta);
			}

			// remove this task as a parent for all subtasks
			for (ITask child : task.getChildren()) {
				removeFromContainerInternal(task, child, delta);
				addToUnmatched((AbstractTask) child, delta);
			}

			tasks.remove(task.getHandleIdentifier());
			delta.add(new TaskContainerDelta(task, TaskContainerDelta.Kind.REMOVED));
			delta.add(new TaskContainerDelta(task, TaskContainerDelta.Kind.DELETED));
		} finally {
			unlock();
		}
	}

	private void fireDelta(HashSet<TaskContainerDelta> deltasToFire) {
		for (ITaskListChangeListener listener : changeListeners) {
			try {
				listener.containersChanged(Collections.unmodifiableSet(deltasToFire));
			} catch (Throwable t) {
				StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Notification failed for: " //$NON-NLS-1$
						+ listener, t));
			}
		}
	}

	public Collection<AbstractTask> getAllTasks() {
		return Collections.unmodifiableCollection(tasks.values());
	}

	public Set<AbstractTaskCategory> getCategories() {
		return Collections.unmodifiableSet(new HashSet<AbstractTaskCategory>(categories.values()));
	}

	/**
	 * Exposed for unit testing
	 * 
	 * @return unmodifiable collection of ITaskActivityListeners
	 */
	public Set<ITaskListChangeListener> getChangeListeners() {
		return Collections.unmodifiableSet(changeListeners);
	}

	public AbstractTaskCategory getContainerForHandle(String categoryHandle) {
		Assert.isNotNull(categoryHandle);
		for (AbstractTaskCategory cat : categories.values()) {
			if (cat.getHandleIdentifier().equals(categoryHandle)) {
				return cat;
			}
		}
		return null;
	}

	public AbstractTaskCategory getDefaultCategory() {
		return defaultCategory;
	}

	public int getLastLocalTaskId() {
		return maxLocalTaskId;
	}

	public int getNextLocalTaskId() {
		try {
			lock();
			return ++maxLocalTaskId;
		} finally {
			unlock();
		}
	}

	private AbstractTask getOrCreateTask(AbstractTask taskListElement) {
		AbstractTask task = tasks.get(taskListElement.getHandleIdentifier());
		if (task == null) {
			tasks.put(taskListElement.getHandleIdentifier(), taskListElement);
			task = taskListElement;
			if (task instanceof LocalTask) {
				try {
					int taskId = Integer.parseInt(task.getTaskId());
					maxLocalTaskId = Math.max(maxLocalTaskId, taskId);
				} catch (NumberFormatException e) {
					// ignore
				}
			}
		}
		return task;
	}

	public Set<RepositoryQuery> getQueries() {
		return Collections.unmodifiableSet(new HashSet<RepositoryQuery>(queries.values()));
	}

	/**
	 * return all queries for the given repository url
	 */
	public Set<RepositoryQuery> getRepositoryQueries(String repositoryUrl) {
		Assert.isNotNull(repositoryUrl);

		Set<RepositoryQuery> repositoryQueries = new HashSet<RepositoryQuery>();
		for (RepositoryQuery query : queries.values()) {
			if (query.getRepositoryUrl().equals(repositoryUrl)) {
				repositoryQueries.add(query);
			}
		}
		return repositoryQueries;
	}

	public Set<AbstractTaskContainer> getRootElements() {
		Set<AbstractTaskContainer> roots = new HashSet<AbstractTaskContainer>();
		roots.add(defaultCategory);
		for (AbstractTaskCategory cat : categories.values()) {
			roots.add(cat);
		}
		for (RepositoryQuery query : queries.values()) {
			roots.add(query);
		}
		for (UnmatchedTaskContainer orphanContainer : unmatchedMap.values()) {
			roots.add(orphanContainer);
		}
		for (UnsubmittedTaskContainer unsubmitedTaskContainer : unsubmittedTasksMap.values()) {
			roots.add(unsubmitedTaskContainer);
		}
		return roots;
	}

	/**
	 * TODO: consider removing, if everything becomes a repository task
	 * 
	 * @return null if no such task.
	 */
	public AbstractTask getTask(String handleIdentifier) {
		if (handleIdentifier == null) {
			return null;
		} else {
			return tasks.get(handleIdentifier);
		}
	}

	public ITask getTask(String repositoryUrl, String taskId) {
		if (!RepositoryTaskHandleUtil.isValidTaskId(taskId)) {
			return null;
		}

		String handle = RepositoryTaskHandleUtil.getHandle(repositoryUrl, taskId);
		return getTask(handle);
	}

	public AbstractTask getTaskByKey(String repositoryUrl, String taskKey) {
		for (AbstractTask task : tasks.values()) {
			String currentTaskKey = task.getTaskKey();
			if (currentTaskKey != null && currentTaskKey.equals(taskKey)
					&& task.getRepositoryUrl().equals(repositoryUrl)) {
				return task;
			}
		}
		return null;
	}

	public Set<AbstractTaskCategory> getTaskCategories() {
		Set<AbstractTaskCategory> containers = new HashSet<AbstractTaskCategory>();
		for (AbstractTaskCategory container : categories.values()) {
			if (container instanceof TaskCategory) {
				containers.add(container);
			}
		}
		return containers;
	}

	/**
	 * Returns all tasks for the given repository url.
	 */
	public Set<ITask> getTasks(String repositoryUrl) {
		Set<ITask> repositoryTasks = new HashSet<ITask>();
		if (repositoryUrl != null) {
			for (ITask task : tasks.values()) {
				if (task.getRepositoryUrl().equals(repositoryUrl)) {
					repositoryTasks.add(task);
				}
			}
		}
		return repositoryTasks;
	}

	public AbstractTaskContainer getUnmatchedContainer(String repositoryUrl) {
		if (LocalRepositoryConnector.REPOSITORY_URL.equals(repositoryUrl)) {
			return defaultCategory;
		} else {
			UnmatchedTaskContainer unmatched = unmatchedMap.get(repositoryUrl);
			if (unmatched == null) {
				StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN,
						"Failed to find unmatched container for repository \"" + repositoryUrl + "\"")); //$NON-NLS-1$ //$NON-NLS-2$
			}
			return unmatched;
		}
	}

	public Set<UnmatchedTaskContainer> getUnmatchedContainers() {
		return Collections.unmodifiableSet(new HashSet<UnmatchedTaskContainer>(unmatchedMap.values()));
	}

	/**
	 * Task added if does not exist already. Ensures the element exists in the task list
	 * 
	 * @throws IllegalAgumentException
	 *             if null argument passed or element does not exist in task list
	 * @return element as passed in or instance from task list with same handle if exists
	 */
	private AbstractTaskContainer getValidElement(IRepositoryElement taskListElement) {
		AbstractTaskContainer result = null;
		if (taskListElement instanceof ITask) {
			result = tasks.get(taskListElement.getHandleIdentifier());
		} else if (taskListElement instanceof UncategorizedTaskContainer) {
			result = defaultCategory;
		} else if (taskListElement instanceof UnmatchedTaskContainer) {
			result = unmatchedMap.get(((UnmatchedTaskContainer) taskListElement).getRepositoryUrl());
		} else if (taskListElement instanceof UnsubmittedTaskContainer) {
			result = unsubmittedTasksMap.get(((UnsubmittedTaskContainer) taskListElement).getRepositoryUrl());
		} else if (taskListElement instanceof TaskCategory) {
			result = categories.get(taskListElement.getHandleIdentifier());
		} else if (taskListElement instanceof IRepositoryQuery) {
			result = queries.get(taskListElement.getHandleIdentifier());
		}

		if (result == null) {
			throw new IllegalArgumentException("Element " + taskListElement.getHandleIdentifier() //$NON-NLS-1$
					+ " does not exist in the task list."); //$NON-NLS-1$
		} else {
			return result;
		}
	}

	public void notifyElementsChanged(Set<? extends IRepositoryElement> elements) {
		HashSet<TaskContainerDelta> deltas = new HashSet<TaskContainerDelta>();
		if (elements == null) {
			deltas.add(new TaskContainerDelta(null, TaskContainerDelta.Kind.ROOT));
		} else {
			for (IRepositoryElement element : elements) {
				deltas.add(new TaskContainerDelta(element, TaskContainerDelta.Kind.CONTENT));
			}
		}

		fireDelta(deltas);
	}

	// TODO rename: this indicates a change of the synchronizing/status flag, not of the synchronization state
	public void notifySynchronizationStateChanged(Set<? extends IRepositoryElement> elements) {
		HashSet<TaskContainerDelta> taskChangeDeltas = new HashSet<TaskContainerDelta>();
		for (IRepositoryElement abstractTaskContainer : elements) {
			TaskContainerDelta delta = new TaskContainerDelta(abstractTaskContainer, TaskContainerDelta.Kind.CONTENT);
			delta.setTransient(true);
			taskChangeDeltas.add(delta);
		}

		fireDelta(taskChangeDeltas);
	}

	// TODO rename: this indicates a change of the synchronizing/status flag, not of the synchronization state  
	public void notifySynchronizationStateChanged(IRepositoryElement element) {
		notifySynchronizationStateChanged(Collections.singleton(element));
	}

	public void notifyElementChanged(IRepositoryElement element) {
		notifyElementsChanged(Collections.singleton(element));
	}

	public void refactorRepositoryUrl(String oldRepositoryUrl, String newRepositoryUrl) {
		Assert.isNotNull(oldRepositoryUrl);
		Assert.isNotNull(newRepositoryUrl);

		try {
			lock();
			for (AbstractTask task : tasks.values()) {
				if (oldRepositoryUrl.equals(RepositoryTaskHandleUtil.getRepositoryUrl(task.getHandleIdentifier()))) {
					tasks.remove(task.getHandleIdentifier());
					task.setRepositoryUrl(newRepositoryUrl);
					tasks.put(task.getHandleIdentifier(), task);
					String taskUrl = task.getUrl();
					if (taskUrl != null && taskUrl.startsWith(oldRepositoryUrl)) {
						task.setUrl(newRepositoryUrl + taskUrl.substring(oldRepositoryUrl.length()));
					}
				}
				if (oldRepositoryUrl.equals(task.getAttribute(ITasksCoreConstants.ATTRIBUTE_OUTGOING_NEW_REPOSITORY_URL))) {
					task.setAttribute(ITasksCoreConstants.ATTRIBUTE_OUTGOING_NEW_REPOSITORY_URL, newRepositoryUrl);
				}
			}

			for (RepositoryQuery query : queries.values()) {
				if (query.getRepositoryUrl().equals(oldRepositoryUrl)) {
					query.setRepositoryUrl(newRepositoryUrl);
					delta.add(new TaskContainerDelta(query, TaskContainerDelta.Kind.CONTENT));
				}
			}

			for (UnmatchedTaskContainer unmatched : unmatchedMap.values()) {
				if (unmatched.getRepositoryUrl().equals(oldRepositoryUrl)) {
					unmatchedMap.remove(oldRepositoryUrl);
					//categories.remove(orphans.getHandleIdentifier());
					unmatched.setRepositoryUrl(newRepositoryUrl);
					unmatchedMap.put(newRepositoryUrl, unmatched);
					//categories.put(orphans.getHandleIdentifier(), orphans);
					delta.add(new TaskContainerDelta(unmatched, TaskContainerDelta.Kind.CONTENT));
				}
			}
			for (UnsubmittedTaskContainer unsubmitted : unsubmittedTasksMap.values()) {
				if (unsubmitted.getRepositoryUrl().equals(oldRepositoryUrl)) {
					unsubmittedTasksMap.remove(oldRepositoryUrl);
					unsubmitted.setRepositoryUrl(newRepositoryUrl);
					unsubmittedTasksMap.put(newRepositoryUrl, unsubmitted);
					delta.add(new TaskContainerDelta(unsubmitted, TaskContainerDelta.Kind.CONTENT));
				}
			}
		} finally {
			unlock();
		}
	}

	public void removeChangeListener(ITaskListChangeListener listener) {
		changeListeners.remove(listener);
	}

	public void removeFromContainer(AbstractTaskContainer container, ITask task) {
		Assert.isNotNull(container);
		Assert.isNotNull(task);

		removeFromContainer(container, Collections.singleton(task));
	}

	public void removeFromContainer(AbstractTaskContainer container, Set<ITask> tasks) {
		Assert.isNotNull(container);
		Assert.isNotNull(tasks);
		try {
			lock();
			for (ITask task : tasks) {
				removeFromContainerInternal(container, task, delta);
				addToUnmatched((AbstractTask) task, delta);
			}
		} finally {
			unlock();
		}
	}

	/**
	 * Note: does not add <code>task</code> to the unmatched container.
	 */
	private void removeFromContainerInternal(AbstractTaskContainer container, ITask task, Set<TaskContainerDelta> delta) {
		assert container.getChildren().contains(task);

		container.internalRemoveChild(task);
		((AbstractTask) task).removeParentContainer(container);

		delta.add(new TaskContainerDelta(task, container, TaskContainerDelta.Kind.REMOVED));
	}

	private void removeFromUnmatched(AbstractTask task, Set<TaskContainerDelta> delta) {
		AbstractTaskContainer orphans = getUnmatchedContainer(task.getRepositoryUrl());
		if (orphans != null) {
			if (orphans.internalRemoveChild(task)) {
				delta.add(new TaskContainerDelta(task, orphans, TaskContainerDelta.Kind.REMOVED));
				task.removeParentContainer(orphans);
			}
		}
	}

	/**
	 * TODO separate category/query handle from name
	 * 
	 * @deprecated
	 */
	@Deprecated
	public void renameContainer(AbstractTaskContainer container, String newDescription) {
		Assert.isLegal(!(container instanceof ITask));
		Assert.isLegal(!(container instanceof UnmatchedTaskContainer));
		try {
			lock();
			if (container instanceof TaskCategory) {
				((TaskCategory) container).setSummary(newDescription);
			} else if (container instanceof RepositoryQuery) {
				((RepositoryQuery) container).setSummary(newDescription);
			}
			delta.add(new TaskContainerDelta(container, TaskContainerDelta.Kind.CONTENT));
		} finally {
			unlock();
		}
	}

	/**
	 * Public for testing.
	 */
	public void reset() {
		try {
			lock();
			tasks = new ConcurrentHashMap<String, AbstractTask>();

			unmatchedMap = new ConcurrentHashMap<String, UnmatchedTaskContainer>();
			unsubmittedTasksMap = new ConcurrentHashMap<String, UnsubmittedTaskContainer>();
			categories = new ConcurrentHashMap<String, AbstractTaskCategory>();
			queries = new ConcurrentHashMap<String, RepositoryQuery>();

			defaultCategory = new UncategorizedTaskContainer();

			maxLocalTaskId = 0;
			categories.put(defaultCategory.getHandleIdentifier(), defaultCategory);
		} finally {
			unlock();
		}
	}

	public void run(ITaskListRunnable runnable) throws CoreException {
		run(runnable, null);
	}

	public void run(ITaskListRunnable runnable, IProgressMonitor monitor) throws CoreException {
		run(runnable, monitor, false);
	}

	public void run(ITaskListRunnable runnable, IProgressMonitor monitor, boolean ignoreInterrupts)
			throws CoreException {
		monitor = Policy.monitorFor(monitor);
		try {
			lock(monitor, ignoreInterrupts);

			runnable.execute(monitor);

		} finally {
			unlock();
		}
	}

	private void lock() {
		lock.acquire();
		if (lock.getDepth() == 1) {
			delta = new HashSet<TaskContainerDelta>();
		}
	}

	private void lock(IProgressMonitor monitor, boolean ignoreInterrupts) throws CoreException {
		while (!monitor.isCanceled()) {
			try {
				if (lock.acquire(3000)) {
					if (lock.getDepth() == 1) {
						delta = new HashSet<TaskContainerDelta>();
					}
					// success
					return;
				}
			} catch (InterruptedException e) {
				if (ignoreInterrupts) {
					// clear interrupted status to retry lock.aquire() 
					Thread.interrupted();
				} else {
					break;
				}
			}
		}
		throw new OperationCanceledException();
	}

	private void unlock() {
		HashSet<TaskContainerDelta> toFire = null;
		try {
			if (lock.getDepth() == 1) {
				toFire = new HashSet<TaskContainerDelta>(delta);
			}
		} finally {
			lock.release();
		}
		if (toFire != null && toFire.size() > 0) {
			fireDelta(toFire);
		}
	}

	public static ISchedulingRule getSchedulingRule() {
		return ITasksCoreConstants.TASKLIST_SCHEDULING_RULE;
	}

	public String getUniqueHandleIdentifier() {
		try {
			lock();
			while (nextHandle < Integer.MAX_VALUE) {
				String handle = DEFAULT_HANDLE_PREFIX + nextHandle;
				nextHandle++;
				if (!categories.containsKey(handle) && !queries.containsKey(handle)
						&& !unmatchedMap.containsKey(handle) && !tasks.containsKey(handle)) {
					return handle;
				}
			}
			throw new RuntimeException("No more unique handles for task list"); //$NON-NLS-1$
		} finally {
			unlock();
		}
	}

	public UnsubmittedTaskContainer getUnsubmittedContainer(String repositoryUrl) {
		return unsubmittedTasksMap.get(repositoryUrl);
	}

}
