package cz.muni.fi.lasaris.sbms.model;

import java.util.List;

import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.vocabulary.RDF;
import org.apache.log4j.Logger;

import cz.muni.fi.lasaris.sbms.api.entities.DataPoint;
import cz.muni.fi.lasaris.sbms.api.entities.Influence;
import cz.muni.fi.lasaris.sbms.api.request.Insert;

public class ModelUpdater {
	
	final static Logger logger = Logger.getLogger(ModelUpdater.class);
	
	public static boolean insertDataPoint(Insert insert) {
		logger.debug("Inserting data: " + insert);
		
		if(insert == null || insert.getDataPoint() == null || insert.getDataPoint().getBmsId() == null) {
			//throw new IllegalArgumentException("bmsId must not be null");
			return failInsert("bmsId must not be null");
		}
		DataPoint d = insert.getDataPoint();
		
		try {	
			Model m = TdbConnector.getWritableModel();
			
// creates new DP and Observation or retrieves existing
			Resource dp = m.createResource(NS.sbmsd + d.getResourceId());
			Resource obs = m.createResource(NS.sbmsd + d.getResourceId() + "Obs");
			
			if(!TypesProvider.getDataPointTypes().getMembers().contains(d.getType())) {
				//throw new IllegalArgumentException("Unknown datapoint type");
				return failInsert("Unknown datapoint type");
			}

//			?dataPoint rdf:type ?dataPointClass.
//		    ?observation rdf:type sbms:Observation.
			dp.addProperty(RDF.type, m.createResource(NS.sbms + d.getType()));
			obs.addProperty(RDF.type, m.createResource(NS.sbms + "Observation"));
			
//			?dataPoint sbms:hasBMSId ?bmsId.
			Property p = null;
			p = m.createProperty(NS.sbms + "hasBMSId");
			if(!dp.hasProperty(p)) {
				dp.addLiteral(p, d.getBmsId());
			}
//			?dataPoint sbms:expressesObservation ?observation.
			p = m.createProperty(NS.sbms + "expressesObservation");
			if(!dp.hasProperty(p)) {
				dp.addProperty(p, obs);
			}
			
			if(d.getProperty() != null && d.getProperty().getDomain() != null 
					&& d.getProperty().getQuality() != null 
					&& d.getScope() != null && d.getScope().getBimId() != null) {
				if(!TypesProvider.getQualityTypes().getMembers().contains(d.getProperty().getQuality())) {
					//throw new IllegalArgumentException("Unknown quality type");
					return failInsert("Unknown quality type");
				}
				if(!TypesProvider.getPropDomainTypes().getMembers().contains(d.getProperty().getDomain())) {
					//throw new IllegalArgumentException("Unknown property type");
					return failInsert("Unknown property domain type");
				}
				
//			    ?property rdf:type sbms:ObservedProperty.							
				Resource op = m.createResource(NS.sbmsd + d.getScope().getResourceId() 
						+ d.getProperty().getDomain() 
						+ d.getProperty().getQuality() + "ObsProp");
				op.addProperty(RDF.type, NS.sbms + m.createResource(NS.sbms + "ObservedProperty"));
				
				
//				?observation sbms:observedProperty ?property.				
				p = m.createProperty(NS.sbms + "observedProperty");
				obs.addProperty(p, op);
				
				
//				?property sbms:hasPhysicalQuality ?quality.
				op.addProperty(m.createProperty(NS.sbms + "hasPhysicalQuality"), 
						m.createResource(NS.ucum + d.getProperty().getQuality()));
//				?property sbms:hasPropertyDomain ?propDomain.
				op.addProperty(m.createProperty(NS.sbms + "hasPropertyDomain"), 
						m.createResource(NS.sbms + d.getProperty().getDomain()));
//				?property sbms:isPropertyOf ?scope.				
				op.addProperty(m.createProperty(NS.sbms + "isPropertyOf"),
						m.createResource(NS.sbmsd +d.getScope().getBimId()));
			}
			
			if(d.getSource() != null && d.getSource().getBimId() !=null) {
				if(!checkBIMId(d.getSource().getBimId())) {
					return failInsert("Source: unknown BIM id");
				}
//				?observation sbms:observedBy ?source.
				obs.addProperty(m.createProperty(NS.sbms + "observedBy"), 
						m.createResource(NS.sbmsd + d.getSource().getBimId()));
			}
			if(d.getScope() != null && d.getScope().getBimId() != null) {
				if(!checkBIMId(d.getScope().getBimId())) {
					return failInsert("Scope: unknown BIM id");
				}
//				?observation sbms:featureOfInterest ?scope. 
				obs.addProperty(m.createProperty(NS.sbms + "featureOfInterest"), 
						m.createResource(NS.sbmsd + d.getScope().getBimId()));
			}
			
			if(d.getSensing() != null && d.getSensing().getType() != null) {
				if(!TypesProvider.getAllSensingTypes().getMembers().contains(d.getSensing().getType())) {
					//throw new IllegalArgumentException("Unknown sensing type");
					return failInsert("Unknown sensing type");
				}	
				Resource sensing = m.createResource(NS.sbmsd + d.getResourceId() + "Sensing");
//				?sensing rdf:type ?sensingClass.
				sensing.addProperty(RDF.type, m.createResource(NS.sbms + d.getSensing().getType()));
//				?observation sbms:sensingMethodUsed ?sensing.
				obs.addProperty(m.createProperty(NS.sbms + "sensingMethodUsed"), 
					sensing);

				if(TypesProvider.getStatefulSensingTypes().getMembers().contains(d.getSensing().getType())
						&& d.getSensing().getWindow() == null ) {
					//throw new IllegalArgumentException("Time window must be set for this sensing type");
					return failInsert("Time window must be set for this sensing type");
				}
				
//				?sensing sbms:hasAggregationTimeWindow ?timeWindow.
				if(d.getSensing() != null && d.getSensing().getWindow() != null) {
					if(!TypesProvider.getTimeWindowTypes().getMembers().contains(d.getSensing().getWindow())) {
						//throw new IllegalArgumentException("Unknown time window type");
						return failInsert("Unknown time window type");
					}
					if(!TypesProvider.getStatefulSensingTypes().getMembers().contains(d.getSensing().getType())) {
						//throw new IllegalArgumentException("No time window for this sensing type");
						return failInsert("No time window for this sensing type");
					}
					sensing.addProperty(m.createProperty(NS.sbms + "hasAggregationTimeWindow"), 
							m.createResource(NS.sbms + d.getSensing().getWindow()));
				}
			}
			
//			?dataPoint sbms:influences ?property.			
			if(d.getInfluenceList() != null) {
				for(Influence inf : d.getInfluenceList()) {
					if(inf.getProperty() != null 
							&& inf.getProperty().getDomain() != null 
							&& inf.getProperty().getQuality() != null && inf.getScope() != null &&
							inf.getScope().getBimId() != null) {

						Resource ip = m.createResource(NS.sbmsd + inf.getScope().getResourceId() 
								+ inf.getProperty().getDomain() 
								+ inf.getProperty().getQuality() + "ObsProp");
						ip.addProperty(RDF.type, NS.sbms + m.createResource(NS.sbms + "ObservedProperty"));


						//				?datapoint sbms:influences ?property.				
						p = m.createProperty(NS.sbms + "influences");
						dp.addProperty(p, ip);

						//				?property sbms:hasPhysicalQuality ?quality.
						ip.addProperty(m.createProperty(NS.sbms + "hasPhysicalQuality"), 
								m.createResource(NS.ucum + inf.getProperty().getQuality()));
						//				?property sbms:hasPropertyDomain ?propDomain.
						ip.addProperty(m.createProperty(NS.sbms + "hasPropertyDomain"), 
								m.createResource(NS.sbms + inf.getProperty().getDomain()));
						//				?property sbms:isPropertyOf ?scope.				
						ip.addProperty(m.createProperty(NS.sbms + "isPropertyOf"),
								m.createResource(NS.sbmsd +inf.getScope().getResourceId()));
					}
				}
			}
			TdbConnector.commit();
			logger.debug("Data commited to the TDB");
			return true;
		} catch (Exception e) {
			return failInsert(e);
		}
		
		
	}
	
	public static boolean insertTrend(Insert insert) {
		// TODO Implement insertTrend
		return false;
	}
	
	public static boolean insertInfluence(Insert insert) {
		// TODO Implement insertInfluence
		return false;
	}
	
	public static boolean insertAddress(Insert insert) {
		// TODO Implement program insert
		return false;
	}
	
	private static boolean failInsert(Object msg) {
		TdbConnector.discardChanges();
		logger.info(msg);
		return false;
	}
	
	public static boolean removeDataPointAssertions(String bmsId) {
		logger.debug("Removing data: " + bmsId);
		
		if(bmsId == null) {
			//throw new IllegalArgumentException("bmsId must not be null");
			return failInsert("bmsId must not be null");
		}
		DataPoint d = new DataPoint(bmsId);
		/*
		DataPointsRequest r = new DataPointsRequest();
		r.setDataPoint(d);
		r.getResponseFields().addAll(DataPointsRequest.FIELDS);
		DataPointsResponse s = QueryParser.getDataPoints(r);
		
		List<DataPoint> l = s.getDataPoints().get(DataPointsRequest.DEFAULT_GROUPING);
		if(l.size() != 0) {
			d = l.get(0);
		}
		*/
		try {	
			Model m = TdbConnector.getWritableModel();
			// creates new DP and Observation or retrieves existing
			Resource dp = m.createResource(NS.sbmsd + d.getResourceId());
			Resource obs = m.createResource(NS.sbmsd + d.getResourceId() + "Obs");
			

//			?dataPoint rdf:type ?dataPointClass.
//			?observation rdf:type sbms:Observation.
			dp.removeAll(RDF.type);
			m.remove(obs, RDF.type, m.createResource(NS.sbms + "Observation"));

//						?dataPoint sbms:hasBMSId ?bmsId.
			Property p = null;
			dp.removeAll(m.createProperty(NS.sbms + "hasBMSId"));
			//						?dataPoint sbms:expressesObservation ?observation.
			dp.removeAll(m.createProperty(NS.sbms + "expressesObservation"));
			
			obs.removeAll(m.createProperty(NS.sbms + "observedProperty"));
			
				/* We don't want to remove property now - it might be used by different observation or influence
				//						    ?property rdf:type sbms:ObservedProperty.							
				Resource op = m.createResource(NS.sbmsd + d.getScope().getBimId() 
						+ d.getProperty().getDomain() 
						+ d.getProperty().getQuality() + "ObsProp");
				
				m.remove(op, RDF.type, m.createResource(NS.sbms + "ObservedProperty"));
				
				//							?observation sbms:observedProperty ?property.				
				


				//							?property sbms:hasPhysicalQuality ?quality.
				m.remove(op, m.createProperty(NS.sbms + "hasPhysicalQuality"), 
						m.createResource(NS.ucum + d.getProperty().getQuality()));
				//							?property sbms:hasPropertyDomain ?propDomain.
				m.remove(op, m.createProperty(NS.sbms + "hasPropertyDomain"), 
						m.createResource(NS.sbms + d.getProperty().getDomain()));
				//							?property sbms:isPropertyOf ?scope.				
				m.remove(op, m.createProperty(NS.sbms + "isPropertyOf"),
						m.createResource(NS.sbmsd +d.getScope().getBimId()));
			
				*/
			
				//							?observation sbms:observedBy ?source.
				
				obs.removeAll(m.createProperty(NS.sbms + "observedBy"));
		
				//							?observation sbms:featureOfInterest ?scope. 
				obs.removeAll(m.createProperty(NS.sbms + "featureOfInterest"));
			

				Resource sensing = m.createResource(NS.sbmsd + d.getResourceId() + "Sensing");
				//							?sensing rdf:type ?sensingClass.
				sensing.removeAll(RDF.type);
				//							?observation sbms:sensingMethodUsed ?sensing.
				obs.removeAll(m.createProperty(NS.sbms + "sensingMethodUsed"));

				//							?sensing sbms:hasAggregationTimeWindow ?timeWindow.
				sensing.removeAll(m.createProperty(NS.sbms + "hasAggregationTimeWindow"));
			

			//						?dataPoint sbms:influences ?property.			
/*
				Resource ip = m.createResource(NS.sbmsd + d.getInfluence().getScope().getBimId() 
						+ d.getInfluence().getProperty().getDomain() 
						+ d.getInfluence().getProperty().getQuality() + "ObsProp");
				
				m.remove(ip, RDF.type, NS.sbms + m.createResource(NS.sbms + "ObservedProperty"));
*/

				//							?datapoint sbms:influences ?property.				
				p = m.createProperty(NS.sbms + "influences");
				dp.removeAll(p);
/*
				//							?property sbms:hasPhysicalQuality ?quality.
				ip.addProperty(m.createProperty(NS.sbms + "hasPhysicalQuality"), 
						m.createResource(NS.ucum + d.getInfluence().getProperty().getQuality()));
				//							?property sbms:hasPropertyDomain ?propDomain.
				ip.addProperty(m.createProperty(NS.sbms + "hasPropertyDomain"), 
						m.createResource(NS.sbms + d.getInfluence().getProperty().getDomain()));
				//							?property sbms:isPropertyOf ?scope.				
				ip.addProperty(m.createProperty(NS.sbms + "isPropertyOf"),
						m.createResource(NS.sbmsd +d.getInfluence().getScope().getBimId()));
*/
				
			TdbConnector.commit();
			
			cleanup();
			
			logger.debug("Changes commited to the TDB");
			return true;
		} catch (Exception e) {
			return failInsert(e);
		}
	}
	
	public static void cleanup() {
		List<RDFNode> o = GetUnusedObservations();
		List<RDFNode> p = GetUnusedProperties();
		List<RDFNode> s = GetUnusedSensings();
		
		Model m = TdbConnector.getWritableModel();
		
		for(RDFNode n : o) {
			n.asResource().removeAll(m.createProperty(NS.sbms + "observedBy"));
			n.asResource().removeAll(m.createProperty(NS.sbms + "featureOfInterest"));
			n.asResource().removeAll(m.createProperty(NS.sbms + "sensingMethodUsed"));
			n.asResource().removeAll(m.createProperty(NS.sbms + "observedProperty"));
		}
		
		for(RDFNode n : p) {
			n.asResource().removeAll(RDF.type);
			n.asResource().removeAll(m.createProperty(NS.sbms + "hasPhysicalQuality"));
			n.asResource().removeAll(m.createProperty(NS.sbms + "hasPropertyDomain"));
			n.asResource().removeAll(m.createProperty(NS.sbms + "isPropertyOf"));
		}
		
		for(RDFNode n : s) {
			n.asResource().removeAll(RDF.type);
			n.asResource().removeAll(m.createProperty(NS.sbms + "hasAggregationTimeWindow"));
		}
		
		TdbConnector.commit();
	}
	
	private static List<RDFNode> GetUnusedSensings() {
		/*String query = String.format("SELECT ?member WHERE {?member <%stype> ?type. ?type <%ssubClassOf> <%sSensing>."
				+" FILTER (not exists { ?x <%ssensingMethodUsed> ?member. }).}", NS.rdf, NS.rdfs, NS.sbms, NS.sbms);*/
		String query = String.format("SELECT ?member WHERE {?member <%stype> <%sSensing>."
				+" FILTER (not exists { ?x <%ssensingMethodUsed> ?member. }).}", NS.rdf, NS.sbms, NS.sbms);
		return TdbConnector.executeSPARQLList(query);
		
	}

	private static List<RDFNode> GetUnusedProperties() {
		String query = String.format("SELECT ?member WHERE { ?member <%stype> <%sObservedProperty>."
				+ " FILTER (not exists { ?x <%sobservedProperty> ?member. }).}", NS.rdf, NS.sbms, NS.sbms);
		return TdbConnector.executeSPARQLList(query);
			
	}

	private static List<RDFNode> GetUnusedObservations() {
		String query = String.format("SELECT ?member WHERE { ?member <%stype> <%sObservation>."
				+ " FILTER (not exists { ?x <%sexpressesObservation> ?member. }).}", NS.rdf, NS.sbms, NS.sbms);
		return TdbConnector.executeSPARQLList(query);
		
	}

	private static boolean checkBIMId(String bimId) {
		logger.debug("Checking bimId: " + bimId);
		if( TdbConnector.executeSPARQLList(
				String.format("SELECT * { ?object <%shasBIMId> \"%s\"}", NS.sbim, bimId)).size() > 0) {
			logger.debug("Check succeeded");
			return true;
		} else {
			logger.debug("Check failed");
			return false;
		}
	}
}
