/*******************************************************************************
 * Copyright (c) 2001, 2010 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.doubleclick;

import org.eclipse.jface.text.DefaultTextDoubleClickStrategy;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.swt.graphics.Point;
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.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.w3c.dom.Node;


public class XMLDoubleClickStrategy extends DefaultTextDoubleClickStrategy {
	protected static final char DOUBLE_QUOTE = '\"';
	protected static final char SINGLE_QUOTE = '\'';
	protected static final char SPACE = ' ';
	protected int fCaretPosition = -1;
	protected int fDoubleClickCount = 0;
	protected Node fNode = null;
	protected IStructuredDocumentRegion fStructuredDocumentRegion = null;
	protected String fStructuredDocumentRegionText = ""; //$NON-NLS-1$
	protected IStructuredModel fStructuredModel = null;
	protected ITextViewer fTextViewer;
	protected ITextRegion fTextRegion = null;

	public void doubleClicked(ITextViewer textViewer) {
			fTextViewer = textViewer;
			try {
				fStructuredModel = StructuredModelManager.getModelManager().getExistingModelForRead(fTextViewer.getDocument());

				if (fStructuredModel != null) {
					int caretPosition = textViewer.getSelectedRange().x;
					if (caretPosition < 0) {
						return;
					}

					fNode = (Node) fStructuredModel.getIndexedRegion(caretPosition);
					if (fNode == null) {
						return;
					}

					updateDoubleClickCount(caretPosition);
					updateStructuredDocumentRegion();
					updateTextRegion();

					if (fNode.getNodeType() == Node.TEXT_NODE) {
						processTextDoubleClicked();
					}
					else {
						processElementDoubleClicked();
					}
				}
			}
			finally {
				if (fStructuredModel != null) {
					fStructuredModel.releaseFromRead();
				}
			}
	}

	protected Point getWord(String string, int cursor) {
		if (string == null) {
			return null;
		}

		int wordStart = 0;
		int wordEnd = string.length();

		wordStart = string.lastIndexOf(SPACE, cursor - 1);
		int temp = string.lastIndexOf(SINGLE_QUOTE, cursor - 1);
		wordStart = Math.max(wordStart, temp);
		temp = string.lastIndexOf(DOUBLE_QUOTE, cursor - 1);
		wordStart = Math.max(wordStart, temp);
		if (wordStart == -1) {
			wordStart = cursor;
		}
		else {
			wordStart++;
		}

		wordEnd = string.indexOf(SPACE, cursor);
		if (wordEnd == -1) {
			wordEnd = string.length();
		}
		temp = string.indexOf(SINGLE_QUOTE, cursor);
		if (temp == -1) {
			temp = string.length();
		}
		wordEnd = Math.min(wordEnd, temp);
		temp = string.indexOf(DOUBLE_QUOTE, cursor);
		if (temp == -1) {
			temp = string.length();
		}
		wordEnd = Math.min(wordEnd, temp);
		if (wordEnd == string.length()) {
			wordEnd = cursor;
		}

		if ((wordStart == wordEnd) && !isQuoted(string)) {
			wordStart = 0;
			wordEnd = string.length();
		}

		return new Point(wordStart, wordEnd);
	}

	protected boolean isQuoted(String string) {
		if ((string == null) || (string.length() < 2)) {
			return false;
		}

		int lastIndex = string.length() - 1;
		char firstChar = string.charAt(0);
		char lastChar = string.charAt(lastIndex);

		return (((firstChar == SINGLE_QUOTE) && (lastChar == SINGLE_QUOTE)) || ((firstChar == DOUBLE_QUOTE) && (lastChar == DOUBLE_QUOTE)));
	}

	protected void processElementAttrEqualsDoubleClicked2Times() {
		int prevRegionOffset = fStructuredDocumentRegion.getStartOffset(fTextRegion) - 1;
		ITextRegion prevRegion = fStructuredDocumentRegion.getRegionAtCharacterOffset(prevRegionOffset);
		int nextRegionOffset = fStructuredDocumentRegion.getEndOffset(fTextRegion);
		ITextRegion nextRegion = fStructuredDocumentRegion.getRegionAtCharacterOffset(nextRegionOffset);

		if ((prevRegion != null) && (prevRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) && (nextRegion != null) && (nextRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE)) {
			fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStartOffset(prevRegion), nextRegion.getTextEnd() - prevRegion.getStart());
		}
	}

	protected void processElementAttrNameDoubleClicked2Times() {
		int nextRegionOffset = fStructuredDocumentRegion.getEndOffset(fTextRegion);
		ITextRegion nextRegion = fStructuredDocumentRegion.getRegionAtCharacterOffset(nextRegionOffset);

		if (nextRegion != null) {
			nextRegionOffset = fStructuredDocumentRegion.getEndOffset(nextRegion);
			nextRegion = fStructuredDocumentRegion.getRegionAtCharacterOffset(nextRegionOffset);
			if ((nextRegion != null) && (nextRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE)) {
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStartOffset(fTextRegion), nextRegion.getTextEnd() - fTextRegion.getStart());
			}
			else {
				// attribute has no value
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStart(), fStructuredDocumentRegion.getLength());
				fDoubleClickCount = 0;
			}
		}
	}

	protected void processElementAttrValueDoubleClicked() {
		String regionText = fStructuredDocumentRegion.getText(fTextRegion);

		if (fDoubleClickCount == 1) {
			Point word = getWord(regionText, fCaretPosition - fStructuredDocumentRegion.getStartOffset(fTextRegion));
			if (word.x == word.y) { // no word found; select whole region
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStartOffset(fTextRegion), regionText.length());
				fDoubleClickCount++;
			}
			else {
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStartOffset(fTextRegion) + word.x, word.y - word.x);
			}
		}
		else if (fDoubleClickCount == 2) {
			if (isQuoted(regionText)) {
				// ==> // Point word = getWord(regionText, fCaretPosition -
				// fStructuredDocumentRegion.getStartOffset(fTextRegion));
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStartOffset(fTextRegion), regionText.length());
			}
			else {
				processElementAttrValueDoubleClicked2Times();
			}
		}
		else if (fDoubleClickCount == 3) {
			if (isQuoted(regionText)) {
				processElementAttrValueDoubleClicked2Times();
			}
			else {
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStart(), fStructuredDocumentRegion.getLength());
				fDoubleClickCount = 0;
			}
		}
		else { // fDoubleClickCount == 4
			fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStart(), fStructuredDocumentRegion.getLength());
			fDoubleClickCount = 0;
		}
	}

	protected void processElementAttrValueDoubleClicked2Times() {
		int prevRegionOffset = fStructuredDocumentRegion.getStartOffset(fTextRegion) - 1;
		ITextRegion prevRegion = fStructuredDocumentRegion.getRegionAtCharacterOffset(prevRegionOffset);

		if (prevRegion != null) {
			prevRegionOffset = fStructuredDocumentRegion.getStartOffset(prevRegion) - 1;
			prevRegion = fStructuredDocumentRegion.getRegionAtCharacterOffset(prevRegionOffset);
			if ((prevRegion != null) && (prevRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME)) {
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStartOffset(prevRegion), fTextRegion.getTextEnd() - prevRegion.getStart());
			}
		}
	}

	protected void processElementDoubleClicked() {
		if (fTextRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
			processElementAttrValueDoubleClicked(); // special handling for
		}
		else {
			if (fDoubleClickCount == 1) {
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStart() + fTextRegion.getStart(), fTextRegion.getTextLength());
			}
			else if (fDoubleClickCount == 2) {
				if (fTextRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
					processElementAttrNameDoubleClicked2Times();
				}
				else if (fTextRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) {
					processElementAttrEqualsDoubleClicked2Times();
				}
				else {
					fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStart(), fStructuredDocumentRegion.getLength());
					fDoubleClickCount = 0;
				}
			}
			else { // fDoubleClickCount == 3
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStart(), fStructuredDocumentRegion.getLength());
				fDoubleClickCount = 0;
			}
		}
	}

	protected void processTextDoubleClicked() {
		if (fDoubleClickCount == 1) {
			super.doubleClicked(fTextViewer);

			Point selectedRange = fTextViewer.getSelectedRange();
			if ((selectedRange.x == fStructuredDocumentRegion.getStartOffset(fTextRegion)) && (selectedRange.y == fTextRegion.getTextLength())) {
				// only one word in region, skip one level of double click
				// selection
				fDoubleClickCount++;
			}
		}
		else if (fDoubleClickCount == 2) {
			if (fTextRegion.getType() == DOMRegionContext.UNDEFINED) {
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStart(), fStructuredDocumentRegion.getLength());
				fDoubleClickCount = 0;
			}
			else {
				if (isQuoted(fStructuredDocumentRegion.getFullText(fTextRegion))) {
					fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStartOffset(fTextRegion) + 1, fTextRegion.getTextLength() - 2);
				}
				else {
					fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStartOffset(fTextRegion), fTextRegion.getTextLength());
				}
			}
		}
		else {
			if ((fDoubleClickCount == 3) && isQuoted(fStructuredDocumentRegion.getFullText(fTextRegion))) {
				fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStartOffset(fTextRegion), fTextRegion.getTextLength());
			}
			else {
				if ((fDoubleClickCount == 3) && isQuoted(fStructuredDocumentRegionText)) {
					fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStart() + 1, fStructuredDocumentRegion.getLength() - 2);
				}
				else {
					fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStart(), fStructuredDocumentRegion.getLength());
					fDoubleClickCount = 0;
				}
			}
		}
	}

	public void setModel(IStructuredModel structuredModel) {
		fStructuredModel = structuredModel;
	}

	protected void updateDoubleClickCount(int caretPosition) {
		if (fCaretPosition == caretPosition) {
			if (fStructuredDocumentRegion != null) {
				fDoubleClickCount++;
			}
			else {
				fDoubleClickCount = 1;
			}
		}
		else {
			fCaretPosition = caretPosition;
			fDoubleClickCount = 1;
		}
	}

	protected void updateStructuredDocumentRegion() {
		fStructuredDocumentRegion = fStructuredModel.getStructuredDocument().getRegionAtCharacterOffset(fCaretPosition);
		if (fStructuredDocumentRegion != null) {
			fStructuredDocumentRegionText = fStructuredDocumentRegion.getText();
		}
		else {
			fStructuredDocumentRegionText = ""; //$NON-NLS-1$
		}
	}

	protected void updateTextRegion() {
		if (fStructuredDocumentRegion != null) {
			fTextRegion = fStructuredDocumentRegion.getRegionAtCharacterOffset(fCaretPosition);
			// if fTextRegion is null, it means we are at just past the last
			// fStructuredDocumentRegion,
			// at the very end of the document, so we'll use the last text
			// region in the document
			if (fTextRegion == null) {
				fTextRegion = fStructuredDocumentRegion.getRegionAtCharacterOffset(fCaretPosition - 1);
			}
		}
		else {
			fTextRegion = null;
		}
	}
}
