/*******************************************************************************
 * Copyright (c) 2006, 2012 IBM Corporation and others.
 *
 * 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
 *     Markus Schorn (Wind River Systems)
 *     Anton Leherbauer (Wind River Systems)
 *******************************************************************************/

package org.eclipse.cdt.internal.ui.text;

import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;

/**
 * Recognizes headers specified using angle brackets (e.g. #include <stdio.h>).
 */
public class CHeaderRule implements IRule {

	/** Style token. */
	private IToken fToken;

	/**
	 * Creates a new CHeaderRule.
	 *
	 * @param token
	 *            Style token.
	 */
	public CHeaderRule(IToken token) {
		fToken = token;
	}

	/**
	 * @see org.eclipse.jface.text.rules.IRule#evaluate(org.eclipse.jface.text.rules.ICharacterScanner)
	 */
	@Override
	public IToken evaluate(ICharacterScanner scanner) {
		int current = scanner.read();
		int lookAhead = 1;
		int contentLength = 0;

		if (current == '<' || current == '"') {
			int expected = current == '<' ? '>' : current;
			do {
				current = scanner.read();
				lookAhead++;
				if (current == expected) {
					if (contentLength < 1) {
						break;
					}

					// Rewind and check for an #include.
					seek(scanner, -lookAhead);
					if (!followsIncludeDirective(scanner)) {
						return Token.UNDEFINED;
					}

					seek(scanner, lookAhead);
					return fToken;
				}
				contentLength++;
			} while (current != ICharacterScanner.EOF && current != '\n');
		}

		seek(scanner, -lookAhead);
		return Token.UNDEFINED;
	}

	/**
	 * Repositions the scanner.
	 *
	 * @param scanner
	 *            Scanner.
	 * @param characters
	 *            Number of characters to move ahead (if positive) or behind (if
	 *            negative).
	 */
	private void seek(ICharacterScanner scanner, int characters) {
		if (characters < 0) {
			while (characters < 0) {
				scanner.unread();
				characters++;
			}
		} else {
			while (characters > 0) {
				scanner.read();
				characters--;
			}
		}
	}

	/**
	 * Returns true if the previous contents of the scanner is an #include
	 * directive.
	 *
	 * @param scanner
	 *            Scanner.
	 * @return true if the previous contents of the scanner is an #include
	 *         directive.
	 */
	private boolean followsIncludeDirective(ICharacterScanner scanner) {
		int lookBehind = 0;
		boolean result = false;

		int current = unread(scanner);
		lookBehind++;
		if (Character.isWhitespace((char) current)) {
			do {
				current = unread(scanner);
				lookBehind++;
			} while (Character.isWhitespace((char) current));
		}
		scanner.read();
		--lookBehind;
		if (current == 'e' && searchBackwards(scanner, "include") || //$NON-NLS-1$
				current == 't' && searchBackwards(scanner, "include_next")) { //$NON-NLS-1$
			result = true;
		}

		seek(scanner, lookBehind);
		return result;
	}

	/**
	 * Returns true if the given String was the last String read from the
	 * scanner.
	 *
	 * @param scanner
	 *            Scanner.
	 * @param string
	 *            Expected String.
	 * @return true if the given String was the last String read from the
	 *         scanner.
	 */
	private boolean searchBackwards(ICharacterScanner scanner, String string) {
		int offset = 0;
		for (int i = string.length() - 1; i >= 0; i--) {
			offset++;
			if (string.charAt(i) != unread(scanner)) {
				seek(scanner, offset);
				return false;
			}
		}
		seek(scanner, offset);
		return true;
	}

	/**
	 * Unreads a single character from the scanner.
	 *
	 * @param scanner
	 *            Scanner.
	 * @return the last character read from the scanner.
	 */
	private int unread(ICharacterScanner scanner) {
		scanner.unread();
		int character = scanner.read();
		scanner.unread();
		return character;
	}

}
