/*******************************************************************************
 * Copyright (c) 2014, 2015 Mateusz Matela and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Mateusz Matela <mateusz.matela@gmail.com> - [formatter] Formatter does not format Java code correctly, especially when max line width is set - https://bugs.eclipse.org/303519
 *******************************************************************************/
package org.eclipse.jdt.internal.formatter;

import java.util.List;

/**
 * Helper class that can be subclassed every time an algorithm needs to swipe through all or part of the tokens and
 * easily keep track or previous and future tokens and whitespace.
 */
public abstract class TokenTraverser {
	/** General purpose field that can be used by subclasses to count things */
	protected int counter = 0;
	/** General purpose field that can be used by subclasses to store an integer value */
	protected int value = 0;

	private boolean spaceBefore, spaceAfter;
	private int lineBreaksBefore, lineBreaksAfter;
	private Token previous, current, next;
	private boolean structureChanged = false;

	protected abstract boolean token(Token token, int index);

	/**
	 * Must be called every time tokens are added or removed from the list that is currently being traversed so that
	 * cached data can be refreshed.
	 */
	protected void structureChanged() {
		this.structureChanged = true;
	}

	protected boolean isSpaceBefore() {
		return this.spaceBefore;
	}

	protected boolean isSpaceAfter() {
		return this.spaceAfter;
	}

	protected int getLineBreaksBefore() {
		return this.lineBreaksBefore;
	}

	protected int getLineBreaksAfter() {
		return this.lineBreaksAfter;
	}

	protected Token getPrevious() {
		return this.previous;
	}

	protected Token getCurrent() {
		return this.current;
	}

	protected Token getNext() {
		return this.next;
	}

	private void initTraverse(List<Token> tokens, int startIndex) {
		if (tokens.isEmpty())
			return;
		this.structureChanged = false;

		this.previous = this.next = null;
		if (startIndex > 0)
			this.previous = tokens.get(startIndex - 1);
		this.current = tokens.get(startIndex);
		this.lineBreaksBefore = Math.max(this.previous != null ? this.previous.getLineBreaksAfter() : 0,
				this.current.getLineBreaksBefore());
		this.spaceBefore = this.current.isSpaceBefore();
		if (this.lineBreaksBefore == 0) {
			this.spaceBefore = this.spaceBefore || (this.previous != null && this.previous.isSpaceAfter());
		}
	}

	public int traverse(List<Token> tokens, int startIndex) {
		initTraverse(tokens, startIndex);

		for (int i = startIndex; i < tokens.size(); i++) {
			if (this.structureChanged)
				initTraverse(tokens, i);

			this.next = null;
			if (i < tokens.size() - 1) {
				this.next = tokens.get(i + 1);
			}
			this.lineBreaksAfter = Math.max(this.current.getLineBreaksAfter(),
					this.next != null ? this.next.getLineBreaksBefore() : 0);
			this.spaceAfter = this.current.isSpaceAfter();
			if (this.lineBreaksAfter == 0) {
				this.spaceAfter = this.spaceAfter || (this.next != null && this.next.isSpaceBefore());
			}

			if (!this.token(this.current, i))
				return i;

			if (this.next != null) {
				this.previous = this.current;
				this.current = this.next;
				this.lineBreaksBefore = this.lineBreaksAfter;
				this.spaceBefore = this.spaceAfter;
				if (this.lineBreaksBefore > 0)
					this.spaceBefore = this.current.isSpaceBefore();
			}
		}
		return tokens.size() - 1;
	}
}