/*******************************************************************************
 * Copyright (c) 2000, 2014 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Philippe Ombredanne - bug 84808
 *     William Mitsuda (wmitsuda@gmail.com) - Bug 153879 [Wizards] configurable size of cvs commit comment history
 *     Brock Janiczak <brockj@tpg.com.au> - Bug 161536 Warn user when committing resources with problem markers
 *******************************************************************************/

package org.eclipse.team.internal.ccvs.ui;

import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.dialogs.*;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.osgi.service.debug.DebugOptionsListener;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.Command.KSubstOption;
import org.eclipse.team.internal.ccvs.core.connection.CVSRepositoryLocation;
import org.eclipse.team.internal.ccvs.ui.console.CVSOutputConsole;
import org.eclipse.team.internal.ccvs.ui.model.CVSAdapterFactory;
import org.eclipse.team.internal.ccvs.ui.repo.RepositoryManager;
import org.eclipse.team.internal.ccvs.ui.repo.RepositoryRoot;
import org.eclipse.team.internal.core.subscribers.ActiveChangeSetManager;
import org.eclipse.team.internal.ui.*;
import org.eclipse.ui.*;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;

/**
 * UI Plugin for CVS provider-specific workbench functionality.
 */
public class CVSUIPlugin extends AbstractUIPlugin {
	/**
	 * The id of the CVS plug-in
	 */
	public static final String ID = "org.eclipse.team.cvs.ui"; //$NON-NLS-1$
	public static final String DECORATOR_ID = "org.eclipse.team.cvs.ui.decorator"; //$NON-NLS-1$
	
	/**
	 * Property constant indicating the decorator configuration has changed. 
	 */
	public static final String P_DECORATORS_CHANGED = CVSUIPlugin.ID  + ".P_DECORATORS_CHANGED";	 //$NON-NLS-1$

	private ServiceRegistration debugRegistration;
	private Hashtable imageDescriptors = new Hashtable(20);
	private static List propertyChangeListeners = new ArrayList(5);
	
	/**
	 * The singleton plug-in instance
	 */
	private static CVSUIPlugin plugin;
	
	/**
	 * The CVS console
	 */
	private CVSOutputConsole console;
	
	/**
	 * The repository manager
	 */
	private RepositoryManager repositoryManager;

	/**
	 * CVSUIPlugin constructor
	 * 
	 * @param descriptor  the plugin descriptor
	 */
	public CVSUIPlugin() {
		super();
		plugin = this;
	}
	
	/**
	 * Returns the standard display to be used. The method first checks, if
	 * the thread calling this method has an associated display. If so, this
	 * display is returned. Otherwise the method returns the default display.
	 */
	public static Display getStandardDisplay() {
		Display display= Display.getCurrent();
		if (display == null) {
			display= Display.getDefault();
		}
		return display;		
	}
	
	/**
	 * Creates an image and places it in the image registry.
	 */
	protected void createImageDescriptor(String id) {
		URL url = FileLocator.find(CVSUIPlugin.getPlugin().getBundle(), new Path(ICVSUIConstants.ICON_PATH + id), null);
		ImageDescriptor desc = ImageDescriptor.createFromURL(url);
		imageDescriptors.put(id, desc);
	}
	
	/**
	 * Returns the active workbench page. Note that the active page may not be
	 * the one that the user perceives as active in some situations so this
	 * method of obtaining the activate page should only be used if no other
	 * method is available.
	 * 
	 * @return the active workbench page
	 */
	public static IWorkbenchPage getActivePage() {
		return TeamUIPlugin.getActivePage();
	}
	
	/**
	 * Register for changes made to Team properties.
	 */
	public static void addPropertyChangeListener(IPropertyChangeListener listener) {
		propertyChangeListeners.add(listener);
	}
	
	/**
	 * Remove a Team property changes.
	 */
	public static void removePropertyChangeListener(IPropertyChangeListener listener) {
		propertyChangeListeners.remove(listener);
	}
	
	/**
	 * Broadcast a Team property change.
	 */
	public static void broadcastPropertyChange(PropertyChangeEvent event) {
		for (Iterator it = propertyChangeListeners.iterator(); it.hasNext();) {
			IPropertyChangeListener listener = (IPropertyChangeListener)it.next();			
			listener.propertyChange(event);
		}
	}
	
	/**
	 * Run an operation involving the given resource. If an exception is thrown
	 * and the code on the status is IResourceStatus.OUT_OF_SYNC_LOCAL then
	 * the user will be prompted to refresh and try again. If they agree, then the
	 * supplied operation will be run again.
	 */
	public static void runWithRefresh(Shell parent, IResource[] resources, 
										IRunnableWithProgress runnable, IProgressMonitor monitor) 
	throws InvocationTargetException, InterruptedException {
		boolean firstTime = true;
		while(true) {
			try {
				runnable.run(monitor);
				return;
			} catch (InvocationTargetException e) {
				if (! firstTime) throw e;
				IStatus status = null;
				if (e.getTargetException() instanceof CoreException) {
					status = ((CoreException)e.getTargetException()).getStatus();
				} else if (e.getTargetException() instanceof TeamException) {
					status = ((TeamException)e.getTargetException()).getStatus();
				} else {
					throw e;
				}
				if (status.getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
					if (promptToRefresh(parent, resources, status)) {
						try {
							for (IResource resource : resources) {
								resource.refreshLocal(IResource.DEPTH_INFINITE, null);
							}
						} catch (CoreException coreEx) {
							// Throw the original exception to the caller
							log(coreEx);
							throw e;
						}
						firstTime = false;
						// Fall through and the operation will be tried again
					} else {
						// User chose not to continue. Treat it as a cancel.
						throw new InterruptedException();
					}
				} else {
					throw e;
				}
			}
		}
	}
	
	private static boolean promptToRefresh(final Shell shell, final IResource[] resources, final IStatus status) {
		final boolean[] result = new boolean[] { false};
		Runnable runnable = () -> {
			Shell shellToUse = shell;
			if (shell == null) {
				shellToUse = new Shell(Display.getCurrent());
			}
			String question;
			if (resources.length == 1) {
				question = NLS.bind(CVSUIMessages.CVSUIPlugin_refreshQuestion,
						new String[] { status.getMessage(), resources[0].getFullPath().toString() });
			} else {
				question = NLS.bind(CVSUIMessages.CVSUIPlugin_refreshMultipleQuestion,
						new String[] { status.getMessage() });
			}
			result[0] = MessageDialog.openQuestion(shellToUse, CVSUIMessages.CVSUIPlugin_refreshTitle, question);
		};
		Display.getDefault().syncExec(runnable);
		return result[0];
	}
	
	/**
	 * Creates a busy cursor and runs the specified runnable.
	 * May be called from a non-UI thread.
	 * 
	 * @param parent the parent Shell for the dialog
	 * @param cancelable if true, the dialog will support cancelation
	 * @param runnable the runnable
	 * 
	 * @exception InvocationTargetException when an exception is thrown from the runnable
	 * @exception InterruptedException when the progress monitor is canceled
	 */
	public static void runWithProgress(Shell parent, boolean cancelable,
										final IRunnableWithProgress runnable) throws InvocationTargetException, InterruptedException {
		Utils.runWithProgress(parent, cancelable, runnable);
	}
	
	/**
	 * Returns the image descriptor for the given image ID.
	 * Returns null if there is no such image.
	 */
	public ImageDescriptor getImageDescriptor(String id) {
		return (ImageDescriptor)imageDescriptors.get(id);
	}
	
	/**
	 * Returns the singleton plug-in instance.
	 * 
	 * @return the plugin instance
	 */
	public static CVSUIPlugin getPlugin() {
		return plugin;
	}
	
	/**
	 * Returns the repository manager
	 * 
	 * @return the repository manager
	 */
	public synchronized RepositoryManager getRepositoryManager() {
		if (repositoryManager == null) {
			repositoryManager = new RepositoryManager();
			repositoryManager.startup();
		}
		return repositoryManager;
	}
	
	/**
	 * Initializes the table of images used in this plugin.
	 */
	private void initializeImages() {
		// objects
		createImageDescriptor(ICVSUIConstants.IMG_REPOSITORY); 
		createImageDescriptor(ICVSUIConstants.IMG_REFRESH);
		createImageDescriptor(ICVSUIConstants.IMG_REFRESH_ENABLED);
		createImageDescriptor(ICVSUIConstants.IMG_REFRESH_DISABLED);
		createImageDescriptor(ICVSUIConstants.IMG_LINK_WITH_EDITOR);
		createImageDescriptor(ICVSUIConstants.IMG_LINK_WITH_EDITOR_ENABLED);
		createImageDescriptor(ICVSUIConstants.IMG_COLLAPSE_ALL);
		createImageDescriptor(ICVSUIConstants.IMG_COLLAPSE_ALL_ENABLED);
		createImageDescriptor(ICVSUIConstants.IMG_NEWLOCATION);
		createImageDescriptor(ICVSUIConstants.IMG_CVSLOGO);
		createImageDescriptor(ICVSUIConstants.IMG_TAG);
		createImageDescriptor(ICVSUIConstants.IMG_MODULE);
		createImageDescriptor(ICVSUIConstants.IMG_CLEAR);
		createImageDescriptor(ICVSUIConstants.IMG_CLEAR_ENABLED);
		createImageDescriptor(ICVSUIConstants.IMG_CLEAR_DISABLED);
		createImageDescriptor(ICVSUIConstants.IMG_BRANCHES_CATEGORY);
		createImageDescriptor(ICVSUIConstants.IMG_VERSIONS_CATEGORY);
		createImageDescriptor(ICVSUIConstants.IMG_DATES_CATEGORY);
		createImageDescriptor(ICVSUIConstants.IMG_PROJECT_VERSION);
		createImageDescriptor(ICVSUIConstants.IMG_WIZBAN_MERGE);
		createImageDescriptor(ICVSUIConstants.IMG_WIZBAN_SHARE);
		createImageDescriptor(ICVSUIConstants.IMG_WIZBAN_DIFF);
		createImageDescriptor(ICVSUIConstants.IMG_WIZBAN_KEYWORD);
		createImageDescriptor(ICVSUIConstants.IMG_WIZBAN_NEW_LOCATION);
		createImageDescriptor(ICVSUIConstants.IMG_WIZBAN_IMPORT);
		createImageDescriptor(ICVSUIConstants.IMG_MERGEABLE_CONFLICT);
		createImageDescriptor(ICVSUIConstants.IMG_QUESTIONABLE);
		createImageDescriptor(ICVSUIConstants.IMG_MERGED);
		createImageDescriptor(ICVSUIConstants.IMG_EDITED);
		createImageDescriptor(ICVSUIConstants.IMG_NO_REMOTEDIR);
		createImageDescriptor(ICVSUIConstants.IMG_CVS_CONSOLE);
		createImageDescriptor(ICVSUIConstants.IMG_DATE);
		createImageDescriptor(ICVSUIConstants.IMG_CHANGELOG);
		createImageDescriptor(ICVSUIConstants.IMG_FILTER_HISTORY);
		createImageDescriptor(ICVSUIConstants.IMG_LOCALMODE);
		createImageDescriptor(ICVSUIConstants.IMG_LOCALREMOTE_MODE);
		createImageDescriptor(ICVSUIConstants.IMG_REMOTEMODE);
		createImageDescriptor(ICVSUIConstants.IMG_LOCALMODE_DISABLED);
		createImageDescriptor(ICVSUIConstants.IMG_LOCALREMOTE_MODE_DISABLED);
		createImageDescriptor(ICVSUIConstants.IMG_REMOTEMODE_DISABLED);
		createImageDescriptor(ICVSUIConstants.IMG_LOCALREVISION_TABLE);
		createImageDescriptor(ICVSUIConstants.IMG_REMOTEREVISION_TABLE);
		createImageDescriptor(ICVSUIConstants.IMG_COMPARE_VIEW);
		
		// special
		createImageDescriptor("glyphs/glyph1.gif");  //$NON-NLS-1$
		createImageDescriptor("glyphs/glyph2.gif");  //$NON-NLS-1$
		createImageDescriptor("glyphs/glyph3.gif");  //$NON-NLS-1$
		createImageDescriptor("glyphs/glyph4.gif");  //$NON-NLS-1$
		createImageDescriptor("glyphs/glyph5.gif");  //$NON-NLS-1$
		createImageDescriptor("glyphs/glyph6.gif");  //$NON-NLS-1$
		createImageDescriptor("glyphs/glyph7.gif");  //$NON-NLS-1$
		createImageDescriptor("glyphs/glyph8.gif");  //$NON-NLS-1$
	}
	/**
	 * Convenience method for logging statuses to the plugin log
	 * 
	 * @param status  the status to log
	 */
	public static void log(IStatus status) {
		getPlugin().getLog().log(status);
	}
	
	public static void log(CoreException e) {
		log(e.getStatus().getSeverity(), CVSUIMessages.simpleInternal, e); 
	}
	
	/**
	 * Log the given exception along with the provided message and severity indicator
	 */
	public static void log(int severity, String message, Throwable e) {
		log(new Status(severity, ID, 0, message, e));
	}
	
	// flags to tailor error reporting
	/**
	 * Use this flag if you are calling openError from a none UI thread. The
	 * flag is checked in openDialog method.
	 */
	public static final int PERFORM_SYNC_EXEC = 1;
	/**
	 * Use this flag to log all TeamExceptions and its descendants.
	 */
	public static final int LOG_TEAM_EXCEPTIONS = 2;
	/**
	 * Use this flag to log all CoreExceptions excluding TeamExceptions and its
	 * descendants.
	 */
	public static final int LOG_NONTEAM_CORE_EXCEPTIONS = 4;
	/**
	 * Use this flag to log an exception other than CoreException (e.g.
	 * IOException).
	 */
	public static final int LOG_OTHER_EXCEPTIONS = 8;
	/**
	 * Use this flag to log all CoreExceptions.
	 */
	public static final int LOG_CORE_EXCEPTIONS = LOG_TEAM_EXCEPTIONS
			| LOG_NONTEAM_CORE_EXCEPTIONS;
	/**
	 * Use this flag to log all exceptions different than TeamException and its
	 * descendant.
	 */
	public static final int LOG_NONTEAM_EXCEPTIONS = LOG_NONTEAM_CORE_EXCEPTIONS
			| LOG_OTHER_EXCEPTIONS;
	
	/**
	 * Convenience method for showing an error dialog 
	 * @param shell a valid shell or null
	 * @param exception the exception to be report
	 * @param title the title to be displayed
	 * @return IStatus the status that was displayed to the user
	 */
	public static IStatus openError(Shell shell, String title, String message, Throwable exception) {
		return openError(shell, title, message, exception, LOG_OTHER_EXCEPTIONS);
	}
	
	/**
	 * Convenience method for showing an error dialog 
	 * @param shell a valid shell or null
	 * @param exception the exception to be report
	 * @param title the title to be displayed
	 * @param flags customized attributes for the error handling
	 * @return IStatus the status that was displayed to the user
	 */
	public static IStatus openError(Shell providedShell, String title, String message, Throwable exception, int flags) {
		// Unwrap InvocationTargetExceptions
		if (exception instanceof InvocationTargetException) {
			Throwable target = ((InvocationTargetException)exception).getTargetException();
			// re-throw any runtime exceptions or errors so they can be handled by the workbench
			if (target instanceof RuntimeException) {
				throw (RuntimeException)target;
			}
			if (target instanceof Error) {
				throw (Error)target;
			} 
			return openError(providedShell, title, message, target, flags);
		}
		
		// Determine the status to be displayed (and possibly logged)
		IStatus status = null;
		boolean log = false;
		if (exception instanceof TeamException) {
			status = ((TeamException)exception).getStatus();
			log = ((flags & LOG_TEAM_EXCEPTIONS) > 0);
		} else if (exception instanceof CoreException) {
			status = ((CoreException)exception).getStatus();
			log = ((flags & LOG_NONTEAM_CORE_EXCEPTIONS) > 0);
		} else if (exception instanceof InterruptedException) {
			return new CVSStatus(IStatus.OK, CVSUIMessages.ok); 
		} else if (exception != null) {
			status = new CVSStatus(IStatus.ERROR, CVSUIMessages.internal, exception); 
			log = ((flags & LOG_OTHER_EXCEPTIONS) > 0);
			if (title == null) title = CVSUIMessages.internal; 
		}
		
		// Check for a build error and report it differently
		if (status.getCode() == IResourceStatus.BUILD_FAILED) {
			message = CVSUIMessages.buildError; 
			log = true;
		}
		
		// Check for multi-status with only one child
		if (status.isMultiStatus() && status.getChildren().length == 1) {
			status = status.getChildren()[0];
		}
		if (status.isOK()) return status;
		
		// Log if the user requested it
		if (log) CVSUIPlugin.log(status.getSeverity(), status.getMessage(), exception);

		return openDialog(providedShell, title, message, status, flags);
	}

	/**
	 * Convenience method for showing an error dialog
	 * 
	 * @param shell a valid shell or null
	 * @param status the status to be reported
	 * @param title the title to be displayed
	 * @param flags customized attributes for the error handling
	 * @return IStatus the status that was displayed to the user
	 */
	public static IStatus openError(Shell providedShell, String title,
			String message, IStatus status, int flags) {
		boolean log = false;

		// Check for a build error and report it differently
		if (status.getCode() == IResourceStatus.BUILD_FAILED) {
			message = CVSUIMessages.buildError;
			log = true;
		}

		// Check for multi-status with only one child
		if (status.isMultiStatus() && status.getChildren().length == 1) {
			status = status.getChildren()[0];
		}
		if (status.isOK())
			return status;

		// Log if the user requested it
		if (log)
			CVSUIPlugin.log(status);

		return openDialog(providedShell, title, message, status, flags);
	}

	private static IStatus openDialog(Shell providedShell, final String title,
			final String message, final IStatus status, int flags) {
		// Create a runnable that will display the error status
		final IOpenableInShell openable = shell -> {
			if (status.getSeverity() == IStatus.INFO && !status.isMultiStatus()) {
				MessageDialog.openInformation(shell, CVSUIMessages.information, status.getMessage());
			} else {
				ErrorDialog.openError(shell, title, message, status);
			}
		};
		openDialog(providedShell, openable, flags);

		// return the status we display
		return status;
	}

	/**
	 * Interface that allows a shell to be passed to an open method. The
	 * provided shell can be used without sync-execing, etc.
	 */
	public interface IOpenableInShell {
		public void open(Shell shell);
	}
	
	/**
	 * Open the dialog code provided in the IOpenableInShell, ensuring that 
	 * the provided shell is valid. This method will provide a shell to the
	 * IOpenableInShell if one is not provided to the method.
	 * 
	 * @param providedShell
	 * @param openable
	 * @param flags
	 */
	public static void openDialog(Shell providedShell, final IOpenableInShell openable, int flags) {
		// If no shell was provided, try to get one from the active window
		if (providedShell == null) {
			IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
			if (window != null) {
				providedShell = window.getShell();
				// sync-exec when we do this just in case
				flags = flags | PERFORM_SYNC_EXEC;
			}
		}
		
		// Create a runnable that will display the error status
		final Shell shell = providedShell;
		Runnable outerRunnable = () -> {
			Shell displayShell;
			if (shell == null) {
				Display display = Display.getCurrent();
				displayShell = new Shell(display);
			} else {
				displayShell = shell;
			}
			openable.open(displayShell);
			if (shell == null) {
				displayShell.dispose();
			}
		};
		
		// Execute the above runnable as determined by the parameters
		if (shell == null || (flags & PERFORM_SYNC_EXEC) > 0) {
			Display display;
			if (shell == null) {
				display = Display.getCurrent();
				if (display == null) {
					display = Display.getDefault();
				}
			} else {
				display = shell.getDisplay();
			}
			display.syncExec(outerRunnable);
		} else {
			outerRunnable.run();
		}
	}
	
	
	/**
	 * Initializes the preferences for this plugin if necessary.
	 */
	@Override
	protected void initializeDefaultPluginPreferences() {
		IPreferenceStore store = getPreferenceStore();
		// Get the plugin preferences for CVS Core
		Preferences corePrefs = CVSProviderPlugin.getPlugin().getPluginPreferences();
		
		store.setDefault(ICVSUIConstants.PREF_REPOSITORIES_ARE_BINARY, false);
		store.setDefault(ICVSUIConstants.PREF_SHOW_COMMENTS, true);
		store.setDefault(ICVSUIConstants.PREF_WRAP_COMMENTS, true);
		store.setDefault(ICVSUIConstants.PREF_SHOW_TAGS, true);
		store.setDefault(ICVSUIConstants.PREF_SHOW_SEARCH, false);
		store.setDefault(ICVSUIConstants.PREF_REVISION_MODE, 0);
		store.setDefault(ICVSUIConstants.PREF_GROUPBYDATE_MODE, true);
		store.setDefault(ICVSUIConstants.PREF_HISTORY_VIEW_EDITOR_LINKING, false);
		store.setDefault(ICVSUIConstants.PREF_PRUNE_EMPTY_DIRECTORIES, CVSProviderPlugin.DEFAULT_PRUNE);
		store.setDefault(ICVSUIConstants.PREF_TIMEOUT, CVSProviderPlugin.DEFAULT_TIMEOUT);
		store.setDefault(ICVSUIConstants.PREF_CONSIDER_CONTENTS, true);
		store.setDefault(ICVSUIConstants.PREF_SYNCVIEW_REGEX_FILTER_PATTERN, ""); //default pattern is empty string //$NON-NLS-1$		
		store.setDefault(ICVSUIConstants.PREF_COMPRESSION_LEVEL, CVSProviderPlugin.DEFAULT_COMPRESSION_LEVEL);
		store.setDefault(ICVSUIConstants.PREF_TEXT_KSUBST, CVSProviderPlugin.DEFAULT_TEXT_KSUBST_OPTION.toMode());
		store.setDefault(ICVSUIConstants.PREF_USE_PLATFORM_LINEEND, true);
		store.setDefault(ICVSUIConstants.PREF_REPLACE_UNMANAGED, true);
		store.setDefault(ICVSUIConstants.PREF_CVS_RSH, CVSProviderPlugin.DEFAULT_CVS_RSH);
		store.setDefault(ICVSUIConstants.PREF_CVS_RSH_PARAMETERS, CVSProviderPlugin.DEFAULT_CVS_RSH_PARAMETERS);
		store.setDefault(ICVSUIConstants.PREF_CVS_SERVER, CVSProviderPlugin.DEFAULT_CVS_SERVER);
		store.setDefault(ICVSUIConstants.PREF_EXT_CONNECTION_METHOD_PROXY, "ext"); //$NON-NLS-1$
		store.setDefault(ICVSUIConstants.PREF_PROMPT_ON_CHANGE_GRANULARITY, true);
		store.setDefault(ICVSUIConstants.PREF_DETERMINE_SERVER_VERSION, true);
		store.setDefault(ICVSUIConstants.PREF_CONFIRM_MOVE_TAG, CVSProviderPlugin.DEFAULT_CONFIRM_MOVE_TAG);
		store.setDefault(ICVSUIConstants.PREF_DEBUG_PROTOCOL, false);
		store.setDefault(ICVSUIConstants.PREF_WARN_REMEMBERING_MERGES, true);
		store.setDefault(ICVSUIConstants.PREF_SHOW_COMPARE_REVISION_IN_DIALOG, false);
		store.setDefault(ICVSUIConstants.PREF_COMMIT_SET_DEFAULT_ENABLEMENT, false);
		store.setDefault(ICVSUIConstants.PREF_AUTO_REFRESH_TAGS_IN_TAG_SELECTION_DIALOG, false);
		store.setDefault(ICVSUIConstants.PREF_AUTO_SHARE_ON_IMPORT, true);
		store.setDefault(ICVSUIConstants.PREF_ENABLE_WATCH_ON_EDIT, false);
		store.setDefault(ICVSUIConstants.PREF_USE_PROJECT_NAME_ON_CHECKOUT, false);
		store.setDefault(ICVSUIConstants.PREF_COMMIT_FILES_DISPLAY_THRESHOLD, 1000);
		store.setDefault(ICVSUIConstants.PREF_COMMIT_COMMENTS_MAX_HISTORY, RepositoryManager.DEFAULT_MAX_COMMENTS);
		
		PreferenceConverter.setDefault(store, ICVSUIConstants.PREF_CONSOLE_COMMAND_COLOR, new RGB(0, 0, 0));
		PreferenceConverter.setDefault(store, ICVSUIConstants.PREF_CONSOLE_MESSAGE_COLOR, new RGB(0, 0, 255));
		PreferenceConverter.setDefault(store, ICVSUIConstants.PREF_CONSOLE_ERROR_COLOR, new RGB(255, 0, 0));
		store.setDefault(ICVSUIConstants.PREF_CONSOLE_SHOW_ON_MESSAGE, false);
		store.setDefault(ICVSUIConstants.PREF_CONSOLE_LIMIT_OUTPUT, true);	
		store.setDefault(ICVSUIConstants.PREF_CONSOLE_HIGH_WATER_MARK, 500000);	
		store.setDefault(ICVSUIConstants.PREF_CONSOLE_WRAP, false);	
		store.setDefault(ICVSUIConstants.PREF_CONSOLE_WIDTH, 80);
			
		store.setDefault(ICVSUIConstants.PREF_FILETEXT_DECORATION, CVSDecoratorConfiguration.DEFAULT_FILETEXTFORMAT);
		store.setDefault(ICVSUIConstants.PREF_FOLDERTEXT_DECORATION, CVSDecoratorConfiguration.DEFAULT_FOLDERTEXTFORMAT);
		store.setDefault(ICVSUIConstants.PREF_PROJECTTEXT_DECORATION, CVSDecoratorConfiguration.DEFAULT_PROJECTTEXTFORMAT);
		
		store.setDefault(ICVSUIConstants.PREF_FIRST_STARTUP, true);
		store.setDefault(ICVSUIConstants.PREF_ADDED_FLAG, CVSDecoratorConfiguration.DEFAULT_ADDED_FLAG);
		store.setDefault(ICVSUIConstants.PREF_DIRTY_FLAG, CVSDecoratorConfiguration.DEFAULT_DIRTY_FLAG);	
		store.setDefault(ICVSUIConstants.PREF_SHOW_ADDED_DECORATION, true);
		store.setDefault(ICVSUIConstants.PREF_SHOW_HASREMOTE_DECORATION, true);
		store.setDefault(ICVSUIConstants.PREF_SHOW_DIRTY_DECORATION, false);
		store.setDefault(ICVSUIConstants.PREF_SHOW_NEWRESOURCE_DECORATION, true);
		store.setDefault(ICVSUIConstants.PREF_CALCULATE_DIRTY, true);	
		store.setDefault(ICVSUIConstants.PREF_USE_FONT_DECORATORS, false);
		store.setDefault(ICVSUIConstants.PREF_PROMPT_ON_MIXED_TAGS, true);
		store.setDefault(ICVSUIConstants.PREF_PROMPT_ON_SAVING_IN_SYNC, true);
		store.setDefault(ICVSUIConstants.PREF_SAVE_DIRTY_EDITORS, ICVSUIConstants.OPTION_PROMPT);
		
		store.setDefault(ICVSUIConstants.PREF_ANNOTATE_PROMPTFORBINARY, MessageDialogWithToggle.PROMPT);
		store.setDefault(ICVSUIConstants.PREF_ALLOW_EMPTY_COMMIT_COMMENTS, MessageDialogWithToggle.PROMPT);
		store.setDefault(ICVSUIConstants.PREF_INCLUDE_CHANGE_SETS_IN_COMMIT, MessageDialogWithToggle.NEVER);
		store.setDefault(ICVSUIConstants.PREF_ALLOW_COMMIT_WITH_WARNINGS, MessageDialogWithToggle.ALWAYS);
		store.setDefault(ICVSUIConstants.PREF_ALLOW_COMMIT_WITH_ERRORS, MessageDialogWithToggle.PROMPT);
		
		store.setDefault(ICVSUIConstants.PREF_UPDATE_HANDLING, ICVSUIConstants.PREF_UPDATE_HANDLING_TRADITIONAL);
		store.setDefault(ICVSUIConstants.PREF_UPDATE_PREVIEW, ICVSUIConstants.PREF_UPDATE_PREVIEW_IN_SYNCVIEW);
		
		store.setDefault(ICVSUIConstants.PREF_ENABLE_MODEL_SYNC, true);
		store.setDefault(ICVSUIConstants.PREF_OPEN_COMPARE_EDITOR_FOR_SINGLE_FILE, true);
		
		// Set the watch/edit preferences defaults and values
		store.setDefault(ICVSUIConstants.PREF_CHECKOUT_READ_ONLY, corePrefs.getDefaultBoolean(CVSProviderPlugin.READ_ONLY));
		store.setDefault(ICVSUIConstants.PREF_EDIT_ACTION, ICVSUIConstants.PREF_EDIT_IN_BACKGROUND);
		store.setDefault(ICVSUIConstants.PREF_EDIT_PROMPT, ICVSUIConstants.PREF_EDIT_PROMPT_IF_EDITORS);
		store.setDefault(ICVSUIConstants.PREF_UPDATE_PROMPT, ICVSUIConstants.PREF_UPDATE_PROMPT_NEVER);
		// Ensure that the preference values in UI match Core
		store.setValue(ICVSUIConstants.PREF_CHECKOUT_READ_ONLY, corePrefs.getBoolean(CVSProviderPlugin.READ_ONLY));
		
		// Forward the values to the CVS plugin
		CVSProviderPlugin.getPlugin().setPruneEmptyDirectories(store.getBoolean(ICVSUIConstants.PREF_PRUNE_EMPTY_DIRECTORIES));
		CVSProviderPlugin.getPlugin().setTimeout(store.getInt(ICVSUIConstants.PREF_TIMEOUT));
		CVSProviderPlugin.getPlugin().setCvsRshCommand(store.getString(ICVSUIConstants.PREF_CVS_RSH));
		CVSProviderPlugin.getPlugin().setCvsRshParameters(store.getString(ICVSUIConstants.PREF_CVS_RSH_PARAMETERS));
		CVSProviderPlugin.getPlugin().setCvsServer(store.getString(ICVSUIConstants.PREF_CVS_SERVER));
		CVSRepositoryLocation.setExtConnectionMethodProxy(store.getString(ICVSUIConstants.PREF_EXT_CONNECTION_METHOD_PROXY));
		CVSProviderPlugin.getPlugin().setQuietness(CVSPreferencesPage.getQuietnessOptionFor(store.getInt(ICVSUIConstants.PREF_QUIETNESS)));
		CVSProviderPlugin.getPlugin().setCompressionLevel(store.getInt(ICVSUIConstants.PREF_COMPRESSION_LEVEL));
		CVSProviderPlugin.getPlugin().setReplaceUnmanaged(store.getBoolean(ICVSUIConstants.PREF_REPLACE_UNMANAGED));
		CVSProviderPlugin.getPlugin().setDefaultTextKSubstOption(KSubstOption.fromMode(store.getString(ICVSUIConstants.PREF_TEXT_KSUBST)));
		CVSProviderPlugin.getPlugin().setUsePlatformLineend(store.getBoolean(ICVSUIConstants.PREF_USE_PLATFORM_LINEEND));
		CVSProviderPlugin.getPlugin().setRepositoriesAreBinary(store.getBoolean(ICVSUIConstants.PREF_REPOSITORIES_ARE_BINARY));
		CVSProviderPlugin.getPlugin().setDetermineVersionEnabled(store.getBoolean(ICVSUIConstants.PREF_DETERMINE_SERVER_VERSION));
		CVSProviderPlugin.getPlugin().setDebugProtocol(CVSProviderPlugin.getPlugin().isDebugProtocol() || store.getBoolean(ICVSUIConstants.PREF_DEBUG_PROTOCOL));
		CVSProviderPlugin.getPlugin().setAutoshareOnImport(store.getBoolean(ICVSUIConstants.PREF_AUTO_SHARE_ON_IMPORT));
		
		// code to transfer CVS preference to Team preference
		if (store.getBoolean(ICVSUIConstants.PREF_SHOW_AUTHOR_IN_EDITOR)) {
			store.setValue(ICVSUIConstants.PREF_SHOW_AUTHOR_IN_EDITOR, false);
			IPreferenceStore teamStore = TeamUIPlugin.getPlugin().getPreferenceStore();
			if (teamStore.isDefault(IPreferenceIds.SHOW_AUTHOR_IN_COMPARE_EDITOR))
				teamStore.setValue(IPreferenceIds.SHOW_AUTHOR_IN_COMPARE_EDITOR, true);
		}
	}
	
	@Override
	public void start(BundleContext context) throws Exception {
		super.start(context);

		// register debug options listener
		Hashtable properties = new Hashtable(2);
		properties.put(DebugOptions.LISTENER_SYMBOLICNAME, ID);
		debugRegistration = context.registerService(DebugOptionsListener.class, Policy.DEBUG_OPTIONS_LISTENER, properties);

		initializeImages();
		
		CVSAdapterFactory factory = new CVSAdapterFactory();
		Platform.getAdapterManager().registerAdapters(factory, ICVSRemoteFile.class);
		Platform.getAdapterManager().registerAdapters(factory, ICVSRemoteFolder.class);
		Platform.getAdapterManager().registerAdapters(factory, ICVSRepositoryLocation.class);
		Platform.getAdapterManager().registerAdapters(factory, RepositoryRoot.class);
		
		try {
			console = new CVSOutputConsole();
		} catch (RuntimeException e) {
			// Don't let the console bring down the CVS UI
			log(IStatus.ERROR, "Errors occurred starting the CVS console", e); //$NON-NLS-1$
		}
		
		IPreferenceStore store = getPreferenceStore();
		if (store.getBoolean(ICVSUIConstants.PREF_FIRST_STARTUP)) {
			// If we enable the decorator in the XML, the CVS plugin will be loaded
			// on startup even if the user never uses CVS. Therefore, we enable the 
			// decorator on the first start of the CVS plugin since this indicates that 
			// the user has done something with CVS. Subsequent startups will load
			// the CVS plugin unless the user disables the decorator. In this case,
			// we will not re-enable since we only enable automatically on the first startup.
			PlatformUI.getWorkbench().getDecoratorManager().setEnabled(CVSLightweightDecorator.ID, true);
			store.setValue(ICVSUIConstants.PREF_FIRST_STARTUP, false);
		}

	}
	
	@Override
	public void stop(BundleContext context) throws Exception {
		try {
			// unregister debug options listener
			debugRegistration.unregister();
			debugRegistration = null;

			try {
				if (repositoryManager != null)
					repositoryManager.shutdown();
			} catch (TeamException e) {
				throw new CoreException(e.getStatus());
			}
			
			if (console != null)
				console.shutdown();
		} finally {
			super.stop(context);
		}
	}
	
	/**
	 * @return the CVS console
	 */
	public CVSOutputConsole getConsole() {
		return console;
	}
	
	public IEditorPart openEditor(ICVSRemoteFile file, IProgressMonitor monitor) throws InvocationTargetException {
		IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
		try {
			return Utils.openEditor(page, file.getAdapter(IFileRevision.class), monitor);
		} catch (CoreException e) {
			throw new InvocationTargetException(e);
		}
	}

	/**
	 * Helper method which access the preference store to determine if the 
	 * project name from the project description file (.project) should be used
	 * as the project name on checkout.
	 */
	public boolean isUseProjectNameOnCheckout() {
		return getPreferenceStore().getBoolean(ICVSUIConstants.PREF_USE_PROJECT_NAME_ON_CHECKOUT);
	}

	public ActiveChangeSetManager getChangeSetManager() {
		return CVSProviderPlugin.getPlugin().getChangeSetManager();
	}

	public org.osgi.service.prefs.Preferences getInstancePreferences() {
		return InstanceScope.INSTANCE.getNode(getBundle().getSymbolicName());
	}
}
