/*
 * Copyright (C) 2010 The Project Lombok Authors.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package lombok.ast.grammar;

import java.util.List;
import java.util.Stack;

import com.google.common.collect.Lists;

import lombok.ast.AstException;
import lombok.ast.Node;
import lombok.ast.Position;
import lombok.ast.printer.TextFormatter;

public class PositionCheckingFormatter extends TextFormatter {
	private Stack<Node> nodeStack = new Stack<Node>();
	private List<AstException> problems = Lists.newArrayList();
	private Source source;
	
	public PositionCheckingFormatter(Source source) {
		this.source = source;
		if ("\r\n".equals(System.getProperty("line.separator", "\n")) && source.getRawInput().contains("\r\n")) {
			setNewlineText("\r\n");
		}
	}
	
	public List<AstException> getProblems() {
		List<AstException> p = Lists.newArrayList(problems);
		if (!nodeStack.isEmpty()) p.add(new AstException(nodeStack.peek(), "This node was never closed"));
		return p;
	}
	
	private void registerNode(Node node) {
		nodeStack.add(node);
		if (node == null) return;
		
		Position p = node.getPosition();
		if (p.isUnplaced()) problems.add(new AstException(node, String.format(
				"this node is unplaced [%s]", node.getClass().getSimpleName())));
		else {
			int delta = p.getStart() - getCurrentPosition(true);
			if (delta != 0) {
				int actualPos = getCurrentPosition(true);
				int repPos = p.getStart();
				reportError(node, "start", actualPos, repPos);
			}
		}
	}
	
	@Override public void buildBlock(Node node) {
		super.buildBlock(node);
		registerNode(node);
	}
	
	@Override public void buildInline(Node node) {
		super.buildInline(node);
		registerNode(node);
	}
	
	@Override public void closeBlock() {
		super.closeBlock();
		checkNodeClose();
	}
	
	@Override public void closeInline() {
		super.closeInline();
		checkNodeClose();
	}
	
	private void checkNodeClose() {
		Node node = nodeStack.pop();
		if (node == null) return;
		Position p = node.getPosition();
		if (p.isUnplaced()) return;	//opener already generated error.
		if (p.getStart() > p.getEnd()) problems.add(new AstException(node, "Node ends before it starts."));
		int delta = p.getEnd() - Math.max(getCurrentPosition(false), p.getStart());
		if (delta != 0) {
			int actualPos = getCurrentPosition(false);
			int repPos = p.getEnd();
			reportError(node, "end", actualPos, repPos);
		}
	}
	
	private static final int RANGE = 12;
	private void reportError(Node node, String description, int actualPos, int repPos) {
		int delta = repPos - actualPos;
		String raw = source.getRawInput();
		String actualPrefix = getCharsBeforePosition(actualPos, raw);
		String actualPostfix = getCharsAfterPosition(actualPos, raw);
		String reportedPrefix = getCharsBeforePosition(repPos, raw);
		String reportedPostfix = getCharsAfterPosition(repPos, raw);
		problems.add(new AstException(node, String.format(
			"this[%s] node's " + description + " is misreported with %+d\nactual(%d): \"%s⊚%s\"\nreported(%d): \"%s⊚%s\"\n%s",
			node.getClass().getSimpleName(),
			delta, actualPos, actualPrefix, actualPostfix, repPos, reportedPrefix, reportedPostfix, node)));
	}
	
	private String getCharsAfterPosition(int pos, String raw) {
		int max = raw.length();
		if (pos < 0) pos = 0;
		return pos >= max ? "" : raw.substring(pos, Math.min(pos + RANGE, max));
	}
	
	private String getCharsBeforePosition(int pos, String raw) {
		int max = raw.length();
		return pos > RANGE ? raw.substring(
				Math.min(max, pos-RANGE), Math.min(max, pos)) :
				raw.substring(0, Math.min(max, pos));
	}
}
