/*******************************************************************************
 * Copyright (c) 2000, 2008 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
 *     Brock Janiczak (brockj_eclipse@ihug.com.au) - handler registration
 *******************************************************************************/
package org.eclipse.ui.internal.registry;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.IPerspectiveFactory;
import org.eclipse.ui.IPluginContribution;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.IWorkbenchConstants;
import org.eclipse.ui.internal.WorkbenchImages;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.plugin.AbstractUIPlugin;

/**
 * PerspectiveDescriptor.
 * <p>
 * A PerspectiveDesciptor has 3 states:
 * </p>
 * <ol>
 * <li>It <code>isPredefined()</code>, in which case it was defined from an
 * extension point.</li>
 * <li>It <code>isPredefined()</code> and <code>hasCustomFile</code>, in
 * which case the user has customized a predefined perspective.</li>
 * <li>It <code>hasCustomFile</code>, in which case the user created a new
 * perspective.</li>
 * </ol>
 * 
 */
public class PerspectiveDescriptor implements IPerspectiveDescriptor,
		IPluginContribution {
	private String id;

	private String pluginId;

	private String originalId;

	private String label;

	private String className;

	private String description;

	private boolean singleton;

	private boolean fixed;

	private ImageDescriptor image;

	private IConfigurationElement configElement;

	/**
	 * Create a new empty descriptor.
	 * 
	 * @param id
	 *            the id of the new descriptor
	 * @param label
	 *            the label of the new descriptor
	 * @param originalDescriptor
	 *            the descriptor that this descriptor is based on
	 */
	public PerspectiveDescriptor(String id, String label,
			PerspectiveDescriptor originalDescriptor) {
		this.id = id;
		this.label = label;
		if (originalDescriptor != null) {
			this.originalId = originalDescriptor.getOriginalId();
			this.image = originalDescriptor.image;

			// This perspective is based on a perspective in some bundle -- if
			// that
			// bundle goes away then I think it makes sense to treat this
			// perspective
			// the same as any other -- so store it with the original
			// descriptor's
			// bundle's list.
			//
			// It might also make sense the other way...removing the following
			// line
			// will allow the perspective to stay around when the originating
			// bundle
			// is unloaded.
			//
			// This might also have an impact on upgrade cases -- should we
			// really be
			// destroying all user customized perspectives when the older
			// version is
			// removed?
			//
			// I'm leaving this here for now since its a good example, but
			// wouldn't be
			// surprised if we ultimately decide on the opposite.
			//
			// The reason this line is important is that this is the value used
			// to
			// put the object into the UI level registry. When that bundle goes
			// away,
			// the registry will remove the entire list of objects. So if this
			// desc
			// has been put into that list -- it will go away.
			this.pluginId = originalDescriptor.getPluginId();
		}
	}

	/**
	 * Create a descriptor from a config element.
	 * 
	 * @param id
	 *            the id of the element to create
	 * @param configElement
	 *            the element to base this perspective on
	 * @throws CoreException
	 *             thrown if there are any missing attributes
	 */
	public PerspectiveDescriptor(String id, IConfigurationElement configElement)
			throws CoreException {
		this.configElement = configElement;
		this.id = id;
		// Sanity check.
		if ((getId() == null) || (getLabel() == null)
				|| (getClassName() == null)) {
			throw new CoreException(
					new Status(
							IStatus.ERROR,
							WorkbenchPlugin.PI_WORKBENCH,
							0,
							"Invalid extension (missing label, id or class name): " + getId(),//$NON-NLS-1$
							null));
		}
	}

	/**
	 * Creates a factory for a predefined perspective. If the perspective is not
	 * predefined return <code>null</code>.
	 * 
	 * @return the IPerspectiveFactory or <code>null</code>
	 * @throws CoreException
	 *             if the object could not be instantiated.
	 */
	public IPerspectiveFactory createFactory() throws CoreException {
		// if there is an originalId, then use that descriptor instead
		if (originalId != null) {
			// Get the original descriptor to create the factory. If the
			// original is gone then nothing can be done.
			IPerspectiveDescriptor target = ((PerspectiveRegistry) WorkbenchPlugin
					.getDefault().getPerspectiveRegistry())
					.findPerspectiveWithId(originalId);

			return target == null ? null : ((PerspectiveDescriptor) target)
					.createFactory();
		}

		// otherwise try to create the executable extension
		if (configElement != null) {
			try {
				return (IPerspectiveFactory) configElement
						.createExecutableExtension(IWorkbenchRegistryConstants.ATT_CLASS);
			} catch (CoreException e) {
				// do nothing
			}
		}

		return null;
	}

	/**
	 * Deletes the custom definition for a perspective..
	 */
	public void deleteCustomDefinition() {
		((PerspectiveRegistry) WorkbenchPlugin.getDefault()
				.getPerspectiveRegistry()).deleteCustomDefinition(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.IPerspectiveDescriptor#getDescription()
	 */
	public String getDescription() {
		return configElement == null ? description : RegistryReader
				.getDescription(configElement);
	}

	/**
	 * Returns whether or not this perspective is fixed.
	 * 
	 * @return whether or not this perspective is fixed
	 */
	public boolean getFixed() {
		return configElement == null ? fixed : Boolean.valueOf(
				configElement
						.getAttribute(IWorkbenchRegistryConstants.ATT_FIXED))
				.booleanValue();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.IPerspectiveDescriptor#getId()
	 */
	public String getId() {
		return id;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.IPerspectiveDescriptor#getImageDescriptor()
	 */
	public ImageDescriptor getImageDescriptor() {
		if (image == null) {
			if (configElement != null) {
				String icon = configElement
						.getAttribute(IWorkbenchRegistryConstants.ATT_ICON);
				if (icon != null) {
					image = AbstractUIPlugin.imageDescriptorFromPlugin(
							configElement.getNamespace(), icon);
				}
				if (image == null) {
					image = WorkbenchImages
							.getImageDescriptor(ISharedImages.IMG_ETOOL_DEF_PERSPECTIVE);
				}
			}
		}
		return image;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.IPerspectiveDescriptor#getLabel()
	 */
	public String getLabel() {
		return configElement == null ? label : configElement
				.getAttribute(IWorkbenchRegistryConstants.ATT_NAME);
	}

	/**
	 * Return the original id of this descriptor.
	 * 
	 * @return the original id of this descriptor
	 */
	public String getOriginalId() {
		if (originalId == null) {
			return getId();
		}
		return originalId;
	}

	/**
	 * Returns <code>true</code> if this perspective has a custom definition.
	 * 
	 * @return whether this perspective has a custom definition
	 */
	public boolean hasCustomDefinition() {
		return ((PerspectiveRegistry) WorkbenchPlugin.getDefault()
				.getPerspectiveRegistry()).hasCustomDefinition(this);
	}

	/**
	 * Returns <code>true</code> if this perspective wants to be default.
	 * 
	 * @return whether this perspective wants to be default
	 */
	public boolean hasDefaultFlag() {
		if (configElement == null) {
			return false;
		}

		return Boolean.valueOf(
				configElement
						.getAttribute(IWorkbenchRegistryConstants.ATT_DEFAULT))
				.booleanValue();
	}

	/**
	 * Returns <code>true</code> if this perspective is predefined by an
	 * extension.
	 * 
	 * @return boolean whether this perspective is predefined by an extension
	 */
	public boolean isPredefined() {
		return getClassName() != null && configElement != null;
	}

	/**
	 * Returns <code>true</code> if this perspective is a singleton.
	 * 
	 * @return whether this perspective is a singleton
	 */
	public boolean isSingleton() {
		return configElement == null ? singleton
				: configElement
						.getAttributeAsIs(IWorkbenchRegistryConstants.ATT_SINGLETON) != null;
	}

	/**
	 * Restore the state of a perspective from a memento.
	 * 
	 * @param memento
	 *            the memento to restore from
	 * @return the <code>IStatus</code> of the operation
	 * @see org.eclipse.ui.IPersistableElement
	 */
	public IStatus restoreState(IMemento memento) {
		IMemento childMem = memento
				.getChild(IWorkbenchConstants.TAG_DESCRIPTOR);
		if (childMem != null) {
			id = childMem.getString(IWorkbenchConstants.TAG_ID);
			originalId = childMem.getString(IWorkbenchConstants.TAG_DESCRIPTOR);
			label = childMem.getString(IWorkbenchConstants.TAG_LABEL);
			className = childMem.getString(IWorkbenchConstants.TAG_CLASS);
			singleton = (childMem.getInteger(IWorkbenchConstants.TAG_SINGLETON) != null);

			// Find a descriptor in the registry.
			IPerspectiveDescriptor descriptor = WorkbenchPlugin.getDefault()
					.getPerspectiveRegistry().findPerspectiveWithId(
							getOriginalId());

			if (descriptor != null) {
				// Copy the state from the registred descriptor.
				image = descriptor.getImageDescriptor();
			}
		}
		return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, 0, "", null); //$NON-NLS-1$
	}

	/**
	 * Revert to the predefined extension template. Does nothing if this
	 * descriptor is user defined.
	 */
	public void revertToPredefined() {
		if (isPredefined()) {
			deleteCustomDefinition();
		}
	}

	/**
	 * Save the state of a perspective to a memento.
	 * 
	 * @param memento
	 *            the memento to restore from
	 * @return the <code>IStatus</code> of the operation
	 * @see org.eclipse.ui.IPersistableElement
	 */
	public IStatus saveState(IMemento memento) {
		IMemento childMem = memento
				.createChild(IWorkbenchConstants.TAG_DESCRIPTOR);
		childMem.putString(IWorkbenchConstants.TAG_ID, getId());
		if (originalId != null) {
			childMem.putString(IWorkbenchConstants.TAG_DESCRIPTOR, originalId);
		}
		childMem.putString(IWorkbenchConstants.TAG_LABEL, getLabel());
		childMem.putString(IWorkbenchConstants.TAG_CLASS, getClassName());
		if (singleton) {
			childMem.putInteger(IWorkbenchConstants.TAG_SINGLETON, 1);
		}
		return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, 0, "", null); //$NON-NLS-1$
	}

	/**
	 * Return the configuration element used to create this perspective, if one
	 * was used.
	 * 
	 * @return the configuration element used to create this perspective
	 * @since 3.0
	 */
	public IConfigurationElement getConfigElement() {
		return configElement;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.activities.support.IPluginContribution#getLocalId()
	 */
	public String getLocalId() {
		return getId();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.activities.support.IPluginContribution#getPluginId()
	 */
	public String getPluginId() {
		return configElement == null ? pluginId : configElement.getNamespace();
	}

	/**
	 * Returns the factory class name for this descriptor.
	 * 
	 * @return the factory class name for this descriptor
	 * @since 3.1
	 */
	public String getClassName() {
		return configElement == null ? className : RegistryReader
				.getClassValue(configElement,
						IWorkbenchRegistryConstants.ATT_CLASS);
	}
}
