package org.etsi.mts.tdl.scoping;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.scoping.IScope;
import org.eclipse.xtext.scoping.Scopes;
import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider;
import org.etsi.mts.tdl.Action;
import org.etsi.mts.tdl.ActionReference;
import org.etsi.mts.tdl.Behaviour;
import org.etsi.mts.tdl.Block;
import org.etsi.mts.tdl.CastDataUse;
import org.etsi.mts.tdl.CollectionDataType;
import org.etsi.mts.tdl.ComponentInstance;
import org.etsi.mts.tdl.ComponentInstanceBinding;
import org.etsi.mts.tdl.Connection;
import org.etsi.mts.tdl.DataElementMapping;
import org.etsi.mts.tdl.DataElementUse;
import org.etsi.mts.tdl.DataInstance;
import org.etsi.mts.tdl.DataInstanceUse;
import org.etsi.mts.tdl.DataType;
import org.etsi.mts.tdl.DataUse;
import org.etsi.mts.tdl.Element;
import org.etsi.mts.tdl.ElementImport;
import org.etsi.mts.tdl.EnumDataType;
import org.etsi.mts.tdl.Extension;
import org.etsi.mts.tdl.FormalParameter;
import org.etsi.mts.tdl.FormalParameterUse;
import org.etsi.mts.tdl.FunctionCall;
import org.etsi.mts.tdl.GateInstance;
import org.etsi.mts.tdl.GateReference;
import org.etsi.mts.tdl.LiteralValueUse;
import org.etsi.mts.tdl.LocalExpression;
import org.etsi.mts.tdl.MappableDataElement;
import org.etsi.mts.tdl.MemberAssignment;
import org.etsi.mts.tdl.MemberReference;
import org.etsi.mts.tdl.Message;
import org.etsi.mts.tdl.NamedElement;
import org.etsi.mts.tdl.Package;
import org.etsi.mts.tdl.PackageableElement;
import org.etsi.mts.tdl.Parameter;
import org.etsi.mts.tdl.ParameterBinding;
import org.etsi.mts.tdl.ParameterMapping;
import org.etsi.mts.tdl.PredefinedFunctionCall;
import org.etsi.mts.tdl.ProcedureCall;
import org.etsi.mts.tdl.ProcedureSignature;
import org.etsi.mts.tdl.SimpleDataInstance;
import org.etsi.mts.tdl.StructuredDataInstance;
import org.etsi.mts.tdl.StructuredDataType;
import org.etsi.mts.tdl.Target;
import org.etsi.mts.tdl.TestConfiguration;
import org.etsi.mts.tdl.TestDescription;
import org.etsi.mts.tdl.TestDescriptionReference;
import org.etsi.mts.tdl.TimeLabel;
import org.etsi.mts.tdl.TimeLabelUse;
import org.etsi.mts.tdl.Timer;
import org.etsi.mts.tdl.TimerOperation;
import org.etsi.mts.tdl.ValueAssignment;
import org.etsi.mts.tdl.Variable;
import org.etsi.mts.tdl.VariableUse;
import org.etsi.mts.tdl.tdlPackage;
import org.etsi.mts.tdl.extendedconfigurations.ComponentReference;
import org.etsi.mts.tdl.extendedconfigurations.ExtendedGateReference;
import org.etsi.mts.tdl.extendedconfigurations.TestConfigurationInstance;
import org.etsi.mts.tdl.structuredobjectives.Content;
import org.etsi.mts.tdl.structuredobjectives.ContentReference;
import org.etsi.mts.tdl.structuredobjectives.EntityReference;
import org.etsi.mts.tdl.structuredobjectives.EventSequence;
import org.etsi.mts.tdl.structuredobjectives.LiteralValue;
import org.etsi.mts.tdl.structuredobjectives.LiteralValueReference;
import org.etsi.mts.tdl.structuredobjectives.StructuredTestObjective;

import com.google.common.base.Function;
import com.google.inject.Inject;

/**
 * This class contains custom scoping description.
 * 
 * see : http://www.eclipse.org/Xtext/documentation/latest/xtext.html#scoping on
 * how and when to use it
 * 
 */
public class TDLScopeProvider extends AbstractDeclarativeScopeProvider {
	@Inject
	private IQualifiedNameConverter qualifiedNameConverter;
	
	public Iterator<EObject> getContainerContents(Element context) {
		TestDescription td = context.getParentTestDescription();
		if (td == null)
			return Collections.emptyIterator();
		return td.eAllContents();
	}
	
	public List<? extends Parameter> getContainerParameters(Element context) {
		TestDescription td = context.getParentTestDescription();
		if (td == null)
			return Collections.emptyList();
		return td.getFormalParameter();
	}

	@Override
	public IScope getScope(EObject context, EReference reference) {
		//TODO: check problem
		if (reference.getEType()==null || reference.getEType().getInstanceClass() == null) {
			return null;
		}
		if (PackageableElement.class.isAssignableFrom(reference.getEType().getInstanceClass())
				&& !(context instanceof ElementImport)) {
			EList<EObject> elements = getScopedElementsOfType(context, reference.getEType().getInstanceClass());
			return Scopes.scopeFor(elements);
		}
		if (reference.getEType().getInstanceClass() == GateInstance.class) {
			if (context instanceof GateReference) {
				GateReference gate = (GateReference) context;
				if (gate.getComponent().getType()!=null) {
					IScope scope = Scopes.scopeFor(gate.getComponent().getType().allGates());
					return scope;
				}
			}
		} else if (reference.getEType().getInstanceClass() == ComponentInstance.class) {
			if (context instanceof EntityReference) {
				StructuredTestObjective testObjective = EcoreUtil2.getContainerOfType(context, StructuredTestObjective.class);
				TestConfiguration configuration = testObjective.getConfiguration();
				if (configuration == null) {
					return  IScope.NULLSCOPE;
				}
				IScope scope = Scopes.scopeFor(configuration.getComponentInstance());
				return scope;

			} else if (context instanceof Behaviour || context instanceof Block || context instanceof LocalExpression || context instanceof VariableUse) {
				TestDescription testDescription = getTestDescription((Element) context);
				if (testDescription!=null) {
					TestConfiguration configuration = testDescription.getTestConfiguration();
					IScope scope = Scopes.scopeFor(configuration.getComponentInstance());
					return scope;
				}
			} else if (context instanceof GateReference) {
				TestConfiguration configuration = getTestConfiguration((Element) context);
				if (context instanceof ExtendedGateReference) {
					configuration = ((ExtendedGateReference) context).getConfiguration().getConfiguration();
				}
				EList<ComponentInstance> components = configuration.getComponentInstance();
				IScope scope = Scopes.scopeFor(components);
				return scope;
			} else if (context instanceof ComponentInstanceBinding) {
				//assume actual
				TestDescription testDescription = getTestDescription((Element) context);
				if (reference.getFeatureID() == tdlPackage.COMPONENT_INSTANCE_BINDING__FORMAL_COMPONENT) {
					testDescription = ((TestDescriptionReference)context.eContainer()).getTestDescription();
				}
				TestConfiguration configuration = testDescription.getTestConfiguration();
				if (configuration!=null) {
					EList<ComponentInstance> components = configuration.getComponentInstance();
					IScope scope = Scopes.scopeFor(components);
					return scope;
				}
			} 
		} else if (reference.getEType().getInstanceClass() == GateReference.class) {
			//TODO: safety checks in case invalid configuration is specified
			//TODO: only suggest connected gates as targets?

			if (context instanceof Behaviour || context instanceof Target || context instanceof Block || context instanceof EventSequence) {
				TestConfiguration configuration;
				//TODO: this will need to be abstracted away as it is needed in other contexts too
				StructuredTestObjective testObjective = EcoreUtil2.getContainerOfType(context, StructuredTestObjective.class);
				if (testObjective!=null) {
					configuration = testObjective.getConfiguration();
				} else {
					configuration = getTestDescription((Element) context).getTestConfiguration();
				}
				if (configuration == null) {
					return  IScope.NULLSCOPE;
				}
				EList<EObject> elements = new BasicEList<>();
				for (Connection c : configuration.getConnection()) {
					if (c.getEndPoint().get(0).getName() != null) {
						elements.add(c.getEndPoint().get(0));
					}
					if (c.getEndPoint().get(1).getName() != null) {
						elements.add(c.getEndPoint().get(1));
					}
					//TODO: also for procedure?!
					if (context instanceof Message) {
						//TODO: quick hack until a better solution is found
						//TODO: add further safety checks 
						EList<Target> targets = ((Message) context).getTarget();
						if (!targets.isEmpty()) {
							ICompositeNode oppositeNode = NodeModelUtils.findActualNodeFor(targets.get(0));
							if (oppositeNode != null) {
								String text = NodeModelUtils.getTokenText(oppositeNode);
								String targetGateName = text
										.replaceAll("(the|a|an)\\s+", "")
										.replaceAll("(?s)where\\s+it\\s+is.+", "")
										.replaceAll("(?s)assigned\\s*.+", "")
										.replaceAll("(?s).+=\\s*", "")
										.replaceAll("(?s)with\\s*\\{.+", "")
										.trim();
								String[] split = targetGateName.split("\\."); //TODO: make configurable..
								if (targetGateName.contains("::")) {
									split = targetGateName.split("::");
								}
								if (split.length == 2) {
									String gate = split[1];
									String component = split[0];
									if (component.equals(c.getEndPoint().get(0).getComponent().getName()) &&
											gate.equals(c.getEndPoint().get(0).getGate().getName())) {
										elements.add(c.getEndPoint().get(1));
									} else if (component.equals(c.getEndPoint().get(1).getComponent().getName()) &&
											gate.equals(c.getEndPoint().get(1).getGate().getName())) {
										elements.add(c.getEndPoint().get(0));
									}
								} else {
									//TODO: handle?
								}
							} else {
								//TODO: double check
								//TODO: Make sure this works in the normal use case too.. 
								//		-> seems to work
								GateReference t = ((Message) context).getTarget().get(0).getTargetGate();
								if (t!=null) {
									if (t == c.getEndPoint().get(0)) {
										elements.add(c.getEndPoint().get(1));
									} else if (t == c.getEndPoint().get(1)) {
										elements.add(c.getEndPoint().get(0));
									}
								}
//								elements.addAll(c.getEndPoint());
							}
						}  else {
							//handle
						}
						//TODO: Why was this discarded? -> move above!
//						GateReference opposite = ((Message) context).getTarget().get(0).getTargetGate();
//						if (c.getEndPoint().get(0) == opposite) {
//							elements.add(c.getEndPoint().get(1));
//						} else if (c.getEndPoint().get(1) == opposite) {
//							elements.add(c.getEndPoint().get(0));
//						}
					} else if (context instanceof Target) {
						GateReference opposite = null;
						if (context.eContainer() instanceof Message) {
							opposite = ((Message) context.eContainer()).getSourceGate();
						} else {
							opposite = ((ProcedureCall) context.eContainer()).getSourceGate();							
						}
						if (c.getEndPoint().get(0) == opposite) {
							elements.add(c.getEndPoint().get(1));
						} else if (c.getEndPoint().get(1) == opposite) {
							elements.add(c.getEndPoint().get(0));
						}
					} else {
						elements.addAll(c.getEndPoint());
					}
				}
				IScope scope = Scopes.scopeFor(elements, new Function<EObject, QualifiedName>() {
			        @Override 
			        public QualifiedName apply(EObject o) {
			        	GateReference r = (GateReference) o;
			        	if (r.getName()!=null) {
				            QualifiedName qualifiedName = qualifiedNameConverter.toQualifiedName(r.getName());
				            return qualifiedName;
			        	}
			        	String n = r.getComponent().getName()+"."+r.getGate().getName();
			        	//TODO: make configurable
			        	n = r.getComponent().getName()+"::"+r.getGate().getName();
			        	n = r.getComponent().getName()+getGateReferenceNameSeparator()+r.getGate().getName();
			            QualifiedName qualifiedName = qualifiedNameConverter.toQualifiedName(n);
			            return qualifiedName;
			        }

			    }, IScope.NULLSCOPE); 
				return scope;
			}
		} else if (Parameter.class.isAssignableFrom(reference.getEType().getInstanceClass())) {
			if (context instanceof MemberAssignment) {
				if (context.eContainer() instanceof StructuredDataInstance && ((StructuredDataInstance)context.eContainer()).getDataType() instanceof StructuredDataType) {
					IScope scope = Scopes.scopeFor(((StructuredDataType)((StructuredDataInstance)context.eContainer()).getDataType()).allMembers());
					return scope;
				}
			} else if (context instanceof ParameterMapping) {
				if (context.eContainer() instanceof DataElementMapping) {
					if (((DataElementMapping)context.eContainer()).getMappableDataElement() instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)((DataElementMapping)context.eContainer()).getMappableDataElement()).allMembers());
						return scope;
					} else if (((DataElementMapping)context.eContainer()).getMappableDataElement() instanceof StructuredDataInstance) {
						IScope scope = Scopes.scopeFor(((StructuredDataType) ((StructuredDataInstance)((DataElementMapping)context.eContainer()).getMappableDataElement()).getDataType()).allMembers());
						return scope;
					} else if (((DataElementMapping)context.eContainer()).getMappableDataElement() instanceof Action) {
							IScope scope = Scopes.scopeFor(((Action)((DataElementMapping)context.eContainer()).getMappableDataElement()).getFormalParameter());
							return scope;
					} else if (((DataElementMapping)context.eContainer()).getMappableDataElement() instanceof ProcedureSignature) {
						IScope scope = Scopes.scopeFor(((ProcedureSignature)((DataElementMapping)context.eContainer()).getMappableDataElement()).getParameter());
						return scope;
					}
				}				
			} else if (context instanceof ParameterBinding) {
				if (context.eContainer() instanceof DataInstanceUse) {
					if (((DataInstanceUse)context.eContainer()).getDataInstance() instanceof StructuredDataInstance) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)((StructuredDataInstance)((DataInstanceUse)context.eContainer()).getDataInstance()).getDataType()).allMembers());
						return scope;
					} else if (((DataInstanceUse)context.eContainer()).getDataType() instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)((DataInstanceUse)context.eContainer()).getDataType()).allMembers());
						return scope;
					} else if (context.eContainer().eContainer() instanceof MemberAssignment) {
						DataType dataType = ((MemberAssignment)context
								.eContainer()
								.eContainer())
								.getMember()
								.getDataType();
						if (dataType!=null) {
							IScope scope = Scopes.scopeFor(((StructuredDataType)dataType)
									.allMembers());
							return scope;
						}
					} else if (context.eContainer().eContainer() instanceof ParameterBinding && 
							((ParameterBinding)context.eContainer().eContainer()).getParameter().getDataType() instanceof StructuredDataType 
							) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)((ParameterBinding)context.eContainer().eContainer()).getParameter().getDataType()).allMembers());
						return scope;
					} else {
						DataType resolvedDataType = ((DataInstanceUse)context.eContainer()).resolveDataType();
						if (resolvedDataType instanceof StructuredDataType) {
							IScope scope = Scopes.scopeFor(((StructuredDataType)resolvedDataType).allMembers());
							return scope;
						}
					}
				} else if (context.eContainer() instanceof FunctionCall) {
//					if (((FunctionCall)context.eContainer()).getFunction().get instanceof StructuredDataInstance) {
						IScope scope = Scopes.scopeFor(((FunctionCall)context.eContainer()).getFunction().getFormalParameter());
						return scope;
//					}
				} else if (context.eContainer() instanceof ActionReference) {
//					if (((FunctionCall)context.eContainer()).getFunction().get instanceof StructuredDataInstance) {
						IScope scope = Scopes.scopeFor(((ActionReference)context.eContainer()).getAction().getFormalParameter());
						return scope;
//					}
				} else if (context.eContainer() instanceof TestDescriptionReference) {
//					if (((FunctionCall)context.eContainer()).getFunction().get instanceof StructuredDataInstance) {
						IScope scope = Scopes.scopeFor(((TestDescriptionReference)context.eContainer()).getTestDescription().getFormalParameter());
						return scope;
//					}
				} else if (context.eContainer() instanceof FormalParameterUse) {
					IScope scope = Scopes.scopeFor(((StructuredDataType)((FormalParameterUse)context.eContainer()).getParameter().getDataType()).allMembers());
					return scope;
				} else if (context.eContainer() instanceof VariableUse) {
					IScope scope = Scopes.scopeFor(((StructuredDataType)((VariableUse)context.eContainer()).getVariable().getDataType()).allMembers());
					return scope;
				} else if (context.eContainer() instanceof ProcedureCall) {
//					if (((FunctionCall)context.eContainer()).getFunction().get instanceof StructuredDataInstance) {
						IScope scope = Scopes.scopeFor(((ProcedureCall)context.eContainer()).getSignature().getParameter());
						return scope;
//					}
				} else if (context.eContainer() instanceof DataElementUse) {
					//TODO: extract and simplify
					NamedElement dataElement = ((DataElementUse)context.eContainer()).getDataElement();
					if (dataElement instanceof org.etsi.mts.tdl.Function) {
						IScope scope = Scopes.scopeFor(((org.etsi.mts.tdl.Function)dataElement).getFormalParameter());
						return scope;
					} else if (dataElement instanceof FormalParameter) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)((FormalParameter) dataElement).getDataType()).allMembers());
						return scope;
					} else if (dataElement instanceof StructuredDataInstance) {
						if (((StructuredDataInstance) dataElement).getDataType() instanceof StructuredDataType) {
							IScope scope = Scopes.scopeFor(((StructuredDataType)((StructuredDataInstance) dataElement).getDataType()).allMembers());
							return scope;
						}
					} else if (dataElement instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)dataElement).allMembers());
						return scope;
					} else if (dataElement == null) {
						//TODO: use more widely! Will save a lot of the code in here...
						//if reduction?
						if (context.eContainer().eContainer() instanceof ParameterBinding) {
							//TODO: use newly introduced resolveParameterType?
							EList<MemberReference> reduction = ((ParameterBinding)context.eContainer().eContainer()).getReduction();
							if (reduction.size() > 0) {
								if (reduction.get(reduction.size()-1).getMember() == null) {
									DataType dataType = ((ParameterBinding)reduction.get(reduction.size()-1).eContainer()).getParameter().getDataType();
									if (dataType instanceof StructuredDataType) {
										IScope scope = Scopes.scopeFor(((StructuredDataType)dataType).allMembers());
										return scope;
									} else if (dataType instanceof CollectionDataType) {
										DataType itemType = ((CollectionDataType)dataType).getItemType();
										if (itemType instanceof StructuredDataType) {
											IScope scope = Scopes.scopeFor(((StructuredDataType) itemType).allMembers());
											return scope;
										}
									}
								} else {
									DataType dataType = reduction.get(reduction.size()-1).getMember().getDataType();
									if (dataType instanceof StructuredDataType) {
										IScope scope = Scopes.scopeFor(((StructuredDataType)dataType).allMembers());
										return scope;
									} else if (dataType instanceof CollectionDataType) {
										DataType itemType = ((CollectionDataType)dataType).getItemType();
										if (itemType instanceof StructuredDataType) {
											IScope scope = Scopes.scopeFor(((StructuredDataType) itemType).allMembers());
											return scope;
										}
									}
									IScope scope = Scopes.scopeFor(((StructuredDataType) dataType).allMembers());
									return scope;
								}
							}
						}
						DataType resolvedDataType = ((DataElementUse)context.eContainer()).resolveDataType();
						if (resolvedDataType instanceof StructuredDataType) {
							IScope scope = Scopes.scopeFor(((StructuredDataType)resolvedDataType).allMembers());
							return scope;
						} else if (resolvedDataType instanceof CollectionDataType) {
							DataType itemType = ((CollectionDataType)resolvedDataType).getItemType();
							if (itemType instanceof StructuredDataType) {
								IScope scope = Scopes.scopeFor(((StructuredDataType) itemType).allMembers());
								return scope;
							}
						} else if (resolvedDataType == null) {
//							DataType resolvedContextDataType = null;
//							if (context.eContainer().eContainer() instanceof DataElementUse) {
//								resolvedContextDataType = ((DataElementUse)context.eContainer().eContainer()).resolveDataType();
//							} else if (context.eContainer().eContainer() instanceof DataInstanceUse) {
//								resolvedContextDataType = ((DataInstanceUse)context.eContainer().eContainer()).resolveDataType();
//							}
//							if (resolvedContextDataType instanceof CollectionDataType) {
//								DataType itemType = ((CollectionDataType) resolvedContextDataType).getItemType();
//								IScope scope = Scopes.scopeFor(((StructuredDataType)itemType).allMembers());
//								return scope;
//							}
						}
					}
						
//					IScope scope = Scopes.scopeFor(dataElement.eContents());
//					return scope;
				} else if (context.eContainer() instanceof ParameterBinding) {
					DataType pdt = ((ParameterBinding)context.eContainer()).getParameter().getDataType();
					if (pdt instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType) pdt).allMembers());
						return scope;
					}
				} else if (context.eContainer() instanceof LiteralValueUse) {
					DataType resolvedDataType = ((LiteralValueUse)context.eContainer()).resolveDataType();
					if (resolvedDataType instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)resolvedDataType).allMembers());
						return scope;
					}
				}
			} else if (context instanceof MemberReference) {
				if (context.eContainer() instanceof ParameterBinding) {
					//TODO: use newly introduced resolveParameterType?
					if (((ParameterBinding)context.eContainer()).getReduction().indexOf(context)>0) {
						EObject targetContext = ((ParameterBinding)context.eContainer()).getReduction().get(((ParameterBinding)context.eContainer()).getReduction().indexOf(context)-1);
						if (((MemberReference)targetContext).getMember()!=null) {
							if (((MemberReference)targetContext).getMember().getDataType() instanceof StructuredDataType) {
								IScope scope = Scopes.scopeFor(((StructuredDataType)((MemberReference)targetContext).getMember().getDataType()).allMembers());
								return scope;
							} else if (((MemberReference)targetContext).getMember().getDataType() instanceof CollectionDataType) {
								DataType itemType = ((CollectionDataType)((MemberReference)targetContext).getMember().getDataType()).getItemType();
								if (itemType instanceof StructuredDataType) {
									IScope scope = Scopes.scopeFor(((StructuredDataType) itemType).allMembers());
									return scope;
								}
							}
						} else if (((MemberReference)targetContext).getCollectionIndex() != null) {
							DataType dataType = ((ParameterBinding)targetContext.eContainer()).getParameter().getDataType();
							if (dataType instanceof StructuredDataType) {
								IScope scope = Scopes.scopeFor(((StructuredDataType) dataType).allMembers());
								return scope;
							} else if (dataType instanceof CollectionDataType) {
								DataType itemType = ((CollectionDataType)dataType).getItemType();
								if (itemType instanceof StructuredDataType) {
									IScope scope = Scopes.scopeFor(((StructuredDataType) itemType).allMembers());
									return scope;
								}
							}
						}
					} else {
						DataType pdt = ((ParameterBinding)context.eContainer()).getParameter().getDataType();
						if (pdt instanceof StructuredDataType) {
							IScope scope = Scopes.scopeFor(((StructuredDataType) pdt).allMembers());
							return scope;
						}
					}
				} 
				if (context.eContainer() instanceof DataUse) {
					if (((DataUse)context.eContainer()).getReduction().indexOf(context)>0) {
						EObject targetContext = ((DataUse)context.eContainer()).getReduction().get(((DataUse)context.eContainer()).getReduction().indexOf(context)-1);
						if (((MemberReference)targetContext).getMember()!=null) {
							if (((MemberReference)targetContext).getMember().getDataType() instanceof StructuredDataType) {
								IScope scope = Scopes.scopeFor(((StructuredDataType)((MemberReference)targetContext).getMember().getDataType()).allMembers());
								return scope;
							}
						} else if (((MemberReference)targetContext).getCollectionIndex()!=null) {
							if (targetContext.eContainer() instanceof DataInstanceUse) {
								DataInstanceUse dataInstanceUse = (DataInstanceUse)targetContext.eContainer();
								if (dataInstanceUse.getDataType()!=null) {
									//TODO:?
								} else if (dataInstanceUse.getDataInstance()!=null) {
									//TODO: check type
									DataType itemType = ((CollectionDataType)dataInstanceUse.getDataInstance().getDataType()).getItemType();
									if (itemType instanceof StructuredDataType) {
										IScope scope = Scopes.scopeFor(((StructuredDataType)itemType).allMembers());
										return scope;
									}
								}
							} else if (targetContext.eContainer() instanceof DataElementUse) {
								DataElementUse dataElementUse = (DataElementUse)targetContext.eContainer();
								DataType resolvedDataType = dataElementUse.resolveDataType();
								if (resolvedDataType instanceof StructuredDataType) {
									IScope scope = Scopes.scopeFor(((StructuredDataType)resolvedDataType).allMembers());
									return scope;
								} else if (resolvedDataType instanceof CollectionDataType) {
									DataType itemType = ((CollectionDataType)resolvedDataType).getItemType();
									if (itemType instanceof StructuredDataType) {
										IScope scope = Scopes.scopeFor(((StructuredDataType) itemType).allMembers());
										return scope;
									}
									
								}
								
							}
							//TODO: handle other scenarios?
							
						}
					} 
				}
				if (context.eContainer() instanceof DataInstanceUse) {
					if (((DataInstanceUse)context.eContainer()).getDataInstance() instanceof StructuredDataInstance) {
						DataInstance dataInstance = ((DataInstanceUse)context.eContainer()).getDataInstance();
						if (dataInstance instanceof StructuredDataInstance && dataInstance.getDataType() instanceof StructuredDataType) {
							IScope scope = Scopes.scopeFor(((StructuredDataType)((StructuredDataInstance)dataInstance).getDataType()).allMembers());
							return scope;
						}
					}
				} else if (context.eContainer() instanceof FunctionCall) {
					if (((FunctionCall)context.eContainer()).getFunction().getReturnType() instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)((FunctionCall)context.eContainer()).getFunction().getReturnType()).allMembers());
						return scope;
					}
				} else if (context.eContainer() instanceof FormalParameterUse) {
					if (((FormalParameterUse)context.eContainer()).getParameter().getDataType() instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)((FormalParameterUse)context.eContainer()).getParameter().getDataType()).allMembers());
						return scope;
					}
				} else if (context.eContainer() instanceof VariableUse) {
					if (((VariableUse)context.eContainer()).getVariable().getDataType() instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)((VariableUse)context.eContainer()).getVariable().getDataType()).allMembers());
						return scope;
					}
				} else if (context.eContainer() instanceof DataElementUse) {
					NamedElement dataElement = ((DataElementUse) context.eContainer()).getDataElement();
					//TODO: duplicated from above ->extract
					if (dataElement instanceof org.etsi.mts.tdl.Function) {
						DataType returnType = ((org.etsi.mts.tdl.Function)dataElement).getReturnType();
						if (returnType instanceof StructuredDataType) {
							IScope scope = Scopes.scopeFor(((StructuredDataType)returnType).allMembers());
							return scope;
						}
					} else if (dataElement instanceof FormalParameter) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)((FormalParameter) dataElement).getDataType()).allMembers());
						return scope;
					} else if (dataElement instanceof StructuredDataInstance) {
						if (((StructuredDataInstance) dataElement).getDataType() instanceof StructuredDataType) {
							IScope scope = Scopes.scopeFor(((StructuredDataType)((StructuredDataInstance) dataElement).getDataType()).allMembers());
							return scope;
						}
					} else if (dataElement instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)dataElement).allMembers());
						return scope;
					} else if (dataElement == null) {
						//TODO: use more widely! Will save a lot of the code in here...
						DataType resolvedDataType = ((DataElementUse)context.eContainer()).resolveDataType();
						if (resolvedDataType instanceof StructuredDataType) {
							IScope scope = Scopes.scopeFor(((StructuredDataType)resolvedDataType).allMembers());
							return scope;
						}
					}
				} else if (context.eContainer() instanceof LiteralValueUse) {
					DataType resolvedDataType = ((LiteralValueUse)context.eContainer()).resolveDataType();
					if (resolvedDataType instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)resolvedDataType).allMembers());
						return scope;
					}
				} else if (context.eContainer() instanceof CastDataUse) {
					DataType resolvedDataType = ((CastDataUse)context.eContainer()).resolveDataType();
					if (resolvedDataType instanceof StructuredDataType) {
						IScope scope = Scopes.scopeFor(((StructuredDataType)resolvedDataType).allMembers());
						return scope;
					}
				} else if (context.eContainer() instanceof PredefinedFunctionCall) {
//					DataType resolvedDataType = ((PredefinedFunctionCall)context.eContainer()).resolveDataType();
//					if (resolvedDataType instanceof StructuredDataType) {
//						IScope scope = Scopes.scopeFor(((StructuredDataType)resolvedDataType).allMembers());
//						return scope;
//					}
				} else {
				}
			} else if (context instanceof ValueAssignment) {
				IScope scope = Scopes.scopeFor(((ProcedureCall)context.eContainer().eContainer()).getSignature().getParameter());
				return scope;
			}
		} else if (context instanceof TimeLabelUse) {
			Set<TimeLabel> labels = new HashSet<TimeLabel>();
			getContainerContents((Element) context).forEachRemaining(e -> {
				if (e instanceof TimeLabel)
					labels.add((TimeLabel) e);
			});
			IScope scope = Scopes.scopeFor(labels);
			return scope;
			
		} else if (reference.getEType().getInstanceClass() == Variable.class) {
			//DONE: handle with new super class? -> no longer needed! (clean up)
//			if (context instanceof Assignment) {
//				if (((Assignment)context).getComponentInstance()!=null) {
//					IScope scope = Scopes.scopeFor(((Assignment)context).getComponentInstance().getType().allVariables());
//					return scope;
//				}
//			}
			if (context instanceof VariableUse) {
				if (((VariableUse)context).getComponentInstance()!=null && ((VariableUse)context).getComponentInstance().getType()!=null) {
					IScope scope = Scopes.scopeFor(((VariableUse)context).getComponentInstance().getType().allVariables());
					return scope;
				}
			}
			else if (context instanceof ValueAssignment) {
				if (((Target)context.eContainer()).getTargetGate().getComponent()!=null) {
					IScope scope = Scopes.scopeFor(((Target)context.eContainer()).getTargetGate().getComponent().getType().allVariables());
					return scope;
				}
			}
		} else if (reference.getEType().getInstanceClass() == Timer.class) {
			if (context instanceof TimerOperation && ((TimerOperation)context).getComponentInstance().getType()!=null) {
				IScope scope = Scopes.scopeFor(((TimerOperation)context).getComponentInstance().getType().allTimers());
				return scope;
			}
		} else if (context instanceof Extension) {
			EList<EObject> elements = getScopedElementsOfType(context, ((Element)context.eContainer()).getClass());
			return Scopes.scopeFor(elements);
		} else {
		}
		
		if (context instanceof DataElementUse) {
			EList<EObject> elements = new BasicEList<>();
			elements.addAll(getScopedElementsOfType(context, MappableDataElement.class));
			elements.addAll(getContainerParameters((Element) context));
			//TODO: filter enums
//			if 
			if (!((InternalEObject) context).eIsSet(tdlPackage.DATA_ELEMENT_USE__DATA_ELEMENT)) {
				DataType dataType = ((DataElementUse) context).resolveDataType();
				if (dataType instanceof EnumDataType) {
					elements.removeIf(e->(e instanceof SimpleDataInstance) && ((SimpleDataInstance)e).getDataType() != dataType);
				}
			}
//			elements.addAll(getScopedElementsOfType(context, FormalParameter.class));
//			elements.addAll(getScopedElementsOfType(context, StructuredDataType.class));
			return Scopes.scopeFor(elements);
//			NamedElement dataElement = ((NamedDataElementUse)context).getDataElement();
			//EList<EObject> elements = ;
			//return Scopes.scopeFor(dataElement.eContents());
		}
		if (context instanceof DataElementMapping) {
			//TODO: handle qualified names?!
		}

		if (context instanceof PredefinedFunctionCall) {
			//TODO: handle qualified names?!
		}

		
		if (context instanceof ComponentReference && reference.getEType().getInstanceClass() == ComponentInstance.class) {
			TestConfigurationInstance configuration = ((ComponentReference) context).getConfiguration();
			if (configuration.getConfiguration()!=null) {
				IScope scope = Scopes.scopeFor(configuration.getConfiguration().getComponentInstance());
				return scope;
			}
		}
		
		if (context instanceof Block) {
			TestDescription testDescription = getTestDescription((Element) context);
			if (testDescription!=null) {
				TestConfiguration configuration = testDescription.getTestConfiguration();
				IScope scope = Scopes.scopeFor(configuration.getComponentInstance());
				return scope;
			}
		}
		if (context instanceof LiteralValueReference) {
			//TODO: why unsupported operation?
//			StructuredTestObjective to = ((LiteralValue) context).getTestObjective();
			StructuredTestObjective to = EcoreUtil2.getContainerOfType(context, StructuredTestObjective.class);
			List<LiteralValue> values = EcoreUtil2.getAllContentsOfType(to, LiteralValue.class);
			IScope scope = Scopes.scopeFor(values);
			return scope;
		}
		if (context instanceof ContentReference) {
			//TODO: why unsupported operation?
//			StructuredTestObjective to = ((LiteralValue) context).getTestObjective();
			StructuredTestObjective to = EcoreUtil2.getContainerOfType(context, StructuredTestObjective.class);
			List<Content> values = EcoreUtil2.getAllContentsOfType(to, Content.class);
			IScope scope = Scopes.scopeFor(values);
			return scope;
		}
		return super.getScope(context, reference);
	}

	protected String getGateReferenceNameSeparator() {
		return ".";
	}
	
	private TestConfiguration getTestConfiguration(Element self) {
		if (self.eContainer()!=null) {
			if (!(self.eContainer() instanceof TestConfiguration)) {
				return getTestConfiguration((Element) self.eContainer());
			} else {
				return (TestConfiguration)self.eContainer();
			}
		} 
		return null;
	}

	
	private TestDescription getTestDescription(Element self) {
		if (self.eContainer()!=null) {
			if (!(self.eContainer() instanceof TestDescription)) {
				return getTestDescription((Element) self.eContainer());
			} else {
				return (TestDescription)self.eContainer();
			}
		} 
		return null; 
	}

	public IScope scope_ElementImport_importedElement(ElementImport context, EReference ref) {
		EList<EObject> elements = new BasicEList<>();
		Package ip = context.getImportedPackage();
		for (PackageableElement e : ip.getPackagedElement()) {
			elements.add(e);
		}
		IScope scope = Scopes.scopeFor(elements);
		return scope;
	}
	
	private Package getPackage(EObject e) {
		if (e.eContainer()!=null) {
			if (e.eContainer() instanceof Package) {
				return (Package)e.eContainer();
			} else {
				return getPackage(e.eContainer());
			}
		} else {
			return (Package)e;
		}
	}
	
	protected EList<EObject> getScopedElementsOfType(EObject context, Class c) {
		EList<EObject> elements = new BasicEList<>();
		Package p = getPackage(context);
		
		//within same package
		for (PackageableElement e : p.getPackagedElement()) {
			if (c.isInstance(e)) {
				elements.add(e);
			} 
			if (e instanceof EnumDataType) {
				//TODO: naive enum handling (also below
				for (SimpleDataInstance ev : ((EnumDataType) e).getValue()) {
					elements.add(ev);
				}
			}
		}
		
		//within imported packages
		for (ElementImport i : p.getImport()) {
			Package ip = i.getImportedPackage();
			if (ip != null) {
				if (i.getImportedElement().isEmpty()) {
					//import all
					for (PackageableElement e : ip.getPackagedElement()) {
						if (c.isInstance(e)) {
							elements.add(e);
						}  
						if (e instanceof EnumDataType) {
							//enums
							for (SimpleDataInstance ev : ((EnumDataType) e).getValue()) {
								elements.add(ev);
							}
						}
					}
				} else {
					//specific imports
					for (PackageableElement e : i.getImportedElement()) {
						if (c.isInstance(e)) {
							elements.add(e);
						}  
						if (e instanceof EnumDataType) {
							//enums
							for (SimpleDataInstance ev : ((EnumDataType) e).getValue()) {
								elements.add(ev);
							}
						}
					}
				}
			}
		}
		return elements;
	}

}