/*******************************************************************************
 * Copyright (c) 2000, 2008 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.core.internal.expressions;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.w3c.dom.Element;

import org.eclipse.core.expressions.CompositeExpression;
import org.eclipse.core.expressions.EvaluationResult;
import org.eclipse.core.expressions.Expression;
import org.eclipse.core.expressions.ExpressionInfo;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.core.expressions.IIterable;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;

public class IterateExpression extends CompositeExpression {

	private static class IteratePool implements IEvaluationContext {

		private final Iterator<?> fIterator;
		private Object fDefaultVariable;
		private final IEvaluationContext fParent;

		public IteratePool(IEvaluationContext parent, Iterator<?> iterator) {
			Assert.isNotNull(parent);
			Assert.isNotNull(iterator);
			fParent= parent;
			fIterator= iterator;
		}
		@Override
		public IEvaluationContext getParent() {
			return fParent;
		}
		@Override
		public IEvaluationContext getRoot() {
			return fParent.getRoot();
		}
		@Override
		public Object getDefaultVariable() {
			return fDefaultVariable;
		}
		@Override
		public boolean getAllowPluginActivation() {
			return fParent.getAllowPluginActivation();
		}
		@Override
		public void setAllowPluginActivation(boolean value) {
			fParent.setAllowPluginActivation(value);
		}
		@Override
		public void addVariable(String name, Object value) {
			fParent.addVariable(name, value);
		}
		@Override
		public Object removeVariable(String name) {
			return fParent.removeVariable(name);
		}
		@Override
		public Object getVariable(String name) {
			return fParent.getVariable(name);
		}
		@Override
		public Object resolveVariable(String name, Object[] args) throws CoreException {
			return fParent.resolveVariable(name, args);
		}
		public Object next() {
			fDefaultVariable= fIterator.next();
			return fDefaultVariable;
		}
		public boolean hasNext() {
			return fIterator.hasNext();
		}
	}

	private static final String ATT_OPERATOR= "operator"; //$NON-NLS-1$
	private static final String ATT_IF_EMPTY= "ifEmpty"; //$NON-NLS-1$
	private static final int OR= 1;
	private static final int AND= 2;

	/**
	 * The seed for the hash code for all iterate expressions.
	 */
	private static final int HASH_INITIAL= IterateExpression.class.getName().hashCode();

	private int fOperator;
	private Boolean fEmptyResult;

	public IterateExpression(IConfigurationElement configElement) throws CoreException {
		String opValue= configElement.getAttribute(ATT_OPERATOR);
		initializeOperatorValue(opValue);
		initializeEmptyResultValue(configElement.getAttribute(ATT_IF_EMPTY));
	}

	public IterateExpression(Element element) throws CoreException {
		String opValue= element.getAttribute(ATT_OPERATOR);
		initializeOperatorValue(opValue.isEmpty() ? null : opValue);
		String ifEmpty= element.getAttribute(ATT_IF_EMPTY);
		initializeEmptyResultValue(ifEmpty.isEmpty() ? null : ifEmpty);
	}

	public IterateExpression(String opValue) throws CoreException {
		initializeOperatorValue(opValue);
	}

	public IterateExpression(String opValue, String ifEmpty) throws CoreException {
		initializeOperatorValue(opValue);
		initializeEmptyResultValue(ifEmpty);
	}

	private void initializeOperatorValue(String opValue) throws CoreException {
		if (opValue == null) {
			fOperator= AND;
		} else {
			Expressions.checkAttribute(ATT_OPERATOR, opValue, new String[] {"and", "or"});  //$NON-NLS-1$//$NON-NLS-2$
			if ("and".equals(opValue)) { //$NON-NLS-1$
				fOperator= AND;
			} else {
				fOperator= OR;
			}
		}
	}

	private void initializeEmptyResultValue(String value) {
		if (value == null) {
			fEmptyResult= null;
		} else {
			fEmptyResult= Boolean.valueOf(value);
		}
	}

	@Override
	public EvaluationResult evaluate(IEvaluationContext context) throws CoreException {
		Object var= context.getDefaultVariable();
		if (var instanceof Collection) {
			Collection<?> col= (Collection<?>)var;
			switch (col.size()) {
				case 0:
					if (fEmptyResult == null) {
						return fOperator == AND ? EvaluationResult.TRUE : EvaluationResult.FALSE;
					} else {
						return fEmptyResult.booleanValue() ? EvaluationResult.TRUE : EvaluationResult.FALSE;
					}
				case 1:
					if (col instanceof List)
						return evaluateAnd(new DefaultVariable(context, ((List<?>)col).get(0)));
					//$FALL-THROUGH$
				default:
					IteratePool iter= new IteratePool(context, col.iterator());
					EvaluationResult result= fOperator == AND ? EvaluationResult.TRUE : EvaluationResult.FALSE;
					while (iter.hasNext()) {
						iter.next();
						switch(fOperator) {
							case OR:
								result= result.or(evaluateAnd(iter));
								if (result == EvaluationResult.TRUE)
									return result;
								break;
							case AND:
								result= result.and(evaluateAnd(iter));
								if (result != EvaluationResult.TRUE)
									return result;
								break;
						}
					}
					return result;
			}
		} else {
			IIterable<?> iterable= Expressions.getAsIIterable(var, this);
			if (iterable == null)
				return EvaluationResult.NOT_LOADED;
			int count= 0;
			IteratePool iter= new IteratePool(context, iterable.iterator());
			EvaluationResult result= fOperator == AND ? EvaluationResult.TRUE : EvaluationResult.FALSE;
			while (iter.hasNext()) {
				iter.next();
				count++;
				switch(fOperator) {
					case OR:
						result= result.or(evaluateAnd(iter));
						if (result == EvaluationResult.TRUE)
							return result;
						break;
					case AND:
						result= result.and(evaluateAnd(iter));
						if (result != EvaluationResult.TRUE)
							return result;
						break;
				}
			}
			if (count > 0) {
				return result;
			} else if (fEmptyResult == null) {
				return fOperator == AND ? EvaluationResult.TRUE : EvaluationResult.FALSE;
			} else {
				return fEmptyResult.booleanValue() ? EvaluationResult.TRUE : EvaluationResult.FALSE;
			}
		}
	}

	@Override
	public void collectExpressionInfo(ExpressionInfo info) {
		// Although we access every single variable we only mark the default
		// variable as accessed since we don't have single variables for the
		// elements.
		info.markDefaultVariableAccessed();
		super.collectExpressionInfo(info);
	}

	@Override
	public boolean equals(final Object object) {
		if (!(object instanceof IterateExpression))
			return false;

		final IterateExpression that= (IterateExpression)object;
		return (this.fOperator == that.fOperator) && equals(this.fExpressions, that.fExpressions);
	}

	@Override
	protected int computeHashCode() {
		return HASH_INITIAL * HASH_FACTOR + hashCode(fExpressions)
			* HASH_FACTOR + fOperator;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder(getClass().getSimpleName());
		builder.append(" [type=").append(fOperator == AND ? "AND" : "OR"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		Expression[] children = getChildren();
		if (children.length > 0) {
			builder.append(", children="); //$NON-NLS-1$
			builder.append(Arrays.toString(children));
		}
		builder.append("]"); //$NON-NLS-1$
		return builder.toString();
	}
}
