/*******************************************************************************
 * Copyright (c) 2004, 2013 Eugene Kuleshov 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 - improvements
 *******************************************************************************/

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.DecoratingLabelProvider;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.mylyn.internal.tasks.core.AbstractTaskCategory;
import org.eclipse.mylyn.internal.tasks.core.AbstractTaskContainer;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal;
import org.eclipse.mylyn.internal.tasks.ui.views.TaskRepositoryLabelProvider;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.TaskElementLabelProvider;
import org.eclipse.mylyn.tasks.ui.TasksUi;
import org.eclipse.mylyn.tasks.ui.TasksUiImages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.IWorkingSetManager;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.IWorkingSetPage;
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
import org.eclipse.ui.internal.WorkbenchImages;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.model.WorkbenchLabelProvider;

/**
 * Adapted from org.eclipse.ui.internal.ide.dialogs.ResourceWorkingSetPage
 * 
 * @author Eugene Kuleshov
 * @author Mik Kersten
 * @author David Green fix for bug 274390
 */
public class TaskWorkingSetPage extends WizardPage implements IWorkingSetPage {

	private final static int SIZING_SELECTION_WIDGET_WIDTH = 50;

	private final static int SIZING_SELECTION_WIDGET_HEIGHT = 200;

	private Text text;

	private CheckboxTreeViewer treeViewer;

	private IWorkingSet workingSet;

	private final WorkingSetPageContentProvider workingSetPageContentProvider = new WorkingSetPageContentProvider();

	private boolean firstCheck = false;

	private final class WorkingSetPageContentProvider implements ITreeContentProvider {

		private ElementCategory tasksContainer;

		private ElementCategory resourcesContainer;

		private final Map<IRepositoryQuery, TaskRepository> queryMap = new HashMap<IRepositoryQuery, TaskRepository>();

		private final Map<IProject, TaskRepositoryProjectMapping> projectMap = new HashMap<IProject, TaskRepositoryProjectMapping>();

		public Object[] getChildren(Object parentElement) {
			if (parentElement instanceof List<?>) {
				List<IAdaptable> taskRepositoriesContainers = new ArrayList<IAdaptable>();
				List<IAdaptable> resourcesRepositoriesContainers = new ArrayList<IAdaptable>();

				for (AbstractTaskContainer category : TasksUiInternal.getTaskList().getCategories()) {
					taskRepositoriesContainers.add(category);
				}

				IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
				Set<IProject> unmappedProjects = new HashSet<IProject>();
				for (Object container : (List<?>) parentElement) {
					if (container instanceof TaskRepository) {
						// NOTE: looking down, high complexity
						if (hasChildren(container)) {
							taskRepositoriesContainers.add((TaskRepository) container);
						}

						// NOTE: O(n^2) complexity, could fix
						Set<IProject> mappedProjects = new HashSet<IProject>();

						for (IProject project : projects) {
							TaskRepository taskRepository = TasksUiPlugin.getDefault()
									.getRepositoryForResource(project);
							if (container.equals(taskRepository)) {
								mappedProjects.add(project);
							} else if (taskRepository == null) {
								unmappedProjects.add(project);
							}
						}
						if (!mappedProjects.isEmpty()) {
							TaskRepositoryProjectMapping projectMapping = new TaskRepositoryProjectMapping(
									(TaskRepository) container, mappedProjects);
							resourcesRepositoriesContainers.add(projectMapping);
							for (IProject mappedProject : mappedProjects) {
								projectMap.put(mappedProject, projectMapping);
							}
						}
					}
				}
				resourcesRepositoriesContainers.addAll(unmappedProjects);
				tasksContainer = new ElementCategory(Messages.TaskWorkingSetPage_Tasks, taskRepositoriesContainers);
				resourcesContainer = new ElementCategory(Messages.TaskWorkingSetPage_Resources,
						resourcesRepositoriesContainers);
				return new Object[] { tasksContainer, resourcesContainer };
			} else if (parentElement instanceof TaskRepository) {
				List<IAdaptable> taskContainers = new ArrayList<IAdaptable>();
				for (AbstractTaskContainer element : TasksUiPlugin.getTaskList().getRepositoryQueries(
						((TaskRepository) parentElement).getRepositoryUrl())) {
					if (element instanceof IRepositoryQuery) {
						taskContainers.add(element);
						queryMap.put((IRepositoryQuery) element, (TaskRepository) parentElement);
					}
				}

				return taskContainers.toArray();
			} else if (parentElement instanceof TaskRepositoryProjectMapping) {
				return ((TaskRepositoryProjectMapping) parentElement).getProjects().toArray();
			} else if (parentElement instanceof ElementCategory) {
				return ((ElementCategory) parentElement).getChildren(parentElement);
			} else {
				return new Object[0];
			}
		}

		public boolean hasChildren(Object element) {
			return getChildren(element).length > 0;
		}

		public Object[] getElements(Object element) {
			return getChildren(element);
		}

		public Object getParent(Object element) {
			if (element instanceof AbstractTaskCategory || element instanceof TaskRepository) {
				return tasksContainer;
			} else if (element instanceof IRepositoryQuery) {
				return queryMap.get(element);
			} else if (element instanceof TaskRepositoryProjectMapping) {
				return resourcesContainer;
			} else if (element instanceof IProject) {
				Object repository = projectMap.get(element);
				if (repository != null) {
					return repository;
				} else {
					return resourcesContainer;
				}
			} else {
				return null;
			}
		}

		public void dispose() {
		}

		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}
	}

	private class TaskRepositoryProjectMapping extends PlatformObject {

		private final TaskRepository taskRepository;

		private final Set<IProject> projects;

		public TaskRepositoryProjectMapping(TaskRepository taskRepository, Set<IProject> mappedProjects) {
			this.taskRepository = taskRepository;
			this.projects = mappedProjects;
		}

		public Set<IProject> getProjects() {
			return projects;
		}

		public TaskRepository getTaskRepository() {
			return taskRepository;
		}
	}

	class ElementCategory extends PlatformObject implements IWorkbenchAdapter {

		private final String label;

		private final List<IAdaptable> children;

		public ElementCategory(String label, List<IAdaptable> children) {
			this.label = label;
			this.children = children;
		}

		public Object[] getChildren(Object o) {
			return children.toArray();
		}

		public ImageDescriptor getImageDescriptor(Object object) {
			return WorkbenchImages.getImageDescriptor(IWorkbenchGraphicConstants.IMG_OBJ_WORKING_SETS);
		}

		public String getLabel(Object o) {
			return label;
		}

		public Object getParent(Object o) {
			return null;
		}

	}

	class AggregateLabelProvider implements ILabelProvider {

		private final TaskElementLabelProvider taskLabelProvider = new TaskElementLabelProvider(false);

		private final TaskRepositoryLabelProvider taskRepositoryLabelProvider = new TaskRepositoryLabelProvider();

		private final WorkbenchLabelProvider workbenchLabelProvider = new WorkbenchLabelProvider();

		public Image getImage(Object element) {
			if (element instanceof AbstractTaskContainer) {
				return taskLabelProvider.getImage(element);
			} else if (element instanceof TaskRepository) {
				return taskRepositoryLabelProvider.getImage(element);
			} else if (element instanceof TaskRepositoryProjectMapping) {
				return getImage(((TaskRepositoryProjectMapping) element).getTaskRepository());
			} else {
				return workbenchLabelProvider.getImage(element);
			}
		}

		public String getText(Object element) {
			if (element instanceof AbstractTaskContainer) {
				return taskLabelProvider.getText(element);
			} else if (element instanceof TaskRepository) {
				return taskRepositoryLabelProvider.getText(element);
			} else if (element instanceof TaskRepositoryProjectMapping) {
				return getText(((TaskRepositoryProjectMapping) element).getTaskRepository());
			} else {
				return workbenchLabelProvider.getText(element);
			}
		}

		public void addListener(ILabelProviderListener listener) {
		}

		public void dispose() {
		}

		public boolean isLabelProperty(Object element, String property) {
			return false;
		}

		public void removeListener(ILabelProviderListener listener) {
		}
	}

	class CustomSorter extends ViewerSorter {

		@Override
		public int compare(Viewer viewer, Object e1, Object e2) {
			if (e1 instanceof TaskRepository || e1 instanceof TaskRepositoryProjectMapping) {
				return -1;
			} else if (e2 instanceof TaskRepository || e2 instanceof TaskRepositoryProjectMapping) {
				return 1;
			} else if (e1 instanceof ElementCategory
					&& ((ElementCategory) e1).getLabel(e1).equals(Messages.TaskWorkingSetPage_Tasks)) {
				return -1;
			} else if (e2 instanceof ElementCategory
					&& ((ElementCategory) e1).getLabel(e1).equals(Messages.TaskWorkingSetPage_Tasks)) {
				return 1;
			} else {
				return super.compare(viewer, e1, e2);
			}
		}
	}

	public TaskWorkingSetPage() {
		super("taskWorkingSetPage", Messages.TaskWorkingSetPage_Select_Working_Set_Elements, null); //$NON-NLS-1$
		setDescription(Messages.TaskWorkingSetPage_Page_Description);
		setImageDescriptor(TasksUiImages.BANNER_WORKING_SET);
	}

	public void finish() {
		Object[] elements = treeViewer.getCheckedElements();
		Set<IAdaptable> validElements = new HashSet<IAdaptable>();
		for (Object element : elements) {
			if (element instanceof AbstractTaskContainer || element instanceof IProject) {
				validElements.add((IAdaptable) element);
			}
		}

		addSpecialContainers(validElements);

		if (workingSet == null) {
			IWorkingSetManager workingSetManager = PlatformUI.getWorkbench().getWorkingSetManager();
			workingSet = workingSetManager.createWorkingSet(getWorkingSetName(),
					validElements.toArray(new IAdaptable[validElements.size()]));
		} else {
			if (!getWorkingSetName().equals(workingSet.getName())) {
				workingSet.setName(getWorkingSetName());
			}
			workingSet.setElements(validElements.toArray(new IAdaptable[validElements.size()]));
		}
	}

	private void addSpecialContainers(Set<IAdaptable> validElements) {
		HashSet<AbstractTaskContainer> specialContainers = new HashSet<AbstractTaskContainer>();
		for (IAdaptable element : validElements) {
			if (element instanceof IRepositoryQuery) {
				IRepositoryQuery query = (IRepositoryQuery) element;
				if (query.getRepositoryUrl() != null) {
					// Add Unmatched
					AbstractTaskContainer orphansContainer = TasksUiPlugin.getTaskList().getUnmatchedContainer(
							query.getRepositoryUrl());
					if (orphansContainer != null) {
						specialContainers.add(orphansContainer);
					}

					// Add Unsubmitted
					AbstractTaskContainer unsubmittedContainer = TasksUiPlugin.getTaskList().getUnsubmittedContainer(
							query.getRepositoryUrl());
					if (unsubmittedContainer != null) {
						specialContainers.add(unsubmittedContainer);
					}
				}
			}
		}
		validElements.addAll(specialContainers);
	}

	public IWorkingSet getSelection() {
		return workingSet;
	}

	public void setSelection(IWorkingSet workingSet) {
		this.workingSet = workingSet;
		if (getShell() != null && text != null) {
			firstCheck = true;
			initializeCheckedState();
			text.setText(workingSet.getName());
		}
	}

	private String getWorkingSetName() {
		return text.getText();
	}

	public void createControl(Composite parent) {
		initializeDialogUnits(parent);

		Composite composite = new Composite(parent, SWT.NULL);

		GridLayout layout = new GridLayout();
		layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
		layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
		layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
		setControl(composite);

		// PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, IIDEHelpContextIds.WORKING_SET_RESOURCE_PAGE);
		Label label = new Label(composite, SWT.WRAP);
		label.setText(Messages.TaskWorkingSetPage_Working_set_name);
		label.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL
				| GridData.VERTICAL_ALIGN_CENTER));

		text = new Text(composite, SWT.SINGLE | SWT.BORDER);
		text.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL));
		text.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				validateInput();
			}
		});
		// text.setBackground(FieldAssistColors.getRequiredFieldBackgroundColor(text));

		label = new Label(composite, SWT.WRAP);
		label.setText(Messages.TaskWorkingSetPage_Working_set_contents);
		label.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL
				| GridData.VERTICAL_ALIGN_CENTER));

		treeViewer = new CheckboxTreeViewer(composite);
		treeViewer.setUseHashlookup(true);
		treeViewer.setContentProvider(workingSetPageContentProvider);

		treeViewer.setLabelProvider(new DecoratingLabelProvider(new AggregateLabelProvider(), PlatformUI.getWorkbench()
				.getDecoratorManager()
				.getLabelDecorator()));
		treeViewer.setSorter(new CustomSorter());

		ArrayList<Object> containers = new ArrayList<Object>();
		for (TaskRepository repository : TasksUi.getRepositoryManager().getAllRepositories()) {
			containers.add(repository);
		}

		containers.addAll(Arrays.asList(ResourcesPlugin.getWorkspace().getRoot().getProjects()));

		treeViewer.setInput(containers);

		// tree.setComparator(new ResourceComparator(ResourceComparator.NAME));

		GridData data = new GridData(GridData.FILL_BOTH | GridData.GRAB_VERTICAL);
		data.heightHint = SIZING_SELECTION_WIDGET_HEIGHT;
		data.widthHint = SIZING_SELECTION_WIDGET_WIDTH;
		treeViewer.getControl().setLayoutData(data);

		treeViewer.addCheckStateListener(new ICheckStateListener() {
			public void checkStateChanged(CheckStateChangedEvent event) {
				handleCheckStateChange(event);
			}
		});

		// Add select / deselect all buttons for bug 46669
		Composite buttonComposite = new Composite(composite, SWT.NONE);
		layout = new GridLayout(2, false);
		layout.marginWidth = 0;
		layout.marginHeight = 0;
		layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
		buttonComposite.setLayout(layout);
		buttonComposite.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));

		Button selectAllButton = new Button(buttonComposite, SWT.PUSH);
		selectAllButton.setText(Messages.TaskWorkingSetPage_Select_All);
		selectAllButton.setToolTipText(""); //$NON-NLS-1$
		selectAllButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent selectionEvent) {
				treeViewer.setCheckedElements(workingSetPageContentProvider.getElements(treeViewer.getInput()));
				validateInput();
			}
		});
		setButtonLayoutData(selectAllButton);

		Button deselectAllButton = new Button(buttonComposite, SWT.PUSH);
		deselectAllButton.setText(Messages.TaskWorkingSetPage_Deselect_All);
		deselectAllButton.setToolTipText(""); //$NON-NLS-1$
		deselectAllButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent selectionEvent) {
				treeViewer.setCheckedElements(new Object[0]);
				validateInput();
			}
		});
		setButtonLayoutData(deselectAllButton);

		if (workingSet != null) {
			for (Object object : workingSet.getElements()) {
				treeViewer.expandToLevel(object, 1);
			}
		} else {
			treeViewer.expandToLevel(2);
		}
		initializeCheckedState();

		if (workingSet != null) {
			text.setText(workingSet.getName());
			treeViewer.getControl().setFocus();
		} else {
			text.setFocus();
		}

		setPageComplete(false);
		Dialog.applyDialogFont(composite);
	}

	private void initializeCheckedState() {
		BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
			public void run() {
				Object[] items = null;
				if (workingSet != null) {
					items = workingSet.getElements();
					if (items != null) {
						// see bug 191342
						treeViewer.setCheckedElements(new Object[] {});
						for (Object item : items) {
							if (item != null) {
								treeViewer.setChecked(item, true);
							}
						}
					}
				}
			}
		});
	}

	protected void handleCheckStateChange(final CheckStateChangedEvent event) {
		BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
			public void run() {
				IAdaptable element = (IAdaptable) event.getElement();
				handleCheckStateChangeHelper(event, element);
				validateInput();
			}

			private void handleCheckStateChangeHelper(final CheckStateChangedEvent event, IAdaptable element) {
				if (element instanceof AbstractTaskContainer || element instanceof IProject) {
					treeViewer.setGrayed(element, false);
				} else if (element instanceof ElementCategory) {
					for (Object child : ((ElementCategory) element).getChildren(null)) {
						treeViewer.setChecked(child, event.getChecked());
						if (child instanceof IAdaptable) {
							handleCheckStateChangeHelper(event, (IAdaptable) child);
						}
					}
				} else if (element instanceof TaskRepository || element instanceof TaskRepositoryProjectMapping) {
					for (Object child : workingSetPageContentProvider.getChildren(element)) {
						treeViewer.setChecked(child, event.getChecked());
					}
				}
			}
		});
	}

	protected void validateInput() {
		String errorMessage = null;
		String infoMessage = null;
		String newText = text.getText();

		if (!newText.equals(newText.trim())) {
			errorMessage = Messages.TaskWorkingSetPage_The_name_must_not_have_a_leading_or_trailing_whitespace;
		} else if (firstCheck) {
			firstCheck = false;
			return;
		}
		if ("".equals(newText)) { //$NON-NLS-1$
			errorMessage = Messages.TaskWorkingSetPage_The_name_must_not_be_empty;
		}
		if (errorMessage == null && (workingSet == null || !newText.equals(workingSet.getName()))) {
			IWorkingSet[] workingSets = PlatformUI.getWorkbench().getWorkingSetManager().getWorkingSets();
			for (IWorkingSet workingSet2 : workingSets) {
				if (newText.equals(workingSet2.getName())) {
					errorMessage = Messages.TaskWorkingSetPage_A_working_set_with_the_same_name_already_exists;
				}
			}
		}
		if (treeViewer.getCheckedElements().length == 0) {
			infoMessage = Messages.TaskWorkingSetPage_No_categories_queries_selected;
		}
		setMessage(infoMessage, INFORMATION);
		setErrorMessage(errorMessage);
		setPageComplete(errorMessage == null);
	}

}
