/**
 * <copyright>
 * Copyright (c) 2010-2014 Henshin developers. 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
 * </copyright>
 */
package org.eclipse.emf.henshin.interpreter.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.script.ScriptEngine;
import javax.script.ScriptException;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.henshin.interpreter.ApplicationMonitor;
import org.eclipse.emf.henshin.interpreter.Change;
import org.eclipse.emf.henshin.interpreter.Change.CompoundChange;
import org.eclipse.emf.henshin.interpreter.EGraph;
import org.eclipse.emf.henshin.interpreter.Engine;
import org.eclipse.emf.henshin.interpreter.Match;
import org.eclipse.emf.henshin.interpreter.PartitionedEGraph;
import org.eclipse.emf.henshin.interpreter.impl.ChangeImpl.AttributeChangeImpl;
import org.eclipse.emf.henshin.interpreter.impl.ChangeImpl.CompoundChangeImpl;
import org.eclipse.emf.henshin.interpreter.impl.ChangeImpl.IndexChangeImpl;
import org.eclipse.emf.henshin.interpreter.impl.ChangeImpl.ObjectChangeImpl;
import org.eclipse.emf.henshin.interpreter.impl.ChangeImpl.ReferenceChangeImpl;
import org.eclipse.emf.henshin.interpreter.info.ConditionInfo;
import org.eclipse.emf.henshin.interpreter.info.RuleChangeInfo;
import org.eclipse.emf.henshin.interpreter.info.RuleInfo;
import org.eclipse.emf.henshin.interpreter.info.VariableInfo;
import org.eclipse.emf.henshin.interpreter.matching.conditions.AndFormula;
import org.eclipse.emf.henshin.interpreter.matching.conditions.ApplicationCondition;
import org.eclipse.emf.henshin.interpreter.matching.conditions.ConditionHandler;
import org.eclipse.emf.henshin.interpreter.matching.conditions.IFormula;
import org.eclipse.emf.henshin.interpreter.matching.conditions.NotFormula;
import org.eclipse.emf.henshin.interpreter.matching.conditions.OrFormula;
import org.eclipse.emf.henshin.interpreter.matching.conditions.XorFormula;
import org.eclipse.emf.henshin.interpreter.matching.constraints.BinaryConstraint;
import org.eclipse.emf.henshin.interpreter.matching.constraints.DanglingConstraint;
import org.eclipse.emf.henshin.interpreter.matching.constraints.DomainSlot;
import org.eclipse.emf.henshin.interpreter.matching.constraints.Solution;
import org.eclipse.emf.henshin.interpreter.matching.constraints.SolutionFinder;
import org.eclipse.emf.henshin.interpreter.matching.constraints.TypeConstraint.PartitionThread;
import org.eclipse.emf.henshin.interpreter.matching.constraints.UnaryConstraint;
import org.eclipse.emf.henshin.interpreter.matching.constraints.Variable;
import org.eclipse.emf.henshin.model.And;
import org.eclipse.emf.henshin.model.Attribute;
import org.eclipse.emf.henshin.model.Edge;
import org.eclipse.emf.henshin.model.Formula;
import org.eclipse.emf.henshin.model.Graph;
import org.eclipse.emf.henshin.model.Mapping;
import org.eclipse.emf.henshin.model.MappingList;
import org.eclipse.emf.henshin.model.NestedCondition;
import org.eclipse.emf.henshin.model.Node;
import org.eclipse.emf.henshin.model.Not;
import org.eclipse.emf.henshin.model.Or;
import org.eclipse.emf.henshin.model.Parameter;
import org.eclipse.emf.henshin.model.Rule;
import org.eclipse.emf.henshin.model.Xor;
import org.eclipse.emf.henshin.model.staticanalysis.PathFinder;
import org.eclipse.emf.henshin.model.util.ScriptEngineWrapper;

/**
 * Default {@link Engine} implementation.
 * 
 * @author Christian Krause, Gregor Bonifer, Enrico Biermann
 */
public class EngineImpl implements Engine {

	/**
	 * Default value for option {@link Engine#OPTION_SORT_VARIABLES}.
	 */
	private static final boolean DEFAULT_SORT_VARIABLES = true;

	/**
	 * Default value for option {@link Engine#OPTION_INVERSE_MATCHING_ORDER}.
	 */
	private static final boolean DEFAULT_INVERSE_MATCHING_ORDER = true;

	/**
	 * Default value for option {@link Engine#OPTION_DESTROY_MATCHES}.
	 */
	private static final boolean DEFAULT_DESTROY_MATCHES = false;

	/**
	 * Options to be used.
	 */
	protected final Map<String, Object> options;

	/**
	 * Script engine used to compute Java expressions in attributes.
	 */
	protected final ScriptEngineWrapper scriptEngine;

	/**
	 * Cached information lookup map for each rule.
	 */
	protected final Map<Rule, RuleInfo> ruleInfos;

	/**
	 * Cached graph options.
	 */
	protected final Map<Graph, MatchingOptions> graphOptions;

	/**
	 * Listen for rule changes.
	 */
	protected final EContentAdapter ruleListener;

	/**
	 * Whether to sort variables.
	 */
	protected boolean sortVariables;

	/**
	 * Whether to use inverse matching order.
	 */
	protected boolean inverseMatchingOrder;

	/**
	 * Whether destruction of matches is allowed.
	 */
	protected boolean destroyMatches;

	/**
	 * Worker thread pool.
	 */
	protected ExecutorService workerPool;
	
	//monitor
	/**
	 * Object to monitor execution
	*/
	ApplicationMonitor monitor=null;


	/**
	 * Constructor.
	 * 
	 * @param globalJavaImports
	 *            List of global Java imports to be used in the script engine.
	 */
	public EngineImpl(String... globalJavaImports) {

		// Initialize fields:
		ruleInfos = new HashMap<Rule, RuleInfo>();
		graphOptions = new HashMap<Graph, MatchingOptions>();
		options = new EngineOptions();
		sortVariables = DEFAULT_SORT_VARIABLES;
		inverseMatchingOrder = DEFAULT_INVERSE_MATCHING_ORDER;
		destroyMatches = DEFAULT_DESTROY_MATCHES;

		// Initialize the script engine:
		scriptEngine = new ScriptEngineWrapper(globalJavaImports);

		// Rule listener for automatically clearing caches when rules are
		// changed at run-time:
		ruleListener = new RuleChangeListener();
	}

	/**
	 * Change listener for rules. If a rule is changed externally, the listener
	 * drops all cached options for this rule. This enables dynamic high-order
	 * transformation of rules.
	 */
	private final class RuleChangeListener extends EContentAdapter {

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.emf.ecore.util.EContentAdapter#notifyChanged(org.eclipse
		 * .emf.common.notify.Notification)
		 */
		@Override
		public void notifyChanged(Notification notification) {
			super.notifyChanged(notification);
			int eventType = notification.getEventType();
			if (eventType == Notification.RESOLVE || eventType == Notification.REMOVING_ADAPTER) {
				return;
			}
			if (notification.getNotifier() instanceof EObject) {
				EObject object = (EObject) notification.getNotifier();
				while (object != null) {
					if (object instanceof Rule) {
						ruleInfos.remove(object);
						object.eAdapters().remove(ruleListener);
					}
					if (object instanceof Graph) {
						graphOptions.remove(object);
					}
					object = object.eContainer();
				}
			}
		}
	};

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.emf.henshin.interpreter.Engine#findMatches(org.eclipse.emf
	 * .henshin.model.Rule, org.eclipse.emf.henshin.interpreter.EGraph,
	 * org.eclipse.emf.henshin.interpreter.Match)
	 */
	@Override
	public Iterable<Match> findMatches(Rule rule, EGraph graph, Match partialMatch) {
		if (rule == null || graph == null) {
			throw new NullPointerException("Rule and graph must not be null");
		}
		if (partialMatch == null) {
			partialMatch = new MatchImpl(rule);
		}
		return new MatchGenerator(rule, graph, partialMatch);
	}

	/**
	 * Match generator class. Delegates to {@link MatchFinder}.
	 */
	final class MatchGenerator implements Iterable<Match> {

		/**
		 * Rule to be matched.
		 */
		private final Rule rule;

		/**
		 * Object graph.
		 */
		private final EGraph graph;

		/**
		 * A partial match.
		 */
		private final Match partialMatch;

		/**
		 * Default constructor.
		 * 
		 * @param rule
		 *            Rule to be matched.
		 * @param graph
		 *            Object graph.
		 * @param partialMatch
		 *            Partial match.
		 */
		public MatchGenerator(Rule rule, EGraph graph, Match partialMatch) {
			this.rule = rule;
			this.graph = graph;
			this.partialMatch = partialMatch;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Iterable#iterator()
		 */
		@Override
		public Iterator<Match> iterator() {
			return new MatchFinder(rule, graph, partialMatch, new HashSet<EObject>());
		}

	} // MatchGenerator

	/**
	 * Match finder class. Uses {@link SolutionFinder} to find matches.
	 */
	public final class MatchFinder implements Iterator<Match> {

		/**
		 * The next match.
		 */
		private Match nextMatch;

		/**
		 * Flag indicating whether the next match was computed.
		 */
		private boolean computedNextMatch;

		/**
		 * Target graph.
		 */
		private final EGraph graph;

		/**
		 * Solution finder to be used.
		 */
		private final SolutionFinder solutionFinder;

		/**
		 * Rule to be matched.
		 */
		private final Rule rule;

		/**
		 * Cached rule info.
		 */
		protected final RuleInfo ruleInfo;

		/**
		 * Used objects.
		 */
		private final Set<EObject> usedObjects;

		/**
		 * Default constructor.
		 * 
		 * @param rule
		 *            Rule to be matched.
		 * @param graph
		 *            Object graph.
		 * @param partialMatch
		 *            A partial match.
		 * @param usedObjects
		 *            Used objects (for ensuring injectivity).
		 */
		public MatchFinder(Rule rule, EGraph graph, Match partialMatch, Set<EObject> usedObjects) {
			this.rule = rule;
			this.ruleInfo = getRuleInfo(rule);
			this.graph = graph;
			this.usedObjects = usedObjects;
			this.solutionFinder = createSolutionFinder(partialMatch);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.Iterator#hasNext()
		 */
		@Override
		public boolean hasNext() {
			if (!computedNextMatch) {
				computeNextMatch();
				computedNextMatch = true;
			}
			return (nextMatch != null);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.Iterator#next()
		 */
		@Override
		public Match next() {
			if (hasNext()) {
				computedNextMatch = false;
			}
			return nextMatch;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.Iterator#remove()
		 */
		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

		/**
		 * Compute the next match.
		 */
		private void computeNextMatch() {

			// We definitely need a solution finder:
			if (solutionFinder == null) {
				nextMatch = null;
				return;
			}

			// Find the next solution:
			Solution solution = solutionFinder.getNextSolution();
			
			nextMatch = basicMatchFromSolution(solution);
			if (nextMatch == null)
			{
				return;
			}

			// Handle the multi-rules:
			for (Rule multiRule : rule.getMultiRules()) {

				// The used kernel objects:
				Set<EObject> usedKernelObjects = new HashSet<EObject>(usedObjects);
				usedKernelObjects.addAll(nextMatch.getNodeTargets());

				// Create the partial multi match:
				Match partialMultiMatch = new MatchImpl(multiRule);
				for (Parameter param : rule.getParameters()) {
					Parameter multiParam = multiRule.getParameter(param.getName());
					if (multiParam != null) {
						partialMultiMatch.setParameterValue(multiParam, nextMatch.getParameterValue(param));
					}
				}
				for (Mapping mapping : multiRule.getMultiMappings()) {
					partialMultiMatch.setNodeTarget(mapping.getImage(), nextMatch.getNodeTarget(mapping.getOrigin()));
				}

				// Find nested multi-matches:
				List<Match> nestedMatches = nextMatch.getMultiMatches(multiRule);

				// Check if we can use worker threads:
				if (workerPool != null && (graph instanceof PartitionedEGraph)
						&& (multiRule.getLhs().getNodes().size() > 1)) {

					// Create match finder workers:
					List<Future<List<Match>>> matchFinderFutures = new ArrayList<Future<List<Match>>>();
					int partitions = ((PartitionedEGraph) graph).getNumPartitions();
					for (int p = 0; p < partitions; p++) {
						Set<EObject> freshUsedObjects = new HashSet<EObject>(usedKernelObjects);
						MatchFinder matchFinder = new MatchFinder(multiRule, graph, partialMultiMatch,
								freshUsedObjects);
						MatchFinderWorker worker = new MatchFinderWorker(matchFinder, p);
						matchFinderFutures.add(workerPool.submit(worker));
					}

					// Collect found matches:
					try {
						for (Future<List<Match>> futures : matchFinderFutures) {
							nestedMatches.addAll(futures.get());
						}
					} catch (Throwable t) {
						throw new RuntimeException(t);
					}

				} else {

					// Otherwise execute directly in this thread:
					MatchFinder matchFinder = new MatchFinder(multiRule, graph, partialMultiMatch, usedKernelObjects);
					while (matchFinder.hasNext()) {
						nestedMatches.add(matchFinder.next());
					}
				}

				boolean valid = rule.getMultiRules().isEmpty() || doPostponedDanglingChecks(solution, nextMatch);
				if (!valid)
					computeNextMatch();
			}
		}
		
		/**
		 * Creates a basic {@link Match} from a given {@link Solution}. Does not consider multi-rules.
		 */
		public Match basicMatchFromSolution(Solution solution) {
			if (solution == null) {
				return null;
			}

			// Create the new match:
			Match match = new MatchImpl(rule);

			// Parameter values:
			for (Entry<String, Object> entry : solution.parameterValues.entrySet()) {
				Parameter param = match.getUnit().getParameter(entry.getKey());
				if (param != null) {
					match.setParameterValue(param, entry.getValue());
				}
			}

			// LHS node targets:
			Map<Node, Variable> node2var = ruleInfo.getVariableInfo().getNode2variable();
			for (Node node : rule.getLhs().getNodes()) {
				match.setNodeTarget(node, solution.objectMatches.get(node2var.get(node)));
			}
			
			return match;
		}

		private boolean doPostponedDanglingChecks(Solution solution, Match nextMatch) {
			for (Variable var : solution.objectMatches.keySet()) {
				Node node = ruleInfo.getVariableInfo().getVariableForNode(var);
				EObject val = solution.objectMatches.get(var);

				for (DanglingConstraint constraint : var.danglingConstraints) {
					if (constraint.postpone) {
						boolean result = doCheckOnMultiRulesTransitively(nextMatch, node, val, constraint);
						if (!result)
							return false;
					}
				}
			}
			return true;
		}

		private boolean doCheckOnMultiRulesTransitively(Match nextMatch, Node node, EObject val, DanglingConstraint constraint) {
			boolean result = true;
			DanglingConstraint constraint_ = constraint.copy();
			if (rule.getMultiRules().isEmpty())
				return constraint_.check(val, graph);
			
			// Main idea: The number of matches of each multi-rule determines
			// how many (incoming and outgoing) edges the node provided as value
			// may have s.t. no dangling edge remains.
			for (Rule multi : rule.getMultiRules()) {
				int count = nextMatch.getMultiMatches(multi).size();
				MappingList map = multi.getMultiMappings();
				Node mulNode = map.getImage(node, multi.getLhs());
				if (mulNode != null) {
					for (Edge mulEdge : mulNode.getOutgoing()) {
						Node kerNode = map.getOrigin(mulEdge.getTarget());
						if (kerNode == null) {
							constraint_.increaseOutgoing(mulEdge.getType(), count);
						}
					}
					for (Edge mulEdge : mulNode.getIncoming()) {
						Node kerNode = map.getOrigin(mulEdge.getSource());
						if (kerNode == null) {
							constraint_.increaseIncoming(mulEdge.getType(), count);
						}
					}

					for (Rule nextmulti : multi.getMultiRules()) {
						for (Match m : nextMatch.getMultiMatches(nextmulti)) {
							result &= doCheckOnMultiRulesTransitively(m, mulNode, val, constraint_);							
						}
					}
				}
			}
			return result;
		}

		/**
		 * Create a solution finder.
		 * 
		 * @param partialMatch
		 *            A partial match.
		 * @return The solution finder.
		 */
		protected SolutionFinder createSolutionFinder(Match partialMatch) {

			// Get the required info objects:
			final ConditionInfo conditionInfo = ruleInfo.getConditionInfo();
			final VariableInfo varInfo = ruleInfo.getVariableInfo();
			
			//Set unique id in each Variable-Object
			for(Entry<Node, Variable> varEntry:varInfo.getNode2variable().entrySet()){
				varEntry.getValue().variableId=EcoreUtil.getURI(varEntry.getKey()).toString();
			}
			

			// Evaluates attribute conditions of the rule:
			ConditionHandler conditionHandler = new ConditionHandler(conditionInfo.getConditionParameters(),
					scriptEngine, rule.getAllJavaImports());

			/*
			 * The set "usedObjects" ensures injective matching by removing
			 * already matched objects from other DomainSlots
			 */

			/*
			 * Create a domain map where all variables are mapped to slots.
			 * Different variables may share one domain slot, if there is a
			 * mapping between the nodes of the variables.
			 */

			Map<Variable, DomainSlot> domainMap = new HashMap<Variable, DomainSlot>();

			for (Variable mainVariable : varInfo.getMainVariables()) {
				Node node = varInfo.getVariableForNode(mainVariable);
				MatchingOptions opt = getGraphOptions(node.getGraph());
				DomainSlot domainSlot = new DomainSlot(conditionHandler, usedObjects, opt.injective, opt.dangling,
						opt.deterministic, inverseMatchingOrder,monitor); //added monitor

				// Fix this slot?
				EObject target = partialMatch.getNodeTarget(node);
				if (target != null) {
					domainSlot.fixInstantiation(target);
				}

				// Add the dependent variables to the domain map:
				for (Variable dependendVariable : varInfo.getDependendVariables(mainVariable)) {
					domainMap.put(dependendVariable, domainSlot);
				}
			}

			// Check if any of the conditions failed:
			for (Parameter param : rule.getParameters()) {
				Object value = partialMatch.getParameterValue(param);
				if (value != null && !conditionHandler.setParameter(param.getName(), value)) {
					return null;
				}
			}

			// Sort the main variables based on the size of their domains:
			Map<Graph, List<Variable>> graphMap = new HashMap<Graph, List<Variable>>();
			for (Entry<Graph, List<Variable>> entry : ruleInfo.getVariableInfo().getGraph2variables().entrySet()) {
				List<Variable> vars = new ArrayList<Variable>(entry.getValue());
				if (sortVariables) {
					// sorting the variables
					Collections.sort(vars, new VariableComparator(graph, varInfo, partialMatch));
				}
				graphMap.put(entry.getKey(), vars);
			}

			// Now initialize the match finder:
			SolutionFinder solutionFinder = new SolutionFinder(graph, domainMap, conditionHandler,monitor); //added monitor
			solutionFinder.variables = graphMap.get(rule.getLhs());
			solutionFinder.formula = initFormula(rule.getLhs().getFormula(), graph, graphMap, domainMap);
			return solutionFinder;

		}

		/**
		 * Initialize a formula.
		 */
		private IFormula initFormula(Formula formula, EGraph graph, Map<Graph, List<Variable>> graphMap,
				Map<Variable, DomainSlot> domainMap) {
			if (formula instanceof And) {
				And and = (And) formula;
				IFormula left = initFormula(and.getLeft(), graph, graphMap, domainMap);
				IFormula right = initFormula(and.getRight(), graph, graphMap, domainMap);
				return new AndFormula(left, right);
			} else if (formula instanceof Or) {
				Or or = (Or) formula;
				IFormula left = initFormula(or.getLeft(), graph, graphMap, domainMap);
				IFormula right = initFormula(or.getRight(), graph, graphMap, domainMap);
				return new OrFormula(left, right);
			} else if (formula instanceof Xor) {
				Xor xor = (Xor) formula;
				IFormula left = initFormula(xor.getLeft(), graph, graphMap, domainMap);
				IFormula right = initFormula(xor.getRight(), graph, graphMap, domainMap);
				return new XorFormula(left, right);
			} else if (formula instanceof Not) {
				Not not = (Not) formula;
				IFormula child = initFormula(not.getChild(), graph, graphMap, domainMap);
				return new NotFormula(child);
			} else if (formula instanceof NestedCondition) {
				NestedCondition nc = (NestedCondition) formula;
				// check if we really need nested condition
				if (nc.isTrue() || PathFinder.pacConsistsOnlyOfPaths(nc)) {
					return IFormula.TRUE;
				}
				if (nc.isFalse()) {
					return IFormula.FALSE;
				}
				return initApplicationCondition(nc, graph, graphMap, domainMap);
			}
			return IFormula.TRUE;
		}

		/**
		 * Initialize an application condition.
		 */
		private ApplicationCondition initApplicationCondition(NestedCondition nc, EGraph graph,
				Map<Graph, List<Variable>> graphMap, Map<Variable, DomainSlot> domainMap) {
			ApplicationCondition ac = new ApplicationCondition(graph, domainMap,monitor);//added monitor parameter
			ac.variables = graphMap.get(nc.getConclusion());
			ac.formula = initFormula(nc.getConclusion().getFormula(), graph, graphMap, domainMap);
			return ac;
		}
		
		public SolutionFinder getSolutionFinder() {
			return solutionFinder;
		}
		
	} // MatchFinder

	/**
	 * Match finding worker. To be used in a worker thread pool. It MUST be
	 * executed in a {@link PartitionThread}.
	 * 
	 * @author Christian Krause
	 */
	private final class MatchFinderWorker implements Callable<List<Match>> {

		/**
		 * Match finder to be used.
		 */
		private final MatchFinder matchFinder;

		/**
		 * Partition to be used.
		 */
		private final int partition;

		/**
		 * Constructor.
		 * 
		 * @param matchFinder
		 *            Match finder to be used.
		 * @param partition
		 *            Partition to be used.
		 */
		MatchFinderWorker(MatchFinder matchFinder, int partition) {
			this.matchFinder = matchFinder;
			this.partition = partition;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.concurrent.Callable#call()
		 */
		@Override
		public List<Match> call() throws Exception {
			Variable firstVar = matchFinder.solutionFinder.variables.get(0);
			PartitionThread thread = (PartitionThread) Thread.currentThread();
			thread.partition = partition;
			thread.firstDomainSlot = matchFinder.solutionFinder.domainMap.get(firstVar);
			List<Match> matches = new ArrayList<Match>();
			while (matchFinder.hasNext()) {
				matches.add(matchFinder.next());
			}
			return matches;
		}

	} // MatchFinderWorker

	/**
	 * Comparator for variables. Used to sort variables for optimal matching
	 * order.
	 */
	private class VariableComparator implements Comparator<Variable> {

		/**
		 * Target graph.
		 */
		private final EGraph graph;

		/**
		 * Variable info.
		 */
		private final VariableInfo varInfo;

		/**
		 * Partial match.
		 */
		private final Match partialMatch;

		/**
		 * Default sign to be used.
		 */
		private final int sign;

		/**
		 * Constructor.
		 * 
		 * @param graph
		 *            Target graph
		 * @param varInfo
		 *            Variable info.
		 * @param partialMatch
		 *            Partial match.
		 */
		public VariableComparator(EGraph graph, VariableInfo varInfo, Match partialMatch) {
			this.graph = graph;
			this.varInfo = varInfo;
			this.partialMatch = partialMatch;
			this.sign = inverseMatchingOrder ? 1 : -1;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		@Override
		public int compare(Variable v1, Variable v2) {

			// Get the corresponding nodes:
			Node n1 = varInfo.getVariableForNode(v1);
			if (n1 == null)
				return sign;
			Node n2 = varInfo.getVariableForNode(v2);
			if (n2 == null)
				return -sign;

			// One of the nodes already matched or an attribute given as a
			// parameter?
			if (partialMatch != null) {
				if (isNodeObjectMatched(n1) && !isNodeObjectMatched(n2)) {
					return -sign;
				}
				if (isNodeObjectMatched(n2) && !isNodeObjectMatched(n1)) {
					return sign;
				}
				if (isNodeAttributeMatched(n1) && !isNodeAttributeMatched(n2)) {
					return -sign;
				}
				if (isNodeAttributeMatched(n2) && !isNodeAttributeMatched(n1)) {
					return sign;
				}
			}

			// Get the domain sizes (smaller number wins):
			int s = (graph.getDomainSize(v1.typeConstraint.type, v1.typeConstraint.strictTyping)
					- graph.getDomainSize(v2.typeConstraint.type, v2.typeConstraint.strictTyping));
			if (s != 0) {
				return (sign * s);
			}

			// Attribute count (larger number wins):
			int a = (n2.getAttributes().size() - n1.getAttributes().size());
			if (a != 0) {
				return (sign * a);
			}

			// Outgoing edge count (larger number wins):
			return (sign * (n2.getOutgoing().size() - n1.getOutgoing().size()));

		}

		private boolean isNodeObjectMatched(Node node) {
			return (partialMatch.getNodeTarget(node) != null);
		}

		private boolean isNodeAttributeMatched(Node node) {
			for (Attribute attribute : node.getAttributes()) {
				String value = attribute.getValue();
				if (value != null) {
					Parameter param = node.getGraph().getRule().getParameter(value);
					if (param != null && partialMatch.getParameterValue(param) != null) {
						return true;
					}
				}
			}
			return false;
		}

	} // VariableComparator

	/**
	 * Get the cached rule info for a given rule.
	 * 
	 * @param rule
	 *            Rule.
	 * @return The (cached) rule info.
	 */
	public RuleInfo getRuleInfo(Rule rule) {
		RuleInfo ruleInfo = ruleInfos.get(rule);
		if (ruleInfo == null) {
			ruleInfo = new RuleInfo(rule, this);
			ruleInfos.put(rule, ruleInfo);

			// Listen to changes:
			rule.eAdapters().add(ruleListener);

			// Check for missing factories:
			for (Node node : ruleInfo.getChangeInfo().getCreatedNodes()) {
				if (node.getType() == null) {
					throw new RuntimeException("Missing type for " + node);
				}
				if (node.getType().getEPackage() == null
						|| node.getType().getEPackage().getEFactoryInstance() == null) {
					throw new RuntimeException("Missing factory for '" + node
							+ "'. Register the corresponding package, e.g. using PackageName.eINSTANCE.getName().");
				}
			}
		} else {
			ruleInfo.updateCached();			
		}
		return ruleInfo;
	}

	/**
	 * Clear the cache of this engine.
	 */
	public void clearCache() {
		ruleInfos.clear();
		graphOptions.clear();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.emf.henshin.interpreter.Engine#createChange(org.eclipse.emf
	 * .henshin.model.Rule, org.eclipse.emf.henshin.interpreter.EGraph,
	 * org.eclipse.emf.henshin.interpreter.Match,
	 * org.eclipse.emf.henshin.interpreter.Match)
	 */
	@Override
	public Change createChange(Rule rule, EGraph graph, Match completeMatch, Match resultMatch) {

		// We need a result match:
		if (resultMatch == null) {
			resultMatch = new MatchImpl(rule, true);
		}

		// Create the object changes:
		CompoundChangeImpl complexChange = new CompoundChangeImpl(graph);
		createChanges(rule, graph, completeMatch, resultMatch, complexChange);
		return complexChange;

	}

	/**
	 * Recursively create the changes and result matches.
	 * 
	 * @param rule
	 *            Rule to be applied.
	 * @param graph
	 *            Host graph.
	 * @param completeMatch
	 *            The complete match.
	 * @param resultMatch
	 *            The result match.
	 * @param complexChange
	 *            The final complex change.
	 */
	public void createChanges(Rule rule, EGraph graph, Match completeMatch, Match resultMatch,
			CompoundChange complexChange) {

		// Get the rule change info and the object change list:
		RuleChangeInfo ruleChange = getRuleInfo(rule).getChangeInfo();
		List<Change> changes = complexChange.getChanges();

		// Set parameters:
		for (Parameter param : rule.getParameters()) {
			Object value = completeMatch.getParameterValue(param);
			if (value != null) {
				resultMatch.setParameterValue(param, value);
			} else {
				value = resultMatch.getParameterValue(param);
			}
			// Parameter matched by multi-rules?
			if (value == null && !rule.getMultiRules().isEmpty()) {
				List<Object> valueList = new ArrayList<Object>();
				for (Rule multiRule : rule.getMultiRules()) {
					Parameter p = multiRule.getParameter(param.getName());
					if (p != null) {
						for (Match multiMatch : completeMatch.getMultiMatches(multiRule)) {
							value = multiMatch.getParameterValue(p);
							if (value != null) {
								valueList.add(value);
							}
						}
					}
				}
				value = valueList;
			}

			scriptEngine.getEngine().put(param.getName(), value);
		}

		// Created objects:
		for (Node node : ruleChange.getCreatedNodes()) {
			EClass type = node.getType();
			EObject createdObject = type.getEPackage().getEFactoryInstance().create(type);
			changes.add(new ObjectChangeImpl(graph, createdObject, true));
			resultMatch.setNodeTarget(node, createdObject);
		}

		// Deleted objects:
		for (Node node : ruleChange.getDeletedNodes()) {
			EObject deletedObject = completeMatch.getNodeTarget(node);
			changes.add(new ObjectChangeImpl(graph, deletedObject, false));
			// TODO: Shouldn't we check the rule options?
			if (!rule.isCheckDangling()) {
				Collection<Setting> removedEdges = graph.getCrossReferenceAdapter().getInverseReferences(deletedObject);
				for (Setting edge : removedEdges) {
					EReference ref = (EReference) edge.getEStructuralFeature();
					if (ref.isChangeable()) {
						changes.add(new ReferenceChangeImpl(graph, edge.getEObject(), deletedObject, ref, false));
					}
				}
			}
		}
		
		// Deleted (unset) attributes:
		for (Attribute attribute : ruleChange.getDeletedAttributes()) {
			EObject object = completeMatch.getNodeTarget(attribute.getNode());
			changes.add(new AttributeChangeImpl(graph, object, attribute.getType()));
		}

		// Preserved objects:
		for (Node node : ruleChange.getPreservedNodes()) {
			Node lhsNode = rule.getMappings().getOrigin(node);
			resultMatch.setNodeTarget(node, completeMatch.getNodeTarget(lhsNode));
		}

		// Deleted edges:
		for (Edge edge : ruleChange.getDeletedEdges()) {
			changes.add(new ReferenceChangeImpl(graph, completeMatch.getNodeTarget(edge.getSource()),
					completeMatch.getNodeTarget(edge.getTarget()), edge.getType(), false));
		}

		// Created edges:
		for (Edge edge : ruleChange.getCreatedEdges()) {
			changes.add(new ReferenceChangeImpl(graph, resultMatch.getNodeTarget(edge.getSource()),
					resultMatch.getNodeTarget(edge.getTarget()), edge.getType(), true));
		}

		// Edge index changes:
		for (Edge edge : ruleChange.getIndexChanges()) {
			Integer newIndex = edge.getIndexConstant();
			if (newIndex == null) {
				Parameter param = rule.getParameter(edge.getIndex());
				if (param != null) {
					newIndex = ((Number) resultMatch.getParameterValue(param)).intValue();
				} else {
					try {
						newIndex = ((Number) scriptEngine.eval(edge.getIndex(), rule.getAllJavaImports())).intValue();
					} catch (ScriptException e) {
						throw new RuntimeException(
								"Error evaluating edge index expression \"" + edge.getIndex() + "\": " + e.getMessage(),
								e);
					}
				}
			}
			changes.add(new IndexChangeImpl(graph, resultMatch.getNodeTarget(edge.getSource()),
					resultMatch.getNodeTarget(edge.getTarget()), edge.getType(), newIndex));
		}

		// Attribute changes:
		for (Attribute attribute : ruleChange.getAttributeChanges()) {
			EObject object = resultMatch.getNodeTarget(attribute.getNode());
			Object value;
			Parameter param = rule.getParameter(attribute.getValue());
			if (param != null) {
				value = castValueToDataType(resultMatch.getParameterValue(param),
						attribute.getType().getEAttributeType(), attribute.getType().isMany());
			} else {
				value = evalAttributeExpression(attribute, rule); // casting
																	// done here
																	// automatically
			}
			changes.add(new AttributeChangeImpl(graph, object, attribute.getType(), value));
		}

		// Now recursively for the multi-rules:
		for (Rule multiRule : rule.getMultiRules()) {
			Iterator<Match> multiMatches = completeMatch.getMultiMatches(multiRule).iterator();
			while (multiMatches.hasNext()) {
				Match multiResultMatch = new MatchImpl(multiRule, true);
				for (Mapping mapping : multiRule.getMultiMappings()) {
					if (mapping.getImage().getGraph().isRhs()) {
						multiResultMatch.setNodeTarget(mapping.getImage(),
								resultMatch.getNodeTarget(mapping.getOrigin()));
					}
				}
				createChanges(multiRule, graph, multiMatches.next(), multiResultMatch, complexChange);
				if (destroyMatches) {
					multiMatches.remove();
				} else {
					resultMatch.getMultiMatches(multiRule).add(multiResultMatch);
				}
			}
		}

	}

	/**
	 * Evaluates a given attribute expression using the JavaScript engine.
	 * 
	 * @param attribute
	 *            Attribute to be interpreted.
	 * @return The value.
	 */
	public Object evalAttributeExpression(Attribute attribute, Rule rule) {

		// Is it a constant or null?
		Object constant = attribute.getConstant();
		if (constant != null) {
			return constant;
		}
		if (attribute.isNull()) {
			return null;
		}

		// Try to evaluate the expression and cast it to the correct type:
		try {
			Object evalResult = scriptEngine.eval(attribute.getValue(), rule.getAllJavaImports());
			return castValueToDataType(evalResult, attribute.getType().getEAttributeType(),
					attribute.getType().isMany());
		} catch (ScriptException e) {
			throw new RuntimeException(e.getMessage());
		}

	}

	/**
	 * Cached Ecore package.
	 */
	private static final EcorePackage ECORE = EcorePackage.eINSTANCE;

	/**
	 * Cast a data value into a given data type.
	 * 
	 * @param value
	 *            Value.
	 * @param type
	 *            Data type.
	 * @param isMany
	 *            Many-flag.
	 * @return The casted object.
	 */
	protected static Object castValueToDataType(Object value, EDataType type, boolean isMany) {

		// List of values?
		if (isMany) {
			EList<Object> list = new BasicEList<Object>();
			if (value instanceof Collection) {
				for (Object elem : ((Collection<?>) value)) {
					list.add(castValueToDataType(elem, type, false));
				}
			} else if (value != null) {
				list.add(value);
			}
			return list;
		}

		// Null?
		if (value == null) {
			return null;
		}

		// Number format conversions:
		if (value instanceof Number) {
			if (type == ECORE.getEInt() || type == ECORE.getEIntegerObject()) {
				return ((Number) value).intValue();
			}
			if (type == ECORE.getEDouble() || type == ECORE.getEDoubleObject()) {
				return ((Number) value).doubleValue();
			}
			if (type == ECORE.getEByte() || type == ECORE.getEByteObject()) {
				return ((Number) value).byteValue();
			}
			if (type == ECORE.getELong() || type == ECORE.getELongObject()) {
				return ((Number) value).longValue();
			}
			if (type == ECORE.getEFloat() || type == ECORE.getEFloatObject()) {
				return ((Number) value).floatValue();
			}
		}

		// Just a string?
		if (type == ECORE.getEString()) {
			if (value != null)
				value = value.toString();
			return value;
		}

		// A plain Java object?
		if (type == ECORE.getEJavaObject() || type == ECORE.getEJavaClass()) {
			return value;
		}

		// Generic attribute value creation as fall-back.

		
		//fix cast to integer
		if(!(type instanceof EEnum)&&type.getInstanceClass()==Integer.TYPE){
			return (int)Math.round(((Number) value).doubleValue());
		}
		
		return EcoreUtil.createFromString(type,value.toString());

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.henshin.interpreter.Engine#getOptions()
	 */
	@Override
	public Map<String, Object> getOptions() {
		return options;
	}

	/**
	 * Data class for storing matching options.
	 */
	static class MatchingOptions {
		boolean injective;
		boolean dangling;
		boolean deterministic;
	}

	/**
	 * Get the options for a specific rule graph. The graph should be either the
	 * LHS or a nested condition.
	 * 
	 * @param graph
	 *            The graph.
	 * @return The cached options.
	 */
	protected MatchingOptions getGraphOptions(Graph graph) {
		MatchingOptions options = graphOptions.get(graph);
		if (options == null) {

			// Use the base options:
			options = new MatchingOptions();
			Rule rule = graph.getRule();
			Boolean injective = (Boolean) this.options.get(OPTION_INJECTIVE_MATCHING);
			Boolean dangling = (Boolean) this.options.get(OPTION_CHECK_DANGLING);
			Boolean determistic = (Boolean) this.options.get(OPTION_DETERMINISTIC);
			options.injective = (injective != null) ? injective.booleanValue() : rule.isInjectiveMatching();
			options.dangling = (dangling != null) ? dangling.booleanValue() : rule.isCheckDangling();
			options.deterministic = (determistic == null || determistic.booleanValue());

			// Always use default values for nested conditions:
			if (graph != rule.getLhs()) {
				options.injective = true;
				options.dangling = false;
				options.deterministic = true;
			}

			graphOptions.put(graph, options);
		}
		return options;
	}

	/**
	 * An options map which clears cached rule options.
	 * 
	 * @see #getOptions()
	 */
	private class EngineOptions extends HashMap<String, Object> {
		private static final long serialVersionUID = 1L;

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.HashMap#put(java.lang.String, java.lang.Object)
		 */
		@Override
		public Object put(String key, Object value) {
			Object result = super.put(key, value);
			clearCache();
			updateCachedOptions();
			return result;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.HashMap#putAll(java.util.Map)
		 */
		@Override
		public void putAll(Map<? extends String, ? extends Object> map) {
			super.putAll(map);
			clearCache();
			updateCachedOptions();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.HashMap#remove(java.lang.Object)
		 */
		@Override
		public Object remove(Object key) {
			Object result = super.remove(key);
			clearCache();
			updateCachedOptions();
			return result;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.util.HashMap#clear()
		 */
		@Override
		public void clear() {
			super.clear();
			clearCache();
			updateCachedOptions();
		}

		/**
		 * Update the cached options.
		 */
		private void updateCachedOptions() {

			// Update sort variables flag:
			Boolean sort = (Boolean) get(OPTION_SORT_VARIABLES);
			sortVariables = (sort != null) ? sort.booleanValue() : DEFAULT_SORT_VARIABLES;

			// Update inverse matching order flag:
			Boolean inverse = (Boolean) get(OPTION_INVERSE_MATCHING_ORDER);
			inverseMatchingOrder = (inverse != null) ? inverse.booleanValue() : DEFAULT_INVERSE_MATCHING_ORDER;

			// Update destroy matches flag:
			Boolean destroy = (Boolean) get(OPTION_DESTROY_MATCHES);
			destroyMatches = (destroy != null) ? destroy.booleanValue() : DEFAULT_DESTROY_MATCHES;

			// Update worker thread pool:
			Number workerThreads = (Number) get(OPTION_WORKER_THREADS);
			if (workerPool != null) {
				workerPool.shutdownNow();
				workerPool = null;
			}
			if (workerThreads != null && workerThreads.intValue() > 0) {
				workerPool = Executors.newFixedThreadPool(workerThreads.intValue(), PartitionThread.Factory.INSTANCE);
			}
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.henshin.interpreter.Engine#getScriptEngine()
	 */
	@Override
	public ScriptEngine getScriptEngine() {
		return scriptEngine.getEngine();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.henshin.interpreter.Engine#shutdown()
	 */
	@Override
	public void shutdown() {
		if (workerPool != null) {
			workerPool.shutdownNow();
			workerPool = null;
		}
	}

	/**
	 * Create user constraints for a node.
	 * 
	 * @param node
	 *            A node.
	 * @return The created user constraints.
	 */
	public UnaryConstraint createUserConstraints(Node node) {
		return null;
	}

	/**
	 * Create user constraints for an edge.
	 * 
	 * @param edge
	 *            An edge.
	 * @return The created user constraint.
	 */
	public BinaryConstraint createUserConstraints(Edge edge) {
		return null;
	}

	/**
	 * Create user constraints for an attribute.
	 * 
	 * @param attribute
	 *            An attribute.
	 * @return The created user constraint.
	 */
	public UnaryConstraint createUserConstraints(Attribute attribute) {
		return null;
	}

	@Override
	public void setMonitor(ApplicationMonitor monitor) {
		this.monitor=monitor;
	}

	@Override
	public ApplicationMonitor getMonitor() {
		return this.monitor;
	}


}
