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.FinalNode;
import ua.be.wee.model.nodes.ForkJoinNode;
import ua.be.wee.model.nodes.InitialNode;
import ua.be.wee.model.nodes.Node;
import ua.be.wee.model.nodes.ports.ControlInputPort;
import ua.be.wee.model.nodes.ports.ControlOutputPort;
import ua.be.wee.model.nodes.ports.DataInputPort;
import ua.be.wee.model.nodes.ports.DataOutputPort;
import ua.be.wee.model.pm.PM;

@Repository
public class NodeRespository {

	@Autowired
	private PMRepository pmRepo;

	public List<Node> getNodes(String pmIri) throws Exception {
		List<Node> nodes = new ArrayList<Node>();

		PM pm = pmRepo.getPM(pmIri);

		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
				+ "PREFIX ob: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>\n"
				+ "SELECT ?node ?nodetype WHERE {\n" 
				+ "  ?pm a pm:Model ;\n" 
				+ "  	ob:hasName ?pmName ;\n"
				+ "  	owl:sameAs <" + pm.getIri() + "> ;\n" 
				+ "  	ob:hasObject ?node .\n" 
				+ "  ?node a ?nodetype .\n"
				+ "  FILTER (?nodetype in (pm:Initial, pm:Activity, pm:AutomatedActivity, pm:Final, pm:ForkJoin, pm:Artifact)) .      \n"
				+ "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		while (results.hasNext()) {
			QuerySolution soln = results.nextSolution();

			RDFNode iri = soln.get("?node");
			RDFNode type = soln.get("?nodetype");
			Node node = createNode(iri, type);
			nodes.add(node);
		}
		pm.setNodes(nodes);
		updateArtifactOfActivities(pm);
		updateControlNodesReferences(nodes);

		return nodes;

	}

	private void updateControlNodesReferences(List<Node> nodes) {

		for (Node node : nodes) {
			if (node instanceof InitialNode) {
				updateInitialNode(nodes, (InitialNode) node);
			} else if (node instanceof FinalNode) {
				updateFinalNode(nodes, (FinalNode) node);
			} else if (node instanceof ForkJoinNode) {
				updateForkJoinNode(nodes, (ForkJoinNode) node);
			}
		}

	}

	private void updateForkJoinNode(List<Node> nodes, ForkJoinNode node) {
		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" 
				+ "SELECT ?next ?nodetype ?act\n"
				+ "WHERE { \n" + "  ?node owl:sameAs<" + node.getIri() + ">.\n" 
				+ "  ?node pm:ctrlFrom ?next.\n"
				+ "  ?next a ?nodetype ;\n"
				+ "	FILTER (?nodetype in (pm:CtrlOutputPort,pm:ForkJoin, pm:Initial)) \n" 
				+ "	OPTIONAL {\n"
				+ "		?next pm:ofActivity ?act .\n" + "	}\n" + "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		List<Node> list = new ArrayList<Node>();
		while (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode type = soln.get("?nodetype");
			String nextIri;
			if (type.toString().equals(Node.CTRL_OUTPUT_PORT_IRI)) {
				nextIri = soln.get("?act").toString();
			} else {
				nextIri = soln.get("?next").toString();
			}
			for (Node node2 : nodes) {
				if (node2.getIri().equals(nextIri)) {
					list.add(node2);
				}
			}
		}
		node.setPrevious(list);

		query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" + "SELECT ?next ?nodetype ?act\n"
				+ "WHERE { \n" + "  ?node owl:sameAs<" + node.getIri() + ">.\n" + "  ?node pm:ctrlTo ?next.\n"
				+ "  ?next a ?nodetype ;\n"
				+ "	FILTER (?nodetype in (pm:CtrlInputPort, pm:Initial, pm:Final,pm:ForkJoin)) \n"
				+ "	OPTIONAL {\n" + "		?next pm:ofActivity ?act .\n" + "	}\n" + "}";
		list.clear();
		results = FusekiWrapper.getInstance().execQuery(query);
		while (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode type = soln.get("?nodetype");
			String nextIri;
			if (type.toString().equals(Node.CTRL_INPUT_PORT_IRI)) {
				nextIri = soln.get("?act").toString();
			} else {
				nextIri = soln.get("?next").toString();
			}
			for (Node node2 : nodes) {
				if (node2.getIri().equals(nextIri)) {
					list.add(node2);
				}
			}
		}
		node.setNextNodes(list);

	}

	private void updateFinalNode(List<Node> nodes, FinalNode node) {
		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" + "SELECT ?next ?nodetype ?act\n"
				+ "WHERE { \n" + "  ?node owl:sameAs<" + node.getIri() + ">.\n" + "  ?node pm:ctrlFrom ?next.\n"
				+ "  ?next a ?nodetype ;\n"
				+ "	FILTER (?nodetype in (pm:CtrlOutputPort, pm:Initial, pm:Final,pm:ForkJoin)) \n"
				+ "	OPTIONAL {\n" + "		?next pm:ofActivity ?act .\n" + "	}\n" + "}";
		List<Node> list = new ArrayList<Node>();
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		while (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode type = soln.get("?nodetype");
			String nextIri;
			if (type.toString().equals(Node.CTRL_OUTPUT_PORT_IRI)) {
				nextIri = soln.get("?act").toString();
			} else {
				nextIri = soln.get("?next").toString();
			}
			for (Node node2 : nodes) {
				if (node2.getIri().equals(nextIri)) {
					list.add(node2);
				}
			}
		}
		node.setPrevious(list);
	}

	private void updateInitialNode(List<Node> nodes, InitialNode node) {
		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" + "SELECT ?next ?nodetype ?act\n"
				+ "WHERE { \n" + "  ?node owl:sameAs<" + node.getIri() + ">.\n" + "  ?node pm:ctrlTo ?next.\n"
				+ "  ?next a ?nodetype ;\n" + "	FILTER (?nodetype in (pm:CtrlInputPort, pm:Final,pm:ForkJoin)) \n"
				+ "	OPTIONAL {\n" + "		?next pm:ofActivity ?act .\n" + "	}\n" + "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		while (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode type = soln.get("?nodetype");
			String nextIri;
			if (type.toString().equals(Node.CTRL_INPUT_PORT_IRI)) {
				nextIri = soln.get("?act").toString();
			} else {
				nextIri = soln.get("?next").toString();
			}
			for (Node node2 : nodes) {
				if (node2.getIri().equals(nextIri)) {
					node.setNext(node2);
					break;
				}
			}
		}
	}

	private void updateArtifactOfActivities(PM pm) throws Exception {
		for (Node node : pm.getNodes()) {
			if (node instanceof Activity) {
				Activity act = (Activity) node;
				for (DataInputPort inport : act.getDatalInPorts()) {
					String query = "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
							+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" + "SELECT  ?inport ?art\n" + "WHERE { \n"
							+ "  ?inport owl:sameAs <" + inport.getIri() + "> .\n" + "  ?inport pm:dataFrom ?art .\n"
							+ "}";
					ResultSet results = FusekiWrapper.getInstance().execQuery(query);
					if (results.hasNext()) {
						QuerySolution soln = results.nextSolution();

						RDFNode art = soln.get("?art");
						if (art != null && !art.toString().isEmpty()) {
							Artifact artifact = (Artifact) pm.getNode(art.toString());
							act.addInputArtifact(artifact);
							inport.setArtifact(artifact);
						}
					} else {
						throw new Exception("Query did not return any data: \n" + query );
					}
					
				}

				for (DataOutputPort outport : act.getDataOutPorts()) {
					String query = "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
							+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" + "SELECT  ?outport ?art\n"
							+ "WHERE { \n" + "  ?outport owl:sameAs <" + outport.getIri() + "> .\n"
							+ "  ?outport pm:dataTo ?art .\n" + "}";
					ResultSet results = FusekiWrapper.getInstance().execQuery(query);
					if (results.hasNext()) {
						QuerySolution soln = results.nextSolution();
	
						RDFNode art = soln.get("?art");
						if (art != null && !art.toString().isEmpty()) {
							Artifact artifact = (Artifact) pm.getNode(art.toString());
							act.addOutputArtifact(artifact);
							outport.setArtifact(artifact);
						}
					} else {
						throw new Exception("Query did not return any data: \n" + query );
					}
				}
			}
		}
	}

	private Node createNode(RDFNode iri, RDFNode type) throws Exception {
		Node node = null;
		switch (type.toString()) {
		case Node.ACTIVITY_IRI:
			node = createActivity(iri.toString());
			break;
		case Node.AUTOMATED_ACTIVITY_IRI:
			node = createAutomatedActivity(iri.toString());
			break;
		case Node.INITIAL_IRI:
			node = new InitialNode();
			break;
		case Node.FINAL_IRI:
			node = new FinalNode();
			break;
		case Node.FORKJOIN_IRI:
			// addlogic for fork join
			node = new ForkJoinNode();
			break;
		case Node.ARTIFACT_IRI:
			// addlogic for fork join
			node = createArtifact(iri.toString());
			break;
		}
		node.setIri(iri.toString());
		return node;
	}

	

	private Node createArtifact(String iri) throws Exception {
		Artifact art = new Artifact();
		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n" + "SELECT ?art ?name ?typename WHERE {\n"
				+ "	?art owl:sameAs <" + iri + "> .\n" + " ?art	pm:hasName ?name .\n"
				+ " ?art  	pm:hasType ?type .\n" + " ?type base:hasGUID ?typename .\n" + "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		if (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode name = soln.get("?name");
			RDFNode type = soln.get("?typename");
			art.setIri(iri);
			art.setName(name.toString());
			art.setType(type.toString());
			return art;
		} else {
			throw new Exception("Query did not return any data: \n" + query );
		}
		
	}

	private Node createActivity(String iri) throws Exception {
		Activity act = new Activity();
		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX ftg: <http://ua.be/sdo2l/vocabulary/formalisms/ftg#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" + "SELECT ?act ?name ?type WHERE {\n"
				+ "	?act owl:sameAs <" + iri + ">;\n" + "   		pm:isTransformation ?t ;  \n"
				+ "   		pm:hasName ?name .  \n" + " ?t base:hasGUID ?type .  \n" + "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		if (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode name = soln.get("?name");
			RDFNode type = soln.get("?type");
			act.setIri(iri);
			act.setName(name.toString());
			act.setTransformationName(type.toString());

			act.setCtrlInPorts(getCtrlInPorts(act));
			act.setCtrlOutPorts(getCtrlOutPorts(act));
			act.setDatalInPorts(getDataInPorts(act));
			act.setDataOutPorts(getDataOutPorts(act));

			return act;

		} else {
			throw new Exception("Query did not return any data: \n" + query );
		}
	}
	
	private Node createAutomatedActivity(String iri) throws Exception {
		AutomatedActivity act = new AutomatedActivity();
		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX ftg: <http://ua.be/sdo2l/vocabulary/formalisms/ftg#>\n"
				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" 
				+ "SELECT ?act ?name ?type ?timeout ?endpoint WHERE {\n"
				+ "	?act owl:sameAs <" + iri + ">;\n" 
				+ "   		pm:isTransformation ?t ;  \n"
				+ "   		pm:hasName ?name ;  \n" 
				+ "   		pm:hasTimeout ?timeout .  \n"
				+ " ?t base:hasGUID ?type ;  \n" 
				+ "   		ftg:hasEndpoint ?endpoint .  \n"
				+ "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		if (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode name = soln.get("?name");
			RDFNode type = soln.get("?type");
			RDFNode timeout = soln.get("?timeout");
			RDFNode endpoint = soln.get("?endpoint");
			
			act.setIri(iri);
			act.setName(name.toString());
			act.setTransformationName(type.toString());
			act.setTimeout(timeout.asLiteral().getLong());
			act.setEndpoint(endpoint.toString());

			act.setCtrlInPorts(getCtrlInPorts(act));
			act.setCtrlOutPorts(getCtrlOutPorts(act));
			act.setDatalInPorts(getDataInPorts(act));
			act.setDataOutPorts(getDataOutPorts(act));

			return act;

		} else {
			throw new Exception("Query did not return any data: \n" + query );
		}
	}

	private List<DataOutputPort> getDataOutPorts(Activity act) {
		List<DataOutputPort> list = new ArrayList<DataOutputPort>();
		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" + "SELECT ?port ?pname WHERE {\n"
				+ "	?act owl:sameAs <" + act.getIri() + ">;\n" + "   		pm:hasPort ?port .\n"
				+ "  	?port a pm:DataOutputPort .\n" + "  	?port pm:hasName ?pname .\n" + "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		while (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode iri = soln.get("?port");
			RDFNode name = soln.get("?pname");
			DataOutputPort cout = new DataOutputPort();
			cout.setIri(iri.toString());
			cout.setName(name.toString());
			cout.setActivity(act);
			list.add(cout);
		}
		return list;
	}

	private List<DataInputPort> getDataInPorts(Activity act) {
		List<DataInputPort> list = new ArrayList<DataInputPort>();
		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" + "SELECT ?port ?pname WHERE {\n"
				+ "	?act owl:sameAs <" + act.getIri() + ">;\n" + "   		pm:hasPort ?port .\n"
				+ "  	?port a pm:DataInputPort .\n" + "  	?port pm:hasName ?pname .\n" + "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		while (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode iri = soln.get("?port");
			RDFNode name = soln.get("?pname");
			DataInputPort cout = new DataInputPort();
			cout.setIri(iri.toString());
			cout.setName(name.toString());
			cout.setActivity(act);
			list.add(cout);
		}
		return list;
	}

	private List<ControlOutputPort> getCtrlOutPorts(Activity act) {
		List<ControlOutputPort> list = new ArrayList<ControlOutputPort>();
		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" + "SELECT ?port ?pname WHERE {\n"
				+ "	?act owl:sameAs <" + act.getIri() + ">;\n" + "   		pm:hasPort ?port .\n"
				+ "  	?port a pm:CtrlOutputPort .\n" + "  	?port pm:hasName ?pname .\n" + "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		while (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode iri = soln.get("?port");
			RDFNode name = soln.get("?pname");
			ControlOutputPort cout = new ControlOutputPort();
			cout.setIri(iri.toString());
			cout.setName(name.toString());
			cout.setActivity(act);
			list.add(cout);
		}
		return list;
	}

	private List<ControlInputPort> getCtrlInPorts(Activity act) {
		List<ControlInputPort> list = new ArrayList<ControlInputPort>();
		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" + "SELECT ?port ?pname WHERE {\n"
				+ "	?act owl:sameAs <" + act.getIri() + ">;\n" + "   		pm:hasPort ?port .\n"
				+ "  	?port a pm:CtrlInputPort .\n" + "  	?port pm:hasName ?pname .\n" + "}";
		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
		while (results.hasNext()) {
			QuerySolution soln = results.nextSolution();
			RDFNode iri = soln.get("?port");
			RDFNode name = soln.get("?pname");
			ControlInputPort cout = new ControlInputPort();
			cout.setIri(iri.toString());
			cout.setName(name.toString());
			cout.setAct(act);
			list.add(cout);
		}
		return list;
	}

}
