/*******************************************************************************
 * Copyright (c) 2004, 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.context.tasks.ui.editors;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.commons.workbench.WorkbenchUtil;
import org.eclipse.mylyn.context.core.AbstractContextListener;
import org.eclipse.mylyn.context.core.AbstractContextStructureBridge;
import org.eclipse.mylyn.context.core.ContextChangeEvent;
import org.eclipse.mylyn.context.core.ContextCore;
import org.eclipse.mylyn.context.core.IInteractionContext;
import org.eclipse.mylyn.context.core.IInteractionElement;
import org.eclipse.mylyn.internal.context.core.ContextCorePlugin;
import org.eclipse.mylyn.internal.context.ui.ContextUiPlugin;
import org.eclipse.mylyn.tasks.ui.TasksUiImages;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.navigator.CommonViewer;

/**
 * @author Shawn Minto
 */
public class InvisibleContextElementsPart {

	private static void collectItemData(TreeItem[] items, Set<Object> allVisible) {
		for (TreeItem item : items) {
			allVisible.add(item.getData());
			collectItemData(item.getItems(), allVisible);
		}
	}

	private final class InteractionElementTableSorter extends ViewerSorter {

		private int criteria = 0;

		private boolean isDecending = true;

		private final ITableLabelProvider labelProvider;

		public InteractionElementTableSorter(ITableLabelProvider labelProvider) {
			this.labelProvider = labelProvider;
		}

		@Override
		public int compare(Viewer viewer, Object e1, Object e2) {
			int result = 0;

			String value1 = labelProvider.getColumnText(e1, criteria);
			String value2 = labelProvider.getColumnText(e2, criteria);

			if (value1 == null && value2 != null) {
				result = -1;
			} else if (value1 != null && value2 == null) {
				result = 1;
			} else if (value1 != null && value2 != null) {
				result = value1.compareTo(value2);
			}

			return isDecending() ? (result * -1) : result;
		}

		public boolean isDecending() {
			return isDecending;
		}

		public void setCriteria(int index) {
			if (criteria == index) {
				isDecending = !isDecending;
			} else {
				isDecending = false;
			}
			criteria = index;
		}

	}

	private final AbstractContextListener CONTEXT_LISTENER = new AbstractContextListener() {
		@Override
		public void contextChanged(ContextChangeEvent event) {
			switch (event.getEventKind()) {
			case ACTIVATED:
				if (isActiveTask()) {
					addToolbarActions();
				}
				break;
			case DEACTIVATED:
				toolbarManager.removeAll();
				toolbarManager.update(true);
			}
		}
	};

	private final class InteractionElementTableLabelProvider extends LabelProvider implements ITableLabelProvider {
		@Override
		public String getText(Object element) {
			if (element instanceof IInteractionElement) {
				return ((IInteractionElement) element).getHandleIdentifier();
			}
			return super.getText(element);
		}

		public Image getColumnImage(Object element, int columnIndex) {
			return null;
		}

		public String getColumnText(Object element, int columnIndex) {
			if (element instanceof IInteractionElement) {
				if (columnIndex == 0) {
					return ((IInteractionElement) element).getHandleIdentifier();
				} else if (columnIndex == 1) {
					return ((IInteractionElement) element).getContentType();
				}
			}
			return ""; //$NON-NLS-1$
		}
	}

	private final class RemoveInvisibleAction extends Action {
		public RemoveInvisibleAction() {
			setText(Messages.ContextEditorFormPage_Remove_Invisible_);
			setToolTipText(Messages.ContextEditorFormPage_Remove_Invisible_);
			setImageDescriptor(TasksUiImages.CONTEXT_CLEAR);
		}

		@Override
		public void run() {
			if (commonViewer == null) {
				MessageDialog.openWarning(WorkbenchUtil.getShell(), Messages.ContextEditorFormPage_Remove_Invisible,
						Messages.ContextEditorFormPage_Activate_task_to_remove_invisible);
				return;
			}

			boolean confirmed = MessageDialog.openConfirm(Display.getCurrent().getActiveShell(),
					Messages.ContextEditorFormPage_Remove_Invisible,
					Messages.ContextEditorFormPage_Remove_every_element_not_visible);
			if (confirmed) {

				if (ContextCore.getContextManager().isContextActive()) {
					try {
						final Collection<Object> allVisible = getAllVisibleElementsInContextPage();
						PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() {

							public void run(IProgressMonitor monitor) throws InvocationTargetException,
									InterruptedException {
								monitor.beginTask(Messages.InvisibleContextElementsPart_Collecting_all_invisible,
										IProgressMonitor.UNKNOWN);
								if (allVisible != null) {
									final List<IInteractionElement> allToRemove = getAllInvisibleElements(context,
											allVisible);
									Display.getDefault().asyncExec(new Runnable() {
										public void run() {
											ContextCorePlugin.getContextManager().deleteElements(allToRemove, true);
										}
									});

								} else {
									MessageDialog.openInformation(Display.getCurrent().getActiveShell(),
											Messages.ContextEditorFormPage_Remove_Invisible,
											Messages.ContextEditorFormPage_No_context_active);
								}
							}
						});
					} catch (InvocationTargetException e) {
						StatusHandler.log(new Status(IStatus.ERROR, ContextUiPlugin.ID_PLUGIN, e.getMessage(), e));
					} catch (InterruptedException e) {
						StatusHandler.log(new Status(IStatus.ERROR, ContextUiPlugin.ID_PLUGIN, e.getMessage(), e));
					}

				} else {
					MessageDialog.openInformation(Display.getCurrent().getActiveShell(),
							Messages.ContextEditorFormPage_Remove_Invisible,
							Messages.ContextEditorFormPage_No_context_active);
				}
			}
		}
	}

	private TableViewer invisibleTable;

	private Section invisibleSection;

	private CommonViewer commonViewer;

	private final IInteractionContext context;

	private ToolBarManager toolbarManager;

	public InvisibleContextElementsPart(CommonViewer commonViewer) {
		this.commonViewer = commonViewer;
		context = ContextCore.getContextManager().getActiveContext();
		ContextCore.getContextManager().addListener(CONTEXT_LISTENER);
	}

	public InvisibleContextElementsPart(CommonViewer commonViewer, IInteractionContext context) {
		this.commonViewer = commonViewer;
		this.context = context;
		ContextCore.getContextManager().addListener(CONTEXT_LISTENER);
	}

	public Control createControl(FormToolkit toolkit, Composite composite) {
		invisibleSection = toolkit.createSection(composite, ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE);
		invisibleSection.setText(NLS.bind(Messages.InvisibleContextElementsPart_Invisible_elements, "0")); //$NON-NLS-1$
		invisibleSection.setEnabled(false);

		Composite toolbarComposite = toolkit.createComposite(invisibleSection);
		toolbarComposite.setBackground(null);
		invisibleSection.setTextClient(toolbarComposite);
		RowLayout rowLayout = new RowLayout();
		rowLayout.marginTop = 0;
		rowLayout.marginBottom = 0;
		toolbarComposite.setLayout(rowLayout);

		toolbarManager = new ToolBarManager(SWT.FLAT);
		toolbarManager.createControl(toolbarComposite);
		if (isActiveTask()) {
			addToolbarActions();
		}

		Composite invisibleSectionClient = toolkit.createComposite(invisibleSection);
		invisibleSectionClient.setLayout(new GridLayout());
		invisibleSection.setClient(invisibleSectionClient);

		Composite tableComposite = toolkit.createComposite(invisibleSectionClient);
		GridDataFactory.fillDefaults().hint(450, 200).grab(true, false).applyTo(tableComposite);
		TableColumnLayout layout = new TableColumnLayout();
		tableComposite.setLayout(layout);

		invisibleTable = new TableViewer(tableComposite, SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL
				| SWT.FULL_SELECTION | SWT.HIDE_SELECTION);
		invisibleTable.setColumnProperties(new String[] { Messages.InvisibleContextElementsPart_Structure_handle,
				Messages.InvisibleContextElementsPart_Structure_kind });
		invisibleTable.getTable().setHeaderVisible(true);

		Table table = invisibleTable.getTable();
		toolkit.adapt(table);
		table.setMenu(null);

		InteractionElementTableLabelProvider labelProvider = new InteractionElementTableLabelProvider();
		invisibleTable.setLabelProvider(labelProvider);
		invisibleTable.setContentProvider(new IStructuredContentProvider() {

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

			public void dispose() {
				// ignore
			}

			public Object[] getElements(Object inputElement) {
				if (inputElement instanceof Collection<?>) {
					return ((Collection<?>) inputElement).toArray();
				}
				return new Object[0];
			}
		});

		InteractionElementTableSorter invisibleTableSorter = new InteractionElementTableSorter(labelProvider);
		invisibleTableSorter.setCriteria(0);
		invisibleTable.setSorter(invisibleTableSorter);
		createColumn(layout, 0, Messages.InvisibleContextElementsPart_Structure_handle, 340, table,
				invisibleTableSorter);
		createColumn(layout, 1, Messages.InvisibleContextElementsPart_Structure_kind, 100, table, invisibleTableSorter);
		table.setSortColumn(table.getColumn(0));
		table.setSortDirection(SWT.DOWN);

		Collection<Object> allVisible = getAllVisibleElementsInContextPage();
		if (allVisible != null) {
			updateInvisibleSectionInBackground(context, allVisible);
		}

		return invisibleSection;
	}

	private boolean isActiveTask() {
		if (ContextCore.getContextManager().isContextActive()) {
			IInteractionContext activeContext = ContextCore.getContextManager().getActiveContext();
			if ((context instanceof ContextWrapper && ((ContextWrapper) context).isForSameTaskAs(activeContext))
					|| context.equals(activeContext)) {
				return true;
			}
		}
		return false;
	}

	private void addToolbarActions() {
		toolbarManager.add(new RemoveInvisibleAction());
		toolbarManager.markDirty();
		toolbarManager.update(true);
	}

	private void createColumn(TableColumnLayout layout, final int index, String label, int weight, final Table table,
			final InteractionElementTableSorter invisibleTableSorter) {
		final TableColumn column = new TableColumn(table, SWT.LEFT, index);
		column.setText(label);
		column.setToolTipText(label);
		column.setResizable(true);

		layout.setColumnData(column, new ColumnPixelData(weight, true));

		column.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent e) {
				invisibleTableSorter.setCriteria(index);
				table.setSortColumn(column);
				if (invisibleTableSorter.isDecending()) {
					table.setSortDirection(SWT.UP);
				} else {
					table.setSortDirection(SWT.DOWN);
				}
				invisibleTable.refresh();
			}

		});
	}

	public void updateInvisibleElementsSection() {
		Collection<Object> allVisible = getAllVisibleElementsInContextPage();
		if (allVisible != null) {
			updateInvisibleSectionInBackground(context, allVisible);
		}
	}

	private void updateInvisibleSectionInBackground(final IInteractionContext context,
			final Collection<Object> allVisible) {

		Job j = new Job(Messages.InvisibleContextElementsPart_Updating_invisible_element_list) {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				monitor.beginTask(Messages.InvisibleContextElementsPart_Computing_invisible_elements,
						IProgressMonitor.UNKNOWN);
				final List<IInteractionElement> allInvisibleElements = getAllInvisibleElements(context, allVisible);
				Display.getDefault().asyncExec(new Runnable() {

					public void run() {
						if (invisibleSection != null && !invisibleSection.isDisposed()) {
							invisibleSection.setText(NLS.bind(Messages.InvisibleContextElementsPart_Invisible_elements,
									allInvisibleElements.size()));
							invisibleSection.layout();
							if (allInvisibleElements.size() == 0) {
								invisibleSection.setExpanded(false);
								invisibleSection.setEnabled(false);
							} else {
								invisibleSection.setEnabled(true);
							}
						}

						if (invisibleTable != null && !invisibleTable.getTable().isDisposed()) {
							invisibleTable.setInput(allInvisibleElements);
						}
					}
				});

				return Status.OK_STATUS;
			};
		};
		j.schedule();

	}

	private List<IInteractionElement> getAllInvisibleElements(IInteractionContext context, Collection<Object> allVisible) {
		if (context == null || allVisible == null) {
			return Collections.emptyList();
		}
		List<IInteractionElement> allToRemove = context.getAllElements();

		List<IInteractionElement> allVisibleElements = new ArrayList<IInteractionElement>();
		for (Object visibleObject : allVisible) {
			for (AbstractContextStructureBridge bridge : ContextCorePlugin.getDefault().getStructureBridges().values()) {
//			AbstractContextStructureBridge bridge = ContextCorePlugin.getDefault().getStructureBridge(visibleObject);
				if (bridge != null) {
					String handle = bridge.getHandleIdentifier(visibleObject);
					if (handle != null) {
						IInteractionElement element = context.get(handle);
						if (element != null) {
							allVisibleElements.add(element);
						}
					}
				}
			}
			AbstractContextStructureBridge bridge = ContextCorePlugin.getDefault().getStructureBridge(
					ContextCore.CONTENT_TYPE_RESOURCE);
			if (bridge != null) {
				String handle = bridge.getHandleIdentifier(visibleObject);
				if (handle != null) {
					IInteractionElement element = context.get(handle);
					if (element != null) {
						allVisibleElements.add(element);
					}
				}
			}
		}
		IInteractionElement emptyElement = context.get(""); //$NON-NLS-1$
		if (emptyElement != null) {
			allVisibleElements.add(emptyElement);
		}

		allToRemove.removeAll(allVisibleElements);

		return allToRemove;
	}

	private Collection<Object> getAllVisibleElementsInContextPage() {
		if (commonViewer == null || commonViewer.getTree() == null || commonViewer.getTree().isDisposed()) {
			return null;
		}
		Set<Object> allVisible = new HashSet<Object>();
		collectItemData(commonViewer.getTree().getItems(), allVisible);
		return allVisible;
	}

	protected void setCommonViewer(CommonViewer commonViewer) {
		this.commonViewer = commonViewer;
		updateInvisibleElementsSection();
	}

	public void dispose() {
		ContextCore.getContextManager().removeListener(CONTEXT_LISTENER);
	}

}
