/*******************************************************************************
 * Copyright (c) 2009 Avaloq Evolution AG 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:
 *     Tom Eicher (Avaloq Evolution AG) - initial API and implementation
 *******************************************************************************/
package org.eclipse.jface.text;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jface.internal.text.SelectionProcessor;


/**
 * Standard implementation of {@link org.eclipse.jface.text.IBlockTextSelection}.
 *
 * @since 3.5
 */
public class BlockTextSelection extends TextSelection implements IBlockTextSelection {

	/** The start line. */
	private final int fStartLine;
	/** The start column. */
	private final int fStartColumn;
	/** The end line. */
	private final int fEndLine;
	/** The end column. */
	private final int fEndColumn;
	/** The tabulator width used to compute visual columns from character offsets. */
	private final int fTabWidth;

	/**
	 * Creates a column selection for the given lines and columns.
	 *
	 * @param document the document that this selection refers to
	 * @param startLine the start line
	 * @param startColumn the possibly virtual start column, measured in characters from the start
	 *        of <code>startLine</code>
	 * @param endLine the inclusive end line
	 * @param endColumn the exclusive and possibly virtual end column, measured in characters from
	 *        the start of <code>endLine</code>
	 * @param tabWidth the tabulator width used to compute the visual offsets from character offsets
	 */
	public BlockTextSelection(IDocument document, int startLine, int startColumn, int endLine, int endColumn, int tabWidth) {
		super(document, computeOffset(document, startLine, startColumn), computeOffset(document, endLine, endColumn) - computeOffset(document, startLine, startColumn));
		Assert.isLegal(startLine >= 0);
		Assert.isLegal(startColumn >= 0);
		Assert.isLegal(endLine >= startLine);
		Assert.isLegal(endColumn >= 0);
		Assert.isLegal(tabWidth >= 0);
		fStartLine= startLine;
		fStartColumn= startColumn;
		fEndLine= endLine;
		fEndColumn= endColumn;
		fTabWidth= tabWidth > 0	? tabWidth : 8; // seems to be the default when StyledText.getTabs returns 0
	}

	/**
	 * Returns the document offset for a given tuple of line and column count. If the column count
	 * points beyond the end of the line, the end of the line is returned (virtual location). If the
	 * line points beyond the number of lines, the end of the document is returned; if the line is
	 * &lt; zero, 0 is returned.
	 *
	 * @param document the document to get line information from
	 * @param line the line in the document, may be greater than the line count
	 * @param column the offset in the given line, may be greater than the line length
	 * @return the document offset corresponding to the line and column counts
	 */
	private static int computeOffset(IDocument document, int line, int column) {
		try {
			IRegion lineInfo= document.getLineInformation(line);
			int offsetInLine= Math.min(column, lineInfo.getLength());
			return lineInfo.getOffset() + offsetInLine;
		} catch (BadLocationException x) {
			if (line < 0)
				return 0;
			return document.getLength();
		}
	}

	@Override
	public int getStartLine() {
		return fStartLine;
	}

	@Override
	public int getStartColumn() {
		return fStartColumn;
	}

	@Override
	public int getEndLine() {
		return fEndLine;
	}

	@Override
	public int getEndColumn() {
		return fEndColumn;
	}

	@Override
	public String getText() {
		IDocument document= getDocument();
		if (document != null) {
			try {
				return new SelectionProcessor(document, fTabWidth).getText(this);
			} catch (BadLocationException x) {
				// ignore and default to super implementation
			}
		}
		return super.getText();
	}

	@Override
	public int hashCode() {
		final int prime= 31;
		int result= super.hashCode();
		result= prime * result + fEndColumn;
		result= prime * result + fEndLine;
		result= prime * result + fStartColumn;
		result= prime * result + fStartLine;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		final BlockTextSelection other= (BlockTextSelection) obj;
		if (fEndColumn != other.fEndColumn)
			return false;
		if (fEndLine != other.fEndLine)
			return false;
		if (fStartColumn != other.fStartColumn)
			return false;
		if (fStartLine != other.fStartLine)
			return false;
		return true;
	}

	@Override
	public IRegion[] getRegions() {
		IDocument document= getDocument();
		if (document != null) {
			try {
				return new SelectionProcessor(document, fTabWidth).getRanges(this);
			} catch (BadLocationException x) {
				// default to single region behavior
			}
		}
		return new IRegion[] {new Region(getOffset(), getLength())};
	}

	/**
	 * @since 3.16
	 */
	@Override
	public String toString() {
		StringBuilder sb= new StringBuilder();
		sb.append("BlockTextSelection [offset: ").append(getOffset()); //$NON-NLS-1$
		sb.append(", startLine: ").append(fStartLine); //$NON-NLS-1$
		if (fEndLine != fStartLine) {
			sb.append(", endLine: ").append(fEndLine); //$NON-NLS-1$
		}
		sb.append(", startColumn: ").append(fStartColumn); //$NON-NLS-1$
		if (fEndColumn != fStartColumn) {
			sb.append(", endColumn").append(fEndColumn); //$NON-NLS-1$
		}
		if (getLength() != 0) {
			sb.append(", text: ").append(getText()); //$NON-NLS-1$
		}
		if (getDocument() != null) {
			sb.append(", document: ").append(getDocument()); //$NON-NLS-1$
		}
		sb.append("]"); //$NON-NLS-1$
		return sb.toString();
	}
}
