/*******************************************************************************
 * Copyright (c) 2019 Aston University.
 * 
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License, v. 2.0 are satisfied: GNU General Public License, version 3.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-3.0
 *
 * Contributors:
 *     Antonio Garcia-Dominguez - initial API and implementation
 ******************************************************************************/
package org.eclipse.hawk.timeaware.queries.operations.scopes.predicates;

import java.util.List;
import java.util.ListIterator;
import java.util.function.Function;
import java.util.function.Supplier;

import org.eclipse.epsilon.eol.dom.Expression;
import org.eclipse.epsilon.eol.exceptions.EolInternalException;
import org.eclipse.epsilon.eol.execute.context.FrameStack;
import org.eclipse.epsilon.eol.execute.context.FrameType;
import org.eclipse.epsilon.eol.execute.context.IEolContext;
import org.eclipse.epsilon.eol.execute.context.Variable;
import org.eclipse.hawk.core.graph.timeaware.ITimeAwareGraphNode;
import org.eclipse.hawk.timeaware.queries.TimeAwareEOLQueryEngine;
import org.eclipse.hawk.timeaware.queries.operations.TimeAwareNodeFirstOrderOperation;

/**
 * <p>
 * Generic EOL declarative operation which tries to find a timepoint matching
 * a predicate from the current timepoint of the target node, and passes
 * the original and matching nodes to a wrapper for decoration. The wrapper
 * will usually limit the range of versions available to the returned node.
 * </p>
 * 
 * <p>
 * If no such timepoint exists, the operation will return an undefined value,
 * which can be checked against with <code>.isDefined()</code>.
 * </p>
 */
public class VersionRangeOperation extends TimeAwareNodeFirstOrderOperation {

	@FunctionalInterface
	public interface IRangeBasedNodeScoper {
		ITimeAwareGraphNode scope(ITimeAwareGraphNode original, ITimeAwareGraphNode predicateMatch);
	}

	private final IRangeBasedNodeScoper nodeScoper;

	public VersionRangeOperation(Supplier<TimeAwareEOLQueryEngine> containerModelSupplier, IRangeBasedNodeScoper nodeScoper) {
		super(containerModelSupplier);
		this.nodeScoper = nodeScoper;
	}

	@Override
	protected Object execute(Variable iterator, Expression expression, IEolContext context,
			Function<ITimeAwareGraphNode, Object> versionWrapper, ITimeAwareGraphNode taNode)
			throws EolInternalException {
		try {
			final long latestInstant = taNode.getLatestInstant();
			final List<ITimeAwareGraphNode> versions = taNode.getVersionsBetween(taNode.getTime(), latestInstant);
			final FrameStack scope = context.getFrameStack();

			for (ListIterator<ITimeAwareGraphNode> itVersion = versions.listIterator(versions.size()); itVersion.hasPrevious(); ) {
				final ITimeAwareGraphNode version = itVersion.previous();
				final Object listItem = versionWrapper.apply(version);

				if (iterator.getType()==null || iterator.getType().isKind(listItem)){
					scope.enterLocal(FrameType.UNPROTECTED, expression);

					scope.put(Variable.createReadOnlyVariable(iterator.getName(), listItem));
					Object bodyResult = context.getExecutorFactory().execute(expression, context);
					scope.leaveLocal(expression);

					if (bodyResult instanceof Boolean && (boolean)bodyResult) {
						final ITimeAwareGraphNode scoped = nodeScoper.scope(taNode, version);
						if (scoped == null) {
							return null;
						} else {
							return versionWrapper.apply(scoped);
						}
					}
				}
			}
		} catch (Exception ex) {
			throw new EolInternalException(ex, expression);
		}

		return null;
	}

	@Override
	public boolean isOverridable() {
		return false;
	}

}
