package cz.muni.fi.lasaris.sbms.semantics.logic;

import java.util.ArrayList;
import java.util.List;

import org.apache.jena.graph.NodeFactory;
import org.apache.jena.graph.Triple;
import org.apache.jena.sparql.core.Var;
import org.apache.jena.sparql.expr.E_LogicalAnd;
import org.apache.jena.sparql.expr.E_NotEquals;
import org.apache.jena.sparql.expr.E_NotExists;
import org.apache.jena.sparql.expr.ExprVar;
import org.apache.jena.sparql.expr.nodevalue.NodeValueNode;
import org.apache.jena.sparql.syntax.ElementFilter;
import org.apache.jena.sparql.syntax.ElementGroup;
import org.apache.jena.sparql.syntax.ElementOptional;
import org.apache.jena.sparql.syntax.ElementTriplesBlock;

import cz.muni.fi.lasaris.sbms.semantics.api.entities.Address;
import cz.muni.fi.lasaris.sbms.semantics.api.entities.Algorithm;
import cz.muni.fi.lasaris.sbms.semantics.api.entities.DataPoint;
import cz.muni.fi.lasaris.sbms.semantics.api.request.AddressRequest;
import cz.muni.fi.lasaris.sbms.semantics.api.request.AlgorithmRequest;
import cz.muni.fi.lasaris.sbms.semantics.api.request.DataPointsRequest;
import cz.muni.fi.lasaris.sbms.semantics.api.request.GroupableRequest;
import cz.muni.fi.lasaris.sbms.semantics.api.request.TrendsRequest;

public class BodyBuilders {
	
	private static class TypeClauseContainer {
		public String f1;
		public String f2;
		public String f3;
		public String f4;
		
		public TypeClauseContainer(String f1, String f2, String f3, String f4) {
			this.f1 = f1;
			this.f2 = f2;
			this.f3 = f3;
			this.f4 = f4;
		}
	}
	
	public static ElementGroup getQueryBody(DataPointsRequest request) {
		return getQueryBody(request.getDataPoint(), request);
	}
	
	private static ElementGroup getQueryBody(DataPoint dp, GroupableRequest request) {
		ElementGroup body = new ElementGroup();
		
		// https://gregheartsfield.com/2012/08/26/jena-arq-query-performance.html
		
		// type clauses moved to the end of the query as it significantly decreases performance
		List<TypeClauseContainer> tcc = new ArrayList<TypeClauseContainer>(10);
		
		List<String> fields = request.getResponseFields();

		body.addTriplePattern(Triple.create(Var.alloc("datapoint"), 
				NodeFactory.createURI(NS.sbms + "hasBMSId"),
				Var.alloc(gsv("bmsId"))));
		
		// moved at the and, as it is the most expensive query part
				if(fields.contains("type")) {
					//setTypeClause(body, "datapoint", gsv("type"), NS.sbms + "DataPoint", NS.sbms + "DataPoint");
					tcc.add(new TypeClauseContainer("datapoint", gsv("type"), NS.sbms + "DataPoint", NS.sbms + "DataPoint"));
				}
				if(dp.getType() != null) {
					
//				body.addTriplePattern(Triple.create(Var.alloc("datapoint"), 
//					NodeFactory.createURI(NS.rdf + "type"),
//					NodeFactory.createURI(NS.sbms + dp.getType())));
//					
					tcc.add(new TypeClauseContainer(null, null, "datapoint", NS.sbms + dp.getType()));
				}
		
		body.addTriplePattern(Triple.create(Var.alloc("datapoint"), 
				NodeFactory.createURI(NS.sbms + "expressesObservation"),
				Var.alloc("obs")));

		if(dp.getBmsId() != null) {
			body.addTriplePattern(Triple.create(Var.alloc("datapoint"), 
					NodeFactory.createURI(NS.sbms + "hasBMSId"), 
					NodeFactory.createLiteral(dp.getBmsId()))
					);
		}
		
		if(fields.contains("source.bimId") 
				|| fields.contains("source.type") 
				|| fields.contains("source.location")
				|| request.getGrouping(0).equals("source")
				|| dp.getSource() != null) {
			body.addTriplePattern(Triple.create(Var.alloc("obs"), 
					NodeFactory.createURI(NS.sbms + "observedBy"),
					Var.alloc("source")));

			if(dp.getSource() != null && dp.getSource().getBimId() != null) {
				body.addTriplePattern(Triple.create(Var.alloc("source"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						NodeFactory.createLiteral(dp.getSource().getBimId())));
			}

			if(fields.contains("source.bimId")) {
				body.addTriplePattern(Triple.create(Var.alloc("source"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						Var.alloc(gsv("source.bimId"))));
			}


			if(dp.getSource() != null && dp.getSource().getType() != null) {
				
//				body.addTriplePattern(Triple.create(Var.alloc("source"), 
//						NodeFactory.createURI(NS.rdf + "type"),
//						NodeFactory.createURI(NS.sbim + dp.getSource().getType())));
//				
				tcc.add(new TypeClauseContainer(null, null, "source", NS.sbim + dp.getSource().getType()));
			}

			if(fields.contains("source.type")) {
//				setTypeClause(body, "source", gsv("source.type"), NS.sbim + "Device", NS.sbms + "SensingDevice");
				tcc.add(new TypeClauseContainer("source", gsv("source.type"), NS.sbim + "Device", NS.sbms + "SensingDevice"));
			}

			if((dp.getSource() != null && dp.getSource().getLocation() != null)) {	
				body.addTriplePattern(Triple.create(Var.alloc("source"), 
						//NodeFactory.createURI(NS.sbim + "hasInstallationInRoom"),
						NodeFactory.createURI(NS.sbim + "isPartOf"),
						Var.alloc("sourcelocationR")));

				body.addTriplePattern(Triple.create(Var.alloc("sourcelocationR"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						NodeFactory.createLiteral(dp.getSource().getLocation())));
			}

			if(fields.contains("source.location")) {
				body.addTriplePattern(Triple.create(Var.alloc("source"), 
						NodeFactory.createURI(NS.sbim + "hasInstallationInRoom"),
						//NodeFactory.createURI(NS.dul + "isPartOf"),
						Var.alloc("sourcelocationF")));

				body.addTriplePattern(Triple.create(Var.alloc("sourcelocationF"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						Var.alloc(gsv("source.location"))));
			}

		}

		// SCOPE

		if(fields.contains("scope.bimId") 
				|| fields.contains("scope.type") 
				|| fields.contains("scope.location")
				|| request.getGrouping(0).equals("scope")
				|| dp.getScope() != null) {

			body.addTriplePattern(Triple.create(Var.alloc("obs"), 
					NodeFactory.createURI(NS.sbms + "featureOfInterest"),
					Var.alloc("scope")));

			if(dp.getScope() != null && dp.getScope().getBimId() != null) {
				body.addTriplePattern(Triple.create(Var.alloc("scope"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						NodeFactory.createLiteral(dp.getScope().getBimId())));
			}

			if(fields.contains("scope.bimId")) {
				body.addTriplePattern(Triple.create(Var.alloc("scope"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						Var.alloc(gsv("scope.bimId"))));
			}


			if(dp.getScope() != null && dp.getScope().getType() != null) {
				
//				body.addTriplePattern(Triple.create(Var.alloc("scope"), 
//						NodeFactory.createURI(NS.rdf + "type"),
//						NodeFactory.createURI(NS.sbim + dp.getScope().getType())));
				
				tcc.add(new TypeClauseContainer(null, null, "scope", NS.sbim + dp.getScope().getType()));
			}

			if(fields.contains("scope.type")) {
//				setTypeClause(body, "scope", gsv("scope.type"), NS.sbim + "Facility", NS.sbms + "FeatureOfInterest");
				tcc.add(new TypeClauseContainer("scope", gsv("scope.type"), NS.sbim + "Facility", NS.sbim + "Facility")); 
			}

			// scopeLoc

			if((dp.getScope() != null && dp.getScope().getLocation() != null)) {
				body.addTriplePattern(Triple.create(Var.alloc("scope"),
						NodeFactory.createURI(NS.sbim + "isPartOf"),
						Var.alloc("scopeLocR")
						));
				body.addTriplePattern(Triple.create(Var.alloc("scopeLocR"),
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						NodeFactory.createLiteral(dp.getScope().getLocation())
						));
			}

			if(fields.contains("scope.location")) {
				setOptionalClause(body, new String[] {"scope", "scopeLocF"}, 
						new String[] {NS.sbim + "hasInstallationLocation", NS.sbim + "hasBIMId"}, 
						new String[] {"scopeLocF", gsv("scope.location")});
			}

		}

		if(fields.contains("sensing.type") || fields.contains("sensing.window") || dp.getSensing() != null) {
			body.addTriplePattern(Triple.create(Var.alloc("obs"), 
					NodeFactory.createURI(NS.sbms + "sensingMethodUsed"),
					Var.alloc("sensing")));

			if(dp.getSensing() != null && dp.getSensing().getType() != null) {
//				body.addTriplePattern(Triple.create(Var.alloc("sensing"), 
//						NodeFactory.createURI(NS.rdf + "type"),
//						NodeFactory.createURI(NS.sbms + dp.getSensing().getType())));
				tcc.add(new TypeClauseContainer(null, null, "sensing", NS.sbms + dp.getSensing().getType()));
			}

			if(dp.getSensing() != null && dp.getSensing().getWindow() != null) {
				
				body.addTriplePattern(Triple.create(Var.alloc("sensing"), 
						NodeFactory.createURI(NS.sbms + "hasAggregationTimeWindow"),
						NodeFactory.createURI(NS.sbms + dp.getSensing().getWindow())));
			}

			if(fields.contains("sensing.type")) {
//				setTypeClause(body, "sensing", gsv("sensing.type"), NS.sbms + "Sensing", NS.sbms + "Sensing");
				tcc.add(new TypeClauseContainer("sensing", gsv("sensing.type"), NS.sbms + "Sensing", NS.sbms + "Sensing"));
			}

			if(fields.contains("sensing.window")) {
				setOptionalClause(body, "sensing", NS.sbms + "hasAggregationTimeWindow", gsv("sensing.window"));
			}

		}

		if(fields.contains("property.quality") || fields.contains("property.domain") || dp.getProperty() != null) {
			body.addTriplePattern(Triple.create(Var.alloc("obs"), 
					NodeFactory.createURI(NS.sbms + "observedProperty"),
					Var.alloc("property")));

			if(dp.getProperty() != null && dp.getProperty().getQuality() != null) {
				body.addTriplePattern(Triple.create(Var.alloc("property"), 
						NodeFactory.createURI(NS.sbms + "hasPhysicalQuality"),
						NodeFactory.createURI(NS.ucum + dp.getProperty().getQuality())));
			}

			if(dp.getProperty() != null && dp.getProperty().getDomain() != null) {
				body.addTriplePattern(Triple.create(Var.alloc("property"), 
						NodeFactory.createURI(NS.sbms + "hasPropertyDomain"),
						NodeFactory.createURI(NS.sbms + dp.getProperty().getDomain())));
			}

			if(fields.contains("property.quality")) {
				body.addTriplePattern(Triple.create(Var.alloc("property"), 
						NodeFactory.createURI(NS.sbms + "hasPhysicalQuality"),
						Var.alloc(gsv("property.quality"))));
			}

			if(fields.contains("property.domain")) {
				body.addTriplePattern(Triple.create(Var.alloc("property"), 
						NodeFactory.createURI(NS.sbms + "hasPropertyDomain"),
						Var.alloc(gsv("property.domain"))));
			}
		}
		
		
		// PublishedBy
		if(fields.contains("publisher.bimId") || dp.getPublisher() != null) {
			body.addTriplePattern(Triple.create(Var.alloc("datapoint"), 
				NodeFactory.createURI(NS.sbms + "publishedByDevice"),
				Var.alloc("publisher")));
			
			if(fields.contains("publisher.bimId")) {
			body.addTriplePattern(Triple.create(Var.alloc("publisher"), 
				NodeFactory.createURI(NS.sbim + "hasBIMId"),
				Var.alloc(gsv("publisher.bimId"))));
			}
			
			if(dp.getPublisher() != null && dp.getPublisher().getBimId() != null) {
					body.addTriplePattern(Triple.create(Var.alloc("publisher"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						NodeFactory.createLiteral(dp.getPublisher().getBimId())));
					
			}
		}
		
		// INFLUENCING
		if(dp.getInfluenceList() != null) {
			body.addTriplePattern(Triple.create(Var.alloc("datapoint"), 
					NodeFactory.createURI(NS.sbms + "influences"),
					Var.alloc(gsv("influenced.property"))));

			if((dp.getInfluenced().getScope() != null && dp.getInfluenced().getScope().getBimId() != null)) {
				body.addTriplePattern(Triple.create(Var.alloc(gsv("influenced.scope")), 
						NodeFactory.createURI(NS.sbms + "hasProperty"),
						Var.alloc(gsv("influenced.property"))));
				if(dp.getInfluenced().getScope() != null && dp.getInfluenced().getScope().getBimId() != null) {
					body.addTriplePattern(Triple.create(Var.alloc(gsv("influenced.scope")), 
							NodeFactory.createURI(NS.sbim + "hasBIMId"),
							NodeFactory.createLiteral(dp.getInfluenced().getScope().getBimId())));
				}

			}

			if(dp.getInfluenced().getProperty() != null) {
				if(dp.getInfluenced().getProperty().getDomain() != null) {
					body.addTriplePattern(Triple.create(Var.alloc(gsv("influenced.property")), 
							NodeFactory.createURI(NS.sbms + "hasPropertyDomain"),
							NodeFactory.createURI(NS.sbms + dp.getInfluenced().getProperty().getDomain())));
				}

				if(dp.getInfluenced().getProperty().getQuality() != null) {
					body.addTriplePattern(Triple.create(Var.alloc("influenced.property"), 
							NodeFactory.createURI(NS.sbms + "hasPhysicalQuality"),
							NodeFactory.createURI(NS.ucum + dp.getInfluenced().getProperty().getQuality())));
				}
			}

		}
		
		// GROUPING
		if(request.getGrouping() != null && Fields.GROUPINGS.contains(request.getGrouping())) {
			switch(request.getGrouping()) {
			case "dataPoint.type":
				//setTypeClause(body, "datapoint", "group", NS.sbms + "DataPoint", NS.sbms + "DataPoint");
				tcc.add(new TypeClauseContainer("datapoint", "group", NS.sbms + "DataPoint", NS.sbms + "DataPoint"));
				break;
			case "source.type":
				//setTypeClause(body, "source", "group", NS.sbim + "Device", NS.sbms + "SensingDevice");
				tcc.add(new TypeClauseContainer("source", "group", NS.sbim + "Device", NS.sbms + "SensingDevice"));
				break;
			case "scope.type":
				//setTypeClause(body, "scope", "group", NS.dul + "PhysicalObject", NS.sbms + "Scope");
				//setTypeClause(body, "scope", "group", NS.sbim + "Facility", NS.sbms + "FeatureOfInterest");
				tcc.add(new TypeClauseContainer("scope", "group", NS.sbim + "Facility", NS.sbms + "FeatureOfInterest"));
				break;
			case "scope.room":
			case "scope.floor":
			case "scope.building":
			case "scope.site":
			case "source.room":
			case "source.floor":
			case "source.building":
			case "source.site":
				// source/scope isPartOf group
				body.addTriplePattern(Triple.create(Var.alloc(request.getGrouping(0)), 
						NodeFactory.createURI(NS.sbim + "isPartOf"),
						Var.alloc("groupR")));
				// group rdf:type grouping
//				body.addTriplePattern(Triple.create(Var.alloc("groupR"), 
//						NodeFactory.createURI(NS.rdf + "type"),
//						NodeFactory.createURI(NS.sbim + request.getGrouping(1))));
				tcc.add(new TypeClauseContainer(null, null, "groupR", NS.sbim + request.getGrouping(1)));
				
				body.addTriplePattern(Triple.create(Var.alloc("groupR"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						Var.alloc("group")));
				break;
			case Fields.DEFAULT_GROUPING:
			default:
				break;
			}

		}
		
		// optimization - placing type clauses at the end
		// better performance is reached when the datapoint type is the last - thus adding from the end
		for(int i = 0; i < tcc.size(); i++) {
			TypeClauseContainer c = tcc.get(tcc.size() - i - 1);
			if(c.f1 == null) {
				body.addTriplePattern(Triple.create(Var.alloc(c.f3), 
					NodeFactory.createURI(NS.rdf + "type"),
					NodeFactory.createURI(c.f4)));
			} else {
				setTypeClause(body, c.f1, c.f2, c.f3, c.f4);
			}
		}
		
		return body;
	}
	
	public static ElementGroup getQueryBody(AddressRequest r) {

		ElementGroup body = new ElementGroup();
		Address a = r.getAddress();
		List<String> fields = r.getResponseFields();

		body.addTriplePattern(Triple.create(Var.alloc("address"), 
				NodeFactory.createURI(NS.sbms + "hasBMSId"),
				Var.alloc(gsv("bmsId"))));

		if(a.getBmsId() != null) {
			body.addTriplePattern(Triple.create(Var.alloc("address"), 
					NodeFactory.createURI(NS.sbms + "hasBMSId"),
					NodeFactory.createLiteral(a.getBmsId())));
		}
		

		if(a.getPublisher() != null || fields.contains("publisher.bimId")) {
			body.addTriplePattern(Triple.create(Var.alloc("address"), 
					NodeFactory.createURI(NS.sbms + "publishedByDevice"),
					Var.alloc("publisher")));

			if(fields.contains("publisher.bimId")) {
				body.addTriplePattern(Triple.create(Var.alloc("publisher"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						Var.alloc(gsv("publisher.bimId"))));
			}

			if(a.getPublisher() != null && a.getPublisher().getBimId() != null) {
				body.addTriplePattern(Triple.create(Var.alloc("publisher"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						NodeFactory.createLiteral(a.getPublisher().getBimId())));
			}

		}

		if(a.getInfluenced() != null 
				|| fields.contains("influenced.property.quality")
				|| fields.contains("influenced.property.domain")
				|| fields.contains("influenced.scope.bimId")
				|| fields.contains("influenced.scope.type")
				) {
			body.addTriplePattern(Triple.create(Var.alloc("address"), 
					NodeFactory.createURI(NS.sbms + "influences"),
					Var.alloc("influencedproperty")));

			body.addTriplePattern(Triple.create(Var.alloc("influencedscope"), 
					NodeFactory.createURI(NS.sbms + "hasProperty"),
					Var.alloc("influencedproperty")));

			if(a.getInfluenced() != null 
					&& a.getInfluenced().getScope() != null 
					&& a.getInfluenced().getScope().getBimId() != null) {
				body.addTriplePattern(Triple.create(Var.alloc("influencedscope"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						NodeFactory.createLiteral(a.getInfluenced().getScope().getBimId())));
			}
			if(fields.contains("influenced.scope.bimId")) {
				body.addTriplePattern(Triple.create(Var.alloc("influencedscope"), 
						NodeFactory.createURI(NS.sbim + "hasBIMId"),
						Var.alloc(gsv("influenced.scope.bimId"))));
			}

			if(fields.contains("influenced.scope.type")) {
				//setTypeClause(body, "influencedscope", gsv("influenced.scope.type"), NS.dul + "PhysicalObject", NS.sbms + "Scope");
				setTypeClause(body, "influencedscope", gsv("influenced.scope.type"), NS.sbim + "Facility", NS.sbms + "FeatureOfInterest");
			}

			if(a.getInfluenced() != null 
					&& a.getInfluenced().getProperty() != null 
					&& a.getInfluenced().getProperty().getDomain() != null &&
					a.getInfluenced().getProperty().getQuality() != null) {
				body.addTriplePattern(Triple.create(Var.alloc("influencedproperty"), 
						NodeFactory.createURI(NS.sbms + "hasPropertyDomain"),
						NodeFactory.createURI(NS.ucum + a.getInfluenced().getProperty().getQuality())));

				body.addTriplePattern(Triple.create(Var.alloc("influencedproperty"), 
						NodeFactory.createURI(NS.sbms + "hasPhysicalQuality"),
						NodeFactory.createURI(NS.sbms + a.getInfluenced().getProperty().getDomain())));

			}

			if(fields.contains("influenced.property.domain")) { 
				body.addTriplePattern(Triple.create(Var.alloc("influencedproperty"), 
						NodeFactory.createURI(NS.sbms + "hasPropertyDomain"),
						Var.alloc(gsv("influenced.property.domain"))));
			}
			if(fields.contains("influenced.property.quality")) {
				body.addTriplePattern(Triple.create(Var.alloc("influencedproperty"), 
						NodeFactory.createURI(NS.sbms + "hasPhysicalQuality"),
						Var.alloc(gsv("influenced.property.quality"))));
			}
		}
		
		if(fields.contains("type")) {
			setTypeClause(body, "address", gsv("type"), NS.sbms + "Address", NS.sbms + "X");
		}
		
		return body;
	}
	
	public static ElementGroup getQueryBody(AlgorithmRequest request) {
		ElementGroup body = new ElementGroup();
		Algorithm a = request.getAlgorithm();
		List<String> fields = request.getResponseFields();
		
		
		
		body.addTriplePattern(Triple.create(Var.alloc("algorithm"), 
				NodeFactory.createURI(NS.sbms + "hasBMSId"),
				Var.alloc(gsv("bmsId"))));

		if(a.getBmsId() != null) {
			body.addTriplePattern(Triple.create(Var.alloc("algorithm"), 
					NodeFactory.createURI(NS.sbms + "hasBMSId"),
					NodeFactory.createLiteral(a.getBmsId())));
		}

		/*
			if(fields.contains("type")) {
				setTypeClause(body, "algorithm", gsv("type"), NS.sbms + "Algorithm", NS.sbms + "Algorithm");
			}
		 */


		if(a.getUsedAddress() != null || fields.contains("used.bmsId") || fields.contains("used.type")) {
			body.addTriplePattern(Triple.create(Var.alloc("algorithm"), 
					NodeFactory.createURI(NS.sbms + "uses"),
					Var.alloc(gsv("used"))));

			if(a.getUsedAddress() != null &&  a.getUsedAddress().getBmsId() != null) {
				body.addTriplePattern(Triple.create(Var.alloc("used"), 
						NodeFactory.createURI(NS.sbms + "hasBMSId"),
						NodeFactory.createLiteral(a.getUsedAddress().getBmsId())));
			}

			if(fields.contains("used.bmsId")) {

				body.addTriplePattern(Triple.create(Var.alloc("used"), 
						NodeFactory.createURI(NS.sbms + "hasBMSId"),
						Var.alloc(gsv("used.bmsId"))));

			}

			if(fields.contains("used.type")) {
				setTypeClause(body, "used", gsv("type"), NS.sbms + "Address", NS.sbms + "Address");
			}

			if(a.getUsedAddress() != null && a.getUsedAddress().getType() !=null) {
				body.addTriplePattern(Triple.create(Var.alloc("used"), 
						NodeFactory.createURI(NS.rdf + "type"),
						NodeFactory.createURI(NS.sbms + a.getUsedAddress().getType())));
			}
		}

		return body;
	}
	
	public static ElementGroup getQueryBody(TrendsRequest request) {
		DataPointsRequest r = new DataPointsRequest();
		r.setGrouping(request.getGrouping());
		r.setDataPoint(request.getTrend().getDataPoint());
		r.getResponseFields().addAll(request.getResponseFields()); 
		
		ElementGroup body = getQueryBody(r);
		body.addTriplePattern(Triple.create(Var.alloc("trend"),
				NodeFactory.createURI(NS.sbms + "trends"), 
				Var.alloc("datapoint")));
		body.addTriplePattern(Triple.create(Var.alloc("trend"),
				NodeFactory.createURI(NS.sbms + "hasBMSId"), 
				Var.alloc("trendbmsId")));
		
		return body;
	}
	
	private static void setTypeClause(ElementGroup body, String fieldName, String fieldTypeName, 
			String rootClass, String ignoredClass) {
		/* THE WHOLE QUERY:
		?dataPoint rdf:type ?dataPointClass.
		?dataPointClass rdfs:subClassOf sbms:DataPoint.
				  FILTER (not exists { ?subtype rdfs:subClassOf ?dataPointClass.
					 FILTER (?subtype != ?dataPointClass && ?subtype != owl:Nothing) }
					).
		 */
		// ?dataPoint rdf:type ?dataPointClass.
		body.addTriplePattern(Triple.create(Var.alloc(fieldName), 
				NodeFactory.createURI(NS.rdf + "type"),
				Var.alloc(fieldTypeName)));

		// ?dataPointClass rdfs:subClassOf sbms:DataPoint.
		Triple tp = Triple.create(Var.alloc(fieldTypeName), 
				NodeFactory.createURI(NS.rdfs + "subClassOf"),
				NodeFactory.createURI(rootClass));

		body.addTriplePattern(tp);



		// ?subtype rdfs:subClassOf ?dataPointClass.
		// FILTER (?subtype != ?dataPointClass && ?subtype != sbms:DataPoint && ?subtype != owl:Nothing)
		ElementGroup b = new ElementGroup();
		b.addTriplePattern(Triple.create(Var.alloc("subtype"), 
				NodeFactory.createURI(NS.rdfs + "subClassOf"),
				Var.alloc(fieldTypeName)));

		// FILTER (?subtype != ?dataPointClass && ?subtype != sbms:DataPoint && ?subtype != owl:Nothing)
		b.addElement(new ElementFilter(
						new E_LogicalAnd(
								// ?subtype != ?dataPointClass
								new E_NotEquals(new ExprVar("subtype"), new ExprVar(fieldTypeName)),
								// ?subtype != owl:Nothing
								new E_NotEquals(new ExprVar("subtype"), new NodeValueNode(NodeFactory.createURI(NS.owl+"Nothing")))
								)
						)
				);

		//FILTER (?dataPointClass != sbms:DataPoint
		//&& not exists { ?subtype rdfs:subClassOf ?dataPointClass.
		//			 FILTER (?subtype != ?dataPointClass && ?subtype != sbms:DataPoint && ?subtype != owl:Nothing) }
		//		).

		body.addElementFilter(new ElementFilter(
						// not exists { ?subtype rdfs:subClassOf ?dataPointClass.
						// FILTER (?subtype != ?dataPointClass && ?subtype != owl:Nothing) }
						new E_NotExists(b)));
	}
	
	private static void setTypeClauseOld(ElementGroup body, String fieldName, String fieldTypeName, 
			String rootClass, String ignoredClass) {
		
		/* THE WHOLE QUERY:
		?dataPoint rdf:type ?dataPointClass.
		?dataPointClass rdfs:subClassOf sbms:DataPoint.
				  FILTER (?dataPointClass != sbms:DataPoint
					&& not exists { ?subtype rdfs:subClassOf ?dataPointClass.
					 FILTER (?subtype != ?dataPointClass && ?subtype != sbms:DataPoint && ?subtype != owl:Nothing) }
					).
		*/
		
		// ?dataPoint rdf:type ?dataPointClass.
		body.addTriplePattern(Triple.create(Var.alloc(fieldName), 
				NodeFactory.createURI(NS.rdf + "type"),
				Var.alloc(fieldTypeName)));
		
		// ?dataPointClass rdfs:subClassOf sbms:DataPoint.
		Triple tp = Triple.create(Var.alloc(fieldTypeName), 
				NodeFactory.createURI(NS.rdfs + "subClassOf"),
				NodeFactory.createURI(rootClass));

		body.addTriplePattern(tp);


		
		// ?subtype rdfs:subClassOf ?dataPointClass.
		// FILTER (?subtype != ?dataPointClass && ?subtype != sbms:DataPoint && ?subtype != owl:Nothing)
		ElementGroup b = new ElementGroup();
		b.addTriplePattern(Triple.create(Var.alloc("subtype"), 
				NodeFactory.createURI(NS.rdfs + "subClassOf"),
				Var.alloc(fieldTypeName)));
		
		// FILTER (?subtype != ?dataPointClass && ?subtype != sbms:DataPoint && ?subtype != owl:Nothing)
		b.addElement(new ElementFilter(
				new E_LogicalAnd(
						new E_LogicalAnd(
								// ?subtype != ?dataPointClass
								new E_NotEquals(new ExprVar("subtype"), new ExprVar(fieldTypeName)),
								// ?subtype != sbms:DataPoint
								new E_NotEquals(new ExprVar("subtype"), new NodeValueNode(NodeFactory.createURI(ignoredClass)))	
								),
						// ?subtype != owl:Nothing
						new E_NotEquals(new ExprVar("subtype"), new NodeValueNode(NodeFactory.createURI(NS.owl+"Nothing")))
						)
				)
				);
		
		//FILTER (?dataPointClass != sbms:DataPoint
		//&& not exists { ?subtype rdfs:subClassOf ?dataPointClass.
	    //			 FILTER (?subtype != ?dataPointClass && ?subtype != sbms:DataPoint && ?subtype != owl:Nothing) }
		//		).
		
		body.addElementFilter(new ElementFilter(
				new E_LogicalAnd(
						// ?dataPointClass != sbms:DataPoint
						new E_NotEquals(new ExprVar(fieldTypeName), new NodeValueNode(NodeFactory.createURI(ignoredClass))),
						// not exists { ?subtype rdfs:subClassOf ?dataPointClass.
						// FILTER (?subtype != ?dataPointClass && ?subtype != sbms:DataPoint && ?subtype != owl:Nothing) }
						new E_NotExists(b))));
	}
	
	private static void setOptionalClause(ElementGroup body, String subject, String prop, String object) {
		
		setOptionalClause(body, new String[] {subject}, new String[] {prop}, new String[] {object});
	}
	
	private static void setOptionalClause(ElementGroup body, String[] subjects, String[] props, String[] objects) {
		if(!(subjects.length == props.length && props.length == objects.length)) {
			throw new IllegalArgumentException("different lengths of input arrays");
		}
		ElementTriplesBlock tel = new ElementTriplesBlock();
		for(int i = 0; i < subjects.length; i++) {
			tel.addTriple(Triple.create(Var.alloc(subjects[i]), 
				NodeFactory.createURI(props[i]),
				Var.alloc(objects[i])));
		}
		ElementOptional oel = new ElementOptional(tel);
		body.addElement(oel);
	}
	
	private static String gsv(String f) {
		return QueryParser.getSPARQLvar(f);
	}
}
