/*******************************************************************************
 * Copyright (c) 2006, 2015 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
 *******************************************************************************/
package org.eclipse.ui.internal.texteditor;


import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.window.Window;

import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.revisions.IRevisionRulerColumn;
import org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension;
import org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension.RenderingMode;
import org.eclipse.jface.text.revisions.RevisionInformation;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.IAnnotationHover;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.IChangeRulerColumn;
import org.eclipse.jface.text.source.ILineDifferExtension;
import org.eclipse.jface.text.source.ILineDifferExtension2;
import org.eclipse.jface.text.source.ISharedTextColors;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRulerColumn;
import org.eclipse.jface.text.source.IVerticalRulerInfo;
import org.eclipse.jface.text.source.IVerticalRulerInfoExtension;
import org.eclipse.jface.text.source.IVerticalRulerListener;
import org.eclipse.jface.text.source.LineNumberChangeRulerColumn;
import org.eclipse.jface.text.source.LineNumberRulerColumn;

import org.eclipse.ui.internal.editors.text.EditorsPlugin;

import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.AnnotationPreference;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorExtension;
import org.eclipse.ui.texteditor.ITextEditorExtension2;
import org.eclipse.ui.texteditor.MarkerAnnotationPreferences;
import org.eclipse.ui.texteditor.quickdiff.QuickDiff;
import org.eclipse.ui.texteditor.rulers.AbstractContributedRulerColumn;

import org.eclipse.ui.editors.text.EditorsUI;

/**
 * The line number ruler contribution. Encapsulates a {@link LineNumberChangeRulerColumn} as a
 * contribution to the <code>rulerColumns</code> extension point. Instead of instantiating the
 * delegate itself, it calls <code>createLineNumberRulerColumn()</code> in
 * {@link AbstractDecoratedTextEditor} via {@link ICompatibilityForwarder} to maintain compatibility
 * with previous releases.
 *
 * @since 3.3
 */
public class LineNumberColumn extends AbstractContributedRulerColumn implements IVerticalRulerInfo, IVerticalRulerInfoExtension {
	/**
	 * Forwarder for preference checks and ruler creation. Needed to maintain the forwarded APIs in
	 * {@link AbstractDecoratedTextEditor}.
	 */
	public static interface ICompatibilityForwarder {
		IVerticalRulerColumn createLineNumberRulerColumn();
		boolean isQuickDiffEnabled();
		boolean isLineNumberRulerVisible();
	}

	/** The contribution id of the line number / change ruler. */
	public static final String ID= "org.eclipse.ui.editors.columns.linenumbers"; //$NON-NLS-1$

	private static final String FG_COLOR_KEY= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR;
	private static final String BG_COLOR_KEY= AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND;
	private static final String USE_DEFAULT_BG_KEY= AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT;
	private final static String LINE_NUMBER_KEY= AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER;
	private final static String REVISION_ASK_BEFORE_QUICKDIFF_SWITCH_KEY= AbstractDecoratedTextEditorPreferenceConstants.REVISION_ASK_BEFORE_QUICKDIFF_SWITCH;

	/**
	 * The delegate and implemenation of the ruler.
	 */
	private IVerticalRulerColumn fDelegate;
	/**
	 * The annotation preferences.
	 */
	private final MarkerAnnotationPreferences fAnnotationPreferences= EditorsPlugin.getDefault().getMarkerAnnotationPreferences();

	/**
	 * Preference dispatcher that registers a single listener so we don't have to manage every
	 * single preference listener.
	 */
	private PropertyEventDispatcher fDispatcher;
	private ISourceViewer fViewer;
	private ICompatibilityForwarder fForwarder;

	@Override
	public Control createControl(CompositeRuler parentRuler, Composite parentControl) {
		Assert.isTrue(fDelegate != null);
		ITextViewer viewer= parentRuler.getTextViewer();
		Assert.isLegal(viewer instanceof ISourceViewer);
		fViewer= (ISourceViewer) viewer;
		initialize();
		Control control= fDelegate.createControl(parentRuler, parentControl);
		return control;
	}

	@Override
	public Control getControl() {
		return fDelegate.getControl();
	}

	@Override
	public int getWidth() {
		return fDelegate.getWidth();
	}

	@Override
	public void redraw() {
		fDelegate.redraw();
	}

	@Override
	public void setFont(Font font) {
		fDelegate.setFont(font);
	}

	@Override
	public void setModel(IAnnotationModel model) {
		if (getQuickDiffPreference())
			fDelegate.setModel(model);
	}

	@Override
	public int getLineOfLastMouseButtonActivity() {
		if (fDelegate instanceof IVerticalRulerInfo)
			return ((IVerticalRulerInfo)fDelegate).getLineOfLastMouseButtonActivity();
		return -1;
	}

	@Override
	public int toDocumentLineNumber(int y_coordinate) {
		if (fDelegate instanceof IVerticalRulerInfo)
			return ((IVerticalRulerInfo)fDelegate).toDocumentLineNumber(y_coordinate);
		return -1;
	}

	@Override
	public void addVerticalRulerListener(IVerticalRulerListener listener) {
		if (fDelegate instanceof IVerticalRulerInfoExtension)
			((IVerticalRulerInfoExtension) fDelegate).addVerticalRulerListener(listener);
	}

	@Override
	public IAnnotationHover getHover() {
		if (fDelegate instanceof IVerticalRulerInfoExtension)
			return ((IVerticalRulerInfoExtension) fDelegate).getHover();
		return null;
	}

	@Override
	public IAnnotationModel getModel() {
		if (fDelegate instanceof IVerticalRulerInfoExtension)
			return ((IVerticalRulerInfoExtension) fDelegate).getModel();
		return null;
	}

	@Override
	public void removeVerticalRulerListener(IVerticalRulerListener listener) {
		if (fDelegate instanceof IVerticalRulerInfoExtension)
			((IVerticalRulerInfoExtension) fDelegate).removeVerticalRulerListener(listener);
	}

	@Override
	public void columnRemoved() {
		if (fDispatcher != null) {
			fDispatcher.dispose();
			fDispatcher= null;
		}
	}

	private IPreferenceStore getPreferenceStore() {
		return EditorsUI.getPreferenceStore();
	}

	private ISharedTextColors getSharedColors() {
		return EditorsUI.getSharedTextColors();
	}

	/**
	 * Initializes the given line number ruler column from the preference store.
	 */
	private void initialize() {
		final IPreferenceStore store= getPreferenceStore();
		if (store == null)
			return;

		// initial set up
		updateForegroundColor(store, fDelegate);
		updateBackgroundColor(store, fDelegate);

		updateLineNumbersVisibility(fDelegate);
		updateQuickDiffVisibility(fDelegate);
		updateCharacterMode(store, fDelegate);
		updateRevisionRenderingMode(store, fDelegate);
		updateRevisionAuthorVisibility(store, fDelegate);
		updateRevisionIdVisibility(store, fDelegate);

		Map<Object, AnnotationPreference> annotationPrefs= getAnnotationPreferenceMap();
		final AnnotationPreference changedPref= annotationPrefs.get("org.eclipse.ui.workbench.texteditor.quickdiffChange"); //$NON-NLS-1$
		final AnnotationPreference addedPref= annotationPrefs.get("org.eclipse.ui.workbench.texteditor.quickdiffAddition"); //$NON-NLS-1$
		final AnnotationPreference deletedPref= annotationPrefs.get("org.eclipse.ui.workbench.texteditor.quickdiffDeletion"); //$NON-NLS-1$
		updateChangedColor(changedPref, store, fDelegate);
		updateAddedColor(addedPref, store, fDelegate);
		updateDeletedColor(deletedPref, store, fDelegate);

		fDelegate.redraw();

		// listen to changes
		fDispatcher= new PropertyEventDispatcher(store);

		fDispatcher.addPropertyChangeListener(FG_COLOR_KEY, event -> {
			updateForegroundColor(store, fDelegate);
			fDelegate.redraw();
		});
		IPropertyChangeListener backgroundHandler= event -> {
			updateBackgroundColor(store, fDelegate);
			fDelegate.redraw();
		};
		fDispatcher.addPropertyChangeListener(BG_COLOR_KEY, backgroundHandler);
		fDispatcher.addPropertyChangeListener(USE_DEFAULT_BG_KEY, backgroundHandler);

		fDispatcher.addPropertyChangeListener(LINE_NUMBER_KEY, event -> updateLineNumbersVisibility(fDelegate));

		fDispatcher.addPropertyChangeListener(AbstractDecoratedTextEditorPreferenceConstants.QUICK_DIFF_CHARACTER_MODE, event -> updateCharacterMode(store, fDelegate));

		fDispatcher.addPropertyChangeListener(AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_RENDERING_MODE, event -> updateRevisionRenderingMode(store, fDelegate));

		fDispatcher.addPropertyChangeListener(AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_SHOW_AUTHOR, event -> updateRevisionAuthorVisibility(store, fDelegate));

		fDispatcher.addPropertyChangeListener(AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_SHOW_REVISION, event -> updateRevisionIdVisibility(store, fDelegate));

		fDispatcher.addPropertyChangeListener(AbstractDecoratedTextEditorPreferenceConstants.QUICK_DIFF_ALWAYS_ON, event -> updateQuickDiffVisibility(fDelegate));

		if (changedPref != null) {
			fDispatcher.addPropertyChangeListener(changedPref.getColorPreferenceKey(), event -> {
				updateChangedColor(changedPref, store, fDelegate);
				fDelegate.redraw();
			});
		}
		if (addedPref != null) {
			fDispatcher.addPropertyChangeListener(addedPref.getColorPreferenceKey(), event -> {
				updateAddedColor(addedPref, store, fDelegate);
				fDelegate.redraw();
			});
		}
		if (deletedPref != null) {
			fDispatcher.addPropertyChangeListener(deletedPref.getColorPreferenceKey(), event -> {
				updateDeletedColor(deletedPref, store, fDelegate);
				fDelegate.redraw();
			});
		}
	}

	private Map<Object, AnnotationPreference> getAnnotationPreferenceMap() {
		Map<Object, AnnotationPreference> annotationPrefs= new HashMap<>();
		Iterator<AnnotationPreference> iter= fAnnotationPreferences.getAnnotationPreferences().iterator();
		while (iter.hasNext()) {
			AnnotationPreference pref= iter.next();
			Object type= pref.getAnnotationType();
			annotationPrefs.put(type, pref);
		}
		return annotationPrefs;
	}

	private void updateForegroundColor(IPreferenceStore store, IVerticalRulerColumn column) {
		RGB rgb=  getColorFromStore(store, FG_COLOR_KEY);
		if (rgb == null)
			rgb= new RGB(0, 0, 0);
		ISharedTextColors sharedColors= getSharedColors();
		if (column instanceof LineNumberRulerColumn)
			((LineNumberRulerColumn) column).setForeground(sharedColors.getColor(rgb));
	}

	private void updateBackgroundColor(IPreferenceStore store, IVerticalRulerColumn column) {
		// background color: same as editor, or system default
		RGB rgb;
		if (store.getBoolean(USE_DEFAULT_BG_KEY))
			rgb= null;
		else
			rgb= getColorFromStore(store, BG_COLOR_KEY);
		ISharedTextColors sharedColors= getSharedColors();
		if (column instanceof LineNumberRulerColumn)
			((LineNumberRulerColumn) column).setBackground(sharedColors.getColor(rgb));
	}

	private void updateChangedColor(AnnotationPreference pref, IPreferenceStore store, IVerticalRulerColumn column) {
		if (pref != null && column instanceof IChangeRulerColumn) {
			RGB rgb= getColorFromAnnotationPreference(store, pref);
			((IChangeRulerColumn) column).setChangedColor(getSharedColors().getColor(rgb));
		}
	}

	private void updateAddedColor(AnnotationPreference pref, IPreferenceStore store, IVerticalRulerColumn column) {
		if (pref != null && column instanceof IChangeRulerColumn) {
			RGB rgb= getColorFromAnnotationPreference(store, pref);
			((IChangeRulerColumn) column).setAddedColor(getSharedColors().getColor(rgb));
		}
	}

	private void updateDeletedColor(AnnotationPreference pref, IPreferenceStore store, IVerticalRulerColumn column) {
		if (pref != null && column instanceof IChangeRulerColumn) {
			RGB rgb= getColorFromAnnotationPreference(store, pref);
			((IChangeRulerColumn) column).setDeletedColor(getSharedColors().getColor(rgb));
		}
	}

	private void updateCharacterMode(IPreferenceStore store, IVerticalRulerColumn column) {
		if (column instanceof LineNumberChangeRulerColumn) {
			LineNumberChangeRulerColumn lncrc= (LineNumberChangeRulerColumn) column;
			lncrc.setDisplayMode(store.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.QUICK_DIFF_CHARACTER_MODE));
		}
	}

	private void updateLineNumbersVisibility(IVerticalRulerColumn column) {
		if (column instanceof LineNumberChangeRulerColumn)
			((LineNumberChangeRulerColumn) column).showLineNumbers(getLineNumberPreference());
	}

	private void updateRevisionRenderingMode(IPreferenceStore store, IVerticalRulerColumn column) {
		if (column instanceof IRevisionRulerColumnExtension) {
			String option= store.getString(AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_RENDERING_MODE);
			RenderingMode[] modes= { IRevisionRulerColumnExtension.AUTHOR, IRevisionRulerColumnExtension.AGE, IRevisionRulerColumnExtension.AUTHOR_SHADED_BY_AGE };
			for (RenderingMode mode : modes) {
				if (mode.name().equals(option)) {
					((IRevisionRulerColumnExtension) column).setRevisionRenderingMode(mode);
					return;
				}
			}
		}
	}

	private void updateRevisionAuthorVisibility(IPreferenceStore store, IVerticalRulerColumn column) {
		if (column instanceof IRevisionRulerColumnExtension) {
			boolean show= store.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_SHOW_AUTHOR);
			((IRevisionRulerColumnExtension) column).showRevisionAuthor(show);
		}
	}

	private void updateRevisionIdVisibility(IPreferenceStore store, IVerticalRulerColumn column) {
		if (column instanceof IRevisionRulerColumnExtension) {
			boolean show= store.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.REVISION_RULER_SHOW_REVISION);
			((IRevisionRulerColumnExtension) column).showRevisionId(show);
		}
	}

	private void updateQuickDiffVisibility(IVerticalRulerColumn column) {
		boolean show= getQuickDiffPreference();
		if (show == isShowingChangeInformation())
			return;

		if (show)
			installChangeRulerModel(column);
		else
			uninstallChangeRulerModel(column);
	}

	/**
	 * Returns whether the line number ruler column should be
	 * visible according to the preference store settings. Subclasses may override this
	 * method to provide a custom preference setting.
	 *
	 * @return <code>true</code> if the line numbers should be visible
	 */
	private boolean getLineNumberPreference() {
		if (fForwarder != null)
			return fForwarder.isLineNumberRulerVisible();
		IPreferenceStore store= getPreferenceStore();
		return store != null ? store.getBoolean(LINE_NUMBER_KEY) : false;
	}

	/**
	 * Returns whether quick diff info should be visible upon opening an editor
	 * according to the preference store settings.
	 *
	 * @return <code>true</code> if the line numbers should be visible
	 */
	private boolean getQuickDiffPreference() {
		if (fForwarder != null)
			return fForwarder.isQuickDiffEnabled();
		IPreferenceStore store= getPreferenceStore();
		boolean setting= store != null ? store.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.QUICK_DIFF_ALWAYS_ON) : false;
		if (!setting)
			return false;

		boolean modifiable;
		ITextEditor editor= getEditor();
		if (editor instanceof ITextEditorExtension2) {
			ITextEditorExtension2 ext= (ITextEditorExtension2) editor;
			modifiable= ext.isEditorInputModifiable();
		} else if (editor instanceof ITextEditorExtension) {
			ITextEditorExtension ext= (ITextEditorExtension) editor;
			modifiable= ext.isEditorInputReadOnly();
		} else if (editor != null) {
			modifiable= editor.isEditable();
		} else {
			modifiable= true;
		}
		return modifiable;
	}

	/**
	 * Extracts the color preference for the given preference from the given store.
	 * If the given store indicates that the default value is to be used, or
	 * the value stored in the preferences store is <code>null</code>,
	 * the value is taken from the <code>AnnotationPreference</code>'s default
	 * color value.
	 * <p>
	 * The return value is
	 * </p>
	 *
	 * @param store the preference store
	 * @param pref the annotation preference
	 * @return the RGB color preference, not <code>null</code>
	 */
	private static RGB getColorFromAnnotationPreference(IPreferenceStore store, AnnotationPreference pref) {
		String key= pref.getColorPreferenceKey();
		RGB rgb= null;
		if (store.contains(key)) {
			if (store.isDefault(key))
				rgb= pref.getColorPreferenceValue();
			else
				rgb= PreferenceConverter.getColor(store, key);
		}
		if (rgb == null)
			rgb= pref.getColorPreferenceValue();
		return rgb;
	}

	private static RGB getColorFromStore(IPreferenceStore store, String key) {
		RGB rgb= null;
		if (store.contains(key)) {
			if (store.isDefault(key))
				rgb= PreferenceConverter.getDefaultColor(store, key);
			else
				rgb= PreferenceConverter.getColor(store, key);
		}
		return rgb;
	}

	/**
	 * Ensures that quick diff information is displayed and the quick diff provider is the one with
	 * the specified id. If a different quick diff provider is in use, the user may be asked whether
	 * he wants to switch.
	 *
	 * @param diffProviderId the quick diff provider id to use
	 * @return <code>true</code> if quick diff could be enabled for the given id,
	 *         <code>false</code> otherwise
	 */
	private boolean ensureQuickDiffProvider(String diffProviderId) {
		if (!isShowingChangeInformation())
			installChangeRulerModel(fDelegate); // FIXME pass provider id

		IAnnotationModel annotationModel= fViewer.getAnnotationModel();
		IAnnotationModel oldDiffer= getDiffer();
		if (oldDiffer == null && annotationModel != null)
			return false; // quick diff is enabled, but no differ? not working for whatever reason

		if (annotationModel == null)
			annotationModel= new AnnotationModel();
		if (!(annotationModel instanceof IAnnotationModelExtension))
			return false;

		QuickDiff util= new QuickDiff();
		Object oldDifferId= util.getConfiguredQuickDiffProvider(oldDiffer);
		if (oldDifferId.equals(diffProviderId)) {
			if (oldDiffer instanceof ILineDifferExtension)
				((ILineDifferExtension) oldDiffer).resume();
			return true;
		}

		// Check whether the desired provider is available at all
		IAnnotationModel newDiffer= util.createQuickDiffAnnotationModel(getEditor(), diffProviderId);
		if (util.getConfiguredQuickDiffProvider(newDiffer).equals(oldDifferId)) {
			if (oldDiffer instanceof ILineDifferExtension)
				((ILineDifferExtension) oldDiffer).resume();
				return true;
		}

		// quick diff is showing with the wrong provider - ask the user whether he wants to switch
		IPreferenceStore store= EditorsUI.getPreferenceStore();
		if (oldDiffer != null && !store.getString(REVISION_ASK_BEFORE_QUICKDIFF_SWITCH_KEY).equals(MessageDialogWithToggle.ALWAYS)) {
			MessageDialogWithToggle toggleDialog= MessageDialogWithToggle.openOkCancelConfirm(
					fViewer.getTextWidget().getShell(),
					RulerMessages.AbstractDecoratedTextEditor_revision_quickdiff_switch_title,
					RulerMessages.AbstractDecoratedTextEditor_revision_quickdiff_switch_message,
					RulerMessages.AbstractDecoratedTextEditor_revision_quickdiff_switch_rememberquestion,
					true,
					store,
					REVISION_ASK_BEFORE_QUICKDIFF_SWITCH_KEY);
			if (toggleDialog.getReturnCode() != Window.OK)
				return false;
		}

		IAnnotationModelExtension modelExtension=(IAnnotationModelExtension) annotationModel;
		modelExtension.removeAnnotationModel(IChangeRulerColumn.QUICK_DIFF_MODEL_ID);


		modelExtension.addAnnotationModel(IChangeRulerColumn.QUICK_DIFF_MODEL_ID, newDiffer);

		if (fDelegate instanceof IChangeRulerColumn)
			((IChangeRulerColumn) fDelegate).setModel(annotationModel); // picks up the new model attachment

		return true;
	}

	/**
	 * Installs the differ annotation model with the current quick diff display.
	 *
	 * @param column the column to install the model on
	 */
	private void installChangeRulerModel(IVerticalRulerColumn column) {
		if (column instanceof IChangeRulerColumn) {
			IAnnotationModel model= getAnnotationModelWithDiffer();
			((IChangeRulerColumn) column).setModel(model);
			if (model != null) {
				ISourceViewer viewer= fViewer;
				if (viewer != null && viewer.getAnnotationModel() == null && column.getControl() != null)
					viewer.showAnnotations(true);
				}
		}
	}

	/**
	 * Uninstalls the differ annotation model from the current quick diff display.
	 *
	 * @param column the column to remove the model from
	 */
	private void uninstallChangeRulerModel(IVerticalRulerColumn column) {
		if (column instanceof IChangeRulerColumn)
			((IChangeRulerColumn) column).setModel(null);
		IAnnotationModel model= getDiffer();
		if (model instanceof ILineDifferExtension)
			((ILineDifferExtension) model).suspend();

		ISourceViewer viewer= fViewer;
		if (viewer != null && viewer.getAnnotationModel() == null)
			viewer.showAnnotations(false);
	}

	/**
	 * Returns the annotation model that contains the quick diff annotation model.
	 * <p>
	 * Extracts the line differ from the displayed document's annotation model. If none can be found,
	 * a new differ is created and attached to the annotation model.</p>
	 *
	 * @return the annotation model that contains the line differ, or <code>null</code> if none could be found or created
	 * @see IChangeRulerColumn#QUICK_DIFF_MODEL_ID
	 */
	private IAnnotationModel getAnnotationModelWithDiffer() {
		ISourceViewer viewer= fViewer;
		if (viewer == null)
			return null;

		IAnnotationModel m= viewer.getAnnotationModel();
		IAnnotationModelExtension model= null;
		if (m instanceof IAnnotationModelExtension)
			model= (IAnnotationModelExtension) m;

		IAnnotationModel differ= getDiffer();
		// create diff model if it doesn't
		if (differ == null) {
			IPreferenceStore store= getPreferenceStore();
			if (store != null) {
				String defaultId= store.getString(AbstractDecoratedTextEditorPreferenceConstants.QUICK_DIFF_DEFAULT_PROVIDER);
				differ= new QuickDiff().createQuickDiffAnnotationModel(getEditor(), defaultId);
				if (differ != null) {
					if (model == null)
						model= new AnnotationModel();
					model.addAnnotationModel(IChangeRulerColumn.QUICK_DIFF_MODEL_ID, differ);
				}
			}
		} else if (differ instanceof ILineDifferExtension2) {
			if (((ILineDifferExtension2) differ).isSuspended())
				((ILineDifferExtension) differ).resume();
		} else if (differ instanceof ILineDifferExtension) {
			((ILineDifferExtension) differ).resume();
		}

		return (IAnnotationModel)model;
	}

	/**
	 * Extracts the line differ from the displayed document's annotation model. If none can be found,
	 * <code>null</code> is returned.
	 *
	 * @return the line differ, or <code>null</code> if none could be found
	 */
	private IAnnotationModel getDiffer() {
		// get annotation model extension
		ISourceViewer viewer= fViewer;
		if (viewer == null)
			return null;

		IAnnotationModel m= viewer.getAnnotationModel();
		if (m == null && fDelegate instanceof IChangeRulerColumn)
			m= ((IChangeRulerColumn)fDelegate).getModel();

		if (!(m instanceof IAnnotationModelExtension))
			return null;

		IAnnotationModelExtension model= (IAnnotationModelExtension)m;

		// get diff model if it exists already
		return model.getAnnotationModel(IChangeRulerColumn.QUICK_DIFF_MODEL_ID);
	}

	/**
	 * Sets the forwarder. Used by {@link AbstractDecoratedTextEditor} to maintain the contract of
	 * its {@link AbstractDecoratedTextEditor#createLineNumberRulerColumn} method.
	 *
	 * @param forwarder the forwarder
	 */
	public void setForwarder(ICompatibilityForwarder forwarder) {
		fForwarder= forwarder;
		fDelegate= forwarder.createLineNumberRulerColumn();
	}

	/**
	 * Initializes the given line number ruler column from the preference store.
	 *
	 * @param rulerColumn the ruler column to be initialized
	 */
	public void initializeLineNumberRulerColumn(LineNumberRulerColumn rulerColumn) {
		IPreferenceStore store= getPreferenceStore();
		if (store != null) {
			updateForegroundColor(store, rulerColumn);
			updateBackgroundColor(store, rulerColumn);
			updateLineNumbersVisibility(rulerColumn);
			rulerColumn.redraw();
		}
	}

	/**
	 * Returns <code>true</code> if the ruler is showing line numbers, <code>false</code> if it
	 * is only showing change information.
	 *
	 * @return <code>true</code> if line numbers are shown, <code>false</code> otherwise
	 */
	public boolean isShowingLineNumbers() {
		return fDelegate instanceof LineNumberChangeRulerColumn && ((LineNumberChangeRulerColumn) fDelegate).isShowingLineNumbers();
	}

	/**
	 * Returns <code>true</code> if the ruler is showing change information, <code>false</code>
	 * if it is only showing line numbers.
	 *
	 * @return <code>true</code> if change information is shown, <code>false</code> otherwise
	 */
	public boolean isShowingChangeInformation() {
		return fDelegate instanceof LineNumberChangeRulerColumn && ((LineNumberChangeRulerColumn) fDelegate).isShowingChangeInformation();
	}

	/**
	 * Shows revision information on the receiver.
	 *
	 * @param info the revision information to show
	 * @param quickDiffProviderId the id of the corresponding quick diff provider
	 */
	public void showRevisionInformation(RevisionInformation info, String quickDiffProviderId) {
		if (!ensureQuickDiffProvider(quickDiffProviderId))
			return;

		if (fDelegate instanceof IRevisionRulerColumn)
			((IRevisionRulerColumn) fDelegate).setRevisionInformation(info);
	}

	/**
	 * Hides revision information.
	 */
	public void hideRevisionInformation() {
		if (fDelegate instanceof IRevisionRulerColumn)
			((IRevisionRulerColumn) fDelegate).setRevisionInformation(null);
	}

	/**
	 * Returns <code>true</code> if the ruler is showing revision information, <code>false</code>
	 * if it is only showing line numbers.
	 *
	 * @return <code>true</code> if revision information is shown, <code>false</code> otherwise
	 */
	public boolean isShowingRevisionInformation() {
		if (fDelegate instanceof LineNumberChangeRulerColumn)
			return ((LineNumberChangeRulerColumn) fDelegate).isShowingRevisionInformation();
		return false;
	}

	/**
	 * Returns the selection provider of the revision column, <code>null</code> if none is
	 * available.
	 *
	 * @return the revision selection provider
	 */
	public ISelectionProvider getRevisionSelectionProvider() {
		if (fDelegate instanceof IRevisionRulerColumnExtension)
			return ((IRevisionRulerColumnExtension) fDelegate).getRevisionSelectionProvider();
		return null;
	}
}
