package ua.be.wee.controller;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import ua.be.wee.controller.rest.PTController;
import ua.be.wee.model.EnactmentController;
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.AutomatedStatus;
import ua.be.wee.model.nodes.FinalNode;
import ua.be.wee.model.nodes.ForkJoinNode;
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.pm.PM;
import ua.be.wee.model.pt.EndActivityEvent;
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.repository.FusekiWrapper;
import ua.be.wee.model.util.Pair;

@Controller
@RequestMapping("/")
public class EnactmentControllerMVC {

	@Autowired private EnactmentController controller;

	@Autowired
	private PTController ptController;

	@Autowired
	private Environment env;

    @Value("${base_url}")
    private String baseUrl;

    @ModelAttribute("baseUrl")
    public String getUrl() {
        return this.baseUrl;
    }

    @RequestMapping("/")
    public String index(Model model) throws FileNotFoundException, IOException {
    	if (!env.getProperty("endpoint").equals("")) {
			model.addAttribute("savedEndpoint", env.getProperty("endpoint"));
			model.addAttribute("hasEndpoint", true);
		} else {
			model.addAttribute("hasEndpoint", false);
		}
        return "index";
    }

    @PostMapping("/pm")
    public String getPMs(Model model, @RequestParam String endpoint) throws FileNotFoundException, IOException {
    	FusekiWrapper.getInstance().setServiceURI(endpoint);
    	if (FusekiWrapper.getInstance().testEndpoint()) {
    		model.addAttribute("error",false);
    		model.addAttribute("pms", controller.getAllPMs());
    		model.addAttribute("traces",null);
    		model.addAttribute("current", "1");
            return "pms";
		} else {
			model.addAttribute("error",true);
			return "index";
		}
    }

    @RequestMapping("/gettraces")
    public String getTraces(Model model, @RequestParam String iri, HttpServletRequest request) throws Exception {
    	List<StartTraceEvent> activeTraces = ptController.getActiveTraces(iri);
    	if (activeTraces != null && activeTraces.size() > 0) {
			model.addAttribute("traces",activeTraces);
		} else {
			model.addAttribute("traces",null);
		}
    	model.addAttribute("error",false);
		model.addAttribute("pms", controller.getAllPMs());
		model.addAttribute("current", iri);
        return "pms";
    }


    @RequestMapping(value="/enactpm", method=RequestMethod.POST, params="action=new")
    public String getPMtoEnact(Model model, @RequestParam String pmiri, HttpServletRequest request) throws Exception {
    	if ((pmiri.equals("select PM"))) {
			model.addAttribute("error", true);
			model.addAttribute("pms", controller.getAllPMs());
			model.addAttribute("traces",null);
    		model.addAttribute("current", "1");
			return "pms";
		} else {
			PM pm = controller.getPM(pmiri);
			PT trace = controller.createTrace(pm);
			List<Pair<String,String>> iris = controller.findNextNodes(pm.getInitial().getIri(), trace.getIri());
			List<PMTrigger> acts = findElements(pm, iris);
			model.addAttribute("error", false);
			request.getSession().setAttribute("pm", pm);
			request.getSession().setAttribute("trace", trace);
			request.getSession().setAttribute("acts",acts);
			request.getSession().setAttribute("endacts",new ArrayList<Node>());
			//TODO instead of creating empty check if there is any automated activity being executed
			request.getSession().setAttribute("automated",controller.getAutomatedActivities(trace));
			model.addAttribute("arts", null);
			model.addAttribute("current", "1");
	        return "enact";
		}
    }

    @RequestMapping(value="/enactpm", method=RequestMethod.POST, params="action=continue")
    public String continueEnact(Model model, @RequestParam String pmiri, @RequestParam String contTrace, HttpServletRequest request) throws Exception {
        if ((pmiri.equals("select PM")) || contTrace != null && contTrace.equals("Select Trace")) {
            model.addAttribute("error", true);
			model.addAttribute("pms", controller.getAllPMs());
			model.addAttribute("traces",null);
    		model.addAttribute("current", "1");
			return "pms";
		} else {
            PM pm = controller.getPM(pmiri);
			List<Event> events = ptController.getEvents(contTrace);
			PT trace = new PT();
			trace.setEvents(events);
			trace.setIri(contTrace);
			trace.setPmEnacted(pm);
			List <Node> endActs = new ArrayList<Node>();
			if (events.size() == 1) {
				List<Pair<String,String>> iris = controller.findNextNodes(pm.getInitial().getIri(), trace.getIri());
				List<PMTrigger> acts = findElements(pm, iris);
				request.getSession().setAttribute("acts",acts);
			} else {
				endActs = findEndActs(events);
				List<PMTrigger> acts = findStartActs(trace);
				if (events.get(events.size()-1) instanceof EndActivityEvent) {
					EndActivityEvent last = (EndActivityEvent)events.get(events.size()-1);
					List<Pair<String,String>> iris = controller.findNextNodes(last.getRelatesTo().getIri(), trace.getIri());
					acts.addAll(findElements(pm, iris));
				}
				request.getSession().setAttribute("acts",acts);
			}

			request.getSession().setAttribute("pm", pm);
			request.getSession().setAttribute("trace", trace);
			request.getSession().setAttribute("endacts",endActs);
			//TODO instead of creating empty check if there is any automated activity being executed
			request.getSession().setAttribute("automated",controller.getAutomatedActivities(trace));
			model.addAttribute("error", false);
			model.addAttribute("arts", null);
			model.addAttribute("current", "1");
	        return "enact";
		}
    }


	private List<PMTrigger> findStartActs(PT trace) throws Exception {
		PM pmEnacted = trace.getPmEnacted();
		List<Node> nodes = pmEnacted.getNodes();
		List<PMTrigger> result = new ArrayList<PMTrigger>();
		for (Node node : nodes) {
			if (node instanceof ForkJoinNode) {
				ForkJoinNode fj = ((ForkJoinNode)node);
				if (fj.getNextNodes().size() > 1) {
					List<Pair<String,String>> findNextNodes = controller.findNextNodes(fj.getIri(), trace.getIri());
					List<String> actIris = new ArrayList<String>();
					for (Pair<String,String> pair : findNextNodes) {
						if (pair.getSnd() != null) {
							actIris.add(pair.getSnd());
						}
					}
					List<String> consider = checkTrace(trace,actIris);

					for (Iterator<Pair<String,String>> iterator = findNextNodes.iterator(); iterator.hasNext();) {
						Pair<String,String> pair = iterator.next();

						if (pair.getSnd() == null) {
							findNextNodes.remove(pair);
						} else {
							boolean found = false;
							for (String str : consider) {
								if (str.equals(pair.getSnd())) {
									found = true;
									break;
								}
							}
							if (!found) {
								iterator.remove();
							}
						}
					}
					result.addAll(findElements(trace.getPmEnacted(), findNextNodes));
				}
			}
		}
		return result;
	}


	private List<String> checkTrace(PT trace, List<String> actIris) {
		List<Pair<String,Integer>> pairs = new ArrayList<Pair<String,Integer>>();
		int max = 0;
		for (String iri : actIris) {
			int count = 0;
			for (Event ev : trace.getEvents()) {
				if (ev instanceof StartActivityEvent && ((StartActivityEvent)ev).getRelatesTo().getActivity().getIri().equals(iri)) {
					count++;
				}
			}
			if (count > max) {
				max = count;
			}
			pairs.add(new Pair<String, Integer>(iri, count));
		}

		List<String> result = new ArrayList<String>();
		for (Pair<String, Integer> pair : pairs) {
			if (pair.getSnd() < max) {
				result.add(pair.getFst());
			}
		}
		return result;
	}

	private List<Node> findEndActs(List<Event> events) {
		List<Node> result = new ArrayList<Node>();
		for (Event event : events) {
			String name = event.getIri().split("#")[1];
			if (name.startsWith("start_activity")) {
				String index = name.split("start_activity")[1];
				boolean found = false;
				for (Event event2 : events) {
					String end = event2.getIri().split("#")[1];
					if (end.equals("end_activity" + index)) {
						found = true;
						break;
					}
				}
				if (!found) {
					result.add(((StartActivityEvent)event).getRelatesTo().getActivity());
				}
			}
		}
		return result;
	}

	@PostMapping("/reload")
    public String reload(Model model, HttpServletRequest request) throws Exception {
		PT pt = (PT)request.getSession().getAttribute("trace");
		PM pm = (PM)request.getSession().getAttribute("pm");
		request.getSession().setAttribute("automated",controller.getAutomatedActivities(pt));

		List<Event> events = ptController.getEvents(pt.getIri());
		pt.setEvents(events);

		List <Node> endActs = findEndActs(pt.getEvents());
		List<PMTrigger> acts = findStartActs(pt);
		if (pt.getEvents().get(pt.getEvents().size()-1) instanceof EndActivityEvent) {
			EndActivityEvent last = (EndActivityEvent)pt.getEvents().get(pt.getEvents().size()-1);
			List<Pair<String,String>> iris = controller.findNextNodes(last.getRelatesTo().getIri(), pt.getIri());
			acts.addAll(findElements(pm, iris));
		}
		request.getSession().setAttribute("acts",acts);
		request.getSession().setAttribute("endacts",endActs);

    	request.getSession().setAttribute("trace", pt);

    	model.addAttribute("error", false);
		model.addAttribute("arts", null);
		model.addAttribute("current", "1");
		return "enact";
	}

	@PostMapping("/startAct")
    public String startActivity(Model model, @RequestParam String iri, HttpServletRequest request) throws Exception {
    	PM pm = (PM)request.getSession().getAttribute("pm");
    	PT pt = (PT)request.getSession().getAttribute("trace");
    	List<PMTrigger> acts = (List<PMTrigger>) request.getSession().getAttribute("acts");
    	List<Node> endacts = (List<Node>) request.getSession().getAttribute("endacts");
    	PMTrigger element = findPMTrigger(acts,iri);
    	List<TraceArtifact> arts = (List<TraceArtifact>)request.getSession().getAttribute("arts");
    	if (element.getPort() != null) {
    		ControlInputPort port = (ControlInputPort)element.getPort();
        	Activity activity = port.getActivity();
        	if (activity instanceof AutomatedActivity) {
				AutomatedActivity aut = (AutomatedActivity)activity;
				controller.callAutomatedActivity(pt,aut, port, arts);
				aut.setAutomatedStatus(AutomatedStatus.IN_PROGRESS);
			} else {
				endacts.add(port.getActivity());
			}
        	controller.addStartEvent(pt,port, port.getActivity(),arts);
        	removeElement(acts,iri);
        	request.getSession().setAttribute("automated",controller.getAutomatedActivities(pt));
        	request.getSession().removeAttribute("arts");
        	return "enact";
		} else if (element.getNode() != null) {
			Event endTraceEvent = controller.addEndTraceEvent(pt.getIri(), pt.getLastEvent().getIri(), pm.getIri());
			pt.addEvent(endTraceEvent);
			controller.updatePT(pt);
			request.getSession().setAttribute("automated",controller.getAutomatedActivities(pt));
			return "endEnactment";
		} else {
			return "error";
		}
    }



    @PostMapping("/endAct")
    public String endActivityWithArtifacts(Model model, @RequestParam String port, @RequestParam String activity, HttpServletRequest request) throws Exception {
    	PM pm = (PM)request.getSession().getAttribute("pm");
    	List<PMTrigger> acts = (List<PMTrigger>)request.getSession().getAttribute("acts");
    	List<Node> endacts = (List<Node>)request.getSession().getAttribute("endacts");
    	PT pt = (PT)request.getSession().getAttribute("trace");
    	Activity act = (Activity)pm.getNode(activity);
    	List<ControlOutputPort> ctrlOutPorts = act.getCtrlOutPorts();
    	ControlOutputPort p = null;
    	for (ControlOutputPort out : ctrlOutPorts) {
    		if (out.getIri().equals(port)) {
				p = out;
				break;
			}
		}

    	List<Artifact> arts = act.getOutputs();
    	List<TraceArtifact> traceArts = new ArrayList<TraceArtifact>();
    	for (Artifact artifact : arts) {
    		ArrayList<Part> parts = (ArrayList<Part>)request.getParts();
    		for (Part part : parts) {
				if (part.getName().equals(artifact.getName())) {
					TraceArtifact tArt = new TraceArtifact();
					tArt.setLocation(part.getSubmittedFileName());
					tArt.setRelatesTo(artifact);
					traceArts.add(tArt);
					controller.uploadArtifact(part.getInputStream(),part.getSubmittedFileName());
				}
			}
		}

    	controller.addEndEvent(pt,traceArts,p);
    	for (Node activ : endacts) {
			if (activ.getIri().equals(act.getIri())) {
				endacts.remove(activ);
				break;
			}
		}

    	List<Pair<String,String>> iris = controller.findNextNodes(p.getIri(), pt.getIri());
        acts.addAll(findElements(pm, iris));

    	request.getSession().setAttribute("trace", pt);
    	request.getSession().setAttribute("automated",controller.getAutomatedActivities(pt));
    	//request.getSession().setAttribute("previous", p.getIri());
		model.addAttribute("arts", null);
		model.addAttribute("current", "1");
    	return "enact";
    }

    @RequestMapping("/endselect")
    public String selectActivityToEnd(Model model, @RequestParam String iri, HttpServletRequest request) throws Exception {
    	PM pm = (PM)request.getSession().getAttribute("pm");
    	if (!iri.equals("1")) {
        	if (pm.getNode(iri) instanceof Activity) {
        		Activity act = (Activity)pm.getNode(iri);
            	model.addAttribute("act", pm.getNode(iri));
            	model.addAttribute("arts", act.getOutputs());
			} else {
				model.addAttribute("endBool", true);
			}

		} else {
			model.addAttribute("arts", null);
		}

    	return "enactEnd";
    }

    @RequestMapping("/inarts")
    public String selectActivity(Model model, @RequestParam String iri, HttpServletRequest request) throws Exception {
    	PT pt = (PT)request.getSession().getAttribute("trace");
    	List<PMTrigger> acts = (List<PMTrigger>) request.getSession().getAttribute("acts");
    	if (!iri.equals("1")) {
    		PMTrigger element = findPMTrigger(acts, iri);
        	if (element.getPort() != null) {
        		Activity act = (Activity)element.getPort().getActivity();
        		List<Artifact> inputs = act.getInputs();
        		List<TraceArtifact> arts = new ArrayList<TraceArtifact>();
        		for (Artifact artifact : inputs) {
        			Timestamp time = Timestamp.valueOf("1900-01-01 00:00:00");
        			TraceArtifact chosen = null;
        			for (Event ev : pt.getEvents()) {
        				if (ev instanceof EndActivityEvent) {
							List<TraceArtifact> producedArtifacts = ((EndActivityEvent) ev).getProducedArtifacts();
							for (TraceArtifact traceArtifact : producedArtifacts) {
								if (traceArtifact.getRelatesTo().getIri().equals(artifact.getIri()) && traceArtifact.getTimestamp().after(time)) {
									chosen = traceArtifact;
								}
							}
						}
					}
        			if (chosen != null) {
        				arts.add(chosen);
					}
				}
            	//model.addAttribute("arts", arts);
            	request.getSession().setAttribute("arts", arts);
            	model.addAttribute("storageURL", env.getProperty("storageURL"));
			} else {
				model.addAttribute("endBool", true);
			}

		} else {
			request.getSession().setAttribute("arts", null);
		}
    	request.getSession().setAttribute("automated",controller.getAutomatedActivities(pt));
    	model.addAttribute("current", iri);
    	return "enact";
    }


    // Helper methods

    private List<PMTrigger> findElements(PM pm, List<Pair<String, String>> iris) {
		List<PMTrigger> acts = new ArrayList<PMTrigger>();
		for (Pair<String,String> pair : iris) {
			PMTrigger tr = new PMTrigger();
			if (pair.getSnd() != null) {
				List<ControlInputPort> ctrlInPorts = ((Activity)pm.getNode(pair.getSnd())).getCtrlInPorts();
				ControlInputPort port = null;
				for (ControlInputPort ctr : ctrlInPorts) {
					if (ctr.getIri().equals(pair.getFst())) {
						port = ctr;
						break;
					}
				}
				tr.setPort(port);

			} else {
				tr.setNode((FinalNode)pm.getNode(pair.getFst()));
			}
			acts.add(tr);
		}
		return acts;
	}

    private PMTrigger findPMTrigger(List<PMTrigger> acts, String iri) {
		for (PMTrigger pmTrigger : acts) {
			if (pmTrigger.getIri().equals(iri)) {
				return pmTrigger;
			}
		}
		return null;
	}

	private void removeElement(List<PMTrigger> acts, String iri) {
		for (PMTrigger pmTrigger : acts) {
			if (pmTrigger.getIri().equals(iri)) {
				acts.remove(pmTrigger);
				break;
			}
		}

	}
}
