/*******************************************************************************
 * Copyright (c) 2000, 2010 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
 *     Chris Gross (schtoo@schtoo.com) - patch for bug 16179
 *     Tasktop Technologies - extracted code for Mylyn
 *******************************************************************************/

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

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.operation.ModalContext;
import org.eclipse.jface.wizard.ProgressMonitorPart;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

/**
 * A helper class for running operations in dialogs. Based on {@link WizardDialog}.
 * 
 * @author Steffen Pingel
 */
class ProgressContainer implements IRunnableContext {

	private static final String FOCUS_CONTROL = "focusControl"; //$NON-NLS-1$

	// The number of long running operation executed from the dialog.
	private long activeRunningOperations = 0;

	private Cursor arrowCursor;

	private Button cancelButton;

	private boolean lockedUI = false;

	// The progress monitor
	private final ProgressMonitorPart progressMonitorPart;

	private final Shell shell;

	private Cursor waitCursor;

	public ProgressContainer(Shell shell, ProgressMonitorPart progressMonitorPart) {
		Assert.isNotNull(shell);
		Assert.isNotNull(progressMonitorPart);
		this.shell = shell;
		this.progressMonitorPart = progressMonitorPart;
	}

	/**
	 * About to start a long running operation triggered through the wizard. Shows the progress monitor and disables the
	 * wizard's buttons and controls.
	 * 
	 * @param enableCancelButton
	 *            <code>true</code> if the Cancel button should be enabled, and <code>false</code> if it should be
	 *            disabled
	 * @return the saved UI state
	 */
	private Object aboutToStart(boolean enableCancelButton) {
		Map<Object, Object> savedState = null;
		if (getShell() != null) {
			// Save focus control
			Control focusControl = getShell().getDisplay().getFocusControl();
			if (focusControl != null && focusControl.getShell() != getShell()) {
				focusControl = null;
			}
			//cancelButton.removeSelectionListener(cancelListener);
			// Set the busy cursor to all shells.
			Display d = getShell().getDisplay();
			waitCursor = new Cursor(d, SWT.CURSOR_WAIT);
			setDisplayCursor(waitCursor);
			// Set the arrow cursor to the cancel component.
			arrowCursor = new Cursor(d, SWT.CURSOR_ARROW);
			cancelButton.setCursor(arrowCursor);
			// Deactivate shell
			savedState = new HashMap<Object, Object>(10);
			saveUiState(savedState);
			if (focusControl != null) {
				savedState.put(FOCUS_CONTROL, focusControl);
			}
			// Attach the progress monitor part to the cancel button
			progressMonitorPart.attachToCancelComponent(cancelButton);
			progressMonitorPart.setVisible(true);
		}
		return savedState;
	}

	public Button getCancelButton() {
		return cancelButton;
	}

	private IProgressMonitor getProgressMonitor() {
		return progressMonitorPart;
	}

	public Shell getShell() {
		return shell;
	}

	public boolean isActive() {
		return activeRunningOperations > 0;
	}

	public boolean isLockedUI() {
		return lockedUI;
	}

	protected void restoreUiState(Map<Object, Object> state) {
		// ignore

	}

	/**
	 * This implementation of IRunnableContext#run(boolean, boolean, IRunnableWithProgress) blocks until the runnable
	 * has been run, regardless of the value of <code>fork</code>. It is recommended that <code>fork</code> is set to
	 * true in most cases. If <code>fork</code> is set to <code>false</code>, the runnable will run in the UI thread and
	 * it is the runnable's responsibility to call <code>Display.readAndDispatch()</code> to ensure UI responsiveness.
	 * UI state is saved prior to executing the long-running operation and is restored after the long-running operation
	 * completes executing. Any attempt to change the UI state of the wizard in the long-running operation will be
	 * nullified when original UI state is restored.
	 */
	public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) throws InvocationTargetException,
			InterruptedException {
		// The operation can only be canceled if it is executed in a separate
		// thread.
		// Otherwise the UI is blocked anyway.
		Object state = null;
		if (activeRunningOperations == 0) {
			state = aboutToStart(fork && cancelable);
		}
		activeRunningOperations++;
		try {
			if (!fork) {
				lockedUI = true;
			}
			ModalContext.run(runnable, fork, getProgressMonitor(), getShell().getDisplay());
			lockedUI = false;
		} finally {
			activeRunningOperations--;
			// Stop if this is the last one
			if (state != null) {
				stopped(state);
			}
		}
	}

	protected void saveUiState(Map<Object, Object> savedState) {
		// ignore

	}

	public void setCancelButton(Button cancelButton) {
		this.cancelButton = cancelButton;
	}

	/**
	 * Sets the given cursor for all shells currently active for this window's display.
	 * 
	 * @param c
	 *            the cursor
	 */
	private void setDisplayCursor(Cursor c) {
		Shell[] shells = getShell().getDisplay().getShells();
		for (Shell shell2 : shells) {
			shell2.setCursor(c);
		}
	}

	/**
	 * A long running operation triggered through the wizard was stopped either by user input or by normal end. Hides
	 * the progress monitor and restores the enable state wizard's buttons and controls.
	 * 
	 * @param savedState
	 *            the saved UI state as returned by <code>aboutToStart</code>
	 * @see #aboutToStart
	 */
	@SuppressWarnings("unchecked")
	private void stopped(Object savedState) {
		if (getShell() != null && !getShell().isDisposed()) {
			progressMonitorPart.setVisible(false);
			progressMonitorPart.removeFromCancelComponent(cancelButton);

			Map<Object, Object> state = (Map<Object, Object>) savedState;
			restoreUiState(state);
//				cancelButton.addSelectionListener(cancelListener);
			setDisplayCursor(null);
			cancelButton.setCursor(null);
			waitCursor.dispose();
			waitCursor = null;
			arrowCursor.dispose();
			arrowCursor = null;
			Control focusControl = (Control) state.get(FOCUS_CONTROL);
			if (focusControl != null && !focusControl.isDisposed()) {
				focusControl.setFocus();
			}
		}
	}

}