package ua.be.wee.model.repository;

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

import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.RDFNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import ua.be.wee.model.nodes.Activity;
import ua.be.wee.model.nodes.Artifact;
import ua.be.wee.model.nodes.AutomatedActivity;
import ua.be.wee.model.nodes.ports.ControlInputPort;
import ua.be.wee.model.nodes.ports.ControlOutputPort;
import ua.be.wee.model.pm.PM;
import ua.be.wee.model.pt.EndActivityEvent;
import ua.be.wee.model.pt.EndTraceEvent;
import ua.be.wee.model.pt.Event;
import ua.be.wee.model.pt.PT;
import ua.be.wee.model.pt.StartActivityEvent;
import ua.be.wee.model.pt.StartTraceEvent;
import ua.be.wee.model.pt.TraceArtifact;
import ua.be.wee.model.util.DateTimeConverter;
import ua.be.wee.model.util.Pair;

@Repository
public class PTRepository {
	
	@Autowired
	private PMRepository pmRepo;
	
	@Autowired
	private NodeRespository nodeRepo;
	
	

	public PT createTrace(PM pm) throws Exception {
		int index = getNextIndex(pm);
		String traceiri = pm.getIri().split("#")[0] + "/traces#pt_" + index;
		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "INSERT {\n"
				+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
				+ "	<"+ traceiri + "> rdf:type <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#Event> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#StartTraceEvent> , owl:Thing , <http://ua.be/sdo2l/vocabulary/base/acyclic#element> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#element> ;\n"
				+ "    	tr:relatesTo <" + pm.getIri() + "> ;\n"
				+ " 	tr:hasTimestamp ?now ; \n"
				+ "     owl:sameAs  <" + traceiri + "> .\n"
				+ "}"
				+ "} WHERE {"
				+ "	SELECT ?now\n"
				+ "    WHERE\n"
				+ "      {\n"
				+ "      BIND(now() AS ?now) .\n"
				+ "      }"
				+ "}";
		if (FusekiWrapper.getInstance().updateQuery(query) ) {
			PT pt = new PT();
			pt.setPmEnacted(pm);
			pt.setIri(traceiri);
			
			
			
			query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
					+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
					+ "SELECT ?ts  WHERE {\n"
					+ "	?pt a tr:StartTraceEvent ;\n"
					+ "  		tr:hasTimestamp ?ts ;\n"
					+ "  		owl:sameAs <" + traceiri + "> .\n"
					+ "}";
			ResultSet results = FusekiWrapper.getInstance().execQuery(query);
			if (results.hasNext()) {
				QuerySolution next = results.next();
				RDFNode rdfNode = next.get("?ts");
				
				StartTraceEvent ev = new StartTraceEvent();
				ev.setIri(traceiri);
				ev.setPmEnacted(pm);
				ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(rdfNode.toString()));
				List<Event> list = new ArrayList<Event>();
				list.add(ev);
				pt.setEvents(list);
			}
			return pt;
		} else {
			throw new Exception("Error inserting data.");
		}
		
	}
	
	private int getNextIndex(PM pm) {
		int index = 0;
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "SELECT ?pt  WHERE {\n"
				+ "	?pt a tr:StartTraceEvent ;\n"
				+ "  		tr:relatesTo <" + pm.getIri() + "> .\n"
				+ "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		while (results.hasNext()) {
			results.next();
			index++;
		}
		return index;
	}

	public void createStartEvent(PT pt, ControlInputPort port, Activity act, List<TraceArtifact> arts) throws Exception {
		int index = pt.getEvents().size() == 1 ? 0 : (Integer.parseInt(""+pt.getLastEvent().getIri().charAt(pt.getLastEvent().getIri().length()-1))+1); 

		String iri = pt.getIri().replace('#', '/') + "#" + "start_activity" + index;
		String endiri = pt.getIri().replace('#', '/') + "#" + "end_activity" + index;
		
		String preiri = pt.getLastEvent() != null ? pt.getLastEvent().getIri() : pt.getIri();
		String portiri = port.getIri();
		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/acyclic#>\n"
				+ "INSERT {\n"
				+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
				+ "	<" + iri + "> rdf:type <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#StartActivityEvent> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#element> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#Event> , owl:Thing , <http://ua.be/sdo2l/vocabulary/base/acyclic#element> ;\n"
				+ "    base:acyclicForward <" + endiri + "> ;\n"
				+ "    tr:hasTimestamp ?now ; \n"
				+ "    tr:isPrecededBy <" + preiri + "> ;\n"
				+ "    tr:relatesTo <" + portiri + "> ;\n";
		if (arts != null) {
			for (TraceArtifact artifact : arts) {
				query += "    tr:consumes <" + artifact.getIri() + "> ;\n";
			}
		}
		query +=  "    owl:sameAs <" + iri + "> .\n"
				+ " <"+ preiri + "> tr:isFollowedBy <" + iri + "> .\n"
				+ "}"
				+ "} WHERE {"
				+ "	SELECT ?now\n"
				+ "    WHERE\n"
				+ "      {\n"
				+ "      BIND(now() AS ?now) .\n"
				+ "      }"
				+ "}";
		StartActivityEvent ev = new StartActivityEvent();
		if (FusekiWrapper.getInstance().updateQuery(query) ) {
			ev.setIri(iri);
			ev.setRelatesTo(act.getCtrlInPorts().get(0));
			pt.addEvent(ev);
			
			query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
					+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
					+ "SELECT ?ts  WHERE {\n"
					+ "	?pt		tr:hasTimestamp ?ts ;\n"
					+ "  		owl:sameAs <" + iri + "> .\n"
					+ "}";
			ResultSet results = FusekiWrapper.getInstance().execQuery(query);
			if (results.hasNext()) {
				QuerySolution next = results.next();
				RDFNode rdfNode = next.get("?ts");
				ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(rdfNode.toString()));
			}
			
		} else {
			throw new Exception("Error inserting data.");
		}
		
		if (arts != null) {
			for (TraceArtifact traceArtifact : arts) {
				query = "PREFIX pt: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
						+ "INSERT DATA { \n"
						+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
						+ " <" + traceArtifact.getIri() + "> " 
						+ "        pt:isConsumedBy <" + iri + "> ;\n"
						+ "}"
						+ "}";
				if (FusekiWrapper.getInstance().updateQuery(query) ) {
					traceArtifact.setConsumedBy(ev);
					ev.addTraceArtifact(traceArtifact);
				} else {
					throw new Exception("Error inserting data.");
				}
			}
		}
		updatePT(pt);
		
	}
	
	public void updatePT(PT pt) throws Exception {
		String transitive = "";
		for (Event ev : pt.getEvents()) {
			if (ev instanceof StartTraceEvent) {
				continue;
			} else {
				transitive += "<" + ev.getIri()+">,";
			}
		}
		transitive = transitive.substring(0, transitive.length()-1);
		
		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/acyclic#>\n"
				+ "DELETE { "
				+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
				+ "?pt ?p ?v . }\n"
				+ "}"
				+ "WHERE { \n"
				+ "  ?pt a tr:StartTraceEvent .\n"
				+ "  ?pt owl:sameAs <" + pt.getIri() + "> .\n"
				+ "  ?pt ?p ?v .\n"
				+ "};\n"
				+ "INSERT DATA {\n"
				+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
				+ "	<" + pt.getIri() + "> rdf:type    <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#Event> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#StartTraceEvent> , owl:Thing , <http://ua.be/sdo2l/vocabulary/base/acyclic#element> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#element> ;\n"
				+ "    base:acyclicForward <" + pt.getEvents().get(1).getIri() + "> ;\n"
				+ "    base:transitiveForward "+ transitive + " ;\n"
				+ "    tr:isFollowedBy <" + pt.getEvents().get(1).getIri() + "> ;\n"
				+ "    tr:hasTimestamp " + DateTimeConverter.convertTimestampToSPARQLdateTime(pt.getEvents().get(0).getTimestamp()) + " ; \n "
				+ "    tr:relatesTo <" + pt.getPmEnacted().getIri() + "> ;\n"
				+ "    owl:sameAs <"+ pt.getIri() + "> .\n"
				+ "}"
				+ "};";
		if (!FusekiWrapper.getInstance().updateQuery(query) ) {
			throw new Exception("Error inserting data.");
		}
		
	}

	public void createEndEvent(PT pt, List<TraceArtifact> arts, ControlOutputPort p) throws Exception {
		//TODO: this won't work with concurrent flows (solution: send the start activity event in the request)
		List<Event> events = pt.getEvents();
		Event aux = null;
		Event source = null;
		for (int i = (events.size()-1); i >= 0; i--) {
			aux = events.get(i); 
			if (aux instanceof StartActivityEvent && 
					(((StartActivityEvent) aux).getRelatesTo().getActivity().getIri().equals(p.getActivity().getIri()))) {
				source = aux;
				break;
			}
		}
		String preiri = pt.getLastEvent() != null ? pt.getLastEvent().getIri() : pt.getIri();
		int index = (Integer.parseInt(""+source.getIri().charAt(source.getIri().length()-1)));
		
		//String startiri = pt.getIri().replace('#', '/') + "#" + "start_activity" + index;
		String endiri = pt.getIri().replace('#', '/') + "#" + "end_activity" + index;
		
		List<String> artifactsIRI = new ArrayList<String>();
		for (TraceArtifact art : arts) {
			artifactsIRI.add(createTraceArtifact(endiri,preiri, art, pt));
		}
		
	
		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/acyclic#>\n"
				+ "INSERT {\n"
				+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
				+ "	<" + endiri + "> rdf:type <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#EndActivityEvent> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#element> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#Event> , owl:Thing , <http://ua.be/sdo2l/vocabulary/base/acyclic#element> ;\n"
				+ "    tr:isPrecededBy <" + preiri + "> ;\n"
				+ "    tr:relatesTo <" + p.getIri() + "> ;\n"
				+ "    tr:hasTimestamp ?now ; \n";
				
		for (String iri : artifactsIRI) {
			query += "    tr:produces <" + iri + "> ;\n" ;
		}
		query += "    owl:sameAs <" + endiri + "> .\n"
			  + " <"+ preiri + "> tr:isFollowedBy <" + endiri + "> .\n"
			  + "}"
			  + "} WHERE {"
				+ "	SELECT ?now\n"
				+ "    WHERE\n"
				+ "      {\n"
				+ "      BIND(now() AS ?now) .\n"
				+ "      }"
				+ "}";
		
		if (FusekiWrapper.getInstance().updateQuery(query) ) {
			EndActivityEvent ev = new EndActivityEvent();
			ev.setIri(endiri);
			ev.setRelatesTo(p);
			pt.addEvent(ev);
			
			query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
					+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
					+ "SELECT ?ts  WHERE {\n"
					+ "	?pt		tr:hasTimestamp ?ts ;\n"
					+ "  		owl:sameAs <" + endiri + "> .\n"
					+ "}";
			ResultSet results = FusekiWrapper.getInstance().execQuery(query);
			if (results.hasNext()) {
				QuerySolution next = results.next();
				RDFNode rdfNode = next.get("?ts");
				ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(rdfNode.toString()));
			}
			
			
			for (TraceArtifact art : arts) {
				art.setProducedBy(ev);
				ev.addTraceArtifact(art);
			}
			updatePT(pt);
		} else {
			throw new Exception("Error inserting data.");
		}		
	}

	private String createTraceArtifact(String endEvIRI, String startEvIRI, TraceArtifact art, PT pt)
			throws Exception {
		
		TraceArtifact latest = getLastestVersion(startEvIRI,art);
		if (latest != null) {
			String newTag = (Integer.parseInt(latest.getTag().substring(1))+1)+"";
			art.setTag("v"+newTag);
		} else {
			art.setTag("v1");	
		}
		art.setGUID(art.getRelatesTo().getName()+"-"+art.getTag());
		art.setIri(pt.getIri().replace('#', '/') + "#" + art.getGUID());
		
		
		String query = "PREFIX pt: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
				+ "INSERT { \n"
				+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
				+ " <" + art.getIri() + "> rdf:type base:Versionable , pt:Artifact , owl:Thing , pt:element , base:ImmutableThing ;\n"
				+ "        pt:hasLocation \"" + art.getLocation() + "\" ;\n"
				+ "        pt:isProducedBy <" + endEvIRI + "> ;\n"
				+ "        pt:relatesTo <" + art.getRelatesTo().getIri() + "> ;\n"
				+ "	       pt:addedAt ?now ; \n "
				+ "        owl:sameAs  <" + art.getIri() + "> ;\n";
				if (latest != null) {
					query += "        base:nextVersionOf <" + latest.getIri() + "> ;\n"
					+ "        base:hasGUID \"" + art.getGUID() + "\" ;\n"
					+ "        base:hasTag \"" + art.getTag() + "\" .\n"
					+ "  <" +latest.getIri() + "> base:previousVersionOf <" + art.getIri() +  "> .\n"; 
				} else {
					query += "        base:hasGUID \"" + art.getGUID() + "\" ;\n"
					+ "        base:hasTag \"" + art.getTag() + "\" .\n";
				} 
				query += "}"
					   + "} WHERE {"
				+ "	SELECT ?now\n"
				+ "    WHERE\n"
				+ "      {\n"
				+ "      BIND(now() AS ?now) .\n"
				+ "      }"
				+ "}";
				
		if (FusekiWrapper.getInstance().updateQuery(query) ) {
			
			
			query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
					+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
					+ "SELECT ?ts  WHERE {\n"
					+ "	?pt		tr:addedAt ?ts ;\n"
					+ "  		owl:sameAs <" + art.getIri() + "> .\n"
					+ "}";
			ResultSet results = FusekiWrapper.getInstance().execQuery(query);
			if (results.hasNext()) {
				QuerySolution next = results.next();
				RDFNode rdfNode = next.get("?ts");
				art.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(rdfNode.toString()));
			}
			
			
			if (latest != null) {
				List<TraceArtifact> inputs = pt.getInputs();
				boolean found = false;
				if (inputs != null) {
					for (TraceArtifact traceArtifact : inputs) {
						if (traceArtifact.getIri().equals(latest.getIri())) {
							traceArtifact.setNextVersion(art);
							found = true;
							break;
						}
					}
				}
				
				if (!found) {
					List<Event> events = pt.getEvents();
					for (Event event : events) {
						if (event instanceof StartActivityEvent && ((StartActivityEvent)event).getArtifact(latest.getIri()) != null) {
							((StartActivityEvent)event).getArtifact(latest.getIri()).setNextVersion(art);
							break;
						} else if (event instanceof EndActivityEvent && ((EndActivityEvent)event).getArtifact(latest.getIri()) != null) {
							((EndActivityEvent)event).getArtifact(latest.getIri()).setNextVersion(art);
							break;
						}  
					}
				}
				
			}
			return art.getIri();
		} else {
			throw new Exception("Error inserting data.");
		}

	}
	
	private TraceArtifact getLastestVersion(String startEvIRI, TraceArtifact art) {
		String query = "PREFIX pt: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
				+ "SELECT DISTINCT ?art ?version\n"
				+ "WHERE { \n"
				+ "  ?art a pt:Artifact .\n"
				+ "  ?art pt:relatesTo <" + art.getRelatesTo().getIri() + "> .\n"
				+ "  ?art base:hasTag ?version .\n"
				+ "  {\n"
				+ "    {?art pt:isProducedBy ?endEv .\n"
				+ "    <"+ startEvIRI+ "> pt:isPrecededBy+ ?endEv .} \n"
				+ "    UNION \n"
				+ "    {?art pt:isConsumedBy ?startEv .\n"
				+ "     <"+ startEvIRI+ "> pt:isPrecededBy+ ?startEv .}\n"
				+ "  }  \n"
				+ "  FILTER NOT EXISTS {\n"
				+ "  	?art base:previousVersionOf ?newArt .\n"
				+ "    FILTER (?art != ?newArt) \n"
				+ "  }\n"
				+ "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		if (rs.hasNext()) {
			QuerySolution next = rs.next();
			RDFNode lastestIRI = next.get("?art");
			RDFNode latestVersion = next.get("?version");
			if (lastestIRI != null) {
				TraceArtifact result = new TraceArtifact();
				result.setIri(lastestIRI.toString());
				result.setTag(latestVersion.toString());
				return result;
			}
		}
		return null;
	}

	public Event createEndTraceEvent(String ptIRI, String previous, String pmIRI) throws Exception {
		String traceiri = ptIRI + "_end";
		
		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "INSERT {\n"
				+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
				+ "	<"+ traceiri + "> rdf:type <http://ua.be/sdo2l/vocabulary/base/acyclic#element> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#EndTraceEvent> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#Event> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#element> , owl:Thing ;\n"
				+ " 	tr:isPrecededBy <" + previous + "> ;\n"
				+ "    	tr:relatesTo <" + pmIRI + "> ;\n"
				+ "     tr:hasTimestamp ?now ; \n"
				+ "     owl:sameAs  <" + traceiri + "> .\n"
				+ " <" + previous + "> tr:isFollowedBy <" + traceiri + "> .\n"				
				+ "}"
				+ "} WHERE {"
				+ "	SELECT ?now\n"
				+ "    WHERE\n"
				+ "      {\n"
				+ "      BIND(now() AS ?now) .\n"
				+ "      }"
				+ "}";
				
		if (FusekiWrapper.getInstance().updateQuery(query) ) {
			EndTraceEvent ev = new EndTraceEvent();
			ev.setIri(traceiri);

			query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
					+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
					+ "SELECT ?ts  WHERE {\n"
					+ "	?pt		tr:hasTimestamp ?ts ;\n"
					+ "  		owl:sameAs <" + traceiri + "> .\n"
					+ "}";
			ResultSet results = FusekiWrapper.getInstance().execQuery(query);
			if (results.hasNext()) {
				QuerySolution next = results.next();
				RDFNode rdfNode = next.get("?ts");
				ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(rdfNode.toString()));
			}
			
			ev.setPmEnacted(pmRepo.getPM(pmIRI));
			
			return ev;
		} else {
			throw new Exception("Error inserting data.");
		}
		
	}

	
	public List<StartTraceEvent> getStartTraceEvents() throws Exception {
		List<StartTraceEvent> list = new ArrayList<StartTraceEvent>();
		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "SELECT ?trace ?pm ?ts WHERE {\n"
				+ "	?trace rdf:type <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#StartTraceEvent> ;\n"
				+ "    	tr:relatesTo ?pm ;\n"
				+ " 	tr:hasTimestamp ?ts ; \n"
				+ "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		while (rs.hasNext()) {
			QuerySolution next = rs.next();
			RDFNode trace = next.get("?trace");
			RDFNode pm = next.get("?pm");
			RDFNode ts = next.get("?ts");
			StartTraceEvent pt = new StartTraceEvent();
			pt.setIri(trace.toString());
			pt.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			pt.setPmEnacted(pmRepo.getPM(pm.toString()));
			list.add(pt);
		}
		return list;
	}

	public List<Event> getEvents(String startEventIRI) throws Exception {
		List<Event> list = new ArrayList<Event>();
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "SELECT ?trace ?pm ?ts ?next ?nexttype WHERE {\n"
				+ "	?trace owl:sameAs <" + startEventIRI + "> ;\n"
				+ "    	tr:relatesTo ?pm ;\n"
				+ "	 	tr:hasTimestamp ?ts .\n"
				+ "   	OPTIONAL {\n"
				+ "    	?trace tr:isFollowedBy ?next .\n"
				+ "		?next a ?nexttype .\n"
				+ "     	FILTER (?nexttype IN (tr:StartActivityEvent, tr:EndActivityEvent, tr:EndTraceEvent)) .\n"
				+ "  	}\n"
				+ "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		if (rs.hasNext()) {
			QuerySolution sol = rs.next();
			RDFNode trace = sol.get("?trace");
			RDFNode pm = sol.get("?pm");
			RDFNode ts = sol.get("?ts");
			RDFNode nextQ = sol.get("?next");
			RDFNode nexttypeQ = sol.get("?nexttype");
			
			StartTraceEvent stev = new StartTraceEvent();
			stev.setIri(trace.toString());
			PM pm2 = pmRepo.getPM(pm.toString());
			pm2.setNodes(nodeRepo.getNodes(pm2.getIri()));
			stev.setPmEnacted(pm2);
			stev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			list.add(stev);
			
			String next = nextQ != null ? nextQ.toString() : null;
			String nexttype = nexttypeQ != null ? nexttypeQ.toString() : null;
			
			List<TraceArtifact> artifacts = new ArrayList<TraceArtifact>();
			
			while (next!= null && !next.toString().equals("")) {
				if (nexttype.toString().equals(Event.START_ACTIVITY_IRI)) {
					Pair<String,String> p = getStartActivityEvent(next.toString(), pm2, list, artifacts);
					next = p != null ? p.getFst() : null;
					nexttype = p != null ? p.getSnd() : null;
				} else if (nexttype.toString().equals(Event.END_ACTIVITY_IRI)) {
					Pair<String,String> p = getEndActivityEvent(next.toString(), pm2, list, artifacts);
					next = p != null ? p.getFst() : null;
					nexttype = p != null ? p.getSnd() : null;
				} else if (nexttype.toString().equals(Event.END_TRACE_IRI)) {
					getEndTraceEvent(next.toString(), pm2, list);
					next = null;
				} else {
					throw new Exception("Unexpected type of event");
				}
			}
			
		}
		return list;
	}

	private void getEndTraceEvent(String iri, PM pmo, List<Event> list) throws Exception {
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "SELECT ?trace ?pm ?ts WHERE {\n"
				+ "	?trace owl:sameAs <" + iri + "> ;\n"
				+ "    	tr:relatesTo ?pm ;\n"
				+ "	 	tr:hasTimestamp ?ts ;\n"
				+ "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		if (rs.hasNext()) {
			QuerySolution sol = rs.next();
			RDFNode trace = sol.get("?trace");
			RDFNode ts = sol.get("?ts");
			
			EndTraceEvent ev = new EndTraceEvent();
			ev.setIri(trace.toString());
			ev.setPmEnacted(pmo);
			ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			list.add(ev);
		} else {
			throw new Exception("No data returned by query: \n" + query );
		}
	}
	
	private Pair<String, String> getStartActivityEvent(String iri, PM pm, List<Event> list, List<TraceArtifact> artifacts) throws Exception {
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "SELECT ?trace ?port ?act ?ts ?next ?nexttype WHERE {\n"
				+ "	?trace owl:sameAs <" + iri + "> ;\n"
				+ "    	tr:relatesTo ?port ;\n"
				+ "	 	tr:hasTimestamp ?ts .\n"
				+ " ?act pm:hasPort ?port .\n"
				+ "   OPTIONAL {\n"
				+ "    ?trace tr:isFollowedBy ?next .\n"
				+ "	   ?next a ?nexttype .\n"
				+ "    FILTER (?nexttype IN (tr:StartActivityEvent, tr:EndActivityEvent, tr:EndTraceEvent)) . \n"
				+ "  }\n"
				+ "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		if (rs.hasNext()) {
			QuerySolution sol = rs.next();
			RDFNode trace = sol.get("?trace");
			RDFNode port = sol.get("?port");
			RDFNode act = sol.get("?act");
			
			Activity activity = (Activity)pm.getNode(act.toString());
			List<ControlInputPort> ctrlInPorts = activity.getCtrlInPorts();
			ControlInputPort ctrlPort = null;
			for (ControlInputPort ctrl : ctrlInPorts) {
				if (ctrl.getIri().equals(port.toString())) {
					ctrlPort = ctrl;
					break;
				}
			}
			RDFNode ts = sol.get("?ts");

			StartActivityEvent stev = new StartActivityEvent();
			stev.setIri(trace.toString());
			stev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			stev.setRelatesTo(ctrlPort);
			
			stev.setConsumedArtifacts(getArtifacts(stev,pm, artifacts));

			
			list.add(stev);
			
			RDFNode next = sol.get("?next");
			if (next == null) {
				return null;
			} else {
				return new Pair<String, String>(next.toString(),sol.get("?nexttype").toString());
			}
		} else {
			throw new Exception("No data returned by query: \n" + query );
		}
		
	}

	private List<TraceArtifact> getArtifacts(Event ev, PM pm, List<TraceArtifact> artifacts) {
		List<TraceArtifact> arts = new ArrayList<TraceArtifact>();
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "SELECT ?art ?ts ?guid ?location ?tag ?pmArt ?next WHERE {\n"
				+ "	?art a tr:Artifact .\n"
				+ (ev instanceof StartActivityEvent ? 
						"  	<" + ev.getIri() + "> tr:consumes ?art .\n" : 
							"  	<" + ev.getIri() + "> tr:produces ?art .\n")
				+ "    ?art tr:addedAt ?ts .\n"
				+ "    ?art base:hasGUID ?guid .\n"
				+ "    ?art tr:hasLocation ?location .\n"
				+ "    ?art base:hasTag ?tag .\n"
				+ "    ?art tr:relatesTo ?pmArt .\n"
				+ "	   OPTIONAL {\n"
				+ "    	?art base:nextVersionOf ?next \n"
				+ "    }"
				+ "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		while (rs.hasNext()) {
			QuerySolution next = rs.next();
			RDFNode art = next.get("?art");
			RDFNode guid = next.get("?guid");
			RDFNode ts = next.get("?ts");
			RDFNode location = next.get("?location");
			RDFNode tag = next.get("?tag");
			RDFNode pmArt = next.get("?pmArt");
			RDFNode nextArt = next.get("?next");
			TraceArtifact tArt = new TraceArtifact();
			tArt.setIri(art.toString());
			tArt.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			tArt.setRelatesTo((Artifact)pm.getNode(pmArt.toString()));	
			tArt.setGUID(guid.toString());
			tArt.setLocation(location.toString());
			tArt.setTag(tag.toString());
			arts.add(tArt);
			
			if (nextArt != null && !nextArt.toString().equals("")) {
				for (TraceArtifact traceArtifact : artifacts) {
					if (traceArtifact.getIri().equals(nextArt.toString())) {
						traceArtifact.setNextVersion(tArt);
					}
				}
			}

			artifacts.add(tArt);
		}
		return arts;				
	}

	private Pair<String, String> getEndActivityEvent(String iri, PM pm, List<Event> list, List<TraceArtifact> artifacts) throws Exception {
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "SELECT ?trace ?port ?act ?ts ?next ?nexttype WHERE {\n"
				+ "	?trace owl:sameAs <" + iri + "> ;\n"
				+ "     tr:relatesTo ?port ;\n"
				+ "	 	tr:hasTimestamp ?ts .\n"
				+ " ?act pm:hasPort ?port .\n"
				+ "   OPTIONAL {\n"
				+ "    ?trace tr:isFollowedBy ?next .\n"
				+ "	?next a ?nexttype .\n"
				+ "    FILTER (?nexttype IN (tr:StartActivityEvent, tr:EndActivityEvent, tr:EndTraceEvent)) . \n"
				+ "  }\n"
				+ "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		if (rs.hasNext()) {
			QuerySolution sol = rs.next();
			RDFNode trace = sol.get("?trace");
			RDFNode port = sol.get("?port");
			RDFNode act = sol.get("?act");
			
			Activity activity = (Activity)pm.getNode(act.toString());
			List<ControlOutputPort> ctrlOutPorts = activity.getCtrlOutPorts();
			ControlOutputPort ctrlPort = null;
			for (ControlOutputPort ctrl : ctrlOutPorts) {
				if (ctrl.getIri().equals(port.toString())) {
					ctrlPort = ctrl;
					break;
				}
			}
			RDFNode ts = sol.get("?ts");

			EndActivityEvent ev = new EndActivityEvent();
			ev.setIri(trace.toString());
			ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			ev.setRelatesTo(ctrlPort);
			
			ev.setProducedArtifacts(getArtifacts(ev,pm,artifacts));
						
			list.add(ev);
			
			RDFNode next = sol.get("?next");
			if (next == null) {
				return null;
			} else {
				return new Pair<String, String>(next.toString(),sol.get("?nexttype").toString());
			}
			
		} else {
			throw new Exception("No data returned by query: \n" + query );
		}
	}

	public List<StartTraceEvent> getActiveTraces(String pm) {
		List<StartTraceEvent> list = new ArrayList<StartTraceEvent>();
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "SELECT ?trace ?ts ?next WHERE {\n"
				+ "  ?trace tr:hasTimestamp ?ts .\n"
				+ "  ?trace tr:relatesTo <" + pm + "> .\n"
				+ "  {  \n"
				+ "    ?trace a tr:StartTraceEvent .\n"
				+ "	?trace tr:isFollowedBy+ ?next .\n"
				+ "    ?next a ?nexttype .\n"
				+ "	FILTER (?trace != ?next)\n"
				+ "	FILTER NOT EXISTS {?next tr:isFollowedBy ?other  } .\n"
				+ "    FILTER (?nexttype IN (tr:StartActivityEvent, tr:EndActivityEvent)) .\n"
				+ "  }\n"
				+ "  UNION \n"
				+ "  {\n"
				+ "    ?trace a tr:StartTraceEvent .\n"
				+ "    FILTER NOT EXISTS {?trace tr:isFollowedBy ?next} .\n"
				+ "  }\n"
				+ "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		while (rs.hasNext()) {
			QuerySolution next = rs.next();
			RDFNode trace = next.get("?trace");
			RDFNode ts = next.get("?ts");
			StartTraceEvent ev = new StartTraceEvent();
			ev.setIri(trace.toString());
			ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			PM pmo = new PM();
			pmo.setIri(pm);
			ev.setPmEnacted(pmo);
			list.add(ev);
		}
		return list;		
	}

	public List<StartTraceEvent> getConcludedTraces(String pmiri) {
		List<StartTraceEvent> list = new ArrayList<StartTraceEvent>();
		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
				+ "SELECT ?trace ?ts ?next WHERE {\n"
				+ "  ?trace a tr:StartTraceEvent .\n"
				+ "  ?trace tr:hasTimestamp ?ts .\n"
				+ "  ?trace tr:relatesTo <" + pmiri + "> .\n"
				+ "  ?trace tr:isFollowedBy+ ?next .\n"
				+ "  ?next a ?nexttype .\n"
				+ "  FILTER (?trace != ?next)\n"
				+ "  FILTER NOT EXISTS {?next tr:isFollowedBy ?other  } .\n"
				+ "  FILTER (?nexttype IN (tr:EndTraceEvent)) .\n"
				+ "}";
		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
		while (rs.hasNext()) {
			QuerySolution next = rs.next();
			RDFNode trace = next.get("?trace");
			RDFNode ts = next.get("?ts");
			StartTraceEvent ev = new StartTraceEvent();
			ev.setIri(trace.toString());
			ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
			PM pmo = new PM();
			pmo.setIri(pmiri);
			ev.setPmEnacted(pmo);
			list.add(ev);
		}
		return list;		
	}	

}
