package org.eclipse.emf.henshin.interpreter.matching.conditions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Observer;
import java.util.StringJoiner;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.henshin.HenshinModelPlugin;
import org.eclipse.emf.henshin.interpreter.EGraph;
import org.eclipse.emf.henshin.interpreter.debug.DebugValueEObject;
import org.eclipse.emf.henshin.interpreter.debug.DebugValueList;
import org.eclipse.emf.henshin.interpreter.debug.DebugValueObject;
import org.eclipse.emf.henshin.interpreter.debug.HenshinDebugTarget;
import org.eclipse.emf.henshin.interpreter.debug.HenshinDebugThread;
import org.eclipse.emf.henshin.interpreter.debug.HenshinDebugValue;
import org.eclipse.emf.henshin.interpreter.debug.HenshinDebugVariable;
import org.eclipse.emf.henshin.interpreter.debug.HenshinStackFrame;
import org.eclipse.emf.henshin.interpreter.info.RuleInfo;
import org.eclipse.emf.henshin.interpreter.matching.constraints.AttributeConstraint;
import org.eclipse.emf.henshin.interpreter.matching.constraints.BinaryConstraint;
import org.eclipse.emf.henshin.interpreter.matching.constraints.ContainmentConstraint;
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.PathConstraint;
import org.eclipse.emf.henshin.interpreter.matching.constraints.ReferenceConstraint;
import org.eclipse.emf.henshin.interpreter.matching.constraints.Solution;
import org.eclipse.emf.henshin.interpreter.matching.constraints.UnaryConstraint;
import org.eclipse.emf.henshin.interpreter.matching.constraints.Variable;
import org.eclipse.emf.henshin.model.Node;

public class DebugApplicationCondition extends ApplicationCondition {  
	/**
	 * The Debug Levels from high to low (Variable > Value > Constraint Type > Constraint)
	 * ("step into" tries to go down a level, "step return" tries to go up a level, 
	 * "step over" tries to stay on the same level)
	 */
	public enum DebugLevel {
		NONE, VARIABLE, VALUE, CONSTRAINT_TYPE, CONSTRAINT;
		
		@Override
		public String toString() {
			return name();
		}
	}
	
	/**
	 * A debug code to signal different results in the stepping methods.<br>
	 * TERMINATED_TRUE - match found successfully.<br>
	 * TERMINATED_FALSE - no match found.<br>
	 * 
	 */
	public enum DebugState {
		RUNNING, STEPPING, SUSPENDED, TERMINATED_TRUE, TERMINATED_FALSE
	}
	
	/**
	 * The Constraint Types that are checked by the DomainSlot 
	 * (e.g. TypeConstraint, DanglingConstraint, AttributeConstraint, ...). <br>
	 * IMPORTANT: the order has to be the same as they are checked in 
	 * {@link DomainSlot#instantiate(Variable, Map, EGraph)}.
	 */
	public enum ConstraintType {
		NONE, TYPE, DANGLING, ATTRIBUTE, CONTAINMENT, REFERENCE, PATH, USER;
		
		public ConstraintType next() {
			if (isLast()) {
				return this;
			}
			return values()[(ordinal()+1) % values().length];
		}
		
		public boolean isLast() {
			return ordinal()+1 == values().length;
		}
		
		@Override
		public String toString() {
			// first letter uppercase
			String nameString = name().substring(0, 1);
			// rest lowercase, replace underscores with spaces
			nameString += name().substring(1, name().length()).toLowerCase().replace("_", " ");
			return nameString;
		}
	}
	
	
	/**
	 * the associated debug target
	 */
	private HenshinDebugTarget debugTarget;
	
    /**
     * The current debug state
     */
    private DebugState currentDebugState;
	
	private DebugLevel currentDebugLevel;
	private int currentVariableIndex;
	private ConstraintType currentConstraintType = ConstraintType.NONE;
	private int currentConstraintIndex;
	
	private Variable currentVariable;
	private DomainSlot currentSlot;
		
	private IStackFrame[] stackFrames;

	private Observer matchObserver;
	
	// used to compare currentVariable to HenshinBreakpoints
	private RuleInfo ruleInfo;
	
	private static DebugApplicationCondition instance;
	// needed for value breakpoints
	private EGraph graph;
	public EGraph getGraph() {
		return this.graph;
	}

	public DebugApplicationCondition(HenshinDebugTarget debugTarget, List<Variable> variables,
			Map<Variable, DomainSlot> domainMap, EGraph graph, IFormula formula, Observer matchObserver, RuleInfo ruleInfo) {
		super(graph, domainMap,null); //added PerformanceMonitor as parameter null=no monitoring
		
		this.debugTarget = debugTarget;
		this.variables = variables;
		this.formula = formula;
		this.matchObserver = matchObserver;
		this.ruleInfo = ruleInfo;
		
		currentDebugLevel = DebugLevel.NONE;
		currentVariableIndex = -1;
		currentConstraintType = ConstraintType.NONE;
		currentConstraintIndex = -1;
		currentDebugState = DebugState.SUSPENDED;
		
		// for value breakpoints
		instance = this;
		this.graph = graph;
	}
	
	// TODO: refactor, DebugApplicationCondition should not be static
	public static DebugApplicationCondition getInstance() {
		return instance;
	}

	
	public void initNextVariable() {

		// for the first call (maybe extract to a initFirstVariable() method)
		if (currentVariableIndex == -1) {
			updateDebugState(DebugLevel.VARIABLE, 0, ConstraintType.NONE, -1);

			if (debugTarget != null) {
				debugTarget.fireCreationEvent();				
			}
		}
		
		// Matched all variables?
		if (currentVariableIndex == variables.size()) {
			
			// Final variable re-checks:
			for (Variable variable : variables) {
				if (variable.requiresFinalCheck) {
					DomainSlot slot = domainMap.get(variable);
					if (!slot.recheck(variable, domainMap)) {
						// recheck turned out invalid --> go back
						updateDebugState(DebugLevel.VARIABLE, currentVariableIndex-1, ConstraintType.NONE, -1);
						tryNextValue();
/*						// recheck turned out invalid --> terminate (no match found)
						updateDebugState(DebugLevel.NONE, -1, ConstraintType.NONE, -1);
						setCurrentDebugState(DebugState.TERMINATED_FALSE);
						if (debugTarget != null) {
							debugTarget.fireTerminateEvent();							
						}*/
						return;
					}
				}
			}
			
			// Evaluate formula:
			if (formula.eval()) {
				// final evaluation was successful --> terminate (match found)
				updateDebugState(DebugLevel.VARIABLE, currentVariableIndex-1, ConstraintType.NONE, -1);
				setCurrentDebugState(DebugState.TERMINATED_TRUE);
				
				// create a solution
				Solution solution = new Solution(variables, domainMap, currentSlot.getConditionHandler());
				
				// notify the observer that we have a solution
				if (matchObserver != null) {					
					matchObserver.update(null, solution);
				}
				
				if (debugTarget != null) {
					debugTarget.fireTerminateEvent();							
				}
				return;
			}
			
			// else formula did not work, go back to last variable 
			updateDebugState(DebugLevel.VARIABLE, currentVariableIndex-1, ConstraintType.NONE, -1);
			tryNextValue();
/*			// else: final evaluation was not successful --> terminate (no match found)
			updateDebugState(DebugLevel.NONE, -1, ConstraintType.NONE, -1);
			setCurrentDebugState(DebugState.TERMINATED_FALSE);
			if (debugTarget != null) {
				debugTarget.fireTerminateEvent();			
			}*/
			return;
			
		}
		
		// Otherwise select the last variable:
		currentVariable = variables.get(currentVariableIndex);
		currentSlot = domainMap.get(currentVariable);
		
		// initialize the domain slot
		if (!currentSlot.initialize(currentVariable, graph)) {
			//this can happen and is not a problem
		}
		
		if (debugTarget != null) {
			debugTarget.fireChangeEvent(DebugEvent.CONTENT);
			debugTarget.fireSuspendEvent(DebugEvent.STEP_END);
		}				
	}

	/**
	 * uses the current constraint type & index to check the current constraint
	 * @return <code>true</code> if the current constraint is valid, <code>false</code> otherwise
	 */
	private boolean checkCurrentConstraint() {
		switch (currentConstraintType) {
		case TYPE:
			return currentSlot.checkTypeConstraint(currentVariable);
		case DANGLING:
			DanglingConstraint danglingConstraint = currentVariable.danglingConstraints.get(currentConstraintIndex);
			return currentSlot.checkDanglingConstraint(danglingConstraint, graph);
		case ATTRIBUTE:
			AttributeConstraint attributeConstraint = currentVariable.attributeConstraints.get(currentConstraintIndex);
			UnaryConstraint unaryUserConstraint = currentVariable.attributeUserConstraints.get(attributeConstraint);
			return currentSlot.checkAttributeConstraint(attributeConstraint, unaryUserConstraint);
		case CONTAINMENT:
			ContainmentConstraint containmentConstraint = currentVariable.containmentConstraints.get(currentConstraintIndex);
			return currentSlot.checkContainmentConstraint(containmentConstraint, domainMap);
		case REFERENCE:
			ReferenceConstraint referenceConstraint = currentVariable.referenceConstraints.get(currentConstraintIndex);
			BinaryConstraint binaryUserConstraint = currentVariable.binaryUserConstraints.get(referenceConstraint);
			return currentSlot.checkReferenceConstraint(referenceConstraint, binaryUserConstraint, domainMap);
		case PATH:
			PathConstraint pathConstraint = currentVariable.pathConstraints.get(currentConstraintIndex);
			return currentSlot.checkPathConstraint(pathConstraint, domainMap);
		case USER:
			return currentSlot.checkUserConstraint(currentVariable.userConstraints.get(currentConstraintIndex));
		case NONE:
			throw new IllegalStateException("trying to check the current constraint with currentConstraintType NONE");
		default:
			throw new IllegalStateException("currentConstraintType is \"" + currentConstraintType 
					+ "\", but has to be of the enum type ConstraintType");
		}
	}
	
	/**
	 * Tries to find another constraint type that has to be checked.
	 * If not, tries to continue to the next variable because all constraints for this variable are valid
	 * @return
	 */
	private synchronized void tryNextConstraintType() {
		//are there any constraint types (with constraints) left that we have to check?
		do {
			currentConstraintType = currentConstraintType.next();
			if (currentTypeHasConstraints()) {
				// continue with that constraint type
				updateDebugState(DebugLevel.CONSTRAINT_TYPE, currentVariableIndex, currentConstraintType, -1);
				return;
			}
		} while (!currentConstraintType.isLast());
		
		// there are no other constraint types left => all constraints are valid => go to the next variable
		updateDebugState(DebugLevel.VARIABLE, currentVariableIndex + 1, ConstraintType.NONE, -1);
		initNextVariable();
	}
	
	/**
	 * Tries to go to the next value. Calls {@link #tryLowerIndexVariable()} if no value is left
	 * @return
	 */
	private void tryNextValue() {
		// are any other values left for this variable?
		currentSlot.unlock(currentVariable);
		if (currentSlot.instantiationPossible()) {
			// go to next value
			currentSlot.setValueAndLock();
			updateDebugState(DebugLevel.VALUE, currentVariableIndex, ConstraintType.NONE, -1);
			if (debugTarget != null) {			
				debugTarget.fireSuspendEvent(DebugEvent.STEP_END);
			}
		} else {
			tryLowerIndexVariable();
		}
	}
	
	/*
	 * Checks if the current variable has a next value.
	 */
	private boolean hasNextValue() {
		currentSlot.unlock(currentVariable);
		return currentSlot.instantiationPossible();
	}
	
	/**
	 * Tries to go to the next Variable with a lower index (shallower recursion depth)
	 * to continue the search for a match.
	 * @return
	 */
	private void tryLowerIndexVariable() {
		// clear (+ unlock) the current domain slot
		currentSlot.clear(currentVariable);

		// is there a lower index where a match might be found?
		if (currentVariableIndex > 0) {
			updateDebugState(DebugLevel.VARIABLE, currentVariableIndex - 1, ConstraintType.NONE, -1);
			initNextVariable();
			return;
		}
		
		// this is the "shallowest" recursion level (index 0)
		// --> terminate (no match found)
		updateDebugState(DebugLevel.NONE, -1, ConstraintType.NONE, -1);
		setCurrentDebugState(DebugState.TERMINATED_FALSE);
		if (debugTarget != null) {
			debugTarget.fireTerminateEvent();
		}
	}
	
	/**
	 * Checks if the current variable has constraints of the currently active constraint type
	 * @return the maximum index for the current constraint type (i.e # of constraints -1), 
	 * or -1 if there are no constraints of this type
	 */
	private int maxCurrentConstraintIndex() {
		switch (currentConstraintType) {
		case TYPE:
			// a variable always has one type constraint
			return 0;
		case DANGLING:
			return currentVariable.danglingConstraints.size()-1;
		case ATTRIBUTE:
			return currentVariable.attributeConstraints.size()-1;
		case CONTAINMENT:
			return currentVariable.containmentConstraints.size()-1;
		case REFERENCE:
			return currentVariable.referenceConstraints.size()-1;
		case PATH:
			return currentVariable.pathConstraints.size()-1;
		case USER:
			return currentVariable.userConstraints.size()-1;
		case NONE:
			throw new IllegalStateException("trying to get number of constraints with currentConstraintType NONE");
		default:
			throw new IllegalStateException("currentConstraintType is \"" + currentConstraintType 
					+ "\", but has to be of the enum type ConstraintType");
		}
	}
	
	private boolean currentTypeHasConstraints() {
		return maxCurrentConstraintIndex() >= 0;
	}

	/**
	 * updates the internal debug state
	 * 
	 * @param newDebugLevel the new {@link DebugLevel}
	 * @param newVariableIndex the new variable index (see {@link ApplicationCondition#findMatch(int)}
	 * @param newConstraintType the new constraint type (see {@link constraint#getClass()} or null if no constraint is active)
	 * @param newConstraintIndex the new iteration index for multiple constraints of the same type 
	 * (for example: {@link Variable#attributeConstraints}), or 0 if there is only one constraint or no constraint is active
	 */
	private synchronized void updateDebugState(
			DebugLevel newDebugLevel, 
			int newVariableIndex, 
			ConstraintType newConstraintType, 
			int newConstraintIndex) {
		
		currentDebugLevel = newDebugLevel;
		currentVariableIndex = newVariableIndex;
		currentConstraintType = newConstraintType;
		currentConstraintIndex = newConstraintIndex;
	}
	
	private void setCurrentDebugState(DebugState debugState) {
		if (!isTerminated()) {
			this.currentDebugState = debugState;
		}
	}

	public boolean canResume() {
		return isSuspended();
	}

	public boolean canSuspend() {
        return !isTerminated() && !isSuspended();
	}

	public boolean isSuspended() {
		return getCurrentDebugState() == DebugState.SUSPENDED;
	}

	public void resume() throws DebugException {
		synchronized (this) {
			if (getCurrentDebugState() == DebugState.SUSPENDED) {
				setCurrentDebugState(DebugState.RUNNING);
			}
						
			// if the currentDebugState is not terminated oder suspended we continue stepping
			while(!isTerminated() && getCurrentDebugState() != DebugState.SUSPENDED) {
				step();
			}
        }
	}

	public void suspend() throws DebugException {
		 synchronized (this) {
            setCurrentDebugState(DebugState.SUSPENDED);
        }
	}
	
	public boolean canStep() {
		return isSuspended();
	}

	public boolean canStepInto() {
		return isSuspended();
	}

	public boolean canStepOver() {
		return isSuspended();
	}

	public boolean canStepReturn() {
		return isSuspended() && currentDebugLevel != DebugLevel.VARIABLE;
	}

	public boolean isStepping() {
        return getCurrentDebugState() == DebugState.STEPPING;
	}

	/*
	 * Copy of the stepInto() method but without the static suspends after every step.
	 * We use this method to dive into the deeper logic without suspending the application while we do so.
	 */
	public void step() throws DebugException {
		if (currentVariable == null) {
			throw new IllegalStateException(
					"currentVariable is null - variableInitNext() has to be called before");
		}
		
		if (debugTarget != null) {
			debugTarget.fireResumeEvent(DebugEvent.STEP_INTO);
		}
		
		switch (currentDebugLevel) {
			case VARIABLE:
				// first unlock the variable because we might come from a higher index and have the slot still locked.
				// This was added because otherwise, when coming from a higher index while backtracking,
				// currentSlot.instantiate() would select the old slot value again instead of the next one.
				if (currentSlot.getValue() != null) {
					currentSlot.unlock(currentVariable);
				}
				
				// set the value
				if (!currentSlot.setValueAndLock()) {
					//if no furthe valuer, try lower index variable 
					tryLowerIndexVariable();
					break;
				}
				
				// update the debug state (current index does not change)
				updateDebugState(DebugLevel.VALUE, currentVariableIndex, ConstraintType.NONE, -1);
				break;
			case VALUE:
				// go to the "type" constraint type as it is always the first constraint. 
				updateDebugState(DebugLevel.CONSTRAINT_TYPE, currentVariableIndex, ConstraintType.TYPE, -1);
				break;

			case CONSTRAINT_TYPE:
				// in this state there should be a constraint of the current type left
				// (this has to be ensured by the steps that lead to the "constraint type" level)
				if (!currentTypeHasConstraints()) {
					throw new IllegalStateException("called stepInto for constraint type \"" + currentConstraintType + "\", but this type has no constraint to step into");
				}

				// if there is a constraint, step into it
				updateDebugState(DebugLevel.CONSTRAINT, currentVariableIndex, currentConstraintType, 0);
				break;
	
			case CONSTRAINT:
				if (currentConstraintIndex > maxCurrentConstraintIndex()) {
					throw new IllegalStateException("called stepOver for constraint type \"" + currentConstraintType + "\", but this type has no unchecked constraint left and should have been skipped");
				}
				boolean constraintIsValid = checkCurrentConstraint();
				if (constraintIsValid) {
					// are there any constraints of the current type left that we have to check?
					if (currentConstraintIndex < maxCurrentConstraintIndex()) {
						// go to the next constraint of the current type
						updateDebugState(DebugLevel.CONSTRAINT, currentVariableIndex, currentConstraintType, currentConstraintIndex + 1);
						break;
					}
					// else: see if there is a constraint type left that we have to check
					tryNextConstraintType();
					break;
					
				}
				if (!constraintIsValid) {
					// try to go to the next value
					tryNextValue();
				}
				break;
			default:
				throwExceptionInvalidDebugLevel();
		}

		// initially get all henshin breakpoints
		ArrayList<HenshinBreakpoint> henshinBreakpoints = getHenshinBreakpoints();
		
		// This is the node representation for the variable we're currently at.
		// Compare this node with the node we received (when the user sets a HenshinBreakpoint with right click) when setting HenshinBreakpoints 
		Node currentNode = ruleInfo.getVariableInfo().getVariableForNode(currentVariable);
		// Get relative path to node - unique representation of the node. Looks something like this: '@units.1/@lhs/@nodes.0'
		String currentNodePath = EcoreUtil.getRelativeURIFragmentPath(null, currentNode);
		// get the current type constraint to compare in shouldStopAtVariableBreakpoint later
		String currentTypeName = currentVariable.typeConstraint.type.getName();
				
		// handle variable breakpoints
		ArrayList<VariableBreakpoint> variableBreakpoints = filterVariableBreakpoints(henshinBreakpoints);
		if (variableBreakpoints.size() > 0 && currentDebugLevel == DebugLevel.VARIABLE) {
			for (VariableBreakpoint variableBreakpoint : variableBreakpoints) {
				// pass all three parameters necessary to clearly determine whether to suspend the application or not
				if (shouldStopAtVariableBreakpoint(variableBreakpoint, currentTypeName, currentNodePath)) {
					suspendApplication();
				}
			}
		}
		
		// handle value breakpoints
		if (currentSlot.getValue() != null) {
			// get complete domain (e.g. domain for :Client could be 'charles', 'bob', 'alice')
			List<EObject> domain = graph.getDomain(currentSlot.getValue().eClass(), false);
			//List<EObject> domain = currentSlot.getDomain();
			EObject currentValue = currentSlot.getValue();
			int index = domain.indexOf(currentValue);
			
			// get the current domain
			// List<EObject> currentDomain = currentSlot.getDomain();
			// get all value breakpoints
			ArrayList<ValueBreakpoint> valueBreakpoints = filterValueBreakpoints(henshinBreakpoints);
			if (valueBreakpoints.size() > 0 && currentDebugLevel == DebugLevel.VALUE) {
				for (ValueBreakpoint valueBreakpoint : valueBreakpoints) {
					if (shouldStopAtValueBreakpoint(valueBreakpoint, currentValue, index)) {
						suspendApplication();
					}
				}
			}
		}
		
		// handle constraint type breakpoints
		ArrayList<ConstraintTypeBreakpoint> constraintTypeBreakpoints = filterConstraintTypeBreakpoints(henshinBreakpoints);
		if (constraintTypeBreakpoints.size() > 0 && currentDebugLevel == DebugLevel.CONSTRAINT_TYPE) {
			for (ConstraintTypeBreakpoint constraintTypeBreakpoint : constraintTypeBreakpoints) {
				if (shouldStopAtConstraintTypeBreakpoint(constraintTypeBreakpoint)) {
					suspendApplication();
				}
			}
		}
		
		// handle constraint instance breakpoints
		ArrayList<ConstraintInstanceBreakpoint> constraintInstanceBreakpoints = filterConstraintInstanceBreakpoints(henshinBreakpoints);
		if (constraintInstanceBreakpoints.size() > 0 && currentDebugLevel == DebugLevel.CONSTRAINT) {
			for (ConstraintInstanceBreakpoint constraintInstanceBreakpoint : constraintInstanceBreakpoints) {
				if (shouldStopAtConstraintInstanceBreakpoint(constraintInstanceBreakpoint)) {
					suspendApplication();
				}
			}
		}
		
		if (debugTarget != null) {			
			debugTarget.fireSuspendEvent(DebugEvent.STEP_END);
		}
	}
	
	/*
	 * Step into
	 */
	public void stepInto() throws DebugException {
		if (currentVariable == null) {
			throw new IllegalStateException(
					"currentVariable is null - variableInitNext() has to be called before");
		}
		
		if (debugTarget != null) {
			debugTarget.fireResumeEvent(DebugEvent.STEP_INTO);
		}
		
		switch (currentDebugLevel) {
			case VARIABLE:
				
				// first unlock the variable because we might come from a higher index and have the slot still locked.
				// This was added because otherwise, when coming from a higher index while backtracking,
				// currentSlot.instantiate() would select the old slot value again instead of the next one.
				if (currentSlot.getValue() != null) {
					currentSlot.unlock(currentVariable);
				}
				
				// set the value
				if (!currentSlot.setValueAndLock()) {
					tryLowerIndexVariable();
				}
				else {
					
					// update the debug state (current index does not change)
					updateDebugState(DebugLevel.VALUE, currentVariableIndex, ConstraintType.NONE, -1);
					setCurrentDebugState(DebugState.SUSPENDED);
				}
				break;
			case VALUE:
				// go to the "type" constraint type as it is always the first constraint.
				updateDebugState(DebugLevel.CONSTRAINT_TYPE, currentVariableIndex, ConstraintType.TYPE, -1);
				setCurrentDebugState(DebugState.SUSPENDED);
				break;

			case CONSTRAINT_TYPE:
				// in this state there should be a constraint of the current type left
				// (this has to be ensured by the steps that lead to the "constraint type" level)
				if (!currentTypeHasConstraints()) {
					throw new IllegalStateException("called stepInto for constraint type \"" 
							+ currentConstraintType + "\", but this type has no constraint to step into");
				}

				// if there is a constraint, step into it
				updateDebugState(DebugLevel.CONSTRAINT, currentVariableIndex, currentConstraintType, 0);
				setCurrentDebugState(DebugState.SUSPENDED);
				break;
	
			case CONSTRAINT:
				// identical to stepOver
				step();
				setCurrentDebugState(DebugState.SUSPENDED);
				break;
			default:
				throwExceptionInvalidDebugLevel();
		}
		
		if (debugTarget != null) {			
			debugTarget.fireSuspendEvent(DebugEvent.STEP_END);
		}
		
	}
	
	/*
	 * New step over
	 */
public void stepOver() throws DebugException {
		
		if (currentVariable == null) {
			throw new IllegalStateException(
					"currentVariable is null - variableInitNext() has to be called before");
		}
		
		if (debugTarget != null) {
			debugTarget.fireResumeEvent(DebugEvent.STEP_OVER);
		}
		
		switch (currentDebugLevel) {
			case VARIABLE:
				
				// first unlock the variable because we might come from a higher index and have the slot still locked.
				// This was added because otherwise, when coming from a higher index (i.e. going up in the "recursion"),
				// currentSlot.instantiate() would select the old slot value again instead of the next one.
				if (currentSlot.getValue() != null) {
					currentSlot.unlock(currentVariable);
				}
				
				// keep track of the variable we are currently at
				Variable tempVariable = currentVariable;
				// simulates an ordinary step over but with "looking" at all intermediate steps in the debugger as well
				while(checkForChange(tempVariable, currentVariable)) {
					step();
				}
				break;
			
			case VALUE:
				// stores the current value as we enter the VALUE case
				EObject tempValue = currentSlot.getValue();
				while (checkForChange(tempValue, currentSlot.getValue())) {
					step();
				}
				break;

			case CONSTRAINT_TYPE:
				// keeps track of the constraint type we started from
				ConstraintType tempConstraintType = currentConstraintType;
				while(checkForChange(tempConstraintType, currentConstraintType)) {
					step();
				}
				break;
				
			case CONSTRAINT:
				step();
				setCurrentDebugState(DebugState.SUSPENDED);
				break;
			
			default:
				throwExceptionInvalidDebugLevel();
		}
		
		if (debugTarget != null) {	
			debugTarget.fireChangeEvent(DebugEvent.CONTENT);
			debugTarget.fireSuspendEvent(DebugEvent.STEP_END);
		}		
	}


public void stepReturn() throws DebugException {
	if (currentVariable == null) {
		throw new IllegalStateException(
				"currentVariable is null - variableInitNext() has to be called before");
	}
	
	if (debugTarget != null) {
		debugTarget.fireResumeEvent(DebugEvent.STEP_RETURN);
	}
	
	switch (currentDebugLevel) {
		case VARIABLE:
			// identical to stepOver
			stepOver();
			break;
		case VALUE:
			// keep track of the variable we are currently at
			Variable tempVariable = currentVariable;
			// boolean flag to determine whether we should keep stepping or not
			while(checkForChange(tempVariable, currentVariable)) {
				step();
			}
			break;
			
		case CONSTRAINT_TYPE:
			// stores the current value as we enter the VALUE case
			EObject tempValue = currentSlot.getValue();
			while (checkForChange(tempValue, currentSlot.getValue())) {
				step();
			}
			break;

		case CONSTRAINT:
			// keeps track of the constraint type we started from
			ConstraintType tempConstraintType = currentConstraintType;
			while(checkForChange(tempConstraintType, currentConstraintType)) {
				step();
			}
			break;

		default:
			throwExceptionInvalidDebugLevel();
	}
	
	if (debugTarget != null) {	
		debugTarget.fireSuspendEvent(DebugEvent.STEP_END);
	}
}

	public boolean canTerminate() {
		return !isTerminated();
	}

	public boolean isTerminated() {
		return getCurrentDebugState() == DebugState.TERMINATED_FALSE
				|| getCurrentDebugState() == DebugState.TERMINATED_TRUE;
	}

	public void terminate() throws DebugException {
		updateDebugState(DebugLevel.NONE, -1, ConstraintType.NONE, -1);
		setCurrentDebugState(DebugState.TERMINATED_FALSE);
		if (debugTarget != null) {			
			debugTarget.fireTerminateEvent();
		}		
	}

	public String getName() throws DebugException {
		// if the matching has terminated successfully, display the match to the user
		if (getCurrentDebugState() == DebugState.TERMINATED_TRUE) {
			return "["+variables.size() + "/" + variables.size() + " variables matched]";
		} else if (getCurrentDebugState() == DebugState.TERMINATED_FALSE) {
			return "[no match found]";
		}
		
		// if the matching is still in process, display the number of matched variables
		return currentVariableIndex + "/" + variables.size() + " variables matched";
	}

	/*
	 * Custom methods start here
	 */
	
	public IBreakpointManager getManager() {
		return DebugPlugin.getDefault().getBreakpointManager(); 
	} 
	
	public IBreakpoint[] getBreakpoints() {
		// get manager and its breakpoints
		IBreakpointManager manager = getManager();
		IBreakpoint[] breakpoints = manager.getBreakpoints();
		
		if (breakpoints.length > 0) {
			manager.getTypeName(breakpoints[0]);
		}
		return breakpoints;
	}
	
	/**
	 * Returns all henshin breakpoints we currently have
	 */
	public ArrayList<HenshinBreakpoint> getHenshinBreakpoints() {
		IBreakpoint[] breakpoints = getBreakpoints();
		ArrayList<HenshinBreakpoint> henshinBreakpoints = filterHenshinBreakpoints(breakpoints);
		return henshinBreakpoints;
	}
	
	/**
	 * Sets an example variable breakpoint.
	 */
	public void setVariableBreakpoint(Variable variable) {
		// get all breakpoints
		IBreakpointManager manager = getManager();
		// create breakpoint
		IResource moduleFile = debugTarget.getModuleResource();
		IMarker marker = addMarker(moduleFile, HenshinModelPlugin.PLUGIN_ID, "/variable", "Sample VariableBreakpoint", IStatus.OK);
		VariableBreakpoint breakpoint = new VariableBreakpoint();
		try {
			// set marker for breakpoint
			breakpoint.setMarker(marker);
			// This is the node representation for the variable we're currently at.
			// Compare this node with the node we received (when the user sets a HenshinBreakpoint with right click) when setting HenshinBreakpoints 
			Node currentNode = ruleInfo.getVariableInfo().getVariableForNode(variable);
			// Get relative path to node - unique representation of the node. Looks something like this: '@units.1/@lhs/@nodes.0'
			String nodePath = EcoreUtil.getRelativeURIFragmentPath(null, currentNode);
			// get the current type constraint to compare in shouldStopAtVariableBreakpoint later
			String typeName = variable.typeConstraint.type.getName();
			// uncomment the lines below to stop at from:Account in transferMoney rule or at constraintType 'Manager' in createAccount
			breakpoint.setPath(nodePath); // equals node of 'from: Account' in transferMoney rule
			breakpoint.setTypeName(typeName); // the exact string representation of the current constraint type (in this case 'Manager' from createAccount rule) - set this via right click by using constraintType.type.getName()
			breakpoint.setEnabled(true);
			// configure breakpoint
			breakpoint.setDebugLevel(DebugLevel.VARIABLE);
			// add breakpoint to manager to keep track
			manager.addBreakpoint(breakpoint);
		} catch (CoreException e1) {
			System.out.println("Unable to create custom VariableBreakpoint.");
			e1.printStackTrace();
		}
	}
	
	/**
	 * Sets breakpoint for a given value.
	 * This is determined by the value itself and its corresponding index within the domain.
	 */
	public void setValueBreakpoint(HenshinDebugValue value, int index) {
		// get all breakpoints
		IBreakpointManager manager = getManager();
		// create breakpoint
		IResource moduleFile = debugTarget.getModuleResource();
		IMarker marker = addMarker(moduleFile, HenshinModelPlugin.PLUGIN_ID, "/value", "Sample ValueBreakpoint", IStatus.OK);
		ValueBreakpoint breakpoint = new ValueBreakpoint();
		try {
			// set marker for breakpoint
			breakpoint.setMarker(marker);
			breakpoint.setEnabled(true);
			// configure breakpoint
			breakpoint.setType(value.getReferenceTypeName());
			breakpoint.setValueString(value.getValueString());
			breakpoint.setIndex(index);
			breakpoint.setDebugLevel(DebugLevel.VALUE);
			// add breakpoint to manager to keep track
			manager.addBreakpoint(breakpoint);
		} catch (CoreException e1) {
			System.out.println("Unable to create custom ValueBreakpoint.");
			e1.printStackTrace();
		}
	}
	
	public void setConstraintTypeBreakpoint(String constraintType) {
		// get all breakpoints
		IBreakpointManager manager = getManager();
		// create breakpoint
		IResource moduleFile = debugTarget.getModuleResource();
		IMarker marker = addMarker(moduleFile, HenshinModelPlugin.PLUGIN_ID, "/constraintType", "Sample ConstraintTypeBreakpoint", IStatus.OK);
		ConstraintTypeBreakpoint breakpoint = new ConstraintTypeBreakpoint();
		try {
			// set marker for breakpoint
			breakpoint.setMarker(marker);
			breakpoint.setEnabled(true);
			// configure breakpoint
			breakpoint.setType(ConstraintType.valueOf(constraintType));
			breakpoint.setDebugLevel(DebugLevel.CONSTRAINT_TYPE);
			// add breakpoint to manager to keep track
			manager.addBreakpoint(breakpoint);
		} catch (CoreException e1) {
			System.out.println("Unable to create custom ConstraintTypeBreakpoint.");
			e1.printStackTrace();
		}
	}
	
	public void setConstraintInstanceBreakpoint(String constraintInstance) {
		// get all breakpoints
		IBreakpointManager manager = getManager();
		// create breakpoint
		IResource moduleFile = debugTarget.getModuleResource();
		IMarker marker = addMarker(moduleFile, HenshinModelPlugin.PLUGIN_ID, "/constraintInstance", "Sample ConstraintInstanceBreakpoint", IStatus.OK);
		ConstraintInstanceBreakpoint breakpoint = new ConstraintInstanceBreakpoint();
		try {
			// set marker for breakpoint
			breakpoint.setMarker(marker);
			breakpoint.setEnabled(true);
			// configure breakpoint
			
			breakpoint.setConstraintInstance(removeRuntimeValuesFromConstraintInstance(constraintInstance));
			breakpoint.setDebugLevel(DebugLevel.CONSTRAINT);
			// add breakpoint to manager to keep track
			manager.addBreakpoint(breakpoint);
		} catch (CoreException e1) {
			System.out.println("Unable to create custom ConstraintInstanceBreakpoint.");
			e1.printStackTrace();
		}
	}
	
	protected String removeRuntimeValuesFromConstraintInstance(String constraintInstance) {
		// Remove runtime values from the constraint instance string
		final int indexOfParenthesis = constraintInstance.indexOf('(');
		if (indexOfParenthesis > 0) {
			constraintInstance = constraintInstance.substring(0, indexOfParenthesis - 1);
		}
		return constraintInstance;
	}
	
	
	/**
	 * Returns an array list containing all HenshinBreakpoints currently available
	 */
	public ArrayList<HenshinBreakpoint> filterHenshinBreakpoints(IBreakpoint[] breakpoints) {
		// array list of HenshinBreakpoints to be returned
		ArrayList<HenshinBreakpoint> henshinBreakpoints = new ArrayList<HenshinBreakpoint>();
		
		// loop through all breakpoints and return the ones which are of type henshin breakpoints
		for (IBreakpoint breakpoint : breakpoints) {
			// local variable
			HenshinBreakpoint henshinBreakpoint;
			if (isHenshinBreakpoint(breakpoint)) {
				// cast to henshin breakpoint
				henshinBreakpoint = (HenshinBreakpoint) breakpoint;
				henshinBreakpoints.add(henshinBreakpoint);
			}
		}
		
		return henshinBreakpoints;
	}
	
	/**
	 * Checks whether a given breakpoint is a subclass of HenshinBreakpoint
	 */
	public boolean isHenshinBreakpoint(IBreakpoint breakpoint) {
		return HenshinBreakpoint.class.isAssignableFrom(breakpoint.getClass());
	}
	
	/**
	 * Returns an array list containing all VariableBreakpoints currently available
	 */
	public ArrayList<VariableBreakpoint> filterVariableBreakpoints(ArrayList<HenshinBreakpoint> henshinBreakpoints) {
		ArrayList<VariableBreakpoint> variableBreakpoints = new ArrayList<VariableBreakpoint>();
		
		// loop through all breakpoints and return the ones which are of type VariableBreakpoint
		for (HenshinBreakpoint henshinBreakpoint : henshinBreakpoints) {
			// local variable
			if (isVariableBreakpoint(henshinBreakpoint)) {
				// cast to VariableBreakpoint
				VariableBreakpoint variableBreakpoint = (VariableBreakpoint) henshinBreakpoint;
				variableBreakpoints.add(variableBreakpoint);
			}
		}
		
		return variableBreakpoints;
	}
	
	/**
	 * Checks whether a given breakpoint is of type VariableBreakpoint
	 */
	public boolean isVariableBreakpoint(HenshinBreakpoint breakpoint) {
		return breakpoint instanceof VariableBreakpoint;
	}
	
	/**
	 * Filter all constraint type breakpoints and returns an array list.
	 * @param henshinBreakpoints
	 * @return
	 */
	public ArrayList<ConstraintTypeBreakpoint> filterConstraintTypeBreakpoints(ArrayList<HenshinBreakpoint> henshinBreakpoints) {
		ArrayList<ConstraintTypeBreakpoint> constraintTypeBreakpoints = new ArrayList<ConstraintTypeBreakpoint>();
		
		// loop through all breakpoints and return the ones which are of type ConstraintTypeBreakpoint
		for (HenshinBreakpoint henshinBreakpoint : henshinBreakpoints) {
			// local variable
			if (isConstraintTypeBreakpoint(henshinBreakpoint)) {
				// cast to VariableBreakpoint
				ConstraintTypeBreakpoint constraintTypeBreakpoint = (ConstraintTypeBreakpoint) henshinBreakpoint;
				constraintTypeBreakpoints.add(constraintTypeBreakpoint);
			}
		}
		
		return constraintTypeBreakpoints;
	}
	
	/**
	 * Checks whether a given breakpoint is of type ConstraintTypeBreakpoint
	 */
	public boolean isConstraintTypeBreakpoint(HenshinBreakpoint breakpoint) {
		return breakpoint instanceof ConstraintTypeBreakpoint;
	}
	
	/**
	 * Filter all constraint instance breakpoints and returns an array list.
	 * @param henshinBreakpoints
	 * @return
	 */
	public ArrayList<ConstraintInstanceBreakpoint> filterConstraintInstanceBreakpoints(ArrayList<HenshinBreakpoint> henshinBreakpoints) {
		ArrayList<ConstraintInstanceBreakpoint> constraintInstanceBreakpoints = new ArrayList<ConstraintInstanceBreakpoint>();
		
		// loop through all breakpoints and return the ones which are of type ConstraintTypeBreakpoint
		for (HenshinBreakpoint henshinBreakpoint : henshinBreakpoints) {
			// local variable
			if (isConstraintInstanceBreakpoint(henshinBreakpoint)) {
				// cast to VariableBreakpoint
				ConstraintInstanceBreakpoint constraintInstanceBreakpoint = (ConstraintInstanceBreakpoint) henshinBreakpoint;
				constraintInstanceBreakpoints.add(constraintInstanceBreakpoint);
			}
		}
		
		return constraintInstanceBreakpoints;
	}	
	
	/**
	 * Checks whether a given breakpoint is of type ConstraintTypeBreakpoint
	 */
	public boolean isConstraintInstanceBreakpoint(HenshinBreakpoint breakpoint) {
		return breakpoint instanceof ConstraintInstanceBreakpoint;
	}
	
	
	/**
	 * Checks all the necessary parameters to determine whether to stop at this variable or not.
	 * @param variableBreakpoint
	 * @param currentTypeName
	 * @param currentNodePath
	 * @return true if any of the criteria of the breakpoint match the current variable 
	 */
	public boolean shouldStopAtVariableBreakpoint(VariableBreakpoint variableBreakpoint, String currentTypeName, String currentNodePath) {		
		try {
			// check if enabled
			if (variableBreakpoint.isEnabled()) {
				// check if we want to stop at every breakpoint anyway
				if (variableBreakpoint.isGeneric()) {
					return true;
				}
				
				// check if we want to stop at a specific variable
				if (variableBreakpoint.isSpecificVariable()) {
					if (currentNodePath.equals(variableBreakpoint.getPath())) {
						return true;
					}
				}
				
				// check if we want to stop at a specific variable type
				if (variableBreakpoint.isSpecificType()) {
					if (currentTypeName.equals(variableBreakpoint.getTypeName())) {
						return true;
					}
				}
			}

			// if none of the above match we just return false to keep stepping
			return false;
			
		} catch (CoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return false;
	}
	
	/**
	 * Checks if the application should suspend at the given ValueBreakpoint.
	 * Therefore we check if the value we saved in the ValueBreakpoint is a member of the domain
	 * and if so we additionally check if the saved index matches the index of the value within the domain.
	 * 
	 * @param variableBreakpoint
	 * @param domain
	 * @return boolean
	 */
	public boolean shouldStopAtValueBreakpoint(ValueBreakpoint valueBreakpoint, EObject value, int index) {
		try {
			if (valueBreakpoint.isEnabled()) {
				// breakpoint data
				String breakpointType = valueBreakpoint.getType();
				int breakpointIndex = valueBreakpoint.getIndex();
				
				// current data
				String currentType = value.eClass().getName();
				int currentIndex = index; 
				
				// check
				if (breakpointType.equals(currentType) && breakpointIndex == currentIndex) {
					return true;
				}
			}
		} catch (CoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
		
		return false;
	}
	
	/**
	 * 
	 * @param constraintTypeBreakpoint
	 * @return
	 */
	public boolean shouldStopAtConstraintTypeBreakpoint(ConstraintTypeBreakpoint constraintTypeBreakpoint) {
		return constraintTypeBreakpoint.getType() == currentConstraintType;
	}
	
	/**
	 * 
	 * @param constraintTypeBreakpoint
	 * @return
	 */
	public boolean shouldStopAtConstraintInstanceBreakpoint(ConstraintInstanceBreakpoint constraintInstanceBreakpoint) {
		return removeRuntimeValuesFromConstraintInstance(retrieveConstraintLabel()).equals(constraintInstanceBreakpoint.getConstraintInstance());
	}
	
	/**
	 * Checks if there are any variables left.
	 * 
	 * @return true if current variable is last variable
	 */
	public boolean isLastVariable() {
		return currentVariableIndex == variables.size() - 1;
	}
	
	/**
	 * Returns an array list containing all ValueBreakpoints currently available
	 */
	public ArrayList<ValueBreakpoint> filterValueBreakpoints(ArrayList<HenshinBreakpoint> henshinBreakpoints) {
		ArrayList<ValueBreakpoint> valueBreakpoints = new ArrayList<ValueBreakpoint>();
		
		// loop through all breakpoints and return the ones which are of type VariableBreakpoint
		for (HenshinBreakpoint henshinBreakpoint : henshinBreakpoints) {
			// local variable
			if (isValueBreakpoint(henshinBreakpoint)) {
				// cast to VariableBreakpoint
				ValueBreakpoint valueBreakpoint = (ValueBreakpoint) henshinBreakpoint;
				valueBreakpoints.add(valueBreakpoint);
			}
		}
		
		return valueBreakpoints;
	}
	
	/**
	 * Checks whether a given breakpoint is of type ValueBreakpoint
	 */
	public boolean isValueBreakpoint(IBreakpoint breakpoint) {
		return breakpoint instanceof ValueBreakpoint;
	}
		
	/*
	 * Suspends the application if certain criteria for the breakpoint are met.
	 */
	public void handleDebugState(HenshinBreakpoint henshinBreakpoint) {
		
		// TODO:
		
		// get debug level of current henshinBreakpoint
		DebugLevel henshinBreakpointDebugLevel = henshinBreakpoint.getDebugLevel();
		
		if (currentDebugLevel.equals(henshinBreakpointDebugLevel)) {
			// TODO: Suspend when criteria met
			// we want to suspend here
			try {
				debugTarget.suspend();
			} catch (DebugException e) {
				System.out.println("Application could not be suspended.");
				e.printStackTrace();
			}
		} else {
			// resume the application in case we are suspended
			try {
				debugTarget.resume();
			} catch (DebugException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	/*
	 * Suspend the application
	 */
	public void setSuspended() {
		try {
			debugTarget.suspend();
		} catch (DebugException e) {
			System.out.println("Application could not be suspended.");
			e.printStackTrace();
		}
	}
	
	/*
	 * Resume the application
	 */
	public void setResumed() {
		// resume the application in case we are suspended
		try {
			debugTarget.resume();
		} catch (DebugException e) {
			System.out.println("Application could not be resumed.");
			e.printStackTrace();
		}
	}
	
	/*
	 * Checks for deeper and debug levels and returns a boolean to indicate
	 * whether the application should be suspended or not. 
	 */
	public boolean shouldSuspendApplication(HenshinBreakpoint henshinBreakpoint) {		
		switch (currentDebugLevel) {
		case VARIABLE:
			if (henshinBreakpoint instanceof VariableBreakpoint) {
				// TODO: Implement - Check for VariableBreakpoint attributes
				System.out.println("Matched VariableBreakpoint");
			}
			break;
		case VALUE:
			if (henshinBreakpoint instanceof ValueBreakpoint) {
				// TODO: Implement
				System.out.println("Matched ValueBreakpoint");
			}
			break;
		default:
			break;
		}
		
		
		return false;
	}
			
	/*
	 * Custom methods end here
	 */
	
	/**
	 * returns the current array of stackFrames, each containing variables.
	 * NOTE: maybe we could store the stackframes that do not change
	 * @return
	 */
	public synchronized IStackFrame[] getStackFrames(HenshinDebugThread debugThread) {
		
		// calculate the number of stackframes we need:
		// we need one stackframe for every matched variable as well as one for the current one.
		int lines = currentVariableIndex + 1;
		
		// each debuglevel, if present, gets an extra stackframe
		// (current value, constraint type, constraint instance)
		if (currentDebugLevel.ordinal() > 1) {
			lines += (currentDebugLevel.ordinal() - 1);
		}
		
		stackFrames = new IStackFrame[lines];
		
		StringBuilder labelBuilder = new StringBuilder();
		
		// first loop over all matched variables + the current one.
		for (int i = 0; i <= currentVariableIndex; i++) {
			Variable var = variables.get(i);
			DomainSlot slot = domainMap.get(var);
			
			labelBuilder.append("Variable ").append(retrieveVariableLabel(var));
			
			if (!isTerminated() && i != currentVariableIndex || isTerminated()) {
				labelBuilder.append(" (Match: ")
				.append(retrieveValueLabel(slot.getValue(), graph))
				.append(")");
			}

			HenshinDebugVariable debugVariable =
					new HenshinDebugVariable(
							debugTarget,
							"Variable",
							retrieveVariableLabel(var),
							var);			
			
			String declaredType = var.typeConstraint.type.getName();
			
			// when the domainSlot is null, it is necessary to add the "[]" postfix to the "declared type" string beforehand.
			// in all other cases, the postfix is added inside the HenshinDebugValue constructor
			if (slot.getDomain() == null) {
				declaredType += "[]";
			}
												
			HenshinDebugVariable debugDomain =
					new HenshinDebugVariable(
							debugTarget,
							"Domain",
							new DebugValueList(
									debugTarget,
									graph,
									declaredType,
									(slot.getDomain() != null ? slot.getDomain() : new ArrayList<EObject>()) // if the domain is empty we pass an empty array list, else we pass the domain
									)
							);
			
			IVariable[] stackVariables = new IVariable[]{debugVariable, debugDomain};
			
			stackFrames[i] = new HenshinStackFrame(debugThread, stackVariables, labelBuilder.toString(), i);
			
			// clear the string builder
			labelBuilder.setLength(0);
		}

		// then add the extra stackframes for...
		// ...value
		if (currentDebugLevel.ordinal() > 1) {
			
			/*
			 * Get whole domain (e.g. domain for :Client could be 'charles', 'bob', 'alice').
			 * Domain may also be null, then we use an empty array list.
			 */
			List<EObject> domain;
			if (currentSlot.getValue() != null) {
				domain = graph.getDomain(currentSlot.getValue().eClass(), false);
			} else {
				domain = new ArrayList<EObject>();
			}
			int index = domain.indexOf(currentSlot.getValue());
			
			HenshinDebugVariable debugValue =
					new HenshinDebugVariable(
							debugTarget,
							"Value",
							new DebugValueEObject(
									debugTarget,
									graph,
									currentVariable.typeConstraint.type.getName(),
									currentSlot.getValue(),
									index)
							);
						
			IVariable[] stackFrameVariables = new IVariable[]{debugValue};
			String label = "Value: '" + retrieveValueLabel(currentSlot.getValue(), graph) + "'";
			
			stackFrames[currentVariableIndex+1] = new HenshinStackFrame(debugThread, stackFrameVariables, label, currentVariableIndex+1);
			
			// ... constraint type
			if (currentDebugLevel.ordinal() > 2) {
				HenshinDebugVariable debugConstrType =
						new HenshinDebugVariable(
								debugTarget,
								"Constraint Type",
								currentConstraintType.toString(),
								ConstraintType.class.getSimpleName());

				label = "Constraint Type: '" + currentConstraintType + "'";
				stackFrames[currentVariableIndex+2] = new HenshinStackFrame(debugThread, new IVariable[]{debugConstrType}, label, currentVariableIndex+2);
				
				// ...constraint instance
				if (currentDebugLevel.ordinal() > 3) {
					
					HenshinDebugVariable debugConstrIndex = 
							new HenshinDebugVariable(
									debugTarget,
									"Constraint Index", 
									new DebugValueObject(
											debugTarget,
											graph,
											int.class.getName(),
											currentConstraintIndex,
											0)
									);
					
					label = retrieveConstraintLabel();

					HenshinDebugVariable debugConstraint = 
							new HenshinDebugVariable(debugTarget, "Constraint", label, currentConstraintType.toString() + " Constraint");
					
//					if (stackFrames.length >= currentVariableIndex + 4) {
						stackFrames[currentVariableIndex+3] = new HenshinStackFrame(debugThread, new IVariable[]{debugConstrIndex, debugConstraint}, label, currentVariableIndex+3);
//					}
				}
			}
		}
			
		return stackFrames;
	}

	private String retrieveVariableLabel(Variable var) {
		// include variable name from the rule's LHS
		String varNamePrefix = var.name != null ? var.name + ":" : "";
		return variables.indexOf(var) + ": '" + varNamePrefix + var.typeConstraint.type.getName() + "'";
	}

	/**
	 * Attempts to find an appropriate String representation for the given value EObject.
	 * This is done by looking for a "name" or an "id" attribute.
	 * Tries to create an index from the graph if no such attributes can be found.
	 * Uses the EObjects hashCode, if everything else fails.
	 * @param value the {@link EObject} that a label should be found for
	 * @param graph the {@link EGraph} that contains the value, or <code>null</code>
	 * @return the label String.
	 */
	public static String retrieveValueLabel(EObject value, EGraph graph) {
		if (value == null)
			return "null";
		// the label should start with the eClass name
		String label = value.eClass().getName() + " ";
		
		// look for a "name" or an "id" attribute to use for the label	
		for (EStructuralFeature feature : value.eClass().getEAllStructuralFeatures()) {
			if (feature.getName().equalsIgnoreCase("name")
					|| feature.getName().equalsIgnoreCase("id")) {
				label += value.eGet(feature);
				return label;
			}
		}
		
		// no useful feature found
		if (graph != null) {
			List<EObject> allValues = graph.getDomain(value.eClass(), false);
			int valIndex = allValues.indexOf(value);
			
			if (valIndex != -1) {
				label += "(index=" + valIndex + ")";
			} else {
				// value is not part of the graph -> use hashCode
				label += "(hashCode=" + value.hashCode() + ")";
			}
		} else {
			// graph is not available -> use hashCode
			label += "(hashCode=" + value.hashCode() + ")";
		}
	
		
		return label;
	}
		
	/**
	 * Tries to find an appropriate label for the stackFrame to visualize the current constraint.
	 * This label depends on the current constraint type.
	 * @return a label string describing the current constraint instance
	 */
	@SuppressWarnings("unchecked")
	private synchronized String retrieveConstraintLabel() {
		String label = "";
		
		if (currentDebugLevel != DebugLevel.CONSTRAINT) {
			return "Constraint " + currentConstraintIndex;
		}
		
		switch (currentConstraintType) {
		case TYPE:
			label += ("Type = " + currentVariable.typeConstraint.type.getName());
			break;
		case DANGLING:
			label += retrieveValueLabel(currentSlot.getValue(), graph);
			break;
		case ATTRIBUTE:
			AttributeConstraint attributeConstraint = currentVariable.attributeConstraints.get(currentConstraintIndex);
			String paramName = String.valueOf(attributeConstraint.getValue());
			String paramValue = String.valueOf(currentSlot.getConditionHandler().getParameter(paramName));
			// paramValue should be the param name for unset parameters
			if (!currentSlot.getConditionHandler().isSet(paramName)) {
				paramValue = paramName;
			}
			String attName = attributeConstraint.getAttribute().getName();
			/*
			 * This usually won't be null but if the user steps through 
			 * the debugging process too fast this may result in a null pointer exception.
			 * Therefore we check for a value first. 
			 */
			if (currentSlot.getValue() != null) {
				String attValue = String.valueOf(currentSlot.getValue().eGet(currentSlot.getValue().eClass().getEStructuralFeature(attName)));
				label += attName + " = " + paramName + " (" + attValue + " = " + paramValue + ")";				
			}
			break;
		case CONTAINMENT:
			ContainmentConstraint containmentConstraint = currentVariable.containmentConstraints.get(currentConstraintIndex);
			Variable targetVariable = containmentConstraint.getTargetVariable();

			EObject targetValue = domainMap.get(targetVariable).getValue();
			
			// when the container has no locked value yet, targetValue is null.
			// In this case, we want to display the containing variable to the user			
			if (targetValue == null) {
				label += retrieveVariableLabel(targetVariable);
			} else {
				label += targetValue.eClass().getName() + " " + retrieveValueLabel(targetValue, graph);				
			}
			break;
		case REFERENCE:
			ReferenceConstraint referenceConstraint = currentVariable.referenceConstraints.get(currentConstraintIndex);
			EReference reference = referenceConstraint.getReference();
			
			label += reference.getName() + ": ";
			
			List<EObject> targetObjects;
			if (reference.isMany()) {
				targetObjects = (List<EObject>) currentSlot.getValue().eGet(reference);
			} else {
				EObject obj = (EObject) currentSlot.getValue().eGet(reference);
				targetObjects = Collections.singletonList(obj);
			}
			
			StringJoiner listJoiner = new StringJoiner(", ", "[", "]");
			for (EObject obj : targetObjects) {
				listJoiner.add(retrieveValueLabel(obj, graph));	
			}
			label += listJoiner.toString();
			
			break;
		case PATH:
			PathConstraint pathConstraint = currentVariable.pathConstraints.get(currentConstraintIndex);
			List<EReference> references = pathConstraint.getReferences();
			listJoiner = new StringJoiner(", ", "[", "]");
			for (EReference ref : references) {
				listJoiner.add(ref.getName());
			}
			label += "Path to variable <" + retrieveVariableLabel(pathConstraint.getTargetVariable()) 
				+ "> via references " + listJoiner.toString();
			break;
		case USER:
			label += "User";
			break;
		case NONE:
			throw new IllegalStateException("trying to find label for constraint with currentConstraintType NONE");
		default:
			throw new IllegalStateException("missing ConstraintType in switch statement?");
		}
		
		return label;
	}

	/*
	 * Get the current DebugState.
	 */
	public DebugState getCurrentDebugState() {
		return currentDebugState;
	}

	/*
	 * Determines the value of the shouldStep variable used in stepOver().
	 */
	public <U> boolean checkForChange(Object a, Object b) {
		// TODO: Check that a and b are of same type in method declaration
		// if we are at DebugLevel.NONE or the suspendCondition is met we stop stepping
		if (currentDebugLevel == DebugLevel.NONE || a != b || currentDebugState == DebugState.TERMINATED_TRUE || currentDebugState == DebugState.TERMINATED_FALSE) {
			// suspend application when one ore more of the two conditions are met
			suspendApplication();
			// gets assigned to the shouldStep variable in stepOver()
			return false;
		}
		
		// gets assigned to the shouldStep variable in stepOver()		
		return true;
	}
	
	/*
	 * Suspends the application by setting currentDebugState SUSPENDED.
	 */
	public void suspendApplication() {
		if (!isTerminated())
			setCurrentDebugState(DebugState.SUSPENDED);
	}

	/**
	 * checks the current debug state with the state provided by the parameters
	 * 
	 * @param expectedDebugLevel the expected {@link DebugLevel}
	 * @param expectedVariableIndex the expected variable index (see {@link ApplicationCondition#findMatch(int)}
	 * @param expectedConstraintType the expected constraint type (see {@link constraint#getClass()})
	 * @param expectedConstraintIndex the expected iteration index for multiple constraints of the same type (for example: {@link Variable#attributeConstraints}).
	 * 
	 * @return <code>true</code> if the state is the same, <code>false</code> otherwise
	 */
	public boolean checkDebugState(DebugLevel expectedDebugLevel, int expectedVariableIndex,
			EObject expectedValue, ConstraintType expectedConstraintType, int expectedConstraintIndex) {	
		
		return (expectedDebugLevel == currentDebugLevel
				&& expectedVariableIndex == currentVariableIndex
				&& (currentSlot == null? expectedValue == null : expectedValue == currentSlot.getValue())
				&& expectedConstraintType == currentConstraintType
				&& expectedConstraintIndex == currentConstraintIndex);

	}
	
	/**
	 * Checks the values of the match if one was found.
	 * @param expectedValues The values in the same order as the corresponding variables
	 * @return <code>true</code> if the values of the match are the same as the given values
	 */
	public boolean checkMatch(List<EObject> expectedValues) {
		for (int i = 0; i < variables.size(); i++) {
			DomainSlot slot = domainMap.get(variables.get(i));
			EObject actualValue = slot.getValue();
			try {
				if (expectedValues.get(i) != actualValue) {
					return false;
				}
			} catch (IndexOutOfBoundsException e) {
				e.printStackTrace();
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Checks the values of the match if one was found.
	 * @param expectedMatch The expected match
	 * @return <code>true</code> if the values of the match are the same as the given values
	 */
	public boolean checkMatch(Map<Variable, EObject> expectedValues) {
		for (Variable variable : expectedValues.keySet()) {
			EObject expectedValue = expectedValues.get(variable);
			EObject actualValue = domainMap.get(variable).getValue();
			if (expectedValue != actualValue) {
				return false;
			}
		}
		return true;
	}
	
	private void throwExceptionInvalidDebugLevel() {
		throw new IllegalStateException("currentDebugLevel is \"" + currentDebugLevel 
				+ "\", but has to be of the enum type DebugLevel");
	}
	
	@Override
	public String toString() {
		return "[debugLevel: " + currentDebugLevel
				+ ", variableIndex: " + currentVariableIndex
				+ ", value: " + retrieveValueLabel(currentSlot.getValue(), graph)
				+ ", constraintType: " + currentConstraintType
				+ ", " + retrieveConstraintLabel()
				+ "]";
	}

	private static final String MARKER_TYPE = "org.eclipse.emf.henshin.diagram" + ".diagnostic"; //$NON-NLS-1$
	// use the same type as in org.eclipse.emf.henshin.diagram.providers.HenshinMarkerNavigationProvider so the markers can still be deleted from there.

	private static IMarker addMarker(IResource ressource, String elementId, String location, String message, int statusSeverity) {
		IMarker marker = null;
		try {
			marker = ressource.createMarker(MARKER_TYPE);
			marker.setAttribute(IMarker.MESSAGE, message);
			marker.setAttribute(IMarker.LOCATION, location);
			marker.setAttribute("elementId", elementId);
			int markerSeverity = IMarker.SEVERITY_INFO;
			if (statusSeverity == IStatus.WARNING) {
				markerSeverity = IMarker.SEVERITY_WARNING;
			} else if (statusSeverity == IStatus.ERROR || statusSeverity == IStatus.CANCEL) {
				markerSeverity = IMarker.SEVERITY_ERROR;
			}
			marker.setAttribute(IMarker.SEVERITY, markerSeverity);
		} catch (CoreException e) {
			HenshinModelPlugin.INSTANCE.logError("Failed to create validation marker", e); //$NON-NLS-1$
		}
		return marker;
	}
}
