/*
 * 03/09/2013
 *
 * XmlOccurrenceMarker - Marks occurrences of the current token for XML.
 * 
 * This library is distributed under a modified BSD license.  See the included
 * RSyntaxTextArea.License.txt file for details.
 */
package com.fr.design.gui.syntax.ui.rsyntaxtextarea;

import java.util.Stack;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;

import com.fr.design.gui.syntax.ui.rtextarea.SmartHighlightPainter;


/**
 * Marks occurrences of the current token for XML.
 *
 * @author Robert Futrell
 * @version 1.0
 */
public class XmlOccurrenceMarker implements OccurrenceMarker {

	private static final char[] CLOSE_TAG_START = { '<', '/' };
	private static final char[] TAG_SELF_CLOSE = { '/', '>' };


	/**
	 * {@inheritDoc}
	 */
	public void markOccurrences(RSyntaxDocument doc, Token t,
			RSyntaxTextAreaHighlighter h, SmartHighlightPainter p) {

		char[] lexeme = t.getLexeme().toCharArray();
		int tokenOffs = t.getOffset();
		Element root = doc.getDefaultRootElement();
		int lineCount = root.getElementCount();
		int curLine = root.getElementIndex(t.getOffset());

		// For now, we only check for tags on the current line, for
		// simplicity.  Tags spanning multiple lines aren't common anyway.
		boolean found = false;
		boolean forward = true;
		t = doc.getTokenListForLine(curLine);
		while (t!=null && t.isPaintable()) {
			if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
				if (t.isSingleChar('<') && t.getOffset()+1==tokenOffs) {
					found = true;
					break;
				}
				else if (t.is(CLOSE_TAG_START) && t.getOffset()+2==tokenOffs) {
					found = true;
					forward = false;
					break;
				}
			}
			t = t.getNextToken();
		}

		if (!found) {
			return;
		}

		if (forward) {

			int depth = 0;
			t = t.getNextToken().getNextToken();

			do {

				while (t!=null && t.isPaintable()) {
					if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
						if (t.isSingleChar('<')) {
							depth++;
						}
						else if (t.is(TAG_SELF_CLOSE)) {
							if (depth>0) {
								depth--;
							}
							else {
								return; // No match for this tag
							}
						}
						else if (t.is(CLOSE_TAG_START)) {
							if (depth>0) {
								depth--;
							}
							else {
								Token match = t.getNextToken();
								if (match!=null && match.is(lexeme)) {
									try {
										int end = match.getOffset() + match.length();
										h.addMarkedOccurrenceHighlight(match.getOffset(), end, p);
										end = tokenOffs + match.length();
										h.addMarkedOccurrenceHighlight(tokenOffs, end, p);
									} catch (BadLocationException ble) {
										ble.printStackTrace(); // Never happens
									}
								}
								return; // We're done!
							}
						}
					}
					t = t.getNextToken();
				}

				if (++curLine<lineCount) {
					t = doc.getTokenListForLine(curLine);
				}

			} while (curLine<lineCount);
				

		}

		else { // !forward

			Stack<Token> matches = new Stack<Token>();
			boolean inPossibleMatch = false;
			t = doc.getTokenListForLine(curLine);
			final int endBefore = tokenOffs - 2; // Stop before "</".

			do {

				while (t!=null && t.getOffset()<endBefore && t.isPaintable()) {
					if (t.getType()==Token.MARKUP_TAG_DELIMITER) {
						if (t.isSingleChar('<')) {
							Token next = t.getNextToken();
							if (next!=null) {
								if (next.is(lexeme)) {
									matches.push(next);
									inPossibleMatch = true;
								}
								else {
									inPossibleMatch = false;
								}
								t = next;
							}
						}
						else if (t.isSingleChar('>')) {
							inPossibleMatch = false;
						}
						else if (inPossibleMatch && t.is(TAG_SELF_CLOSE)) {
							matches.pop();
						}
						else if (t.is(CLOSE_TAG_START)) {
							Token next = t.getNextToken();
							if (next!=null) {
								// Invalid XML might not have a match
								if (next.is(lexeme) && !matches.isEmpty()) {
									matches.pop();
								}
								t = next;
							}
						}
					}
					t = t.getNextToken();
				}

				if (!matches.isEmpty()) {
					try {
						Token match = matches.pop();
						int end = match.getOffset() + match.length();
						h.addMarkedOccurrenceHighlight(match.getOffset(), end, p);
						end = tokenOffs + match.length();
						h.addMarkedOccurrenceHighlight(tokenOffs, end, p);
					} catch (BadLocationException ble) {
						ble.printStackTrace(); // Never happens
					}
					return;
				}

				if (--curLine>=0) {
					t = doc.getTokenListForLine(curLine);
				}

			} while (curLine>=0);
				

		}

	}


}