/*******************************************************************************
 * Copyright (c) 2000, 2006 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal.progress;

import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.progress.IProgressConstants;

/**
 * A dialog that can display the errors from multiple jobs at once but is
 * visually optimal for the case of one job (which is the case 99% of the time).
 */
public class JobErrorDialog extends ErrorDialog {

	/*
	 * Preference used to indicate whether the user should be prompted to
	 * confirm the execution of the job's goto action
	 */
	private static final String PREF_SKIP_GOTO_ACTION_PROMPT = "pref_skip_goto_action_prompt"; //$NON-NLS-1$

	/*
	 * The id of the goto action button
	 */
	private static final int GOTO_ACTION_ID = IDialogConstants.CLIENT_ID + 1;

	private TableViewer jobListViewer;

	private ErrorInfo selectedError;

	/**
	 * Create a new instance of the receiver.
	 * 
	 * @param parentShell
	 * @param title
	 * @param msg
	 * @param errorInfo
	 * @param displayMask
	 */
	public JobErrorDialog(Shell parentShell, String title, String msg,
			ErrorInfo errorInfo, int displayMask) {
		super(parentShell,
				title == null ? errorInfo.getJob().getName() : title, msg,
				errorInfo.getErrorStatus(), displayMask);
		setShellStyle(SWT.DIALOG_TRIM | SWT.MODELESS | SWT.RESIZE | SWT.MIN
				| getDefaultOrientation()); // Do not want this one to be modal
		this.selectedError = errorInfo;
		setBlockOnOpen(false);
	}

	/**
	 * Method which should be invoked when new errors become available for
	 * display
	 */
	void refresh() {

		if (AUTOMATED_MODE) {
			return;
		}

		// Do not refresh if we are in the process of
		// opening or shutting down
		if (dialogArea == null || dialogArea.isDisposed()) {
			return;
		}

		if (isMultipleJobErrors()) {
			if (jobListViewer == null) {
				// The job list doesn't exist so create it.
				setMessage(ProgressMessages.JobErrorDialog_MultipleErrorsMessage);
				getShell().setText(
						ProgressMessages.JobErrorDialog_MultipleErrorsTitle);
				createJobListArea((Composite) dialogArea);
				showDetailsArea();
			}
			refreshJobList();
		}
		updateEnablements();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.dialogs.ErrorDialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite)
	 */
	protected void createButtonsForButtonBar(Composite parent) {
		IAction gotoAction = getGotoAction();
		String text = null;
		if (gotoAction != null) {
			text = gotoAction.getText();
		}
		if (text == null) {
			// Text is set to this initiallybut will be changed for active job
			text = ProgressMessages.JobErrorDialog_CustomJobText;
		}
		createButton(parent, GOTO_ACTION_ID, text, false);
		super.createButtonsForButtonBar(parent);
	}

	/*
	 * Update the button enablements
	 */
	private void updateEnablements() {
		Button details = getButton(IDialogConstants.DETAILS_ID);
		if (details != null) {
			details.setEnabled(selectedError.getErrorStatus().isMultiStatus()
					|| isMultipleJobErrors());
		}
		Button gotoButton = getButton(GOTO_ACTION_ID);
		if (gotoButton != null) {
			IAction gotoAction = getGotoAction();
			boolean hasValidGotoAction = gotoAction != null;
			String text = gotoButton.getText();
			String newText = null;
			if (hasValidGotoAction) {
				newText = gotoAction.getText();
			}
			if (newText == null) {
				hasValidGotoAction = false;
				newText = ProgressMessages.JobErrorDialog_CustomJobText;
			}
			if (!newText.equals(text)) {
				gotoButton.setText(newText);
			}
			gotoButton.setEnabled(hasValidGotoAction);
			gotoButton.setVisible(hasValidGotoAction);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.dialogs.ErrorDialog#buttonPressed(int)
	 */
	protected void buttonPressed(int id) {
		if (id == GOTO_ACTION_ID) {
			IAction gotoAction = getGotoAction();
			if (gotoAction != null) {
				if (!isMultipleJobErrors() || isPromptToClose()) {
					okPressed(); // close the dialog
					gotoAction.run(); // run the goto action
				}
			}
		}
		super.buttonPressed(id);
	}

	/*
	 * Prompt to inform the user that the dialog will close and the errors
	 * cleared.
	 */
	private boolean isPromptToClose() {
		IPreferenceStore store = WorkbenchPlugin.getDefault()
				.getPreferenceStore();
		if (!store.contains(PREF_SKIP_GOTO_ACTION_PROMPT)
				|| !store.getString(PREF_SKIP_GOTO_ACTION_PROMPT).equals(
						MessageDialogWithToggle.ALWAYS)) {
			MessageDialogWithToggle dialog = MessageDialogWithToggle
					.openOkCancelConfirm(
							getShell(),
							ProgressMessages.JobErrorDialog_CloseDialogTitle,
							ProgressMessages.JobErrorDialog_CloseDialogMessage,
							ProgressMessages.JobErrorDialog_DoNotShowAgainMessage,
							false, store, PREF_SKIP_GOTO_ACTION_PROMPT);
			return dialog.getReturnCode() == OK;
		}
		return true;
	}

	private IAction getGotoAction() {
		Object property = selectedError.getJob().getProperty(
				IProgressConstants.ACTION_PROPERTY);
		if (property instanceof IAction) {
			return (IAction) property;
		}
		return null;
	}

	/**
	 * This method sets the message in the message label.
	 * 
	 * @param messageString -
	 *            the String for the message area
	 */
	private void setMessage(String messageString) {
		// must not set null text in a label
		message = messageString == null ? "" : messageString; //$NON-NLS-1$
		if (messageLabel == null || messageLabel.isDisposed()) {
			return;
		}
		messageLabel.setText(message);
	}

	/**
	 * Create an area that allow the user to select one of multiple jobs that
	 * have reported errors
	 * 
	 * @param parent -
	 *            the parent of the area
	 */
	private void createJobListArea(Composite parent) {
		// Display a list of jobs that have reported errors
		jobListViewer = new TableViewer(parent, SWT.SINGLE | SWT.H_SCROLL
				| SWT.V_SCROLL | SWT.BORDER);
		jobListViewer.setSorter(getViewerSorter());
		Control control = jobListViewer.getControl();
		GridData data = new GridData(GridData.FILL_BOTH
				| GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
		data.heightHint = convertHeightInCharsToPixels(10);
		control.setLayoutData(data);
		initContentProvider();
		initLabelProvider();
		jobListViewer
				.addSelectionChangedListener(new ISelectionChangedListener() {
					public void selectionChanged(SelectionChangedEvent event) {
						handleSelectionChange();
					}
				});
		applyDialogFont(parent);
	}

	/*
	 * Return whether there are multiple errors to be displayed
	 */
	private boolean isMultipleJobErrors() {
		return getManager().getErrors().size() > 1;
	}

	/*
	 * Get the notificationManager that this is being created for.
	 */
	private ErrorNotificationManager getManager() {
		return ProgressManager.getInstance().errorManager;
	}

	/**
	 * Return the selected error info.
	 * 
	 * @return ErrorInfo
	 */
	public ErrorInfo getSelectedError() {
		return selectedError;
	}

	/**
	 * Return a viewer sorter for looking at the jobs.
	 * 
	 * @return ViewerSorter
	 */
	private ViewerSorter getViewerSorter() {
		return new ViewerSorter() {
			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.ViewerSorter#compare(org.eclipse.jface.viewers.Viewer,
			 *      java.lang.Object, java.lang.Object)
			 */
			public int compare(Viewer testViewer, Object e1, Object e2) {
				return ((Comparable) e1).compareTo(e2);
			}
		};
	}

	/**
	 * Sets the content provider for the viewer.
	 */
	protected void initContentProvider() {
		IContentProvider provider = new IStructuredContentProvider() {
			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IContentProvider#dispose()
			 */
			public void dispose() {
				// Nothing of interest here
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
			 */
			public Object[] getElements(Object inputElement) {
				return getManager().getErrors().toArray();
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
			 *      java.lang.Object, java.lang.Object)
			 */
			public void inputChanged(Viewer viewer, Object oldInput,
					Object newInput) {
				if (newInput != null) {
					refreshJobList();
				}
			}
		};
		jobListViewer.setContentProvider(provider);
		jobListViewer.setInput(getManager());
		jobListViewer.setSelection(new StructuredSelection(selectedError));
	}

	/**
	 * Refresh the contents of the viewer.
	 */
	void refreshJobList() {
		if (jobListViewer != null && !jobListViewer.getControl().isDisposed()) {
			jobListViewer.refresh();
			Point newSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT);
			getShell().setSize(newSize);
		}
		setStatus(selectedError.getErrorStatus());
	}

	private void initLabelProvider() {
		ITableLabelProvider provider = new ITableLabelProvider() {
			Map imageTable = new HashMap();

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener)
			 */
			public void addListener(ILabelProviderListener listener) {
				// Do nothing
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
			 */
			public void dispose() {
				if (!imageTable.isEmpty()) {
					for (Iterator iter = imageTable.values().iterator(); iter
							.hasNext();) {
						Image image = (Image) iter.next();
						image.dispose();
					}
				}
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object,
			 *      int)
			 */
			public Image getColumnImage(Object element, int columnIndex) {
				return getIcon(((ErrorInfo) element).getJob());
			}

			/*
			 * Get the icon for the job. Code copied from NewProgressViewer
			 */
			private Image getIcon(Job job) {
				if (job != null) {

					Object property = job
							.getProperty(IProgressConstants.ICON_PROPERTY);

					// If we already have an image cached, return it
					Image im = (Image) imageTable.get(property);
					if (im != null) {
						return im;
					}

					// Create an image from the job's icon property or family
					Display display = getShell().getDisplay();
					if (property instanceof ImageDescriptor) {
						im = ((ImageDescriptor) property).createImage(display);
						imageTable.put(property, im); // Cache for disposal
					} else if (property instanceof URL) {
						im = ImageDescriptor.createFromURL((URL) property)
								.createImage(display);
						imageTable.put(property, im); // Cache for disposal
					} else {
						im = ProgressManager.getInstance().getIconFor(job);
						// No need to cache since the progress manager will
					}
					return im;
				}
				return null;
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object,
			 *      int)
			 */
			public String getColumnText(Object element, int columnIndex) {
				return ((ErrorInfo) element).getDisplayString();
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object,
			 *      java.lang.String)
			 */
			public boolean isLabelProperty(Object element, String property) {
				return false;
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener)
			 */
			public void removeListener(ILabelProviderListener listener) {
				// Do nothing
			}
		};
		jobListViewer.setLabelProvider(provider);
	}

	/**
	 * Get the single selection. Return null if the selection is not just one
	 * element.
	 * 
	 * @return ErrorInfo or <code>null</code>.
	 */
	private ErrorInfo getSingleSelection() {
		ISelection rawSelection = jobListViewer.getSelection();
		if (rawSelection != null
				&& rawSelection instanceof IStructuredSelection) {
			IStructuredSelection selection = (IStructuredSelection) rawSelection;
			if (selection.size() == 1) {
				return (ErrorInfo) selection.getFirstElement();
			}
		}
		return null;
	}

	public boolean close() {
		Rectangle shellPosition = getShell().getBounds();
		boolean result = super.close();
		ProgressManagerUtil.animateDown(shellPosition);
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.dialogs.Dialog#initializeBounds()
	 */
	protected void initializeBounds() {
		// We need to refesh here instead of in createContents
		// because the showDetailsArea requires that the content
		// composite be set
		refresh();
		super.initializeBounds();
		Rectangle shellPosition = getShell().getBounds();
		ProgressManagerUtil.animateUp(shellPosition);
	}

	/**
	 * The selection in the multiple job list has changed. Update widget
	 * enablements and repopulate the list.
	 */
	void handleSelectionChange() {
		ErrorInfo newSelection = getSingleSelection();
		if (newSelection != null && newSelection != selectedError) {
			selectedError = newSelection;
			setStatus(selectedError.getErrorStatus());
			updateEnablements();
			showDetailsArea();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.dialogs.ErrorDialog#shouldShowDetailsButton()
	 */
	protected boolean shouldShowDetailsButton() {
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.dialogs.ErrorDialog#configureShell(org.eclipse.swt.widgets.Shell)
	 */
	protected void configureShell(Shell shell) {
		super.configureShell(shell);
		shell.addDisposeListener(new DisposeListener() {
			/* (non-Javadoc)
			 * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
			 */
			public void widgetDisposed(org.eclipse.swt.events.DisposeEvent e) {
				ProgressManager.getInstance().errorManager.dialogClosed();
			}

		});
	}
}
