/*******************************************************************************
 * Copyright (c) 2010, 2018 IBM Corporation and others.
 * All rights reserved. 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
 *******************************************************************************/
package org.eclipse.wst.sse.ui.contentassist;

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

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.action.LegacyActionTools;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ContentAssistEvent;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.ICompletionListener;
import org.eclipse.jface.text.contentassist.ICompletionListenerExtension;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContentAssistantExtension2;
import org.eclipse.jface.text.contentassist.IContentAssistantExtension3;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.ui.internal.ExtendedConfigurationBuilder;
import org.eclipse.wst.sse.ui.internal.IReleasable;
import org.eclipse.wst.sse.ui.internal.Logger;
import org.eclipse.wst.sse.ui.internal.SSEUIMessages;
import org.eclipse.wst.sse.ui.internal.SSEUIPlugin;
import org.eclipse.wst.sse.ui.internal.contentassist.CompletionProposalCategory;
import org.eclipse.wst.sse.ui.internal.contentassist.CompletionProposalComputerRegistry;
import org.eclipse.wst.sse.ui.internal.contentassist.CompletionProposoalCatigoriesConfigurationRegistry;
import org.eclipse.wst.sse.ui.internal.contentassist.CompoundContentAssistProcessor;
import org.eclipse.wst.sse.ui.internal.contentassist.ContextInformationValidator;
import org.eclipse.wst.sse.ui.internal.contentassist.OptionalMessageDialog;
import org.eclipse.wst.sse.ui.preferences.ICompletionProposalCategoriesConfigurationReader;
import org.eclipse.wst.sse.ui.preferences.ICompletionProposalCategoriesConfigurationWriter;


/**
 * <p>A content assist processor that aggregates the proposals of the
 * {@link org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer}s contributed via the
 * <code>org.eclipse.wst.sse.ui.completionProposal</code> extension point.</p>
 * <p>
 * Extenders may extend:
 * <ul>
 * <li>{@link #propertyChange(PropertyChangeEvent)}to react to property change events
 * that occur in the {@link IPreferenceStore} given to the constructor in case the behavior
 * of the processor needs to change according to user preferences</li>
 * <li>{@link #getCompletionProposalAutoActivationCharacters()}</li>
 * <li>{@link #getContextInformationAutoActivationCharacters()}</li>
 * <li>{@link #filterAndSortProposals(List, IProgressMonitor, CompletionProposalInvocationContext)}
 * to add sorting and filtering</li>
 * <li>{@link #filterAndSortContextInformation(List, IProgressMonitor)} to add sorting and filtering</li>
 * <li>{@link #createProgressMonitor()} to change the way progress is reported</li>
 * <li>{@link #createContext(ITextViewer, int)} to provide the context object
 * passed to the computers</li>
 * <li>{@link #getContextInformationValidator()} to add context validation (needed if any
 * contexts are provided)</li>
 * <li>{@link #getErrorMessage()} to change error reporting</li>
 * </ul>
 * </p>
 * 
 * @base org.eclipse.jdt.internal.ui.text.java.ContentAssistProcessor
 */
public class StructuredContentAssistProcessor implements IContentAssistProcessor, IPropertyChangeListener, IReleasable {

	/** Legacy editor configuration extension point. */
	private static final String CONTENT_ASSIST_PROCESSOR_EXTENDED_ID = "contentassistprocessor"; //$NON-NLS-1$

	/** Content assist processors added through the now legacy editor configuration extension point */
	private List fLegacyExtendedContentAssistProcessors;
	
	/**
	 * Dialog settings key for the "all categories are disabled" warning dialog. See
	 * {@link OptionalMessageDialog}.
	 */
	private static final String PREF_WARN_ABOUT_EMPTY_ASSIST_CATEGORY= "EmptyDefaultAssistCategory"; //$NON-NLS-1$

	/**
	 * Used to sort categories by their page order so they are cycled in the correct order
	 */
	private final Comparator PAGE_ORDER_COMPARATOR = new Comparator() {
		public int compare(Object o1, Object o2) {
			CompletionProposalCategory d1= (CompletionProposalCategory) o1;
			CompletionProposalCategory d2= (CompletionProposalCategory) o2;

			return d1.getPageSortRank(fContentTypeID) - d2.getPageSortRank(fContentTypeID);
		}
	};
	
	/**
	 * Used to sort categories by their default page order so they are
	 * ordered correctly on the default page
	 */
	private final Comparator DEFAULT_PAGE_ORDER_COMPARATOR = new Comparator() {
		public int compare(Object o1, Object o2) {
			CompletionProposalCategory d1= (CompletionProposalCategory) o1;
			CompletionProposalCategory d2= (CompletionProposalCategory) o2;

			return d1.getDefaultPageSortRank(fContentTypeID) - d2.getDefaultPageSortRank(fContentTypeID);
		}
	};

	/** List of {@link CompletionProposalCategory}s supported by this processor */
	private List fCategories;
	
	/** content type ID this processor is associated with */
	String fContentTypeID;
	
	/** partition type ID this processor is associated with */
	private final String fPartitionTypeID;
	
	/** Content assistant used for giving the user status messages and listening to completion results*/
	private ContentAssistant fAssistant;

	/* cycling stuff */
	private int fRepetition= -1;
	private List fCategoryIteration = null;
	private String fIterationGesture = null;
	private int fNumberOfComputedResults = 0;
	private String fErrorMessage;
	
	/** Optionally specified preference store for listening to property change events */
	private IPreferenceStore fPreferenceStore;
	
	/** The viewer this processor is associated with */
	private ITextViewer fViewer;

	/**
	 * the {@link ITextInputListener} used to set the content type when
	 * a document is set for this processors associated viewer.
	 */
	private ITextInputListener fTextInputListener;

	private CompletionListener fCompletionListener;
	
	/** the context information validator for this processor */
	private IContextInformationValidator fContextInformationValidator;
	
	private AutoActivationDelegate fAutoActivation;

	/**
	 * <p>Create a new content assist processor for a specific partition type. 
	 * The content type will be determined when a document is set on the viewer</p>
	 * 
	 * <p>If the given {@link IPreferenceStore} is not <code>null</code> then this
	 * processor will be registered as a {@link IPropertyChangeListener} on the given store
	 * so that implementers of this class can change the way the processor acts based on
	 * user preferences</p>
	 * 
	 * @param assistant {@link ContentAssistant} to use
	 * @param partitionTypeID the partition type this processor is for
	 * @param viewer {@link ITextViewer} this processor is acting in
	 * @param preferenceStore This processor will be registered as a {@link IPropertyChangeListener}
	 * on this store and the processor itself will take care of removing itself as a listener, if
	 * <code>null</code> then will not be registered as a {@link IPropertyChangeListener}
	 */
	public StructuredContentAssistProcessor(ContentAssistant assistant, String partitionTypeID,
			ITextViewer viewer, IPreferenceStore preferenceStore) {
		
		Assert.isNotNull(partitionTypeID);
		Assert.isNotNull(assistant);
		
		//be sure the registry has been loaded, none blocking
		CompletionProposalComputerRegistry.getDefault().initialize();
		
		//register on the preference store
		this.fPreferenceStore = preferenceStore;
		if(this.fPreferenceStore != null) {
			this.fPreferenceStore.addPropertyChangeListener(this);
		}
		
		//The content type can not be determined until a document has been set
		this.fContentTypeID = null;
		this.fViewer = viewer;
		if(viewer != null) {
			this.fTextInputListener = new TextInputListener();
			viewer.addTextInputListener(this.fTextInputListener);
		
			if(viewer.getDocument() != null) {
				/* it is highly unlike the document has already been set, but check
				 * just for sanity
				 */
				this.fTextInputListener.inputDocumentChanged(null, viewer.getDocument());
			}
		}
		
		//set the associated partition type
		this.fPartitionTypeID = partitionTypeID;
		
		//add completion listener
		fAssistant = assistant;
		fCompletionListener = new CompletionListener();
		fAssistant.addCompletionListener(fCompletionListener);
		
		//lazy load these to speed up initial editor opening
		fLegacyExtendedContentAssistProcessors = null;
		fCategories = null;
	}

	/**
	 * <p>Collect the proposals using the extension points</p>
	 * 
	 * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int)
	 */
	public final ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
		clearState();

		IProgressMonitor monitor= createProgressMonitor();
		monitor.beginTask(SSEUIMessages.ContentAssist_computing_proposals, getProposalCategories().size() + 1);

		CompletionProposalInvocationContext context = createContext(viewer, offset);

		monitor.subTask(SSEUIMessages.ContentAssist_collecting_proposals);
		List proposals = collectProposals(viewer, offset, monitor, context);

		monitor.subTask(SSEUIMessages.ContentAssist_sorting_proposals);
		List filtered = filterAndSortProposals(proposals, monitor, context);
		fNumberOfComputedResults= filtered.size();

		ICompletionProposal[] result= (ICompletionProposal[]) filtered.toArray(new ICompletionProposal[filtered.size()]);
		monitor.done();

		return result;
	}

	/**
	 * <p>Collect the context information using the extension points</p>
	 * 
	 * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeContextInformation(org.eclipse.jface.text.ITextViewer, int)
	 */
	public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
		clearState();

		IProgressMonitor monitor= createProgressMonitor();
		monitor.beginTask(SSEUIMessages.ContentAssist_computing_contexts, getProposalCategories().size() + 1);

		monitor.subTask(SSEUIMessages.ContentAssist_collecting_contexts);
		List proposals= collectContextInformation(viewer, offset, monitor);

		monitor.subTask(SSEUIMessages.ContentAssist_sorting_contexts);
		List filtered= filterAndSortContextInformation(proposals, monitor);
		fNumberOfComputedResults= filtered.size();

		IContextInformation[] result= (IContextInformation[]) filtered.toArray(new IContextInformation[filtered.size()]);
		monitor.done();
		return result;
	}

	/**
	 * <p>Default implementation is to return <code>null</code></p>
	 * <p>Extenders may override</p>
	 * 
	 * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getCompletionProposalAutoActivationCharacters()
	 */
	public char[] getCompletionProposalAutoActivationCharacters() {
		return (fAutoActivation != null) ? fAutoActivation.getCompletionProposalAutoActivationCharacters() : null;
	}

	/**
	 * <p>Default implementation is to return <code>null</code></p>
	 * <p>Extenders may override</p>
	 * 
	 * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationAutoActivationCharacters()
	 */
	public char[] getContextInformationAutoActivationCharacters() {
		return (fAutoActivation != null) ? fAutoActivation.getContextInformationAutoActivationCharacters() : null;
	}

	/**
	 * <p>Extenders may override this function to change error reporting</p>
	 * 
	 * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage()
	 */
	public String getErrorMessage() {
		if (fErrorMessage != null)
			return fErrorMessage;
		if (fNumberOfComputedResults > 0)
			return null;
		return SSEUIMessages.ContentAssist_no_completions;
	}

	/**
	 * @see org.eclipse.wst.sse.ui.contentassist.StructuredContentAssistProcessor#getContextInformationValidator()
	 */
	public IContextInformationValidator getContextInformationValidator() {
		if (this.fContextInformationValidator == null) {
			this.fContextInformationValidator = new ContextInformationValidator();
		}
		return this.fContextInformationValidator;
	}
	
	public void install(ITextViewer viewer) {
		if (fPreferenceStore != null) {
			fPreferenceStore.addPropertyChangeListener(this);
		}
		if (fViewer != null) {
			fViewer.removeTextInputListener(fTextInputListener);
		}
		fViewer = viewer;
		if (fViewer != null) {
			if (fTextInputListener == null) {
				fTextInputListener = new TextInputListener();
			}
			fViewer.addTextInputListener(fTextInputListener);
		}
		if (fAssistant != null) {
			if (fCompletionListener == null) {
				fCompletionListener = new CompletionListener();
			}
			fAssistant.addCompletionListener(fCompletionListener);
		}
	}

	/**
	 * <p>Extenders may override, but should always be sure to call the super implementation</p>
	 * 
	 * @see org.eclipse.wst.sse.ui.internal.IReleasable#release()
	 */
	public void release() {
		if (fAutoActivation != null) {
			fAutoActivation.dispose();
			fAutoActivation = null;
		}
		if(this.fPreferenceStore != null) {
			this.fPreferenceStore.removePropertyChangeListener(this);
		}
		
		if(this.fViewer != null) {
			this.fViewer.removeTextInputListener(this.fTextInputListener);
			this.fViewer = null;
			this.fTextInputListener = null;
		}
		if (this.fAssistant != null && fCompletionListener != null) {
			this.fAssistant.removeCompletionListener(fCompletionListener);
			this.fCompletionListener = null;
		}
	}
	
	/**
	 * <p>Intended to be overridden by extenders wishing to change the behavior
	 * of the processor based on user preferences from the store optionally 
	 * associated with this processor.  If no store was given to the constructor
	 * when creating this assistant then this method will never be invoked.</p>
	 * 
	 * <p>The default implementation does not react to the events in any way</p>
	 * 
	 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
	 */
	public void propertyChange(PropertyChangeEvent event) {
	}
	
	/**
	 * <p>Filters and sorts the proposals. The passed list may be modified
	 * and returned, or a new list may be created and returned.</p>
	 * 
	 * <p>The default implementation does not do any sorting or filtering.</p>
	 * <p>Extenders may override this function.</p>
	 *
	 * @param proposals the list of collected proposals (element type:
	 *        {@link ICompletionProposal})
	 * @param monitor a progress monitor
	 * @param context TODO
	 * @return the list of filtered and sorted proposals, ready for
	 *         display (element type: {@link ICompletionProposal})
	 */
	protected List filterAndSortProposals(List proposals, IProgressMonitor monitor, CompletionProposalInvocationContext context) {
		return proposals;
	}
	
	/**
	 * <p>Filters and sorts the context information objects. The passed
	 * list may be modified and returned, or a new list may be created
	 * and returned.</p>
	 * 
	 * <p>The default implementation does not do any sorting or filtering</p>
	 * <p>Extenders may override this method</p>
	 *
	 * @param contexts the list of collected proposals (element type:
	 *        {@link IContextInformation})
	 * @param monitor a progress monitor
	 * @return the list of filtered and sorted proposals, ready for
	 *         display (element type: {@link IContextInformation})
	 */
	protected List filterAndSortContextInformation(List contexts, IProgressMonitor monitor) {
		return contexts;
	}

	/**
	 * <p>Creates a progress monitor.</p>
	 * <p>The default implementation creates a {@link NullProgressMonitor}.</p>
	 *
	 * <p>Extenders may override this method</p>
	 *
	 * @return a progress monitor
	 */
	protected IProgressMonitor createProgressMonitor() {
		return new NullProgressMonitor();
	}

	/**
	 * <p>Creates the context that is passed to the completion proposal
	 * computers.</p>
	 * 
	 * <p>Extenders may override this method</p>
	 *
	 * @param viewer the viewer that content assist is invoked on
	 * @param offset the content assist offset
	 * @return the context to be passed to the computers
	 */
	protected CompletionProposalInvocationContext createContext(ITextViewer viewer, int offset) {
		return new CompletionProposalInvocationContext(viewer, offset);
	}
	
	/**
	 * @return the associated preference store
	 */
	protected IPreferenceStore getPreferenceStore() {
		return this.fPreferenceStore;
	}
	
	/**
	 * Clears the state
	 */
	private void clearState() {
		fErrorMessage=null;
		fNumberOfComputedResults= 0;
	}

	/**
	 * <p>Collects the proposals from the extensions.</p>
	 *
	 * @param viewer the text viewer
	 * @param offset the offset
	 * @param monitor the progress monitor
	 * @param context the code assist invocation context
	 * @return the list of proposals
	 */
	private List collectProposals(ITextViewer viewer, int offset, IProgressMonitor monitor, CompletionProposalInvocationContext context) {
		List proposals = new ArrayList();
		List categories = getCategories();
		for (Iterator it = categories.iterator(); it.hasNext();) {
			CompletionProposalCategory cat = (CompletionProposalCategory) it.next();
			List computed = cat.computeCompletionProposals(context, this.fContentTypeID, this.fPartitionTypeID, new SubProgressMonitor(monitor, 1));
			proposals.addAll(computed);
			if (fErrorMessage == null) {
				fErrorMessage= cat.getErrorMessage();
			}
		}
		
		// if default page
		// Deal with adding in proposals from processors added through the legacy extension
		if (isFirstPage() && getLegacyExtendedContentAssistProcessors() != null &&
				!getLegacyExtendedContentAssistProcessors().isEmpty()) {
			
			Iterator iter = getLegacyExtendedContentAssistProcessors().iterator();
			while (iter.hasNext()) {
				IContentAssistProcessor legacyProcessor = (IContentAssistProcessor) iter.next();
				ICompletionProposal[] legacyComputed = null;
				try {
					legacyComputed = legacyProcessor.computeCompletionProposals(viewer, offset);
				}
				catch (Exception e) {
					Logger.logException("Problem occurred while gathering proposals from " + legacyProcessor.getClass().getName(), e); //$NON-NLS-1$
				}
				if (legacyComputed != null) {
					proposals.addAll(Arrays.asList(legacyComputed));
				}
			}
		}
		
		return proposals;
	}
	
	/**
	 * <p>Collects the context information from the extensions.</p>
	 * 
	 * @param viewer
	 * @param offset
	 * @param monitor
	 * @return
	 */
	private List collectContextInformation(ITextViewer viewer, int offset, IProgressMonitor monitor) {
		List proposals = new ArrayList();
		CompletionProposalInvocationContext context= createContext(viewer, offset);

		List providers= getCategories();
		for (Iterator it= providers.iterator(); it.hasNext();) {
			CompletionProposalCategory cat= (CompletionProposalCategory) it.next();
			List computed= cat.computeContextInformation(context, this.fContentTypeID, this.fPartitionTypeID, new SubProgressMonitor(monitor, 1));
			proposals.addAll(computed);
			if (fErrorMessage == null) {
				fErrorMessage= cat.getErrorMessage();
			}
		}
		
		// Deal with adding in contexts from processors added through the legacy extension
		if (getLegacyExtendedContentAssistProcessors() != null &&
				!getLegacyExtendedContentAssistProcessors().isEmpty()) {
			
			Iterator iter = getLegacyExtendedContentAssistProcessors().iterator();
			while (iter.hasNext()) {
				IContentAssistProcessor legacyProcessor = (IContentAssistProcessor) iter.next();
				IContextInformation[] legacyComputed = legacyProcessor.computeContextInformation(viewer, offset);
				if(legacyComputed != null) {
					proposals.addAll(Arrays.asList(legacyComputed));
				}
			}
		}

		return proposals;
	}

	/**
	 * @return the next set of categories
	 */
	private List getCategories() {
		List categories;
		if (fCategoryIteration == null) {
			categories =  getProposalCategories();
		} else {
			int iteration= fRepetition % fCategoryIteration.size();
			fAssistant.setStatusMessage(createIterationMessage());
			fAssistant.setEmptyMessage(createEmptyMessage());
			fRepetition++;
	
			categories = (List) fCategoryIteration.get(iteration);
		}
		
		return categories;
	}

	/**
	 *  This may show the warning dialog if all categories are disabled
	 */
	private void resetCategoryIteration() {
		fCategoryIteration = getCategoryIteration();
	}

	/**
	 * @return {@link List} of {@link List}s of {@link CompletionProposalCategory}s, this is
	 * the ordered list of the completion categories to cycle through
	 */
	private List getCategoryIteration() {
		List sequence= new ArrayList();
		sequence.add(getDefaultCategories());
		for (Iterator it= getSortedOwnPageCategories().iterator(); it.hasNext();) {
			CompletionProposalCategory cat= (CompletionProposalCategory) it.next();
			sequence.add(Collections.singletonList(cat));
		}
		return sequence;
	}

	/**
	 * @return the sorted categories for the default page
	 */
	private List getDefaultCategories() {
		// default mix - enable all included computers
		List included= getDefaultCategoriesUnchecked();

		if (included.size() == 0 && CompletionProposalComputerRegistry.getDefault().hasUninstalledComputers()) {
			if (informUserAboutEmptyDefaultCategory()) {
				// preferences were restored - recompute the default categories
				included= getDefaultCategoriesUnchecked();
			}
			CompletionProposalComputerRegistry.getDefault().resetUnistalledComputers();
		}
		
		Collections.sort(included, DEFAULT_PAGE_ORDER_COMPARATOR);

		return included;
	}

	/**
	 * <p>Gets the default categories with no error checking.</p>
	 * 
	 * @return the default {@link CompletionProposalCategory}s
	 */
	private List getDefaultCategoriesUnchecked() {
		List included = new ArrayList();
		for (Iterator it = getProposalCategories().iterator(); it.hasNext();) {
			CompletionProposalCategory category = (CompletionProposalCategory) it.next();
			if (category.isIncludedOnDefaultPage(this.fContentTypeID) && category.hasComputers(fContentTypeID, fPartitionTypeID))
				included.add(category);
		}
		return included;
	}

	/**
	 * <p>Informs the user about the fact that there are no enabled categories in the default content
	 * assist set and shows a link to the preferences.</p>
	 *
	 * @return  <code>true</code> if the default should be restored
	 */
	private boolean informUserAboutEmptyDefaultCategory() {
		/*If warn about empty default category and there are associated properties for this
		 * processors content type and those properties have an associated properties page then
		 * display warning message to user.
		 */
		ICompletionProposalCategoriesConfigurationReader properties = CompletionProposoalCatigoriesConfigurationRegistry.getDefault().getReadableConfiguration(this.fContentTypeID);
		if (OptionalMessageDialog.isDialogEnabled(PREF_WARN_ABOUT_EMPTY_ASSIST_CATEGORY) &&
				properties instanceof ICompletionProposalCategoriesConfigurationWriter  &&
				((ICompletionProposalCategoriesConfigurationWriter)properties).hasAssociatedPropertiesPage()) {
			
			ICompletionProposalCategoriesConfigurationWriter propertiesExtension = (ICompletionProposalCategoriesConfigurationWriter)properties;
			
			final Shell shell= SSEUIPlugin.getActiveWorkbenchShell();
			String title= SSEUIMessages.ContentAssist_all_disabled_title;
			String message= SSEUIMessages.ContentAssist_all_disabled_message;
			// see PreferencePage#createControl for the 'defaults' label
			final String restoreButtonLabel= JFaceResources.getString("defaults"); //$NON-NLS-1$
			final String linkMessage= NLS.bind(SSEUIMessages.ContentAssist_all_disabled_preference_link, LegacyActionTools.removeMnemonics(restoreButtonLabel));
			final int restoreId= IDialogConstants.CLIENT_ID + 10;
			final int settingsId= IDialogConstants.CLIENT_ID + 11;
			final OptionalMessageDialog dialog= new OptionalMessageDialog(PREF_WARN_ABOUT_EMPTY_ASSIST_CATEGORY, shell, title, null /* default image */, message, MessageDialog.WARNING, new String[] { restoreButtonLabel, IDialogConstants.CLOSE_LABEL }, 1) {
				/*
				 * @see org.eclipse.jdt.internal.ui.dialogs.OptionalMessageDialog#createCustomArea(org.eclipse.swt.widgets.Composite)
				 */
				protected Control createCustomArea(Composite composite) {
					// wrap link and checkbox in one composite without space
					Composite parent= new Composite(composite, SWT.NONE);
					GridLayout layout= new GridLayout();
					layout.marginHeight= 0;
					layout.marginWidth= 0;
					layout.verticalSpacing= 0;
					parent.setLayout(layout);

					Composite linkComposite= new Composite(parent, SWT.NONE);
					layout= new GridLayout();
					layout.marginHeight= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
					layout.marginWidth= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
					layout.horizontalSpacing= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
					linkComposite.setLayout(layout);

	        		Link link= new Link(linkComposite, SWT.NONE);
	        		link.setText(linkMessage);
	        		link.addSelectionListener(new SelectionAdapter() {
	        			public void widgetSelected(SelectionEvent e) {
	        				setReturnCode(settingsId);
	        				close();
	        			}
	        		});
	        		GridData gridData= new GridData(SWT.FILL, SWT.BEGINNING, true, false);
	        		gridData.widthHint= this.getMinimumMessageWidth();
					link.setLayoutData(gridData);

					// create checkbox and "don't show this message" prompt
					super.createCustomArea(parent);

					return parent;
	        	}

				/*
				 * @see org.eclipse.jface.dialogs.MessageDialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite)
				 */
				protected void createButtonsForButtonBar(Composite parent) {
			        Button[] buttons= new Button[2];
					buttons[0]= createButton(parent, restoreId, restoreButtonLabel, false);
			        buttons[1]= createButton(parent, IDialogConstants.CLOSE_ID, IDialogConstants.CLOSE_LABEL, true);
			        setButtons(buttons);
				}
	        };
	        int returnValue = dialog.open();
	        
	        //based on user actions either reset defaults or open preference dialog
	        if (restoreId == returnValue || settingsId == returnValue) {
	        	if (restoreId == returnValue) {
	        		propertiesExtension.loadDefaults();
	        		propertiesExtension.saveConfiguration();
	        	}
	        	if (settingsId == returnValue) {
					PreferencesUtil.createPreferenceDialogOn(shell,
							propertiesExtension.getPropertiesPageID(), null, null).open();
	        	}
	        	
	        	return true;
	        }
		}
		return false;
	}

	/**
	 * @return a sorted {@link List} of {@link CompletionProposalCategory}s that
	 * should be displayed on their own content assist page
	 */
	private List getSortedOwnPageCategories() {
		ArrayList sorted= new ArrayList();
		for (Iterator it= getProposalCategories().iterator(); it.hasNext();) {
			CompletionProposalCategory category= (CompletionProposalCategory) it.next();
			if (category.isDisplayedOnOwnPage(this.fContentTypeID) && 
					category.hasComputers(fContentTypeID, fPartitionTypeID)) {
				
				sorted.add(category);
			}
		}
		Collections.sort(sorted, PAGE_ORDER_COMPARATOR);
		return sorted;
	}

	/**
	 * @return a user message describing that there are no content assist suggestions for the current page
	 */
	private String createEmptyMessage() {
		return NLS.bind(SSEUIMessages.ContentAssist_no_message, new String[]{getCategoryLabel(fRepetition)});
	}

	/**
	 * @return user message describing what the next page of content assist holds
	 */
	private String createIterationMessage() {
		return NLS.bind(SSEUIMessages.ContentAssist_toggle_affordance_update_message,
				new String[]{ getCategoryLabel(fRepetition), fIterationGesture, getCategoryLabel(fRepetition + 1) });
	}

	/**
	 * @param repetition which category to get the label for
	 * @return the label of the category
	 */
	private String getCategoryLabel(int repetition) {
		int iteration= (fCategoryIteration != null ? repetition % fCategoryIteration.size() : 0);
		if (iteration == 0)
			return SSEUIMessages.ContentAssist_defaultProposalCategory_title;
		return ((CompletionProposalCategory) ((List) fCategoryIteration.get(iteration)).get(0)).getDisplayName();
	}

	/**
	 * @return {@link String} representing the user command to iterate to the next page
	 */
	private String getIterationGesture() {
		TriggerSequence binding= getIterationBinding();
		return binding != null ?
				NLS.bind(SSEUIMessages.ContentAssist_press, new Object[] { binding.format() })
				: SSEUIMessages.ContentAssist_click;
	}

	/**
	 * @return {@link KeySequence} used by user to iterate to the next page
	 */
	private KeySequence getIterationBinding() {
	    final IBindingService bindingSvc= PlatformUI.getWorkbench().getAdapter(IBindingService.class);
		TriggerSequence binding= bindingSvc.getBestActiveBindingFor(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
		if (binding instanceof KeySequence)
			return (KeySequence) binding;
		return null;
    }
	
	/**
	 * @return <code>true</code> if displaying first page, <code>false</code> otherwise
	 */
	private boolean isFirstPage() {
		return fCategoryIteration == null || fCategoryIteration.size() == 1 || fRepetition % fCategoryIteration.size() == 1;
	}
	
	/**
	 * <p><b>NOTE: </b>This method should be used over accessing the
	 * {@link #fLegacyExtendedContentAssistProcessors} field directly so as to
	 * facilitate the lazy initialization of the field.</p>
	 * 
	 * @return the legacy extended content assist processors
	 */
	private List getLegacyExtendedContentAssistProcessors() {
		if(fLegacyExtendedContentAssistProcessors == null) {
			fLegacyExtendedContentAssistProcessors =
				ExtendedConfigurationBuilder.getInstance().getConfigurations(
						CONTENT_ASSIST_PROCESSOR_EXTENDED_ID, fPartitionTypeID);
		}
		
		return fLegacyExtendedContentAssistProcessors;
	}
	
	/**
	 * <p><b>NOTE: </b>This method should be used over accessing the {@link #fCategories}
	 * field directly so as to facilitate the lazy initialization of the field.</p>
	 * 
	 * @return the categories associated with the content type this processor is associated with
	 */
	private List getProposalCategories() {
		if(fCategories == null) {
			fCategories =
				CompletionProposalComputerRegistry.getDefault().getProposalCategories(fContentTypeID);
		}
		
		return fCategories;
	}
	
	/**
	 * The completion listener class for this processor.
	 */
	private final class CompletionListener implements ICompletionListener, ICompletionListenerExtension {
		/**
		 * @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionStarted(org.eclipse.jface.text.contentassist.ContentAssistEvent)
		 */
		public void assistSessionStarted(ContentAssistEvent event) {
			if (event.processor == StructuredContentAssistProcessor.this ||
					(event.processor instanceof CompoundContentAssistProcessor &&
							((CompoundContentAssistProcessor)event.processor).containsProcessor(StructuredContentAssistProcessor.this))) {
				
				fIterationGesture= getIterationGesture();
				KeySequence binding= getIterationBinding();
	
				// This may show the warning dialog if all categories are disabled
				resetCategoryIteration();
				for (Iterator it= StructuredContentAssistProcessor.this.getProposalCategories().iterator(); it.hasNext();) {
					CompletionProposalCategory cat= (CompletionProposalCategory) it.next();
					cat.sessionStarted();
				}
	
				fRepetition= 0;
				if (event.assistant instanceof IContentAssistantExtension2) {
					IContentAssistantExtension2 extension= (IContentAssistantExtension2) event.assistant;
	
					if (fCategoryIteration.size() == 1) {
						extension.setRepeatedInvocationMode(false);
						extension.setShowEmptyList(false);
					} else {
						extension.setRepeatedInvocationMode(true);
						extension.setStatusLineVisible(true);
						extension.setStatusMessage(createIterationMessage());
						extension.setShowEmptyList(true);
						if (extension instanceof IContentAssistantExtension3) {
							IContentAssistantExtension3 ext3= (IContentAssistantExtension3) extension;
							((ContentAssistant) ext3).setRepeatedInvocationTrigger(binding);
						}
					}
				}
			}
		}

		/**
		 * @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionEnded(org.eclipse.jface.text.contentassist.ContentAssistEvent)
		 */
		public void assistSessionEnded(ContentAssistEvent event) {
			if (event.processor == StructuredContentAssistProcessor.this ||	(event.processor instanceof CompoundContentAssistProcessor && ((CompoundContentAssistProcessor)event.processor).containsProcessor(StructuredContentAssistProcessor.this))) {
				for (Iterator it= StructuredContentAssistProcessor.this.getProposalCategories().iterator(); it.hasNext();) {
					CompletionProposalCategory cat= (CompletionProposalCategory) it.next();
					cat.sessionEnded();
				}
	
				fCategoryIteration= null;
				fRepetition= -1;
				fIterationGesture= null;
				if (event.assistant instanceof IContentAssistantExtension2) {
					IContentAssistantExtension2 extension= (IContentAssistantExtension2) event.assistant;
					extension.setShowEmptyList(false);
					extension.setRepeatedInvocationMode(false);
					extension.setStatusLineVisible(false);
					if (extension instanceof IContentAssistantExtension3) {
						IContentAssistantExtension3 ext3= (IContentAssistantExtension3) extension;
						((ContentAssistant) ext3).setRepeatedInvocationTrigger(null);
					}
				}
			}
		}

		/**
		 * @see org.eclipse.jface.text.contentassist.ICompletionListener#selectionChanged(org.eclipse.jface.text.contentassist.ICompletionProposal, boolean)
		 */
		public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
			//ignore
		}

		/**
		 * @see org.eclipse.jface.text.contentassist.ICompletionListenerExtension#assistSessionRestarted(org.eclipse.jface.text.contentassist.ContentAssistEvent)
		 */
		public void assistSessionRestarted(ContentAssistEvent event) {
			fRepetition= 0;
		}
	}
	
	/**
	 * 
	 */
	private class TextInputListener implements ITextInputListener {

		/**
		 * <p>Set the content type based on the new document if it has not already been
		 * set yet.</p>
		 * 
		 * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
		 */
		public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
			if(fContentTypeID == null) {
				if(newInput instanceof IStructuredDocument) {
					IStructuredModel model = null;
					try {
						model = StructuredModelManager.getModelManager().getModelForRead((IStructuredDocument)newInput);
						if(model != null) {
							fContentTypeID = model.getContentTypeIdentifier();
							if (fAutoActivation != null) {
								fAutoActivation.dispose();
							}
							fAutoActivation = CompletionProposalComputerRegistry.getDefault().getActivator(fContentTypeID, fPartitionTypeID);
						}
					} finally {
						if(model != null) {
							model.releaseFromRead();
						}
					}
				}
			}
		}
		
		/**
		 * <p>Ignored</p>
		 * 
		 * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
		 */
		public void inputDocumentAboutToBeChanged(IDocument oldInput,
				IDocument newInput) {
			//ignore
		}
	}

	protected void setAutoActivationDelay(int delay) {
		fAssistant.setAutoActivationDelay(delay);
	}

}
