/**********************************************************************
 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 org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IDocument;

/**
 * Auto indent strategy sensitive to brackets.
 */
public class PHPAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy {

	public PHPAutoIndentStrategy() {
	}

	/*
	 * (non-Javadoc) Method declared on IAutoIndentStrategy
	 */
	public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
		if (c.length == 0 && c.text != null && endsWithDelimiter(d, c.text))
			smartIndentAfterNewLine(d, c);
		else if ("}".equals(c.text)) {
			smartInsertAfterBracket(d, c);
		}
	}

	/**
	 * Returns whether or not the text ends with one of the given search
	 * strings.
	 */
	private boolean endsWithDelimiter(IDocument d, String txt) {

		String[] delimiters = d.getLegalLineDelimiters();

		for (int i = 0; i < delimiters.length; i++) {
			if (txt.endsWith(delimiters[i]))
				return true;
		}

		return false;
	}

	/**
	 * Returns the line number of the next bracket after end.
	 * 
	 * @returns the line number of the next matching bracket after end
	 * @param document -
	 *            the document being parsed
	 * @param line -
	 *            the line to start searching back from
	 * @param end -
	 *            the end position to search back from
	 * @param closingBracketIncrease -
	 *            the number of brackets to skip
	 */
	protected int findMatchingOpenBracket(IDocument document, int line,
			int end, int closingBracketIncrease) throws BadLocationException {

		int start = document.getLineOffset(line);
		int brackcount = getBracketCount(document, start, end, false)
				- closingBracketIncrease;

		// sum up the brackets counts of each line (closing brackets count
		// negative,
		// opening positive) until we find a line the brings the count to zero
		while (brackcount < 0) {
			line--;
			if (line < 0) {
				return -1;
			}
			start = document.getLineOffset(line);
			end = start + document.getLineLength(line) - 1;
			brackcount += getBracketCount(document, start, end, false);
		}
		return line;
	}

	/**
	 * Returns the bracket value of a section of text. Closing brackets have a
	 * value of -1 and open brackets have a value of 1.
	 * 
	 * @returns the line number of the next matching bracket after end
	 * @param document -
	 *            the document being parsed
	 * @param start -
	 *            the start position for the search
	 * @param end -
	 *            the end position for the search
	 * @param ignoreCloseBrackets -
	 *            whether or not to ignore closing brackets in the count
	 */
	private int getBracketCount(IDocument document, int start, int end,
			boolean ignoreCloseBrackets) throws BadLocationException {

		int begin = start;
		int bracketcount = 0;
		while (begin < end) {
			char curr = document.getChar(begin);
			begin++;
			switch (curr) {
			case '/':
				if (begin < end) {
					char next = document.getChar(begin);
					if (next == '*') {
						// a comment starts, advance to the comment end
						begin = getCommentEnd(document, begin + 1, end);
					} else if (next == '/') {
						// '//'-comment: nothing to do anymore on this line
						begin = end;
					}
				}
				break;
			case '*':
				if (begin < end) {
					char next = document.getChar(begin);
					if (next == '/') {
						// we have been in a comment: forget what we read before
						bracketcount = 0;
						begin++;
					}
				}
				break;
			case '{':
				bracketcount++;
				ignoreCloseBrackets = false;
				break;
			case '}':
				if (!ignoreCloseBrackets) {
					bracketcount--;
				}
				break;
			case '"':
			case '\'':
				begin = getStringEnd(document, begin, end, curr);
				break;
			default:
			}
		}
		return bracketcount;
	}

	/**
	 * Returns the end position a comment starting at pos.
	 * 
	 * @returns the end position a comment starting at pos
	 * @param document -
	 *            the document being parsed
	 * @param position -
	 *            the start position for the search
	 * @param end -
	 *            the end position for the search
	 */
	private int getCommentEnd(IDocument document, int position, int end)
			throws BadLocationException {
		int currentPosition = position;
		while (currentPosition < end) {
			char curr = document.getChar(currentPosition);
			currentPosition++;
			if (curr == '*') {
				if (currentPosition < end
						&& document.getChar(currentPosition) == '/') {
					return currentPosition + 1;
				}
			}
		}
		return end;
	}

	/**
	 * Returns the String at line with the leading whitespace removed.
	 * 
	 * @returns the String at line with the leading whitespace removed.
	 * @param document -
	 *            the document being parsed
	 * @param line -
	 *            the line being searched
	 */
	protected String getIndentOfLine(IDocument document, int line)
			throws BadLocationException {
		if (line > -1) {
			int start = document.getLineOffset(line);
			int end = start + document.getLineLength(line) - 1;
			int whiteend = findEndOfWhiteSpace(document, start, end);
			return document.get(start, whiteend - start);
		} else {
			return ""; //$NON-NLS-1$
		}
	}

	/**
	 * Returns the position of the character in the document after position.
	 * 
	 * @returns the next location of character.
	 * @param document -
	 *            the document being parsed
	 * @param position -
	 *            the position to start searching from
	 * @param end -
	 *            the end of the document
	 * @param character -
	 *            the character you are trying to match
	 */
	private int getStringEnd(IDocument document, int position, int end,
			char character) throws BadLocationException {
		int currentPosition = position;
		while (currentPosition < end) {
			char currentCharacter = document.getChar(currentPosition);
			currentPosition++;
			if (currentCharacter == '\\') {
				// ignore escaped characters
				currentPosition++;
			} else if (currentCharacter == character) {
				return currentPosition;
			}
		}
		return end;
	}

	/**
	 * Set the indent of a new line based on the command provided in the
	 * supplied document.
	 * 
	 * @param document -
	 *            the document being parsed
	 * @param command -
	 *            the command being performed
	 */
	protected void smartIndentAfterNewLine(IDocument document,
			DocumentCommand command) {

		int docLength = document.getLength();
		if (command.offset == -1 || docLength == 0)
			return;

		try {
			int p = (command.offset == docLength ? command.offset - 1
					: command.offset);
			int line = document.getLineOfOffset(p);

			StringBuffer buf = new StringBuffer(command.text);
			if (command.offset < docLength
					&& document.getChar(command.offset) == '}') {
				int indLine = findMatchingOpenBracket(document, line,
						command.offset, 0);
				if (indLine == -1) {
					indLine = line;
				}
				buf.append(getIndentOfLine(document, indLine));
			} else {
				int start = document.getLineOffset(line);
				int whiteend = findEndOfWhiteSpace(document, start,
						command.offset);
				int offset = -1;
				// if (command.offset > 0 && command.offset < docLength &&
				// document.getChar(command.offset-1) == '{') {
				// offset = command.offset;
				// }
				buf.append(document.get(start, whiteend - start));
				if (getBracketCount(document, start, command.offset, true) > 0) {
					buf.append('\t');
				}
				// if (offset >= 0) {
				// buf.append('}');
				// }
			}
			command.text = buf.toString();

		} catch (BadLocationException excp) {
			System.out.println(PHPEditorMessages
					.getString("AutoIndent.error.bad_location_1")); //$NON-NLS-1$
		}
	}

	/**
	 * Set the indent of a bracket based on the command provided in the supplied
	 * document.
	 * 
	 * @param document -
	 *            the document being parsed
	 * @param command -
	 *            the command being performed
	 */
	protected void smartInsertAfterBracket(IDocument document,
			DocumentCommand command) {
		if (command.offset == -1 || document.getLength() == 0)
			return;

		try {
			int p = (command.offset == document.getLength() ? command.offset - 1
					: command.offset);
			int line = document.getLineOfOffset(p);
			int start = document.getLineOffset(line);
			int whiteend = findEndOfWhiteSpace(document, start, command.offset);

			// shift only when line does not contain any text up to the closing
			// bracket
			if (whiteend == command.offset) {
				// evaluate the line with the opening bracket that matches out
				// closing bracket
				int indLine = findMatchingOpenBracket(document, line,
						command.offset, 1);
				if (indLine != -1 && indLine != line) {
					// take the indent of the found line
					StringBuffer replaceText = new StringBuffer(
							getIndentOfLine(document, indLine));
					// add the rest of the current line including the just added
					// close bracket
					replaceText.append(document.get(whiteend, command.offset
							- whiteend));
					replaceText.append(command.text);
					// modify document command
					command.length = command.offset - start;
					command.offset = start;
					command.text = replaceText.toString();
				}
			}
		} catch (BadLocationException excp) {
			System.out.println(PHPEditorMessages
					.getString("AutoIndent.error.bad_location_2")); //$NON-NLS-1$
		}
	}
}
