/*******************************************************************************
 * Copyright (c) 2011 Wind River Systems, Inc. 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:
 *    Markus Schorn - initial API and implementation
 *******************************************************************************/ 

package org.eclipse.cdt.internal.core.dom.parser.cpp;

import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.internal.core.dom.parser.AbstractGNUSourceCodeParser.BinaryOperator;

/**
 * Tracks variants of expressions due to the ambiguity between template-id and '<' operator.
 */
public class NameOrTemplateIDVariants {
	/**
	 * A point where a '<' can be interpreted as less-than or as the angle-bracket of a template-id.
	 */
	static class BranchPoint {
		private BranchPoint fNext;
		private Variant fFirstVariant;
		private final boolean fAllowAssignment;
		private final int fConditionCount;
		private final BinaryOperator fLeftOperator;

		BranchPoint(BranchPoint next, Variant variant,
				 BinaryOperator left, boolean allowAssignment, int conditionCount) {
			fNext= next;
			fFirstVariant= variant;
			fAllowAssignment= allowAssignment;
			fConditionCount= conditionCount;
			fLeftOperator= left;
			// Set owner
			while (variant != null) {
				variant.fOwner= this;
				variant= variant.getNext();
			}
		}

		public boolean isAllowAssignment() {
			return fAllowAssignment;
		}
		public int getConditionCount() {
			return fConditionCount;
		}
		public BinaryOperator getLeftOperator() {
			return fLeftOperator;
		}
		public Variant getFirstVariant() {
			return fFirstVariant;
		}
		public BranchPoint getNext() {
			return fNext;
		}

		public void reverseVariants() {
			Variant prev= null;
			Variant curr= fFirstVariant;
			while (curr != null) {
				Variant next= curr.getNext();
				curr.fNext= prev;
				prev= curr;
				curr= next;
			}
			fFirstVariant= prev;
		}
	}
	
	/**
	 * A variant for a branch-point is a cast-expression that can be used within a binary expression.
	 */
	static class Variant {
		private BranchPoint fOwner;
		private Variant fNext;
		private final IASTExpression fExpression;
		private BinaryOperator fTargetOperator;
		private final int fRightOffset;
		private final IASTName[] fTemplateNames;

		public Variant(Variant next, IASTExpression expr, IASTName[] templateNames, int rightOffset) {
			fNext= next;
			fExpression= expr;
			fRightOffset= rightOffset;
			fTemplateNames= templateNames;
		}

		public BranchPoint getOwner() {
			return fOwner;
		}
		public int getRightOffset() {
			return fRightOffset;
		}
		public IASTName[] getTemplateNames() {
			return fTemplateNames;
		}
		public Variant getNext() {
			return fNext;
		}
		public IASTExpression getExpression() {
			return fExpression;
		}
		public BinaryOperator getTargetOperator() {
			return fTargetOperator;
		}
		public void setTargetOperator(BinaryOperator lastOperator) {
			fTargetOperator= lastOperator;
		}
	}

	
	private BranchPoint fFirst;

	public boolean isEmpty() {
		return fFirst == null;
	}

	public void addBranchPoint(Variant variants, BinaryOperator left,
			boolean allowAssignment, int conditionCount) {
		fFirst= new BranchPoint(fFirst, variants, left, allowAssignment, conditionCount);
	}

	public void closeVariants(int offset, BinaryOperator lastOperator) {
		for (BranchPoint p = fFirst; p != null; p= p.getNext()) {
			for (Variant v= p.getFirstVariant(); v != null; v= v.getNext()) {
				if (v.getTargetOperator() == null) {
					if (offset == v.getRightOffset()) {
						v.setTargetOperator(lastOperator);
					} 
				}
			}
		}
	}
	
	public Variant selectFallback() {
		// Search for an open variant, with a small right offset and a large left offset
		for (BranchPoint p = fFirst; p != null; p= p.getNext()) {
			Variant best= null;
			for (Variant v= p.getFirstVariant(); v != null; v= v.getNext()) {
				if (v.getTargetOperator() == null) {
					if (best == null || v.fRightOffset < best.fRightOffset) {
						best= v;
					} 
				}
			}
			if (best != null) {
				remove(best);
				return best;
			}
		}
		return null;
	}

	private void remove(Variant remove) {
		final BranchPoint owner = remove.fOwner;
		final Variant next = remove.getNext();
		Variant prev= owner.getFirstVariant();
		if (remove == prev) {
			owner.fFirstVariant= next;
			if (next == null) {
				remove(owner);
			}
		} else {
			while (prev != null) {
				Variant n = prev.getNext();
				if (n == remove) {
					prev.fNext= next;
					break;
				}
				prev= n;
			}
		}
	}
		
	private void remove(BranchPoint remove) {
		final BranchPoint next = remove.getNext();
		if (remove == fFirst) {
			fFirst= next;
		} else {
			BranchPoint prev= fFirst;
			while (prev != null) {
				BranchPoint n = prev.getNext();
				if (n == remove) {
					prev.fNext= next;
					break;
				}
				prev= n;
			} 
		}
	}

	public BranchPoint getOrderedBranchPoints() {
		BranchPoint prev= null;
		BranchPoint curr= fFirst;
		while (curr != null) {
			curr.reverseVariants();
			BranchPoint next= curr.getNext();
			curr.fNext= prev;
			prev= curr;
			curr= next;
		}
		fFirst= null;
		return prev;
	}

	public boolean hasRightBound(int opOffset) {
		// Search for an open variant, with a small right offset and a large left offset
		for (BranchPoint p = fFirst; p != null; p= p.getNext()) {
			for (Variant v= p.getFirstVariant(); v != null; v= v.getNext()) {
				if (v.fRightOffset > opOffset) 
					return false;
			}
		}
		return true;
	}

	public void removeInvalid(BinaryOperator lastOperator) {
		for (BranchPoint p = fFirst; p != null; p= p.getNext()) {
			if (!isReachable(p, lastOperator)) {
				remove(p);
			} else { 
				for (Variant v= p.getFirstVariant(); v != null; v= v.getNext()) {
					if (v.getTargetOperator() == null) {
						remove(v);
					}
				}
			}
		}
	}	
	
	private boolean isReachable(BranchPoint bp, BinaryOperator endOperator) {
		BinaryOperator op = bp.getLeftOperator();
		if (op == null)
			return true;
		
		for(; endOperator != null; endOperator= endOperator.getNext()) {
			if (endOperator == op)
				return true;
		}
		return false;
	}
}
