/*******************************************************************************
 * Copyright (c) 2006, 2009 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.statushandlers;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ILogListener;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.application.WorkbenchAdvisor;
import org.eclipse.ui.internal.WorkbenchErrorHandlerProxy;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.statushandlers.StatusHandlerRegistry;
import org.eclipse.ui.progress.IProgressConstants;

/**
 * <p>
 * StatusManager is the entry point for all statuses to be reported in the user
 * interface.
 * </p>
 * 
 * <p>
 * Handlers shoudn't be used directly but through the StatusManager singleton
 * which keeps the status handling policy and chooses handlers.
 * <code>StatusManager.getManager().handle(IStatus)</code> and
 * <code>handle(IStatus status, int style)</code> are the methods are the
 * primary access points to the StatusManager.
 * </p>
 * 
 * <p>
 * Acceptable styles (can be combined with logical OR)
 * <ul>
 * <li>NONE - a style indicating that the status should not be acted on. This
 * is used by objects such as log listeners that do not want to report a status
 * twice</li>
 * <li>LOG - a style indicating that the status should be logged only</li>
 * <li>SHOW - a style indicating that handlers should show a problem to an user
 * without blocking the calling method while awaiting user response. This is
 * generally done using a non modal {@link Dialog}</li>
 * <li>BLOCK - a style indicating that the handling should block the calling
 * method until the user has responded. This is generally done using a modal
 * window such as a {@link Dialog}</li>
 * </ul>
 * </p>
 * 
 * <p>
 * Handlers are intended to be accessed via the status manager. The
 * StatusManager chooses which handler should be used for a particular error.
 * There are two ways for adding handlers to the handling flow. First using
 * extension point <code>org.eclipse.ui.statusHandlers</code>, second by the
 * workbench advisor and its method
 * {@link WorkbenchAdvisor#getWorkbenchErrorHandler()}. If a handler is
 * associated with a product, it is used instead of this defined in advisor.
 * </p>
 * 
 * @since 3.3
 * @see AbstractStatusHandler
 */
public class StatusManager {
	/**
	 * A style indicating that the status should not be acted on. This is used
	 * by objects such as log listeners that do not want to report a status
	 * twice.
	 */
	public static final int NONE = 0;

	/**
	 * A style indicating that the status should be logged only.
	 */
	public static final int LOG = 0x01;

	/**
	 * A style indicating that handlers should show a problem to an user without
	 * blocking the calling method while awaiting user response. This is
	 * generally done using a non modal {@link Dialog}.
	 */
	public static final int SHOW = 0x02;

	/**
	 * A style indicating that the handling should block the calling thread
	 * until the status has been handled.
	 * <p>
	 * A typical usage of this would be to ensure that the user's actions are
	 * blocked until they've dealt with the status in some manner. It is
	 * therefore likely but not required that the <code>StatusHandler</code>
	 * would achieve this through the use of a modal dialog. 
	 * </p><p>Due to the fact
	 * that use of <code>BLOCK</code> will block any thread, care should be
	 * taken in this use of this flag.
	 * </p>
	 */
	public static final int BLOCK = 0x04;

	private static StatusManager MANAGER;

	private AbstractStatusHandler statusHandler;

	private List loggedStatuses = new ArrayList();

	private ListenerList listeners = new ListenerList();

	/**
	 * Returns StatusManager singleton instance.
	 * 
	 * @return the manager instance
	 */
	public static StatusManager getManager() {
		if (MANAGER == null) {
			MANAGER = new StatusManager();
		}
		return MANAGER;
	}

	private StatusManager() {
		Platform.addLogListener(new StatusManagerLogListener());
	}

	private AbstractStatusHandler getStatusHandler(){
		if(statusHandler == null && StatusHandlerRegistry.getDefault()
					.getDefaultHandlerDescriptor() != null){
			try {
				statusHandler = StatusHandlerRegistry.getDefault()
						.getDefaultHandlerDescriptor().getStatusHandler();
			} catch (CoreException ex) {
				logError("Errors during the default handler creating", ex); //$NON-NLS-1$
			}
		}
		if(statusHandler == null){
			statusHandler = new WorkbenchErrorHandlerProxy();
		}
		return statusHandler;
	}
	/**
	 * Handles the given status adapter due to the style. Because the facility
	 * depends on Workbench, this method will log the status, if Workbench isn't
	 * initialized and the style isn't {@link #NONE}. If Workbench isn't
	 * initialized and the style is {@link #NONE}, the manager will do nothing.
	 * 
	 * @param statusAdapter
	 *            the status adapter
	 * @param style
	 *            the style. Value can be combined with logical OR. One of
	 *            {@link #NONE}, {@link #LOG}, {@link #SHOW} and
	 *            {@link #BLOCK}.
	 */
	public void handle(StatusAdapter statusAdapter, int style) {
		try {
			// The manager will only log the error when the status adapter or
			// the embedded status is null.
			if (statusAdapter == null) {
				logError(
						"Error occurred during status handling",//$NON-NLS-1$
						new NullPointerException("StatusAdapter object is null")); //$NON-NLS-1$
				return;
			}
			if (statusAdapter.getStatus() == null) {
				logError("Error occurred during status handling",//$NON-NLS-1$
						new NullPointerException("Status object is null")); //$NON-NLS-1$
				return;
			}

			// The manager will only log the status, if Workbench isn't
			// initialized and the style isn't NONE. If Workbench isn't
			// initialized and the style is NONE, the manager will do nothing.
			if (!PlatformUI.isWorkbenchRunning()) {
				if (style != StatusManager.NONE) {
					logError(statusAdapter.getStatus());
				}
				return;
			}

			// delegates the problem to workbench handler
			getStatusHandler().handle(statusAdapter, style);
			
			// if attached status handler is not able to notify StatusManager
			// about particular event, use the default policy and fake the
			// notification
			if (!getStatusHandler().supportsNotification(
					INotificationTypes.HANDLED)) {
				generateFakeNotification(statusAdapter, style);
			}
		} catch (Throwable ex) {
			// The used status handler failed
			logError(statusAdapter.getStatus());
			logError("Error occurred during status handling", ex); //$NON-NLS-1$
		}
	}

	/**
	 * Handles the given status adapter. The {@link #LOG} style is used when
	 * this method is called.
	 * 
	 * @param statusAdapter
	 *            the status adapter
	 */
	public void handle(StatusAdapter statusAdapter) {
		handle(statusAdapter, StatusManager.LOG);
	}

	/**
	 * Handles the given status due to the style. Because the facility depends
	 * on Workbench, this method will log the status, if Workbench isn't
	 * initialized and the style isn't {@link #NONE}. If Workbench isn't
	 * initialized and the style is {@link #NONE}, the manager will do nothing.
	 * 
	 * @param status
	 *            the status to handle
	 * @param style
	 *            the style. Value can be combined with logical OR. One of
	 *            {@link #NONE}, {@link #LOG}, {@link #SHOW} and
	 *            {@link #BLOCK}.
	 */
	public void handle(IStatus status, int style) {
		StatusAdapter statusAdapter = new StatusAdapter(status);
		handle(statusAdapter, style);
	}

	/**
	 * Handles the given status. The {@link #LOG} style is used when this method
	 * is called.
	 * 
	 * @param status
	 *            the status to handle
	 */
	public void handle(IStatus status) {
		handle(status, StatusManager.LOG);
	}

	/**
	 * Handles given CoreException. This method has been introduced to prevent
	 * anti-pattern: <br/><code>
	 * StatusManager.getManager().handle(coreException.getStatus());
	 * </code><br/>
	 * that does not print the stack trace to the log.
	 * 
	 * @param coreException
	 *            a CoreException to be handled.
	 * @param pluginId
	 *            the unique identifier of the relevant plug-in
	 * @see StatusManager#handle(IStatus)
	 * @since 3.4
	 * 
	 */
	public void handle(CoreException coreException,String pluginId) {
		handle(new Status(IStatus.WARNING, pluginId, coreException
				.getLocalizedMessage(), coreException));
	}

	/**
	 * This method informs the StatusManager that this IStatus is being handled
	 * by the handler and to ignore it when it shows up in our ILogListener.
	 * 
	 * @param status
	 *            already handled and logged status
	 */
	public void addLoggedStatus(IStatus status) {
		loggedStatuses.add(status);
	}

	private void logError(String message, Throwable ex) {
		IStatus status = StatusUtil.newStatus(WorkbenchPlugin.PI_WORKBENCH,
				message, ex);
		addLoggedStatus(status);
		WorkbenchPlugin.log(status);
	}

	private void logError(IStatus status) {
		addLoggedStatus(status);
		WorkbenchPlugin.log(status);
	}

	/**
	 * This log listener handles statuses added to a plug-in's log. If our own
	 * WorkbenchErrorHandler inserts it into the log, then ignore it.
	 * 
	 * @see #addLoggedStatus(IStatus)
	 * @since 3.3
	 */
	private class StatusManagerLogListener implements ILogListener {

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.core.runtime.ILogListener#logging(org.eclipse.core.runtime.IStatus,
		 *      java.lang.String)
		 */
		public void logging(IStatus status, String plugin) {
			if (!loggedStatuses.contains(status)) {
				handle(status, StatusManager.NONE);
			} else {
				loggedStatuses.remove(status);
			}
		}
	}

	/**
	 * This method should be called by custom status handlers when an event
	 * occurs. This method has no effect if statushandler does not support
	 * particular event type.
	 * 
	 * @param type
	 *            - type of the event.
	 * @param adapters
	 *            - array of affected {@link StatusAdapter}s.
	 * @see INotificationTypes
	 * @see AbstractStatusHandler#supportsNotification(int)
	 * @since 3.5
	 */
	public void fireNotification(int type, StatusAdapter[] adapters){
		if(getStatusHandler().supportsNotification(type)){
			doFireNotification(type, adapters);
		}
	}
	
	private void doFireNotification(int type, StatusAdapter[] adapters) {
		Object[] oListeners = listeners.getListeners();
		for (int i = 0; i < oListeners.length; i++) {
			if (oListeners[i] instanceof INotificationListener) {
				((INotificationListener) oListeners[i])
						.statusManagerNotified(type, adapters);
			}
		}
	}
	
	private void generateFakeNotification(StatusAdapter statusAdapter, int style) {
		if (((style & StatusManager.SHOW) == StatusManager.SHOW || (style & StatusManager.BLOCK) == StatusManager.BLOCK)
				&& statusAdapter
						.getProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY) != Boolean.TRUE) {
			doFireNotification(INotificationTypes.HANDLED,
					new StatusAdapter[] { statusAdapter });
		}
	}
	
	/**
	 * Adds a listener to the StatusManager.
	 * 
	 * @param listener
	 *            - a listener to be added.
	 * @since 3.5
	 */
	public void addListener(INotificationListener listener) {
		this.listeners.add(listener);
	}

	/**
	 * Removes a listener from StatusManager.
	 * 
	 * @param listener
	 *            - a listener to be removed.
	 * @since 3.5
	 */
	public void removeListener(INotificationListener listener){
		this.listeners.remove(listener);
	}
	
	/**
	 * This interface allows for listening to status handling framework changes.
	 * Currently it is possible to be notified when:
	 * <ul>
	 * 	<li>all statuses has been handled</li>.
	 * </ul>
	 * @since 3.5
	 *
	 */
	public interface INotificationListener{
		/**
		 * 
		 * @param type
		 *            - a type of notification.
		 * @param adapters
		 *            - affected {@link StatusAdapter}s
		 */
		public void statusManagerNotified(int type, StatusAdapter[] adapters);
	}
	
	/**
	 * This interface declares types of notification.
	 * 
	 * @since 3.5
	 * @noextend This interface is not intended to be extended by clients.
	 * @noimplement This interface is not intended to be implemented by clients.
	 * 
	 */
	public interface INotificationTypes {

		/**
		 * This type notifications are used when a particular
		 * {@link StatusAdapter} was handled.
		 */
		public static final int HANDLED = 0x01;
		
	}
}
