/*
 * LpLookaheadLexer.java
 *
 * Copyright (C) 2006 - 2007 Martin Slota
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/*
 * History:
 * v0.1 (2006-12-31): initial version
 * v0.2 (2007-01-02):
 * - implementation, tests, documentation (it is a pitty I realized I don't need
 *   it after I finished it :o)
 * v0.2.1 (2007-01-28):
 * - checked exceptions eliminated (see
 *   http://www.mindview.net/Etc/Discussions/CheckedExceptions)
 * - documentation updated
 * - constructors implemented differently
 * v0.2.2 (2007-02-08):
 * - made reusable (see also LpLexer)
 * - documentation updated
 * v0.2.3 (2007-03-05):
 * - initialize overriden (buffer must be cleared when it is called)
 * 1.0.0 (2007-05-04):
 * - promoted to version 1.0.0 :o)
 */

package lp.parse;

import java.io.*;
import java.util.LinkedList;
import lp.util.ExceptionAdapter;

/**
 * This class provides the same functionality as {@link LpLexer} and it offers the
 * possibility to take a look at future tokens and not loose the current
 * position in the input.
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see LpLexer
 */
public class LpLookaheadLexer extends LpLexer {
	/**
	 * A string with an exception message used multiple times in the
	 * implementation.
	 */
	private static final String NEGATIVE_STEPS_MESSAGE =
			"The number of steps must be non-negative.";
	
	/**
	 * A {@link LpToken} buffer used to implement the lookahead.
	 */
	private final LinkedList<LpToken> buffer = new LinkedList<LpToken>();
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void close() {
		buffer.clear();
		super.close();
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void initialize() {
		super.initialize();
		buffer.clear();
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void nextToken() {
		if (!buffer.isEmpty()) {
			buffer.removeFirst();
		}
		if (buffer.isEmpty()) {
			load(1);
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public LpTokenType getTokenType() {
		LpTokenType result = null;
		try {
			result = getTokenType(0);
		} catch (ExceptionAdapter e) {
			// should never reach here:
			// a) if nextToken() has already been called, one token must be in
			//    the buffer and no reading takes place
			// b) if it wasn't called, nothing is read and the default value
			//    (null) is returned
			assert false;
		}
		return result;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getLexem() {
		String result = null;
		try {
			result = getLexem(0);
		} catch (ExceptionAdapter e) {
			// should never reach here:
			// a) if nextToken() has already been called, one token must be in
			//    the buffer and no reading takes place
			// b) if it wasn't called, nothing is read and the default value
			//    (empty string) is returned
			assert false;
		}
		return result;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getLineNumber() {
		int result = 0;
		try {
			result = getLineNumber(0);
		} catch (ExceptionAdapter e) {
			// should never reach here:
			// a) if nextToken() has already been called, one token must be in
			//    the buffer and no reading takes place
			// b) if it wasn't called, nothing is read and the default value
			//    (1) is returned
			assert false;
		}
		return result;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getPosition() {
		int result = 0;
		try {
			result = getPosition(0);
		} catch (ExceptionAdapter e) {
			// should never reach here:
			// a) if nextToken() has already been called, one token must be in
			//    the buffer and no reading takes place
			// b) if it wasn't called, nothing is read and the default value
			//    (0) is returned
			assert false;
		}
		return result;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public LpToken getToken() {
		LpToken result = null;
		try {
			result = getToken(0);
		} catch (ExceptionAdapter e) {
			// should never reach here:
			// a) if nextToken() has already been called, one token must be in
			//    the buffer and no reading takes place
			// b) if it wasn't called, nothing is read and the default value
			//    (null) is returned
			assert false;
		}
		return result;
	}
	
	/**
	 * Returns the type of the token {@code n} steps further in the input. The
	 * same result will be returned if {@link #getTokenType()} is called after
	 * {@code n} times calling {@link #nextToken()}. The difference is that the
	 * current token doesn't change by calling this method&#8212;new tokens are
	 * read but the old ones are stored in a buffer.
	 *
	 * This method is not meant to be called before {@link #nextToken()} is
	 * called at least once. But if such a situation occurs, {@code null} is
	 * returned. Similarily, if {@link #close()} has already been called,
	 * {@code null} is returned.
	 *
	 * @param n number of steps further in the input to look at
	 * @return type of token {@code n} steps further in the input
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter})
	 * in case an I/O exception occurs while reading the input
	 * @throws IllegalArgumentException if {@code n} is negative
	 */
	public LpTokenType getTokenType(int n) {
		if (n < 0)
			throw new IllegalArgumentException(NEGATIVE_STEPS_MESSAGE);
		return (buffer.isEmpty()) ?
			super.getTokenType() : getToken(n).getType();
	}
	
	/**
	 * Returns the lexem corresponding the token {@code n} steps further in the
	 * input. In case it is a {@link LpTokenType#EOF} token, empty string is
	 * returned. The same result will be returned if {@link #getLexem()} is
	 * called after {@code n} times calling {@link #nextToken()}. The difference
	 * is that the current token doesn't change by calling this method&#8212;new
	 * tokens are read but the old ones are stored in a buffer.
	 *
	 * This method is not meant to be called before {@link #nextToken()} is
	 * called at least once. But if such a situation occurs, {@code null} is
	 * returned. Similarily, if {@link #close()} has already been called,
	 * {@code null} is returned.
	 *
	 * @param n number of steps further in the input to look at
	 * @return lexem corresponding to the last tokenn {@code n} steps further in
	 * the input
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while reading the input
	 * @throws IllegalArgumentException if {@code n} is negative
	 */
	public String getLexem(int n) {
		if (n < 0)
			throw new IllegalArgumentException(NEGATIVE_STEPS_MESSAGE);
		return (buffer.isEmpty()) ?
			super.getLexem() : getToken(n).getLexem();
	}
	
	/**
	 * Returns the number of line of input on which the token {@code n} steps
	 * further in the input occurs. Lines are numbered from 1. A newline starts
	 * when either a '\n' or a '\r' character is detected. There is one
	 * exception: a '\n' character occuring right after a '\r' character is
	 * ignored, i.e. not considered to be another line delimiter.
	 * The same result will be returned if {@link #getLineNumber()} is
	 * called after {@code n} times calling {@link #nextToken()}. The difference
	 * is that the current token doesn't change by calling this method&#8212;new
	 * tokens are read but the old ones are stored in a buffer.
	 *
	 * This method is not meant to be called before {@link #nextToken()} is
	 * called at least once. But if such a situation occurs, -1 is
	 * returned. Similarily, if {@link #close()} has already been called,
	 * -1 is returned.
	 *
	 * @param n number of steps further in the input to look at
	 * @return number of line on which the token {@code n} steps further in the
	 * input occurs
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while reading the input
	 * @throws IllegalArgumentException if {@code n} is negative
	 */
	public int getLineNumber(int n) {
		if (n < 0)
			throw new IllegalArgumentException(NEGATIVE_STEPS_MESSAGE);
		return (buffer.isEmpty()) ?
			super.getLineNumber() : getToken(n).getLineNumber();
	}
	
	/**
	 * Returns the position of the token {@code n} steps further in the input.
	 * The characters on the line are numbered from 1. The same result will be
	 * returned if {@link #getPosition()} is called after {@code n} times
	 * calling {@link #nextToken()}. The difference is that the current token
	 * doesn't change by calling this method&#8212;new tokens are read but the
	 * old ones are stored in a buffer.
	 *
	 * This method is not meant to be called before {@link #nextToken()} is
	 * called at least once. But if such a situation occurs, -1 is
	 * returned. Similarily, if {@link #close()} has already been called,
	 * -1 is returned.
	 *
	 * @param n number of steps further in the input to look at
	 * @return position of the token's beginning within a line of input
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while reading the input
	 * @throws IllegalArgumentException if {@code n} is negative
	 */
	public int getPosition(int n) {
		if (n < 0)
			throw new IllegalArgumentException(NEGATIVE_STEPS_MESSAGE);
		return (buffer.isEmpty()) ?
			super.getPosition() : getToken(n).getPosition();
	}
	
	/**
	 * Returns a {@link LpToken} instance containing information about the token
	 * {@code n} steps further in the input. The same result will be returned if
	 * {@link #getToken()} is called after {@code n} times calling
	 * {@link #nextToken()}. The difference is that the current token doesn't
	 * change by calling this method&#8212;new tokens are read but the old ones
	 * are stored in a buffer.
	 *
	 * This method is not meant to be called before {@link #nextToken()} is
	 * called at least once. But if such a situation occurs, {@code null} is
	 * returned. Similarily, if {@link #close()} has already been called,
	 * {@code null} is returned.
	 *
	 * @param n number of steps further in the input to look at
	 * @return a {@link LpToken} instance containing information about the token
	 * {@code n} steps further in the input
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while reading the input
	 * @throws IllegalArgumentException if {@code n} is negative
	 */
	public LpToken getToken(int n) {
		if (n < 0)
			throw new IllegalArgumentException(NEGATIVE_STEPS_MESSAGE);
		if (buffer.isEmpty()) {
			return super.getToken();
		}
		if (buffer.size() <= n) {
			load(n - buffer.size() + 1);
		}
		return buffer.get(n);
	}
	
	/**
	 * Reads {@code count} tokens and stores them in the buffer.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while reading the input
	 */
	private void load(int count) {
		for (int i = 0; i < count; i++) {
			super.nextToken();
			buffer.add(new LpToken(super.getTokenType(), super.getLexem(),
					super.getLineNumber(), super.getPosition()));
		}
	}
}