/*******************************************************************************
 * Copyright (c) 2009 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.ui.actions;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
import org.eclipse.mylyn.internal.tasks.core.AbstractTaskCategory;
import org.eclipse.mylyn.internal.tasks.core.LocalRepositoryConnector;
import org.eclipse.mylyn.internal.tasks.core.TaskCategory;
import org.eclipse.mylyn.internal.tasks.core.UncategorizedTaskContainer;
import org.eclipse.mylyn.internal.tasks.ui.IDynamicSubMenuContributor;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.internal.tasks.ui.actions.CopyTaskDetailsAction.Mode;
import org.eclipse.mylyn.internal.tasks.ui.views.Messages;
import org.eclipse.mylyn.internal.tasks.ui.views.UpdateRepositoryConfigurationAction;
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.ITaskContainer;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds;

/**
 * @author Steffen Pingel
 */
public class RepositoryElementActionGroup {

	protected static final String ID_SEPARATOR_NEW = "new"; //$NON-NLS-1$

	private static final String ID_SEPARATOR_OPERATIONS = "operations"; //$NON-NLS-1$

	private static final String ID_SEPARATOR_TASKS = "tasks"; //$NON-NLS-1$

	protected static final String ID_SEPARATOR_REPOSITORY = "repository"; //$NON-NLS-1$

	private static final String ID_SEPARATOR_PROPERTIES = "properties"; //$NON-NLS-1$

	protected static final String ID_SEPARATOR_NAVIGATE = "navigate"; //$NON-NLS-1$

	private static final String ID_SEPARATOR_OPEN = "open"; //$NON-NLS-1$

	private static final String ID_SEPARATOR_SHOW_IN = "showIn"; //$NON-NLS-1$

	protected static final String ID_SEPARATOR_EDIT = "edit"; //$NON-NLS-1$

	private final CopyTaskDetailsAction copyUrlAction;

	private final CopyTaskDetailsAction copyKeyAction;

	private final CopyTaskDetailsAction copyDetailsAction;

	private final OpenTaskListElementAction openAction;

	private final OpenWithBrowserAction openWithBrowserAction;

	private final DeleteAction deleteAction;

	private final DeleteTaskEditorAction deleteTaskEditorAction;

	private final RemoveFromCategoryAction removeFromCategoryAction;

	private final ShowInSearchViewAction showInSearchViewAction;

	private final ShowInTaskListAction showInTaskListAction;

	private final TaskActivateAction activateAction;

	private final TaskDeactivateAction deactivateAction;

	private ISelectionProvider selectionProvider;

	private final List<ISelectionChangedListener> actions;

	private final AutoUpdateQueryAction autoUpdateAction;

	private final NewSubTaskAction newSubTaskAction;

	private final CloneTaskAction cloneTaskAction;

	public RepositoryElementActionGroup() {
		actions = new ArrayList<ISelectionChangedListener>();

		newSubTaskAction = add(new NewSubTaskAction());

		cloneTaskAction = add(new CloneTaskAction());

		activateAction = add(new TaskActivateAction());
		deactivateAction = new TaskDeactivateAction();

		copyKeyAction = add(new CopyTaskDetailsAction(Mode.KEY));
		copyUrlAction = add(new CopyTaskDetailsAction(Mode.URL));
		copyDetailsAction = add(new CopyTaskDetailsAction(Mode.SUMMARY_URL));
		if (!isInEditor()) {
			copyDetailsAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.COPY);
		}

		removeFromCategoryAction = add(new RemoveFromCategoryAction());

		deleteAction = add(new DeleteAction());
		deleteTaskEditorAction = add(new DeleteTaskEditorAction());
		openAction = add(new OpenTaskListElementAction());
		openWithBrowserAction = add(new OpenWithBrowserAction());
		showInSearchViewAction = add(new ShowInSearchViewAction());
		showInTaskListAction = add(new ShowInTaskListAction());

		autoUpdateAction = add(new AutoUpdateQueryAction());
	}

	protected <T extends ISelectionChangedListener> T add(T action) {
		actions.add(action);
		return action;
	}

	public void setSelectionProvider(ISelectionProvider selectionProvider) {
		if (this.selectionProvider != null) {
			for (ISelectionChangedListener action : actions) {
				this.selectionProvider.removeSelectionChangedListener(action);
			}
		}
		this.selectionProvider = selectionProvider;
		if (selectionProvider != null) {
			for (ISelectionChangedListener action : actions) {
				this.selectionProvider.addSelectionChangedListener(action);
				ISelection selection = selectionProvider.getSelection();
				if (selection == null) {
					selection = StructuredSelection.EMPTY;
				}
				action.selectionChanged(new SelectionChangedEvent(selectionProvider, selection));
			}
		}
	}

	public void fillContextMenu(final IMenuManager manager) {
		manager.add(new Separator(ID_SEPARATOR_NEW)); // new, schedule
		manager.add(new GroupMarker(ID_SEPARATOR_NAVIGATE)); // mark, go into, go up
		manager.add(new Separator(ID_SEPARATOR_OPEN)); // open, activate
		manager.add(new GroupMarker(ID_SEPARATOR_SHOW_IN)); // open, activate
		manager.add(new Separator(ID_SEPARATOR_EDIT)); // cut, copy paste, delete, rename
		manager.add(new Separator(ID_SEPARATOR_TASKS)); // move to
		manager.add(new GroupMarker(ID_SEPARATOR_OPERATIONS)); // repository properties, import/export, context
		manager.add(new Separator(ID_SEPARATOR_REPOSITORY)); // synchronize
		manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
		manager.add(new Separator(ID_SEPARATOR_PROPERTIES)); // properties

		final ITaskContainer element;
		IStructuredSelection selection = getSelection();
		final Object firstSelectedObject = selection.getFirstElement();
		if (firstSelectedObject instanceof ITaskContainer) {
			element = (ITaskContainer) firstSelectedObject;
		} else {
			element = null;
		}
		final List<IRepositoryElement> selectedElements = getSelectedTaskContainers(selection);
		AbstractTask task = null;
		if (element instanceof ITask) {
			task = (AbstractTask) element;
		}

		if (!isInTaskList()) {
			MenuManager newSubMenu = new MenuManager(Messages.RepositoryElementActionGroup_New);
			if (newSubTaskAction.isEnabled()) {
				newSubMenu.add(newSubTaskAction);
			}
			if (cloneTaskAction.isEnabled()) {
				newSubMenu.add(new Separator());
				newSubMenu.add(cloneTaskAction);
			}
			manager.appendToGroup(ID_SEPARATOR_NEW, newSubMenu);
		}

		if (element instanceof ITask && !isInEditor()) {
			addAction(ID_SEPARATOR_OPEN, openAction, manager, element);
		}
		if (openWithBrowserAction.isEnabled()) {
			manager.appendToGroup(ID_SEPARATOR_OPEN, openWithBrowserAction);
		}
		showInSearchViewAction.selectionChanged(selection);
		if (showInSearchViewAction.isEnabled()) {
			manager.appendToGroup(ID_SEPARATOR_OPEN, showInSearchViewAction);
		}
		showInTaskListAction.selectionChanged(selection);
		if (showInTaskListAction.isEnabled() && !isInTaskList()) {
			manager.appendToGroup(ID_SEPARATOR_OPEN, showInTaskListAction);
		}
		if (task != null) {
			if (task.isActive()) {
				manager.appendToGroup(ID_SEPARATOR_SHOW_IN, deactivateAction);
			} else {
				manager.appendToGroup(ID_SEPARATOR_SHOW_IN, activateAction);
			}
		}

		if (!selection.isEmpty()) {
			MenuManager copyDetailsSubMenu = new MenuManager(
					Messages.RepositoryElementActionGroup_Copy_Detail_Menu_Label, CopyTaskDetailsAction.ID);
			copyDetailsSubMenu.add(copyKeyAction);
			copyDetailsSubMenu.add(copyUrlAction);
			copyDetailsSubMenu.add(copyDetailsAction);
			manager.appendToGroup(ID_SEPARATOR_EDIT, copyDetailsSubMenu);
		}
		if (isInTaskList() && !selection.isEmpty()) {
			manager.appendToGroup(ID_SEPARATOR_EDIT, deleteAction);
		}

		if (isInEditor()) {
			manager.appendToGroup(ID_SEPARATOR_TASKS, deleteTaskEditorAction);
		}

		removeFromCategoryAction.selectionChanged(selection);
		removeFromCategoryAction.setEnabled(isRemoveFromCategoryEnabled(selectedElements));
		if (removeFromCategoryAction.isEnabled()) {
			manager.appendToGroup(ID_SEPARATOR_EDIT, removeFromCategoryAction);
		}

		if (autoUpdateAction.isEnabled()) {
			manager.appendToGroup(ID_SEPARATOR_REPOSITORY, autoUpdateAction);
		}

		if (element instanceof IRepositoryQuery) {
			EditRepositoryPropertiesAction repositoryPropertiesAction = new EditRepositoryPropertiesAction();
			repositoryPropertiesAction.selectionChanged(new StructuredSelection(element));
			if (repositoryPropertiesAction.isEnabled()) {
				MenuManager subMenu = new MenuManager(Messages.TaskListView_Repository);
				manager.appendToGroup(ID_SEPARATOR_OPERATIONS, subMenu);

				UpdateRepositoryConfigurationAction resetRepositoryConfigurationAction = new UpdateRepositoryConfigurationAction();
				resetRepositoryConfigurationAction.selectionChanged(new StructuredSelection(element));
				subMenu.add(resetRepositoryConfigurationAction);
				subMenu.add(new Separator());
				subMenu.add(repositoryPropertiesAction);
			}
		}

		Map<String, List<IDynamicSubMenuContributor>> dynamicMenuMap = TasksUiPlugin.getDefault().getDynamicMenuMap();
		for (final String menuPath : dynamicMenuMap.keySet()) {
			for (final IDynamicSubMenuContributor contributor : dynamicMenuMap.get(menuPath)) {
				SafeRunnable.run(new ISafeRunnable() {
					public void handleException(Throwable e) {
						StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Menu contributor failed")); //$NON-NLS-1$
					}

					public void run() throws Exception {
						MenuManager subMenuManager = contributor.getSubMenuManager(selectedElements);
						if (subMenuManager != null) {
							addMenuManager(menuPath, subMenuManager, manager, element);
						}
					}
				});
			}
		}
	}

	private boolean isInTaskList() {
		return (this instanceof TaskListViewActionGroup);
	}

	private IStructuredSelection getSelection() {
		ISelection selection = (selectionProvider != null) ? selectionProvider.getSelection() : null;
		if (selection instanceof IStructuredSelection) {
			return (IStructuredSelection) selection;
		}
		return StructuredSelection.EMPTY;
	}

	private boolean isInEditor() {
		return (this instanceof TaskEditorActionGroup);
	}

	private boolean isRemoveFromCategoryEnabled(final List<IRepositoryElement> selectedElements) {
		if (selectedElements.isEmpty()) {
			return false;
		}
		for (IRepositoryElement element : selectedElements) {
			if (element instanceof AbstractTask) {
				boolean hasCategory = false;
				for (ITaskContainer container : ((AbstractTask) element).getParentContainers()) {
					if (container instanceof TaskCategory) {
						hasCategory = true;
					}
					if (container instanceof UncategorizedTaskContainer
							&& !LocalRepositoryConnector.CONNECTOR_KIND.equals(((AbstractTask) element).getConnectorKind())) {
						hasCategory = true;
					}
				}
				if (!hasCategory) {
					return false;
				}
			} else {
				return false;
			}
		}
		return true;
	}

	private void addMenuManager(String path, IMenuManager menuToAdd, IMenuManager manager, ITaskContainer element) {
		if (element instanceof ITask || element instanceof IRepositoryQuery) {
			manager.appendToGroup(path, menuToAdd);
		}
	}

	private void addAction(String path, Action action, IMenuManager manager, ITaskContainer element) {
		action.setEnabled(false);
		if (element != null) {
			updateActionEnablement(action, element);
		}
		manager.appendToGroup(path, action);
	}

	// TODO move the enablement to the action classes
	private void updateActionEnablement(Action action, ITaskContainer element) {
		if (element instanceof ITask) {
			if (action instanceof OpenTaskListElementAction) {
				action.setEnabled(true);
			} else if (action instanceof CopyTaskDetailsAction) {
				action.setEnabled(true);
			} else if (action instanceof RenameAction) {
				action.setEnabled(true);
			}
		} else if (element != null) {
			if (action instanceof GoIntoAction) {
				TaskCategory cat = (TaskCategory) element;
				if (cat.getChildren().size() > 0) {
					action.setEnabled(true);
				} else {
					action.setEnabled(false);
				}
			} else if (action instanceof OpenTaskListElementAction) {
				action.setEnabled(true);
			} else if (action instanceof CopyTaskDetailsAction) {
				action.setEnabled(true);
			} else if (action instanceof RenameAction) {
				if (element instanceof AbstractTaskCategory) {
					AbstractTaskCategory container = (AbstractTaskCategory) element;
					action.setEnabled(container.isUserManaged());
				} else if (element instanceof IRepositoryQuery) {
					action.setEnabled(true);
				}
			}
		} else {
			action.setEnabled(true);
		}
	}

	public List<IRepositoryElement> getSelectedTaskContainers(IStructuredSelection selection) {
		List<IRepositoryElement> selectedElements = new ArrayList<IRepositoryElement>();
		for (Iterator<?> i = selection.iterator(); i.hasNext();) {
			Object object = i.next();
			if (object instanceof ITaskContainer) {
				selectedElements.add((IRepositoryElement) object);
			}
		}
		return selectedElements;
	}

	public OpenTaskListElementAction getOpenAction() {
		return openAction;
	}

	public TaskActivateAction getActivateAction() {
		return activateAction;
	}

	public DeleteAction getDeleteAction() {
		return deleteAction;
	}

	public CopyTaskDetailsAction getCopyDetailsAction() {
		return copyDetailsAction;
	}

}
