/*******************************************************************************
 * Copyright (c) 2009 QNX Software Systems 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:
 *     QNX Software Systems - initial API and implementation
 *******************************************************************************/

package org.eclipse.cdt.internal.ui.workingsets;

import java.util.Collection;

import org.eclipse.core.resources.IProject;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.IFilter;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.model.WorkbenchLabelProvider;

import org.eclipse.cdt.internal.ui.CPluginImages;
import org.eclipse.cdt.internal.ui.dialogs.OptionalMessageDialog;

/**
 * A block of UI controls for management of working set configurations. These collect the selection of project
 * configurations for the member projects of the working sets into named presets.
 * 
 * @author Christian W. Damus (cdamus)
 * 
 * @since 6.0
 */
public class WorkingSetConfigurationBlock {

	private static final String BUILD_PROMPT_DIALOG_ID = "workingsets.build.prompt"; //$NON-NLS-1$
	private static final int BUILD_PROMPT_DIALOG_NO = 0;
	private static final int BUILD_PROMPT_DIALOG_CANCEL = 1;
	private static final int BUILD_PROMPT_DIALOG_YES = 2;

	private WorkspaceSnapshot workspace;
	private WorkingSetConfigsController controller;

	private IWorkingSetProxy.ISnapshot initialSelection;
	private IFilter workingSetFilter;

	private Control contents;

	/**
	 * Initializes me. I take the most recently used working set as my initial selection.
	 * 
	 * @param workspace
	 *            the workspace snapshot to edit
	 */
	public WorkingSetConfigurationBlock(WorkspaceSnapshot workspace) {
		this(workspace, null);
	}

	/**
	 * Initializes me with my initial selection.
	 * 
	 * @param workspace
	 *            the workspace snapshot to edit
	 * @param initialSelection
	 *            my initial selection
	 */
	public WorkingSetConfigurationBlock(WorkspaceSnapshot workspace,
			IWorkingSetProxy.ISnapshot initialSelection) {
		
		this.workspace = workspace;
		this.initialSelection = initialSelection;
	}

	/**
	 * Queries the working set filter, if any, that restricts the display of working sets.
	 * 
	 * @return my working-set filter
	 */
	public IFilter getWorkingSetFilter() {
		return workingSetFilter;
	}

	/**
	 * Assigns a filter to restrict the working sets that are shown.
	 * 
	 * @param filter
	 *            a working-set filter
	 */
	public void setWorkingSetFilter(IFilter filter) {
		this.workingSetFilter = filter;
	}

	/**
	 * Creates the contents of the working set configuration management control block.
	 * 
	 * @param parent
	 *            the parent composite in which to create my controls
	 * 
	 * @return my controls
	 */
	public Control createContents(Composite parent) {
		SashForm sashForm = new SashForm(parent, SWT.VERTICAL | SWT.SMOOTH);

		GridLayoutFactory layoutFactory = GridLayoutFactory.fillDefaults();
		createWorkingSetConfigsArea(sashForm, layoutFactory.extendedMargins(0, 0, 0, 15));
		createProjectConfigsArea(sashForm, layoutFactory.extendedMargins(0, 0, 15, 0));

		sashForm.setWeights(new int[] { 1, 1 });

		contents = sashForm;

		return sashForm;
	}

	/**
	 * Creates the "working set configurations" pane in the upper part of the sash form.
	 * 
	 * @param parent
	 *            the parent composite
	 * @param layoutFactory
	 *            a layout-factory to use to lay out the composite in a grid, possibly pre-configured. Its use
	 *            is optional
	 * 
	 * @return the working set configurations pane
	 */
	protected Composite createWorkingSetConfigsArea(Composite parent, GridLayoutFactory layoutFactory) {
		Composite result = new Composite(parent, SWT.NONE);
		layoutFactory.numColumns(2).applyTo(result);

		GridDataFactory layoutDataFactory = GridDataFactory.fillDefaults();

		Label label = new Label(result, SWT.NONE);
		label.setText(WorkingSetMessages.WSConfigDialog_wsTree_label);
		layoutDataFactory.span(2, 1).applyTo(label);

		controller = new WorkingSetConfigsController(workspace, initialSelection);

		TreeViewer tree = new TreeViewer(result);
		layoutDataFactory.span(1, 1).align(SWT.FILL, SWT.FILL).grab(true, true).hint(250, SWT.DEFAULT)
				.applyTo(tree.getControl());

		tree.setContentProvider(new WSConfigsContentProvider());
		tree.setLabelProvider(new WSConfigsLabelProvider(tree));
		controller.setTreeViewer(tree);

		tree.setComparator(new ViewerComparator() {
			@Override
			public int category(Object element) {
				if (element instanceof IWorkingSetConfiguration.ISnapshot) {
					IWorkingSetConfiguration.ISnapshot config = (IWorkingSetConfiguration.ISnapshot) element;
					if (config.isReadOnly()) {
						return 0;
					}
				}
				return 1;
			}
		});

		tree.getTree().getAccessible().addAccessibleListener(new AccessibleAdapter() {
			@Override
			public void getName(AccessibleEvent e) {
				e.result = WorkingSetMessages.WSConfigDialog_wsTree_accessible_name;
			}
		});

		Composite buttons = new Composite(result, SWT.NONE);
		layoutDataFactory.grab(false, false).hint(SWT.DEFAULT, SWT.DEFAULT).applyTo(buttons);
		layoutFactory.numColumns(1).extendedMargins(0, 0, 0, 0).applyTo(buttons);

		Button button = new Button(buttons, SWT.PUSH);
		layoutDataFactory.align(SWT.FILL, SWT.BEGINNING).applyTo(button);
		button.setText(WorkingSetMessages.WSConfigDialog_add_label);
		controller.setAddButton(button);

		button = new Button(buttons, SWT.PUSH);
		layoutDataFactory.applyTo(button);
		button.setText(WorkingSetMessages.WSConfigDialog_remove_label);
		controller.setRemoveButton(button);

		button = new Button(buttons, SWT.PUSH);
		layoutDataFactory.applyTo(button);
		button.setText(WorkingSetMessages.WSConfigDialog_rename_label);
		controller.setRenameButton(button);

		button = new Button(buttons, SWT.PUSH);
		layoutDataFactory.applyTo(button);
		button.setText(WorkingSetMessages.WSConfigDialog_activate_label);
		controller.setActivateButton(button);

		button = new Button(buttons, SWT.PUSH);
		layoutDataFactory.applyTo(button);
		button.setText(WorkingSetMessages.WSConfigDialog_build_label);
		controller.setBuildButton(button);

		return result;
	}

	/**
	 * Creates the "project configurations" pane in the lower part of the sash form.
	 * 
	 * @param parent
	 *            the parent composite
	 * @param layoutFactory
	 *            a layout-factory to use to lay out the composite in a grid, possibly pre-configured. Its use
	 *            is optional
	 * 
	 * @return the project configurations pane
	 */
	protected Composite createProjectConfigsArea(Composite parent, GridLayoutFactory layoutFactory) {
		Composite result = new Composite(parent, SWT.NONE);
		layoutFactory.numColumns(1).applyTo(result);

		GridDataFactory layoutDataFactory = GridDataFactory.fillDefaults();

		Label label = new Label(result, SWT.NONE);
		label.setText(WorkingSetMessages.WSConfigDialog_projTree_label);
		layoutDataFactory.applyTo(label);

		ProjectConfigsController projectsController = new ProjectConfigsController();
		CheckboxTreeViewer tree = new CheckboxTreeViewer(result);
		layoutDataFactory.span(1, 1).align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(tree.getControl());

		controller.setProjectConfigsController(projectsController);
		projectsController.setWorkingSetConfigurationsController(controller);

		projectsController.setTreeViewer(tree);

		tree.setComparator(new ViewerComparator());

		tree.getTree().getAccessible().addAccessibleListener(new AccessibleAdapter() {
			@Override
			public void getName(AccessibleEvent e) {
				e.result = WorkingSetMessages.WSConfigDialog_projTree_accessible_name;
			}
		});

		return result;
	}

	/**
	 * Saves the working set configurations to storage.
	 */
	public void save() {
		workspace.save();
	}

	/**
	 * Builds the projects that were reconfigured by the dialog, if any. The user is prompted (if prompting is
	 * not disabled via the preference) before building. The user has the options to build, not build, or
	 * cancel. The result indicates cancellation.
	 * 
	 * @return <code>true</code> if the user opted to save changes and exit the dialog (with or without
	 *         build); <code>false</code> if the user cancelled and the dialog should remain open and unsaved
	 */
	public boolean build() {
		boolean result = true;
		Collection<IProject> projects = workspace.getProjectsToBuild();

		if (!projects.isEmpty()) {
			int defaultButton = OptionalMessageDialog.getDialogDetail(BUILD_PROMPT_DIALOG_ID);
			if (defaultButton == OptionalMessageDialog.NO_DETAIL) {
				defaultButton = BUILD_PROMPT_DIALOG_YES; // yes button is the default-default
			}

			int button = OptionalMessageDialog.open(BUILD_PROMPT_DIALOG_ID, contents.getShell(),
					WorkingSetMessages.WSConfigDialog_buildPrompt_title, null,
					WorkingSetMessages.WSConfigDialog_buildPrompt_message, MessageDialog.QUESTION,
					new String[] { IDialogConstants.NO_LABEL, IDialogConstants.CANCEL_LABEL,
							IDialogConstants.YES_LABEL }, defaultButton);

			if (button == OptionalMessageDialog.NOT_SHOWN) {
				// handle the case where the dialog was suppressed. Get the current default
				button = defaultButton;
			} else if (button != BUILD_PROMPT_DIALOG_CANCEL) {
				// store non-cancel selection as the new default answer
				OptionalMessageDialog.setDialogDetail(BUILD_PROMPT_DIALOG_ID, button);
			}

			switch (button) {
			case BUILD_PROMPT_DIALOG_YES:
				// do the build
				new BuildJob(projects).schedule();
				break;
			case BUILD_PROMPT_DIALOG_NO:
				// just don't build
				break;
			default: // BUILD_PROMPT_DIALOG_CANCEL
				result = false;
				break;
			}
		}

		return result;
	}

	//
	// Nested classes
	//

	/**
	 * Simple content provider for the working set configurations tree.
	 * 
	 * @author Christian W. Damus (cdamus)
	 */
	private class WSConfigsContentProvider implements ITreeContentProvider {
		private Collection<IWorkingSetProxy> workingSets;

		public Object[] getChildren(Object parentElement) {
			if (parentElement == workingSets) {
				Collection<IWorkingSetProxy> filtered = filterWorkingSets(workingSets);
				return filtered.toArray();
			} else if (parentElement instanceof IWorkingSetProxy) {
				return ((IWorkingSetProxy) parentElement).getConfigurations().toArray();
			} else {
				return new Object[0];
			}
		}

		private Collection<IWorkingSetProxy> filterWorkingSets(Collection<IWorkingSetProxy> workingSets) {
			if (workingSetFilter == null) {
				return workingSets;
			}

			Collection<IWorkingSetProxy> result = new java.util.ArrayList<IWorkingSetProxy>();

			for (IWorkingSetProxy next : workingSets) {
				if (workingSetFilter.select(next)) {
					result.add(next);
				}
			}

			return result;
		}

		public Object getParent(Object element) {
			return (element instanceof IWorkingSetConfiguration) ? ((IWorkingSetConfiguration) element)
					.getWorkingSet() : null;
		}

		public boolean hasChildren(Object element) {
			return (element != null) && !(element instanceof IWorkingSetConfiguration);
		}

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

		public void dispose() {
			// nothing to dispose
		}

		@SuppressWarnings("unchecked")
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			workingSets = (Collection<IWorkingSetProxy>) newInput;
		}

	}

	/**
	 * Label provider for working sets and their configurations. The active configuration is highlighted in
	 * bold and affixed with an "(active)" decoration. The special read-only configuration is further
	 * differentiated with an italic font.
	 * 
	 * @author Christian W. Damus (cdamus)
	 */
	private class WSConfigsLabelProvider extends LabelProvider implements IFontProvider {
		private WorkbenchLabelProvider wbLabels = new WorkbenchLabelProvider();
		private Image configImage = CPluginImages.get(CPluginImages.IMG_OBJS_CONFIG);
		private Font defaultFont;
		private ResourceManager fonts = new LocalResourceManager(JFaceResources.getResources());

		WSConfigsLabelProvider(Viewer viewer) {
			defaultFont = viewer.getControl().getFont();
		}

		@Override
		public String getText(Object element) {
			if (element instanceof IWorkingSetConfiguration) {
				IWorkingSetConfiguration config = (IWorkingSetConfiguration) element;

				if (config.isActive()) {
					return WorkingSetConfiguration.isReadOnly(config)
							? WorkingSetMessages.WSConfigDialog_implicit_config : NLS.bind(
									WorkingSetMessages.WSConfigDialog_active_config, config.getName());
				}
				return config.getName();
			} else if (element instanceof IWorkingSetProxy) {
				return ((IWorkingSetProxy) element).resolve().getLabel();
			}

			return wbLabels.getText(element);
		}

		@Override
		public Image getImage(Object element) {
			if (element instanceof IWorkingSetConfiguration) {
				return configImage;
			} else if (element instanceof IWorkingSetProxy) {
				return wbLabels.getImage(((IWorkingSetProxy) element).resolve());
			}

			return wbLabels.getImage(element);
		}

		@Override
		public void dispose() {
			wbLabels.dispose();
			fonts.dispose();
			super.dispose();
		}

		public Font getFont(Object element) {
			if (element instanceof IWorkingSetConfiguration) {
				IWorkingSetConfiguration config = (IWorkingSetConfiguration) element;
				if (config.isActive()) {
					FontDescriptor desc = FontDescriptor.createFrom(defaultFont);

					desc = WorkingSetConfiguration.isReadOnly(config) ? desc.withStyle(SWT.BOLD | SWT.ITALIC)
							: desc.withStyle(SWT.BOLD);
					return (Font) fonts.get(desc);
				}
			}

			return wbLabels.getFont(element);
		}
	}
}
