/*******************************************************************************
 * Copyright (c) 2003, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.ide;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorRegistry;
import org.eclipse.ui.IMarkerHelpRegistry;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.EditorManager;
import org.eclipse.ui.internal.Workbench;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.registry.MarkerHelpRegistry;
import org.eclipse.ui.internal.ide.registry.MarkerHelpRegistryReader;
import org.eclipse.ui.part.FileEditorInput;

/**
 * Collection of IDE-specific APIs factored out of existing workbench.
 * This class cannot be instantiated; all functionality is provided by 
 * static methods and fields.
 * 
 * @since 3.0
 */
public final class IDE {
	/**
	 * The persistent property key used on IFile resources to contain
	 * the preferred editor ID to use.
	 * <p>
	 * Example of retrieving the persisted editor id:
	 * <pre><code>
	 * IFile file = ...
	 * IEditorDescriptor editorDesc = null;
	 * try {
	 * 	String editorID = file.getPersistentProperty(EDITOR_KEY);
	 * 	if (editorID != null) {
	 * 		editorDesc = editorReg.findEditor(editorID);
	 * 	}
	 * } catch (CoreException e) {
	 * 	// handle problem accessing persistent property here
	 * }
	 * </code></pre>
	 * </p><p>
	 * Example of persisting the editor id:
	 * <pre><code>
	 * IFile file = ...
	 * try {
	 * 	file.setPersistentProperty(EDITOR_KEY, editorDesc.getId());
	 * } catch (CoreException e) {
	 * 	// handle problem setting persistent property here
	 * }
	 * </code></pre>
	 * </p>
	 */
	public static final QualifiedName EDITOR_KEY = new QualifiedName("org.eclipse.ui.internal.registry.ResourceEditorRegistry","EditorProperty");//$NON-NLS-2$//$NON-NLS-1$

	/**
	 * An optional attribute within a workspace marker (<code>IMarker</code>) which
	 * identifies the preferred editor type to be opened.
	 */	
	public static final String EDITOR_ID_ATTR = "org.eclipse.ui.editorID"; //$NON-NLS-1$
	
	/**
	 * The resource based perspective identifier.
	 */
	public static final String RESOURCE_PERSPECTIVE_ID = "org.eclipse.ui.resourcePerspective"; //$NON-NLS-1$

	/**
	 * Marker help registry mapping markers to help context ids and resolutions;
	 * lazily initialized on fist access.
	 */
	private static MarkerHelpRegistry markerHelpRegistry = null;

	/**
	 * An empty unmodifiable list. Used to avoid garbage creation.
	 */
	private static final List emptyUnmodifiableList = Collections.unmodifiableList(new ArrayList(0));
	
	/**
	 * Standard shared images defined by the IDE. These are over and above the
	 * standard workbench images declared in {@link org.eclipse.ui.ISharedImages
	 * ISharedImages}.
	 * <p>
	 * This interface is not intended to be implemented by clients.
	 * </p>
	 * 
	 * @see org.eclipse.ui.ISharedImages
	 */
	public interface SharedImages {
		/**
		 * Identifies a project image.
		 */
		public final static String IMG_OBJ_PROJECT = "IMG_OBJ_PROJECT"; //$NON-NLS-1$

		/**
		 * Identifies a closed project image.
		 */
		public final static String IMG_OBJ_PROJECT_CLOSED = "IMG_OBJ_PROJECT_CLOSED"; //$NON-NLS-1$
	
		/**
		 * Identifies the image used for "open marker".
		 */
		public final static String IMG_OPEN_MARKER = "IMG_OPEN_MARKER"; //$NON-NLS-1$
	
		/**
		 * Identifies the default image used to indicate a task.
		 */
		public final static String IMG_OBJS_TASK_TSK = "IMG_OBJS_TASK_TSK"; //$NON-NLS-1$
		/**
		 * Identifies the default image used to indicate a bookmark.
		 */
		public final static String IMG_OBJS_BKMRK_TSK = "IMG_OBJS_BKMRK_TSK"; //$NON-NLS-1$
	}
	
	/**
	 * Preferences defined by the IDE workbench.
	 * <p>
	 * This interface is not intended to be implemented by clients.
	 * </p>
	 */
	public interface Preferences {
		/**
		 * A named preference for how a new perspective should be opened
		 * when a new project is created.
		 * <p>
		 * Value is of type <code>String</code>.  The possible values are defined 
		 * by the constants <code>OPEN_PERSPECTIVE_WINDOW, OPEN_PERSPECTIVE_PAGE, 
		 * OPEN_PERSPECTIVE_REPLACE, and NO_NEW_PERSPECTIVE</code>.
		 * </p>
		 * 
		 * @see org.eclipse.ui.IWorkbenchPreferenceConstants#OPEN_PERSPECTIVE_WINDOW
		 * @see org.eclipse.ui.IWorkbenchPreferenceConstants#OPEN_PERSPECTIVE_PAGE
		 * @see org.eclipse.ui.IWorkbenchPreferenceConstants#OPEN_PERSPECTIVE_REPLACE
		 * @see org.eclipse.ui.IWorkbenchPreferenceConstants#NO_NEW_PERSPECTIVE
		 */
		public static final String PROJECT_OPEN_NEW_PERSPECTIVE =
			"PROJECT_OPEN_NEW_PERSPECTIVE"; //$NON-NLS-1$
	}
	
	/**
	 * Block instantiation.
	 */
	private IDE() {
		// do nothing
	}
	
	/**
	 * Returns the marker help registry for the workbench.
	 * 
	 * @return the marker help registry
	 */
	public static IMarkerHelpRegistry getMarkerHelpRegistry() {
		if (markerHelpRegistry == null) {
			markerHelpRegistry = new MarkerHelpRegistry();
			new MarkerHelpRegistryReader().addHelp(markerHelpRegistry);
		}
		return markerHelpRegistry;
	}
	
	
	/**
	 * Sets the cursor and selection state for the given editor to 
	 * reveal the position of the given marker.
	 * This is done on a best effort basis.  If the editor does not
	 * provide an <code>IGotoMarker</code> interface (either directly
     * or via <code>IAdaptable.getAdapter</code>), this has no effect.
	 * 
	 * @param editor the editor
	 * @param marker the marker
	 */
	public static void gotoMarker(IEditorPart editor, IMarker marker) {
		IGotoMarker gotoMarker = null;
		if (editor instanceof IGotoMarker) {
			gotoMarker = (IGotoMarker) editor;
		} else {
			gotoMarker = (IGotoMarker) editor.getAdapter(IGotoMarker.class);
		}
		if (gotoMarker != null) {
			gotoMarker.gotoMarker(marker);
		} 
	}

	/**
     * Opens an editor on the given object.
     * <p>
     * If the page already has an editor open on the target object then that
     * editor is brought to front; otherwise, a new editor is opened.
     * <p>
     * 
     * @param page
     *            the page in which the editor will be opened
     * @param input
     *            the editor input
     * @param editorId
     *            the id of the editor extension to use
     * @return an open editor or <code>null</code> if an external editor was
     *         opened
     * @exception PartInitException
     *                if the editor could not be initialized
     * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String)
     */
    public static IEditorPart openEditor(IWorkbenchPage page,
            IEditorInput input, String editorId) throws PartInitException {
        //sanity checks
        if (page == null)
            throw new IllegalArgumentException();

        // open the editor on the file
        return page.openEditor(input, editorId);
    }

	/**
     * Opens an editor on the given object.
     * <p>
     * If the page already has an editor open on the target object then that
     * editor is brought to front; otherwise, a new editor is opened. If
     * <code>activate == true</code> the editor will be activated.
     * <p>
     * @param page
     *            the page in which the editor will be opened
     * @param input
     *            the editor input
     * @param editorId
     *            the id of the editor extension to use
     * @param activate
     * 			  if <code>true</code> the editor will be activated
     * @return an open editor or <code>null</code> if an external editor was
     *         opened
     * @exception PartInitException
     *                if the editor could not be initialized
     * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String, boolean)
     */
    public static IEditorPart openEditor(IWorkbenchPage page,
            IEditorInput input, String editorId, boolean activate)
            throws PartInitException {
        //sanity checks
        if (page == null)
            throw new IllegalArgumentException();

        // open the editor on the file
        return page.openEditor(input, editorId, activate);
    }

	/**
     * Opens an editor on the given file resource.
     * <p>
     * If the page already has an editor open on the target object then that
     * editor is brought to front; otherwise, a new editor is opened. If
     * <code>activate == true</code> the editor will be activated.
     * <p>
     * @param page
     *            the page in which the editor will be opened
     * @param input
     *            the editor input
     * @param activate
     * 			  if <code>true</code> the editor will be activated
     * @return an open editor or <code>null</code> if an external editor was
     *         opened
     * @exception PartInitException
     *                if the editor could not be initialized
     * @see org.eclipse.ui.IWorkbenchPage#openEditor(org.eclipse.ui.IEditorInput,
     *      String, boolean)
     */
    public static IEditorPart openEditor(IWorkbenchPage page, IFile input,
            boolean activate) throws PartInitException {
        //sanity checks
        if (page == null) 
            throw new IllegalArgumentException();

        // open the editor on the file
        IEditorDescriptor editorDesc = getEditorDescriptor(input);
        return page.openEditor(new FileEditorInput(input), editorDesc.getId(),
                activate);
    }

	/**
     * Opens an editor on the given file resource.
     * <p>
     * If the page already has an editor open on the target object then that
     * editor is brought to front; otherwise, a new editor is opened.
     * <p>
     * @param page
     *            the page in which the editor will be opened
     * @param input
     *            the editor input
     * @return an open editor or <code>null</code> if an external editor was
     *         opened
     * @exception PartInitException
     *                if the editor could not be initialized
     * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String)
     */
    public static IEditorPart openEditor(IWorkbenchPage page, IFile input)
            throws PartInitException {
        //sanity checks
        if (page == null)
            throw new IllegalArgumentException();

        // open the editor on the file
        IEditorDescriptor editorDesc = getEditorDescriptor(input);
        return page.openEditor(new FileEditorInput(input), editorDesc.getId());
    }

	/**
     * Opens an editor on the given file resource.
     * <p>
     * If the page already has an editor open on the target object then that
     * editor is brought to front; otherwise, a new editor is opened.
     * <p>
     * @param page
     *            the page in which the editor will be opened
     * @param input
     *            the editor input
     * @param editorId
     *            the id of the editor extension to use
     * @return an open editor or <code>null</code> if an external editor was
     *         opened
     * @exception PartInitException
     *                if the editor could not be initialized
     * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String)
     */
    public static IEditorPart openEditor(IWorkbenchPage page, IFile input,
            String editorId) throws PartInitException {
        //sanity checks
        if (page == null)
            throw new IllegalArgumentException();

        // open the editor on the file
        return page.openEditor(new FileEditorInput(input), editorId);
    }

	/**
     * Opens an editor on the given file resource.
     * <p>
     * If the page already has an editor open on the target object then that
     * editor is brought to front; otherwise, a new editor is opened. If
     * <code>activate == true</code> the editor will be activated.
     * <p>
     * @param page
     *            the page in which the editor will be opened
     * @param input
     *            the editor input
     * @param editorId
     *            the id of the editor extension to use
     * @param activate
     * 			  if <code>true</code> the editor will be activated
     * @return an open editor or <code>null</code> if an external editor was
     *         opened
     * @exception PartInitException
     *                if the editor could not be initialized
     * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String, boolean)
     */
    public static IEditorPart openEditor(IWorkbenchPage page, IFile input,
            String editorId, boolean activate) throws PartInitException {
        //sanity checks
        if (page == null)
            throw new IllegalArgumentException();

        // open the editor on the file
        return page.openEditor(new FileEditorInput(input), editorId, activate);
    }

	/**
	 * Returns an editor descriptor appropriate for opening the given file resource.
	 * <p>
	 * The editor descriptor is determined using a multistep process.
	 * </p>
	 * <ol>
	 *   <li>The file is consulted for a persistent property named
	 *       <code>IDE.EDITOR_KEY</code> containing the preferred editor id
	 *       to be used.</li>
	 *   <li>The workbench editor registry is consulted to determine if an editor 
	 *			extension has been registered for the file type.  If so, an 
	 *			instance of the editor extension is opened on the file.  
	 *			See <code>IEditorRegistry.getDefaultEditor(String)</code>.</li>
	 *   <li>The operating system is consulted to determine if an in-place
	 *       component editor is available (e.g. OLE editor on Win32 platforms).</li>
	 *   <li>The operating system is consulted to determine if an external
	 * 		editor is available.</li>
	 * </ol>
	 * </p>
	 * @param file the file
	 * @return an editor descriptor, appropriate for opening the file
	 * @throws PartInitException if no editor can be found
	 */
	public static IEditorDescriptor getEditorDescriptor(IFile file) throws PartInitException {
		
		if (file == null) {
			throw new IllegalArgumentException();
		}	
		IEditorRegistry editorReg = PlatformUI.getWorkbench().getEditorRegistry();
		
		// check for a default editor
		IEditorDescriptor editorDesc = getDefaultEditor(file);
		
		// next check the OS for in-place editor (OLE on Win32)
		if (editorDesc == null && editorReg.isSystemInPlaceEditorAvailable(file.getName())) {
			editorDesc = editorReg.findEditor(IEditorRegistry.SYSTEM_INPLACE_EDITOR_ID);
		}
		
		// next check with the OS for an external editor
		if (editorDesc == null && editorReg.isSystemExternalEditorAvailable(file.getName())) {
			editorDesc = editorReg.findEditor(IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID);
		}
		
		// next lookup the default text editor
		if (editorDesc == null) {
			editorDesc = editorReg.findEditor(IDEWorkbenchPlugin.DEFAULT_TEXT_EDITOR_ID);
		}
		
		// if no valid editor found, bail out
		if (editorDesc == null) {
			throw new PartInitException(IDEWorkbenchMessages.getString("IDE.noFileEditorFound")); //$NON-NLS-1$
		}
		return editorDesc;
	}

	/**
     * Opens an editor on the file resource of the given marker.
     * <p>
     * If this page already has an editor open on the marker resource file that
     * editor is brought to front; otherwise, a new editor is opened.The cursor
     * and selection state of the editor are then updated from information
     * recorded in the marker.
     * </p>
     * <p>
     * If the marker contains an <code>EDITOR_ID_ATTR</code> attribute the
     * attribute value will be used to determine the editor type to be opened.
     * If not, the registered editor for the marker resource file will be used.
     * </p>
     * 
     * @param page
     *            the workbench page to open the editor in
     * @param marker
     *            the marker to open
     * @return an open editor or <code>null</code> not possible
     * @exception PartInitException
     *                if the editor could not be initialized
     * @see #openEditor(org.eclipse.ui.IWorkbenchPage,
     *      org.eclipse.core.resources.IMarker, boolean)
     */
	public static IEditorPart openEditor(IWorkbenchPage page, IMarker marker) throws PartInitException {
	    return openEditor(page, marker, true);
	}

	/**
	 * Opens an editor on the file resource of the given marker. 
	 * <p>
	 * If this page already has an editor open on the marker resource file that 
	 * editor is brought to front; otherwise, a new editor is opened. If 
	 * <code>activate == true</code> the editor will be activated.  The cursor and 
	 * selection state of the editor are then updated from information recorded in 
	 * the marker.
	 * </p><p>
	 * If the marker contains an <code>EDITOR_ID_ATTR</code> attribute 
	 * the attribute value will be used to determine the editor type to be opened. 
	 * If not, the registered editor for the marker resource file will be used. 
	 * </p>
	 *
	 * @param page the workbench page to open the editor in
	 * @param marker the marker to open
	 * @param activate if <code>true</code> the editor will be activated
	 * @return an open editor or <code>null</code> not possible
	 * @exception PartInitException if the editor could not be initialized
	 */
	public static IEditorPart openEditor(IWorkbenchPage page, IMarker marker, boolean activate) throws PartInitException {
		// sanity checks
		if (page == null || marker == null) {
			throw new IllegalArgumentException();
		}
		
		// get the marker resource file
		if (!(marker.getResource() instanceof IFile)) {
			IDEWorkbenchPlugin.log("Open editor on marker failed; marker resource not an IFile"); //$NON-NLS-1$
			return null;
		}
		IFile file = (IFile) marker.getResource();

		// get the preferred editor id from the marker
		IEditorRegistry editorReg = PlatformUI.getWorkbench().getEditorRegistry();
		IEditorDescriptor editorDesc = null;
		try {
			String editorID = (String) marker.getAttribute(EDITOR_ID_ATTR);
			if (editorID != null) {
				editorDesc = editorReg.findEditor(editorID);
			}
		}
		catch (CoreException e) {
			// ignore this
		}

		// open the editor on the marker resource file
		IEditorPart editor = null;
		if (editorDesc == null) {
			editor = openEditor(page, file, activate);
		} else {
			editor = page.openEditor(new FileEditorInput(file), editorDesc.getId(), activate);
		}
		
		// get the editor to update its position based on the marker
		if (editor != null) {
			gotoMarker(editor, marker);
		}
		
		return editor;
	}
	
	/**
	 * Save all dirty editors in the workbench whose editor input is  
	 * a child resource of one of the <code>IResource</code>'s provided.
	 * Opens a dialog to prompt the user if <code>confirm</code> is true. 
	 * Return true if successful. Return false if the user has cancelled 
	 * the command.
	 * 
	 * @since 3.0
	 * 
	 * @param resourceRoots
	 *            the resource roots under which editor input should be saved,
	 *            other will be left dirty
	 * @param confirm
	 *            prompt the user if true
	 * @return boolean false if the operation was cancelled.
	 */
	public static boolean saveAllEditors(IResource[] resourceRoots, boolean confirm) {
		final IResource[] finalResources = resourceRoots;
		final boolean finalConfirm = confirm;
		final boolean[] result = new boolean[1];
		result[0] = true;

		if (resourceRoots.length == 0) 
			return result[0];
		
		Platform.run(new SafeRunnable(WorkbenchMessages.getString("ErrorClosing")) { //$NON-NLS-1$
			public void run() {
				//Collect dirtyEditors
				ArrayList dirtyEditors = new ArrayList();
				
			    IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows();
			    for (int i = 0; i < windows.length; i++) {
		            IWorkbenchWindow window = windows[i];
		            IWorkbenchPage[] pages = window.getPages();
		            for (int j = 0; j < pages.length; j++) {
		                IWorkbenchPage page = pages[j];
		                IEditorPart[] dirty = page.getDirtyEditors();
		                for (int k = 0; k < dirty.length; k++) {
		                    IEditorPart part = dirty[k];
		                    IFile file = (IFile) part.getEditorInput().getAdapter(IFile.class);
		                    if (file != null) {
		                    	for (int l = 0; l < finalResources.length; l++) {
		                    		IResource resource = finalResources[l];
		                    		if (resource.getFullPath().isPrefixOf(file.getFullPath())) {
		                    			dirtyEditors.add(part);
		                    			break;
		                    		}
		                    }
		                }
		            }
		        }

				}
				if (dirtyEditors.size() > 0) {
					IWorkbenchWindow w = Workbench.getInstance().getActiveWorkbenchWindow();
					if (w == null)
						w = windows[0];
					result[0] = EditorManager.saveAll(dirtyEditors, finalConfirm, w);
				}
			}
		});
		return result[0];
	}

	
	
	/**
	 * Sets the default editor id for a given file.  This value will be used
	 * to determine the default editor descriptor for the file in future calls to
	 * <code>getDefaultEditor(IFile)</code>.
	 *
	 * @param file the file
	 * @param editorID the editor id
	 */
	public static void setDefaultEditor(IFile file, String editorID) {
		try {
			file.setPersistentProperty(EDITOR_KEY,editorID);
		} catch (CoreException e) {
			// do nothing
		}
	}
	
	/**
	 * Returns the default editor for a given file.
	 * <p>
	 * A default editor id may be registered for a specific file using
	 * <code>setDefaultEditor</code>.  If the given file has a registered
	 * default editor id the default editor will derived from it.  If not, 
	 * the default editor is determined by taking the file name for the 
	 * file and obtaining the default editor for that name.
	 * </p>
	 *
	 * @param file the file
	 * @return the descriptor of the default editor, or <code>null</code> if not
	 *   found
	 */
	public static IEditorDescriptor getDefaultEditor(IFile file) {
		// Try file specific editor.
		IEditorRegistry editorReg = PlatformUI.getWorkbench().getEditorRegistry();
		try {
			String editorID = file.getPersistentProperty(EDITOR_KEY);
			if (editorID != null) {
				IEditorDescriptor desc = editorReg.findEditor(editorID);
				if (desc != null)
					return desc;
			}
		} catch (CoreException e) {
			// do nothing
		}
		
		// Try lookup with filename
		return editorReg.getDefaultEditor(file.getName());
	}

	/**
	 * Extracts and returns the <code>IResource</code>s in the given
	 * selection or the resource objects they adapts to.
	 * 
	 * @param originalSelection the original selection, possibly empty
	 * @return list of resources (element type: <code>IResource</code>), 
	 *    possibly empty
	 */
	public static List computeSelectedResources(IStructuredSelection originalSelection) {
		List resources = null;
		for (Iterator e = originalSelection.iterator(); e.hasNext();) {
			Object next = e.next();
			Object resource = null;
			if (next instanceof IResource) {
				resource = next;
			} else if (next instanceof IAdaptable) {
				resource = ((IAdaptable) next).getAdapter(IResource.class);
			}
			if (resource != null) {
				if (resources == null) {
					// lazy init to avoid creating empty lists
					// assume selection contains mostly resources most times
					resources = new ArrayList(originalSelection.size());
				}
				resources.add(resource);
			}
		}
		if (resources == null ) {
			return emptyUnmodifiableList;
		} else {
			return resources;
		}
	}
	
}
