/*******************************************************************************
 * Copyright (c) 2000, 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.jface.text.source;

import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jface.internal.text.revisions.RevisionPainter;
import org.eclipse.jface.internal.text.source.DiffPainter;
import org.eclipse.jface.viewers.ISelectionProvider;

import org.eclipse.jface.text.revisions.IRevisionListener;
import org.eclipse.jface.text.revisions.IRevisionRulerColumn;
import org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension;
import org.eclipse.jface.text.revisions.RevisionInformation;

/**
 * A vertical ruler column displaying line numbers and serving as a UI for quick diff. Clients
 * usually instantiate and configure object of this class.
 *
 * @since 3.0
 * @noextend This class is not intended to be subclassed by clients.
 */
public class LineNumberChangeRulerColumn extends LineNumberRulerColumn implements IChangeRulerColumn, IRevisionRulerColumn, IRevisionRulerColumnExtension {
	/** The ruler's annotation model. */
	private IAnnotationModel fAnnotationModel;
	/** <code>true</code> if changes should be displayed using character indications instead of background colors. */
	private boolean fCharacterDisplay;
	/**
	 * The revision painter strategy.
	 *
	 * @since 3.2
	 */
	private final RevisionPainter fRevisionPainter;
	/**
	 * The diff information painter strategy.
	 *
	 * @since 3.2
	 */
	private final DiffPainter fDiffPainter;
	/**
	 * Whether to show number or to behave like a change ruler column.
	 * @since 3.3
	 */
	private boolean fShowNumbers= true;

	/**
	 * Creates a new instance.
	 *
	 * @param sharedColors the shared colors provider to use
	 */
	public LineNumberChangeRulerColumn(ISharedTextColors sharedColors) {
		Assert.isNotNull(sharedColors);
		fRevisionPainter= new RevisionPainter(this, sharedColors);
		fDiffPainter= new DiffPainter(this, sharedColors);
	}

	@Override
	public Control createControl(CompositeRuler parentRuler, Composite parentControl) {
		Control control= super.createControl(parentRuler, parentControl);
		fRevisionPainter.setParentRuler(parentRuler);
		fDiffPainter.setParentRuler(parentRuler);
		return control;
	}

	@Override
	public int getLineOfLastMouseButtonActivity() {
		return getParentRuler().getLineOfLastMouseButtonActivity();
	}

	@Override
	public int toDocumentLineNumber(int y_coordinate) {
		return getParentRuler().toDocumentLineNumber(y_coordinate);
	}

	@Override
	public void setModel(IAnnotationModel model) {
		setAnnotationModel(model);
		fRevisionPainter.setModel(model);
		fDiffPainter.setModel(model);
		updateNumberOfDigits();
		computeIndentations();
		layout(true);
		postRedraw();
	}

	private void setAnnotationModel(IAnnotationModel model) {
		if (fAnnotationModel != model)
			fAnnotationModel= model;
	}


	/**
	 * Sets the display mode of the ruler. If character mode is set to <code>true</code>, diff
	 * information will be displayed textually on the line number ruler.
	 *
	 * @param characterMode <code>true</code> if diff information is to be displayed textually.
	 */
	public void setDisplayMode(boolean characterMode) {
		if (characterMode != fCharacterDisplay) {
			fCharacterDisplay= characterMode;
			updateNumberOfDigits();
			computeIndentations();
			layout(true);
		}
	}

	@Override
	public IAnnotationModel getModel() {
		return fAnnotationModel;
	}

	@Override
	protected String createDisplayString(int line) {
		StringBuilder buffer= new StringBuilder();
		if (fShowNumbers)
			buffer.append(super.createDisplayString(line));
		if (fCharacterDisplay && getModel() != null)
			buffer.append(fDiffPainter.getDisplayCharacter(line));
		return buffer.toString();
	}

	@Override
	protected int computeNumberOfDigits() {
		int digits;
		if (fCharacterDisplay && getModel() != null) {
			if (fShowNumbers)
				digits= super.computeNumberOfDigits() + 1;
			else
				digits= 1;
		} else {
			if (fShowNumbers)
				digits= super.computeNumberOfDigits();
			else
				digits= 0;
		}
		if (fRevisionPainter.hasInformation())
			digits+= fRevisionPainter.getRequiredWidth();
		return digits;
	}

	@Override
	public void addVerticalRulerListener(IVerticalRulerListener listener) {
		throw new UnsupportedOperationException();
	}

	@Override
	public void removeVerticalRulerListener(IVerticalRulerListener listener) {
		throw new UnsupportedOperationException();
	}

	@Override
	void doPaint(GC gc, ILineRange visibleLines) {
		Color foreground= gc.getForeground();
		if (visibleLines != null) {
			if (fRevisionPainter.hasInformation()) {
				fRevisionPainter.setZoom(fZoom);
				fRevisionPainter.paint(gc, visibleLines);
			} else if (fDiffPainter.hasInformation()) { // don't paint quick diff colors if revisions are painted
				fDiffPainter.setZoom(fZoom);
				fDiffPainter.paint(gc, visibleLines);
			}
		}
		gc.setForeground(foreground);
		if (fShowNumbers || fCharacterDisplay)
			super.doPaint(gc, visibleLines);
	}

	@Override
	public IAnnotationHover getHover() {
		int activeLine= getParentRuler().getLineOfLastMouseButtonActivity();
		if (fRevisionPainter.hasHover(activeLine))
			return fRevisionPainter.getHover();
		if (fDiffPainter.hasHover(activeLine))
			return fDiffPainter.getHover();
		return null;
	}

	@Override
	public void setHover(IAnnotationHover hover) {
		fRevisionPainter.setHover(hover);
		fDiffPainter.setHover(hover);
	}

	@Override
	public void setBackground(Color background) {
		super.setBackground(background);
		fRevisionPainter.setBackground(background);
		fDiffPainter.setBackground(background);
	}

	@Override
	public void setAddedColor(Color addedColor) {
		fDiffPainter.setAddedColor(addedColor);
	}

	@Override
	public void setChangedColor(Color changedColor) {
		fDiffPainter.setChangedColor(changedColor);
	}

	@Override
	public void setDeletedColor(Color deletedColor) {
		fDiffPainter.setDeletedColor(deletedColor);
	}

	@Override
	public void setRevisionInformation(RevisionInformation info) {
		fRevisionPainter.setRevisionInformation(info);
		updateNumberOfDigits();
		computeIndentations();
		layout(true);
		postRedraw();
	}

	@Override
	public ISelectionProvider getRevisionSelectionProvider() {
		return fRevisionPainter.getRevisionSelectionProvider();
	}

	/*
	 * @see org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension#setRenderingMode(org.eclipse.jface.text.revisions.IRevisionRulerColumnExtension.RenderingMode)
	 * @since 3.3
	 */
	@Override
	public void setRevisionRenderingMode(RenderingMode renderingMode) {
		fRevisionPainter.setRenderingMode(renderingMode);
	}

	/**
	 * Sets the line number display mode.
	 *
	 * @param showNumbers <code>true</code> to show numbers, <code>false</code> to only show
	 *        diff / revision info.
	 * @since 3.3
	 */
	public void showLineNumbers(boolean showNumbers) {
		if (fShowNumbers != showNumbers) {
			fShowNumbers= showNumbers;
			updateNumberOfDigits();
			computeIndentations();
			layout(true);
		}
	}

	@Override
	public int getWidth() {
		int width= super.getWidth();
		return width > 0 ? width : 8; // minimal width to display quick diff / revisions if no textual info is shown
	}

	/**
	 * Returns <code>true</code> if the ruler is showing line numbers, <code>false</code>
	 * otherwise
	 *
	 * @return <code>true</code> if line numbers are shown, <code>false</code> otherwise
	 * @since 3.3
	 */
	public boolean isShowingLineNumbers() {
		return fShowNumbers;
	}

	/**
	 * Returns <code>true</code> if the ruler is showing revision information, <code>false</code>
	 * otherwise
	 *
	 * @return <code>true</code> if revision information is shown, <code>false</code> otherwise
	 * @since 3.3
	 */
	public boolean isShowingRevisionInformation() {
		return fRevisionPainter.hasInformation();
	}

	/**
	 * Returns <code>true</code> if the ruler is showing change information, <code>false</code>
	 * otherwise
	 *
	 * @return <code>true</code> if change information is shown, <code>false</code> otherwise
	 * @since 3.3
	 */
	public boolean isShowingChangeInformation() {
		return fDiffPainter.hasInformation();
	}

	@Override
	public void showRevisionAuthor(boolean show) {
		fRevisionPainter.showRevisionAuthor(show);
		updateNumberOfDigits();
		computeIndentations();
		layout(true);
		postRedraw();
	}

	@Override
	public void showRevisionId(boolean show) {
		fRevisionPainter.showRevisionId(show);
		updateNumberOfDigits();
		computeIndentations();
		layout(true);
		postRedraw();
	}

	@Override
	public void addRevisionListener(IRevisionListener listener) {
		fRevisionPainter.addRevisionListener(listener);
	}

	@Override
	public void removeRevisionListener(IRevisionListener listener) {
		fRevisionPainter.removeRevisionListener(listener);
	}

	@Override
	protected void handleDispose() {
		fRevisionPainter.setParentRuler(null);
		fRevisionPainter.setModel(null);
		fDiffPainter.setParentRuler(null);
		fDiffPainter.setModel(null);
		super.handleDispose();
	}

	@Override
	void handleMouseScrolled(MouseEvent e) {
		if (fRevisionPainter.isWheelHandlerInstalled()) {
			return; // scroll event is already handled; don't interfere
		}
		super.handleMouseScrolled(e);
	}
}
