/**********************************************************************
 Copyright (c) 2000, 2002 IBM Corp. and others.
 All rights reserved. This program and the accompanying materials
 are made available under the terms of the Common Public License v1.0
 which accompanies this distribution, and is available at
 http://www.eclipse.org/legal/cpl-v10.html

 Contributors:
 IBM Corporation - Initial implementation
 www.phpeclipse.de
 **********************************************************************/
package net.sourceforge.phpeclipse.phpeditor.php;

import net.sourceforge.phpdt.internal.compiler.parser.Scanner;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextDoubleClickStrategy;
import org.eclipse.jface.text.ITextViewer;

/**
 * Double click strategy aware of PHP identifier syntax rules.
 */
public class PHPDoubleClickSelector implements ITextDoubleClickStrategy {

	protected ITextViewer fText;

	protected int fPos;

	protected int fStartPos;

	protected int fEndPos;

	protected static char[] fgBrackets = { '{', '}', '(', ')', '[', ']', '"',
			'"' };

	/*
	 * Create a PHPDoubleClickSelector.
	 */
	public PHPDoubleClickSelector() {
		super();
	}

	/*
	 * (non-Javadoc) Method declared on ITextDoubleClickStrategy
	 */
	public void doubleClicked(ITextViewer text) {

		fPos = text.getSelectedRange().x;

		if (fPos < 0)
			return;

		fText = text;

		if (!selectBracketBlock())
			selectWord();
	}

	/**
	 * Match the brackets at the current selection. Return true if successful,
	 * false otherwise.
	 */
	protected boolean matchBracketsAt() {

		char prevChar, nextChar;

		int i;
		int bracketIndex1 = fgBrackets.length;
		int bracketIndex2 = fgBrackets.length;

		fStartPos = -1;
		fEndPos = -1;

		// get the chars preceding and following the start position
		try {

			IDocument doc = fText.getDocument();

			prevChar = doc.getChar(fPos - 1);
			nextChar = doc.getChar(fPos);

			// is the char either an open or close bracket?
			for (i = 0; i < fgBrackets.length; i = i + 2) {
				if (prevChar == fgBrackets[i]) {
					fStartPos = fPos - 1;
					bracketIndex1 = i;
				}
			}
			for (i = 1; i < fgBrackets.length; i = i + 2) {
				if (nextChar == fgBrackets[i]) {
					fEndPos = fPos;
					bracketIndex2 = i;
				}
			}

			if (fStartPos > -1 && bracketIndex1 < bracketIndex2) {
				fEndPos = searchForClosingBracket(fStartPos, prevChar,
						fgBrackets[bracketIndex1 + 1], doc);
				if (fEndPos > -1)
					return true;
				else
					fStartPos = -1;
			} else if (fEndPos > -1) {
				fStartPos = searchForOpenBracket(fEndPos,
						fgBrackets[bracketIndex2 - 1], nextChar, doc);
				if (fStartPos > -1)
					return true;
				else
					fEndPos = -1;
			}

		} catch (BadLocationException x) {
		}

		return false;
	}

	/**
	 * Select the word at the current selection. Return true if successful,
	 * false otherwise.
	 */
	protected boolean matchWord() {

		IDocument doc = fText.getDocument();

		try {

			int pos = fPos;
			char c;

			while (pos >= 0) {
				c = doc.getChar(pos);
				if (!Scanner.isPHPIdentifierPart(c) && (c != '$')) {
					break;
				}
				--pos;
			}

			fStartPos = pos;

			pos = fPos;
			int length = doc.getLength();

			while (pos < length) {
				c = doc.getChar(pos);
				if (!Scanner.isPHPIdentifierPart(c) && (c != '$'))
					break;
				++pos;
			}

			fEndPos = pos;

			return true;

		} catch (BadLocationException x) {
		}

		return false;
	}

	/**
	 * Returns the position of the closing bracket after startPosition.
	 * 
	 * @returns the location of the closing bracket.
	 * @param startPosition -
	 *            the beginning position
	 * @param openBracket -
	 *            the character that represents the open bracket
	 * @param closeBracket -
	 *            the character that represents the close bracket
	 * @param document -
	 *            the document being searched
	 */
	protected int searchForClosingBracket(int startPosition, char openBracket,
			char closeBracket, IDocument document) throws BadLocationException {
		int stack = 1;
		int closePosition = startPosition + 1;
		int length = document.getLength();
		char nextChar;

		while (closePosition < length && stack > 0) {
			nextChar = document.getChar(closePosition);
			if (nextChar == openBracket && nextChar != closeBracket)
				stack++;
			else if (nextChar == closeBracket)
				stack--;
			closePosition++;
		}

		if (stack == 0)
			return closePosition - 1;
		else
			return -1;

	}

	/**
	 * Returns the position of the open bracket before startPosition.
	 * 
	 * @returns the location of the starting bracket.
	 * @param startPosition -
	 *            the beginning position
	 * @param openBracket -
	 *            the character that represents the open bracket
	 * @param closeBracket -
	 *            the character that represents the close bracket
	 * @param document -
	 *            the document being searched
	 */
	protected int searchForOpenBracket(int startPosition, char openBracket,
			char closeBracket, IDocument document) throws BadLocationException {
		int stack = 1;
		int openPos = startPosition - 1;
		char nextChar;

		while (openPos >= 0 && stack > 0) {
			nextChar = document.getChar(openPos);
			if (nextChar == closeBracket && nextChar != openBracket)
				stack++;
			else if (nextChar == openBracket)
				stack--;
			openPos--;
		}

		if (stack == 0)
			return openPos + 1;
		else
			return -1;
	}

	/**
	 * Select the area between the selected bracket and the closing bracket.
	 * Return true if successful.
	 */
	protected boolean selectBracketBlock() {
		if (matchBracketsAt()) {

			if (fStartPos == fEndPos)
				fText.setSelectedRange(fStartPos, 0);
			else
				fText.setSelectedRange(fStartPos + 1, fEndPos - fStartPos - 1);

			return true;
		}
		return false;
	}

	/**
	 * Select the word at the current selection.
	 */
	protected void selectWord() {
		if (matchWord()) {

			if (fStartPos == fEndPos)
				fText.setSelectedRange(fStartPos, 0);
			else
				fText.setSelectedRange(fStartPos + 1, fEndPos - fStartPos - 1);
		}
	}
}
