//Supporting operation aliases for OCL
operation Any oclIsUndefined() : Boolean {
	return self.isUndefined();
}

operation Any oclIsTypeOf(t:Any) : Boolean {
	return self.isTypeOf(t);
}

operation Any oclIsKindOf(t:Any) : Boolean {
	return self.isKindOf(t);
}

operation Any oclType() : Any {
    return self.type();
}

operation Any oclAsType(t:Any) : Any {
	return self;
}

//TODO: does not seem to work
operation Any union(other:Any) : Collection {
    return self.includingAll(other);
}

operation Any notEmpty() : Boolean {
	return not self.isEmpty();
}


operation Any container() : Any {
	return self.eContainer;
}

operation String size() : Integer {
	return self.length();
}

operation Any isUnique(): Boolean {
	return self.forAll(v1 | self.select(v2 | v1 == v2).size() == 1);
}

//TODO: is it possible to define custom predicated operations?
//operation Collection isUnique() : Boolean {
//    return not self.isEmpty();
//}


//Supporting operations
operation Any getTestDescription() : Any {
	if (self.eContainer.isDefined()) {
		if (not self.eContainer.isKindOf(TestDescription)) {
			return self.eContainer.getTestDescription();
		} else {
			return self.eContainer;
		}
	} 
}

operation Any getTestObjective() : Any {
	if (self.eContainer.isDefined()) {
		if (not self.eContainer.isKindOf(StructuredTestObjective)) {
			return self.eContainer.getTestObjective();
		} else {
			return self.eContainer;
		}
	} 
}

operation Any contains(object : Any) : Boolean {
	return self.eAllContents().includes(object);
}

operation TDL!StructuredTestObjective indexOf(object : Any) : Integer {
	return self.eAllContents().indexOf(object);
}


//QualifiedNames
operation Any qualifiedName() : String {
	var p = self.getNamedParent();
	if (p.isDefined()) {
		var suffix = "";
		if (self.name.isDefined()) {
			suffix = "::"+self.name;
		}
		return p.qualifiedName()+suffix;
	} else {
		return self.name;
	}
}

operation Any getNamedParent() : Any {
	if (self.eContainer.isDefined()) {
		if (not self.eContainer.isKindOf(NamedElement)) {
			return self.eContainer.getNamedParent();
		} else {
			return self.eContainer;
		}
	}
}


//Get Data Type
//TODO: add support for reduction..
//TODO: check for reduction too once support is added
operation TDL!DataInstanceUse getDataType() : Any {
	if (not self.dataInstance.oclIsUndefined()) {
		return self.dataInstance.dataType;
	} else if (not self.dataType.oclIsUndefined()) {
		return self.dataType;
	} else {
		if (self.container().oclIsTypeOf(MemberAssignment)) {
			return self.container().member.dataType;
		} else if (self.container().oclIsTypeOf(ParameterBinding)) {
			return self.container().parameter.dataType;
		} else {
			(self.container().type.name+".getDataType() not yet defined").println();
		}
	}
}

operation TDL!DataElementUse getDataType() : Any {
	if (not self.dataElement.oclIsUndefined()) {
		//TODO: consider extracting into resolveDataType or similar
		//TODO: consider using the resolveDataType from the model?
		//("Start: "+self.dataElement).println();
		if (self.dataElement.oclIsKindOf(DataInstance)) {
			return self.dataElement.dataType;
		} else if (self.dataElement.oclIsKindOf(DataType)) {
			return self.dataElement;
		} else if (self.dataElement.oclIsKindOf(Function)) {
			return self.dataElement.returnType; 
		} else if (self.dataElement.oclIsKindOf(FormalParameter)) {
			return self.dataElement.dataType; 
		} else {
			//why is this called on AnnotationType?!
			(self.dataElement+".getDataType() not yet defined").println();
//			(self.dataElement.type+".getDataType() not yet defined").println();
//			(self.dataElement.type.name+".getDataType() not yet defined").println();
		}
		//TODO: extract
	} else {
		if (self.container().oclIsTypeOf(MemberAssignment)) {
			return self.container().member.dataType;
		} else if (self.container().oclIsTypeOf(ParameterBinding)) {
			return self.container().parameter.dataType;
		} else {
			(self.container().type.name+".getDataType() not yet defined").println();
		}
	}
}


operation TDL!FunctionCall getDataType() : Any {
	return self.`function`.returnType;
}

operation TDL!PredefinedFunctionCall getDataType() : Any {
	return self.`function`.returnType;
}


operation TDL!VariableUse getDataType() : Any {
	return self.variable.dataType;
}

operation TDL!TimeLabelUse getDataType() : Any {
	return Time.allInstances().first();
}

operation TDL!FormalParameterUse getDataType() : Any {
	return self.parameter.dataType;
}

operation TDL!SpecialValueUse getDataType() : Any {
	var t;
	if (self.eContainer.isTypeOf(MemberAssignment)) {
		t = self.eContainer.member.dataType;
	} else if (self.eContainer.isTypeOf(ParameterBinding)) {
		t = self.eContainer.parameter.dataType;
	} else if (self.eContainer.isTypeOf(Interaction) and 
				self.isTypeOf(AnyValue)) {
		t = self.dataType;
	}
	return t;
}

//Check if Behaviour is tester input event
//TODO: this should be handled already in Ecore/OCL -> rename and test, then remove!
operation TDL!Behaviour isTesterInputEvent() : Boolean {
    return	self.oclIsKindOf(TimeOut)
       	 or	self.oclIsKindOf(Quiescence)
   		or (self.oclIsKindOf(Interaction)
  		and self.sourceGate.component.role.name = 'SUT'
   		and self.target->exists(t | t.targetGate.component.role.name = 'Tester'));
}

//TODO: migrate to tdl.ecore?
operation TDL!TestConfiguration compatibleWith(tc:TDL!TestConfiguration, cb:Set) : Boolean {
    //TODO:copy and adapt from previous draft, add connections
    //self: configuration of invoked test description / TConf2
    //tc: configuration of invoking test description / TConf1
    //formalComponent: component in invoked test description
    //actualComponent: component in invoking test description
    
    if (
        //same test configuration shortcut? bindings necessary
        //(self = tc and cb->isEmpty()) or
        //valid bindings exist (validity deferred to binding constraints) 
        (self.componentInstance->forAll(c | cb.exists(b | b.formalComponent = c)) and
        self.connection->forAll(c | c.hasMatchingConnection(tc, cb))
        )
    ) {
        return true;
    }
    return false;
}

//helper
//TODO: move to tdl.ecore in case as well
//TODO: assumes binding exists, handle same?
operation TDL!ComponentInstance getActualComponent(cb: Set) : TDL!ComponentInstance {
    return cb->select(b | b.formalComponent = self)->first().actualComponent;
}

//helper
//TODO: move to tdl.ecore in case as well?
//TODO: assumes binding exists, handle same?
//operation TDL!GateInstance getActualGate(cb: Set) : TDL!GateInstance {
//    return cb->select(b | b.formalComponent = self)->first().actualComponent;
//}

//helper - get candidate matching connections with corresponding components
//TODO: move to tdl.ecore in case as well?
operation TDL!Connection getMatchingConnections(tc: TDL!TestConfiguration, cb: Set) : Set {
    return tc.connection->select(p |
            (p.endPoint->at(0).component = self.endPoint->at(0).component.getActualComponent(cb)
            and p.endPoint->at(1).component = self.endPoint->at(1).component.getActualComponent(cb))
         or (p.endPoint->at(1).component = self.endPoint->at(0).component.getActualComponent(cb)
            and p.endPoint->at(0).component = self.endPoint->at(1).component.getActualComponent(cb))
           );
}

//helper - get matching connections with corresponding components
operation Set perfectMatchExists(c:TDL!Connection) : Boolean {
    return self->exists(m |
                        (m.endPoint->at(0).gate.type = c.endPoint->at(0).gate.type
                     and m.endPoint->at(1).gate.type = c.endPoint->at(1).gate.type)
                     or (m.endPoint->at(0).gate.type = c.endPoint->at(1).gate.type
                     and m.endPoint->at(1).gate.type = c.endPoint->at(0).gate.type)
                 );
}

//helper - get check if there is perfect matching connection
operation TDL!Connection hasMatchingConnection(tc: TDL!TestConfiguration, cb: Set) : Boolean {
    return self.getMatchingConnections(tc, cb).perfectMatchExists(self);
}
