/*******************************************************************************
 * Copyright (c) 2005, 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
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.xml.ui.internal.autoedit;

import org.eclipse.core.runtime.Preferences;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.ConfigurableLineTracker;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ILineTracker;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
import org.eclipse.wst.xml.core.internal.XMLCorePlugin;
import org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames;
import org.eclipse.wst.xml.ui.internal.Logger;

/**
 * AutoEditStrategy to handle characters inserted when Tab key is pressed
 */
public class AutoEditStrategyForTabs implements IAutoEditStrategy {
	private final String TAB_CHARACTER = "\t"; //$NON-NLS-1$

	public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
		// if not in smart insert mode just ignore
		if (!isSmartInsertMode()) {
			return;
		}

		// spaces for tab character
		if ((command.length == 0) && (command.text != null) && (command.text.length() > 0) && (command.text.indexOf(TAB_CHARACTER) != -1)) {
			smartInsertForTab(command, document);
		}
	}

	/**
	 * Insert spaces for tabs
	 * 
	 * @param command
	 */
	private void smartInsertForTab(DocumentCommand command, IDocument document) {
		// tab key was pressed. now check preferences to see if need to insert
		// spaces instead of tab
		int indentationWidth = getIndentationWidth();
		if (indentationWidth > -1) {
			String originalText = command.text;
			StringBuffer newText = new StringBuffer(originalText);

			// determine where in line this command begins
			int lineOffset = -1;
			try {
				IRegion lineInfo = document.getLineInformationOfOffset(command.offset);
				lineOffset = command.offset - lineInfo.getOffset();
			}
			catch (BadLocationException e) {
				Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e);
			}

			ILineTracker lineTracker = getLineTracker(document, originalText);

			int startIndex = 0;
			int index = newText.indexOf(TAB_CHARACTER);
			while (index != -1) {
				String indent = getIndentString(indentationWidth, lineOffset, lineTracker, index);

				// replace \t character with spaces
				newText.replace(index, index + 1, indent);
				if (lineTracker != null) {
					try {
						lineTracker.replace(index, 1, indent);
					}
					catch (BadLocationException e) {
						// if something goes wrong with replacing text, just
						// reset to current string
						lineTracker.set(newText.toString());
						Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e);
					}
				}

				startIndex = index + indent.length();
				index = newText.indexOf(TAB_CHARACTER, startIndex);
			}
			command.text = newText.toString();
		}
	}

	/**
	 * Calculate number of spaces for next tab stop
	 */
	private String getIndentString(int indentationWidth, int lineOffset, ILineTracker lineTracker, int index) {
		int indentSize = indentationWidth;
		int offsetInLine = -1;
		if (lineTracker != null) {
			try {
				IRegion lineInfo = lineTracker.getLineInformationOfOffset(index);
				if ((lineInfo.getOffset() == 0) && (lineOffset > -1)) {
					offsetInLine = lineOffset + index;
				}
				else {
					offsetInLine = index - lineInfo.getOffset();
				}
			}
			catch (BadLocationException e) {
				Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e);
			}
		}
		else {
			if (lineOffset > -1) {
				offsetInLine = lineOffset + index;
			}
		}
		if ((offsetInLine > -1) && (indentationWidth > 0)) {
			int remainder = offsetInLine % indentationWidth;
			indentSize = indentationWidth - remainder;
		}

		StringBuffer indent = new StringBuffer();
		for (int i = 0; i < indentSize; i++) {
			indent.append(' ');
		}
		return indent.toString();
	}

	/**
	 * Set up a line tracker for text within command if text is multi-line
	 */
	private ILineTracker getLineTracker(IDocument document, String originalText) {
		ConfigurableLineTracker lineTracker = null;
		int[] delims = TextUtilities.indexOf(document.getLegalLineDelimiters(), originalText, 0);
		if ((delims[0] != -1) || (delims[1] != -1)) {
			lineTracker = new ConfigurableLineTracker(document.getLegalLineDelimiters());
			lineTracker.set(originalText);
		}
		return lineTracker;
	}

	/**
	 * Return true if active editor is in smart insert mode, false otherwise
	 * 
	 * @return boolean
	 */
	private boolean isSmartInsertMode() {
		boolean isSmartInsertMode = false;

		ITextEditor textEditor = null;
		IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
		if (window != null) {
			IWorkbenchPage page = window.getActivePage();
			if (page != null) {
				IEditorPart editor = page.getActiveEditor();
				if (editor != null) {
					if (editor instanceof ITextEditor) {
						textEditor = (ITextEditor) editor;
					}
					else {
						textEditor = editor.getAdapter(ITextEditor.class);
					}
				}
			}
		}

		// check if smart insert mode
		if ((textEditor instanceof ITextEditorExtension3) && (((ITextEditorExtension3) textEditor).getInsertMode() == ITextEditorExtension3.SMART_INSERT)) {
			isSmartInsertMode = true;
		}
		return isSmartInsertMode;
	}

	/**
	 * Returns indentation width if using spaces for indentation, -1 otherwise
	 * 
	 * @return int
	 */
	private int getIndentationWidth() {
		int width = -1;

		Preferences preferences = XMLCorePlugin.getDefault().getPluginPreferences();
		if (XMLCorePreferenceNames.SPACE.equals(preferences.getString(XMLCorePreferenceNames.INDENTATION_CHAR))) {
			width = preferences.getInt(XMLCorePreferenceNames.INDENTATION_SIZE);
		}

		return width;
	}
}
