/*******************************************************************************
 * 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:
 *     Eugene Kuleshov - initial API and implementation
 *     Tasktop Technologies - initial API and implementation
 *     Yatta Solutions - fix for bug 327262 
 *******************************************************************************/

package org.eclipse.mylyn.internal.tasks.ui.workingsets;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.mylyn.internal.tasks.core.AbstractTaskContainer;
import org.eclipse.mylyn.internal.tasks.core.ITaskListChangeListener;
import org.eclipse.mylyn.internal.tasks.core.TaskCategory;
import org.eclipse.mylyn.internal.tasks.core.TaskContainerDelta;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal;
import org.eclipse.mylyn.monitor.ui.MonitorUi;
import org.eclipse.mylyn.tasks.core.IRepositoryElement;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.IWorkingSetUpdater;
import org.eclipse.ui.PlatformUI;

/**
 * @author Eugene Kuleshov
 * @author Mik Kersten
 * @author Steffen Pingel
 * @author Carsten Reckord
 */
public class TaskWorkingSetUpdater implements IWorkingSetUpdater, ITaskListChangeListener, IResourceChangeListener {

	public static String ID_TASK_WORKING_SET = "org.eclipse.mylyn.tasks.ui.workingSet"; //$NON-NLS-1$

	private final List<IWorkingSet> workingSets = new CopyOnWriteArrayList<IWorkingSet>();

	private static class TaskWorkingSetDelta {

		private final IWorkingSet workingSet;

		private final List<Object> elements;

		private boolean changed;

		public TaskWorkingSetDelta(IWorkingSet workingSet) {
			this.workingSet = workingSet;
			this.elements = new ArrayList<Object>(Arrays.asList(workingSet.getElements()));
		}

		public int indexOf(Object element) {
			return elements.indexOf(element);
		}

		public void set(int index, Object element) {
			elements.set(index, element);
			changed = true;
		}

		public void remove(int index) {
			if (elements.remove(index) != null) {
				changed = true;
			}
		}

		public void process() {
			if (changed) {
				workingSet.setElements(elements.toArray(new IAdaptable[elements.size()]));
			}
		}
	}

	private static boolean enabled = true;

	public TaskWorkingSetUpdater() {
		TasksUiInternal.getTaskList().addChangeListener(this);
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
	}

	/**
	 * Set <code>enabled</code> to false to disable processing of task list changes, e.g. during import operations.
	 * 
	 * @param enabled
	 */
	public static void setEnabled(boolean enabled) {
		TaskWorkingSetUpdater.enabled = enabled;
	}

	public static boolean isEnabled() {
		return enabled;
	}

	public void dispose() {
		TasksUiInternal.getTaskList().removeChangeListener(this);
	}

	public void add(IWorkingSet workingSet) {
		checkElementExistence(workingSet);
		synchronized (workingSets) {
			workingSets.add(workingSet);
		}
	}

	private void checkElementExistence(IWorkingSet workingSet) {
		ArrayList<IAdaptable> list = new ArrayList<IAdaptable>(Arrays.asList(workingSet.getElements()));
		boolean changed = false;
		for (Iterator<IAdaptable> iter = list.iterator(); iter.hasNext();) {
			IAdaptable adaptable = iter.next();
			boolean remove = false;
			if (adaptable instanceof AbstractTaskContainer) {
				String handle = ((AbstractTaskContainer) adaptable).getHandleIdentifier();
				remove = true;
				for (IRepositoryElement element : TasksUiPlugin.getTaskList().getRootElements()) {
					if (element != null && element.getHandleIdentifier().equals(handle)) {
						remove = false;
						break;
					}
				}
			} else if (adaptable instanceof IProject) {
				IProject project = ResourcesPlugin.getWorkspace()
						.getRoot()
						.getProject(((IProject) adaptable).getName());
				if (project == null || !project.exists()) {
					remove = true;
				}
			}
			if (remove) {
				iter.remove();
				changed = true;
			}
		}
		if (changed) {
			workingSet.setElements(list.toArray(new IAdaptable[list.size()]));
		}
	}

	public boolean contains(IWorkingSet workingSet) {
		synchronized (workingSets) {
			return workingSets.contains(workingSet);
		}
	}

	public boolean remove(IWorkingSet workingSet) {
		synchronized (workingSets) {
			return workingSets.remove(workingSet);
		}
	}

	public void containersChanged(Set<TaskContainerDelta> delta) {
		if (!isEnabled()) {
			return;
		}
		for (TaskContainerDelta taskContainerDelta : delta) {
			if (taskContainerDelta.getElement() instanceof TaskCategory
					|| taskContainerDelta.getElement() instanceof IRepositoryQuery) {
				synchronized (workingSets) {
					switch (taskContainerDelta.getKind()) {
					case REMOVED:
						// Remove from all
						for (IWorkingSet workingSet : workingSets) {
							ArrayList<IAdaptable> elements = new ArrayList<IAdaptable>(
									Arrays.asList(workingSet.getElements()));
							elements.remove(taskContainerDelta.getElement());
							workingSet.setElements(elements.toArray(new IAdaptable[elements.size()]));
						}
						break;
					case ADDED:
						// Add to the active working set
						for (IWorkingSet workingSet : TaskWorkingSetUpdater.getEnabledSets()) {
							ArrayList<IAdaptable> elements = new ArrayList<IAdaptable>(
									Arrays.asList(workingSet.getElements()));
							elements.add(taskContainerDelta.getElement());
							workingSet.setElements(elements.toArray(new IAdaptable[elements.size()]));
						}
						break;
					}
				}
			}
		}
	}

	// TODO: consider putting back, but evaluate policy and note bug 197257
//	public void taskActivated(AbstractTask task) {
//		Set<AbstractTaskContainer> taskContainers = new HashSet<AbstractTaskContainer>(
//				TasksUiPlugin.getTaskList().getQueriesForHandle(task.getHandleIdentifier()));
//		taskContainers.addAll(task.getParentContainers());
//
//		Set<AbstractTaskContainer> allActiveWorkingSetContainers = new HashSet<AbstractTaskContainer>();
//		for (IWorkingSet workingSet : PlatformUI.getWorkbench()
//				.getActiveWorkbenchWindow()
//				.getActivePage()
//				.getWorkingSets()) {
//			ArrayList<IAdaptable> elements = new ArrayList<IAdaptable>(Arrays.asList(workingSet.getElements()));
//			for (IAdaptable adaptable : elements) {
//				if (adaptable instanceof AbstractTaskContainer) {
//					allActiveWorkingSetContainers.add((AbstractTaskContainer) adaptable);
//				}
//			}
//		}
//		boolean isContained = false;
//		for (AbstractTaskContainer taskContainer : allActiveWorkingSetContainers) {
//			if (taskContainers.contains(taskContainer)) {
//				isContained = true;
//				break;
//			}
//		}
//
//		;
//		if (!isContained) {
//			IWorkingSet matchingWorkingSet = null;
//			for (IWorkingSet workingSet : PlatformUI.getWorkbench().getWorkingSetManager().getAllWorkingSets()) {
//				ArrayList<IAdaptable> elements = new ArrayList<IAdaptable>(Arrays.asList(workingSet.getElements()));
//				for (IAdaptable adaptable : elements) {
//					if (adaptable instanceof AbstractTaskContainer) {
//						if (((AbstractTaskContainer)adaptable).contains(task.getHandleIdentifier())) {
//							matchingWorkingSet = workingSet;
//						}
//					}
//				}
//			}
//
//			if (matchingWorkingSet != null) {
//				new ToggleWorkingSetAction(matchingWorkingSet).run();
//			} else { 
//				new ToggleAllWorkingSetsAction(PlatformUI.getWorkbench().getActiveWorkbenchWindow()).run();
//			}
//		}
//	}

	public static IWorkingSet[] getEnabledSets() {
		Set<IWorkingSet> workingSets = new HashSet<IWorkingSet>();
		Set<IWorkbenchWindow> windows = MonitorUi.getMonitoredWindows();
		for (IWorkbenchWindow iWorkbenchWindow : windows) {
			IWorkbenchPage page = iWorkbenchWindow.getActivePage();
			if (page != null) {
				workingSets.addAll(Arrays.asList(page.getWorkingSets()));
			}
		}

		return workingSets.toArray(new IWorkingSet[workingSets.size()]);
	}

	/**
	 * TODO: move
	 */
	public static boolean areNoTaskWorkingSetsEnabled() {
		IWorkingSet[] workingSets = PlatformUI.getWorkbench().getWorkingSetManager().getWorkingSets();
		for (IWorkingSet workingSet : workingSets) {
			if (workingSet != null && workingSet.getId().equalsIgnoreCase(ID_TASK_WORKING_SET)) {
				if (isWorkingSetEnabled(workingSet)) {
					return false;
				}
			}
		}
		return true;
	}

	public static boolean isWorkingSetEnabled(IWorkingSet set) {
		IWorkingSet[] enabledSets = TaskWorkingSetUpdater.getEnabledSets();
		for (IWorkingSet enabledSet : enabledSets) {
			if (enabledSet.equals(set)) {
				return true;
			}
		}
		return false;
	}

	public static boolean isOnlyTaskWorkingSetEnabled(IWorkingSet set) {
		if (!TaskWorkingSetUpdater.isWorkingSetEnabled(set)) {
			return false;
		}

		IWorkingSet[] enabledSets = TaskWorkingSetUpdater.getEnabledSets();
		for (int i = 0; i < enabledSets.length; i++) {
			if (!enabledSets[i].equals(set)
					&& enabledSets[i].getId().equalsIgnoreCase(TaskWorkingSetUpdater.ID_TASK_WORKING_SET)) {
				return false;
			}
		}
		return true;
	}

	private void processResourceDelta(TaskWorkingSetDelta result, IResourceDelta delta) {
		IResource resource = delta.getResource();
		int type = resource.getType();
		int index = result.indexOf(resource);
		int kind = delta.getKind();
		int flags = delta.getFlags();
		if (kind == IResourceDelta.CHANGED && type == IResource.PROJECT && index != -1) {
			if ((flags & IResourceDelta.OPEN) != 0) {
				result.set(index, resource);
			}
		}
		if (index != -1 && kind == IResourceDelta.REMOVED) {
			if ((flags & IResourceDelta.MOVED_TO) != 0) {
				result.set(index, ResourcesPlugin.getWorkspace().getRoot().findMember(delta.getMovedToPath()));
			} else {
				result.remove(index);
			}
		}

		// Don't dive into closed or opened projects
		if (projectGotClosedOrOpened(resource, kind, flags)) {
			return;
		}

		IResourceDelta[] children = delta.getAffectedChildren();
		for (IResourceDelta element : children) {
			processResourceDelta(result, element);
		}
	}

	private boolean projectGotClosedOrOpened(IResource resource, int kind, int flags) {
		return resource.getType() == IResource.PROJECT && kind == IResourceDelta.CHANGED
				&& (flags & IResourceDelta.OPEN) != 0;
	}

	public void resourceChanged(IResourceChangeEvent event) {
		for (IWorkingSet workingSet : workingSets) {
			TaskWorkingSetDelta workingSetDelta = new TaskWorkingSetDelta(workingSet);
			if (event.getDelta() != null) {
				processResourceDelta(workingSetDelta, event.getDelta());
			}
			workingSetDelta.process();
		}
	}

	/**
	 * Must be called from the UI thread
	 */
	public static void applyWorkingSetsToAllWindows(Collection<IWorkingSet> workingSets) {
		IWorkingSet[] workingSetArray = workingSets.toArray(new IWorkingSet[workingSets.size()]);
		for (IWorkbenchWindow window : MonitorUi.getMonitoredWindows()) {
			for (IWorkbenchPage page : window.getPages()) {
				page.setWorkingSets(workingSetArray);
			}
		}
	}

	public static Set<IWorkingSet> getActiveWorkingSets(IWorkbenchWindow window) {
		if (window != null && window.getActivePage() != null) {
			Set<IWorkingSet> allSets = new HashSet<IWorkingSet>(Arrays.asList(window.getActivePage().getWorkingSets()));
			Set<IWorkingSet> tasksSets = new HashSet<IWorkingSet>(allSets);
			for (IWorkingSet workingSet : allSets) {
				if (workingSet.getId() == null
						|| !workingSet.getId().equalsIgnoreCase(TaskWorkingSetUpdater.ID_TASK_WORKING_SET)) {
					tasksSets.remove(workingSet);
				}
			}
			return tasksSets;
		} else {
			return Collections.emptySet();
		}
	}

}
