/*******************************************************************************
 * Copyright (c) 2007, 2018 Red Hat, Inc.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Red Hat - initial API and implementation
 *******************************************************************************/

package org.eclipse.linuxtools.internal.rpm.ui.editor.rules;

import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IPredicateRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.linuxtools.internal.rpm.ui.editor.scanners.SpecfileChangelogScanner;

public class VersionReleaseRule implements IPredicateRule {

	/** Buffer used for pattern detection */
	private StringBuilder fBuffer = new StringBuilder();

	/** Buffer to keep track of trailing whitespace */
	private StringBuilder fWhiteSpaceBuffer = new StringBuilder();

	/** The success token */
	private IToken fToken;

	/** The token that has to preceed this token */
	private IToken fPreceedingToken;

	/** Where we can find out what the preceeding token was */
	private SpecfileChangelogScanner fChangelogScanner;

	protected static final char CHARS_SEPERATOR = '-';

	protected static final int STATE_START = 0;

	protected static final int STATE_VERSION = 1;

	protected static final int STATE_RELEASE = 2;

	protected static final int STATE_TRAIL = 3;

	protected static final int STATE_DONE = 4;

	public VersionReleaseRule(IToken token) {
		this.fToken = token;
	}

	public VersionReleaseRule(IToken successToken, IToken preceedingToken, SpecfileChangelogScanner scanner) {
		fToken = successToken;
		fPreceedingToken = preceedingToken;
		fChangelogScanner = scanner;
	}

	@Override
	public IToken getSuccessToken() {
		return fToken;
	}

	@Override
	public IToken evaluate(ICharacterScanner scanner, boolean resume) {
		// if the last token successfully read was not the fPreceedingToke fail
		IToken lastToken = getLastToken();

		if (lastToken != fPreceedingToken) {
			return Token.UNDEFINED;
		}

		fBuffer.setLength(0);
		int state = STATE_START;
		int c;
		int numPreceedingBlanks = 0;

		do {
			c = scanner.read();
			fBuffer.append((char) c);

			// preceeding white space
			if (state == STATE_START) {
				if (Character.isWhitespace((char) c) || c == '-') {
					numPreceedingBlanks++;
				} else {
					state++;
				}
			}
			// version state (first part of version-release)
			if (state == STATE_VERSION) {
				// if we've read some semblance of a version and we've reached
				// the separator character
				if (fBuffer.length() > numPreceedingBlanks && c == CHARS_SEPERATOR) {
					state++;
				}
				// otherwise we allow only digits, letters, underscores, ':' or
				// '.' in the version
				else if (!(Character.isLetterOrDigit((char) c) || c == '.' || c == '_' || c == ':')) {
					unreadBuffer(scanner, fBuffer);
					return Token.UNDEFINED;
				}
			}
			// release state (second part of version-release)
			else if (state == STATE_RELEASE) {
				// an EOF or EOL indicates success
				if (c == ICharacterScanner.EOF || c == '\n') {
					state = STATE_DONE;
				}

				// if we encounter a space, we enter the optional trailing
				// space section which we consider valid (but not part of the
				// token) if and only if it is ended by and EOF or EOL
				else if (Character.isWhitespace((char) c)) {
					state++;
					fWhiteSpaceBuffer.setLength(0);
					fWhiteSpaceBuffer.append(c);
				} else if (!(Character.isLetterOrDigit((char) c) || c == '.' || c == '_')) {
					// allow digits, characters or '.' in the release
					unreadBuffer(scanner, fBuffer);
					return Token.UNDEFINED;
				}

			}
			// whitespace state, we finished redeaing the ver-rel and are
			// now looking for an EOF or EOL for success
			else if (state == STATE_TRAIL) {
				// success, unwind the whitespace
				if (c == ICharacterScanner.EOF || c == '\n') {
					unreadBuffer(scanner, fWhiteSpaceBuffer);
					state++;
				} // some other illegal token after ver-rel unwind the whole
					// deal
				else if (!Character.isWhitespace((char) c)) {
					unreadBuffer(scanner, fBuffer);
					return Token.UNDEFINED;
				} else { // white space, keep reading
					fWhiteSpaceBuffer.append((char) c);
				}

			}
		} while (state != STATE_DONE);

		// we've gone through all states until we've reached STATE_DONE, success
		return fToken;
	}

	@Override
	public IToken evaluate(ICharacterScanner scanner) {
		return evaluate(scanner, false);
	}

	/**
	 * Returns the characters in the buffer to the scanner.
	 *
	 * @param scanner the scanner to be used
	 */
	protected void unreadBuffer(ICharacterScanner scanner, StringBuilder buffer) {
		for (int i = buffer.length() - 1; i >= 0; i--) {
			scanner.unread();
		}
	}

	protected IToken getLastToken() {
		return fChangelogScanner.getLastToken();
	}
}
