package pert;
import java.awt.Color;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.jgrapht.graph.SimpleDirectedGraph;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import utils.TimingInterceptor;

public class GraphEditorUtils {
	/**
	 * Charge un diagramme de PERT au format GraphEditor XML.
	 * @param fileName le chemin d'accès du fichier à lire
	 * @return le graphe du diagramme de PERT
	 */
	public static SimpleDirectedGraph<PertNode, PertEdge> loadGraph(String fileName) {
		return parseGraphXml(new File(fileName));
	}

	/**
	 * Sauvegarde le graphe d'un diagramme de PERT au format GraphEditor XML.
	 * @param fileName le chemin d'accès du fichier à écrire
	 * @param graphTitle le titre du graphe
	 * @param graph le graphe du diagramme de PERT
	 */
	public static void saveGraph(String fileName, String graphTitle, SimpleDirectedGraph<PertNode, PertEdge> graph) {
		try(PrintStream out = new PrintStream(fileName)){
			out.print(generateGraphXml(graphTitle, graph));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/*
	 * Méthodes utilitaires
	 */
	private static String generateGraphXml(String title, SimpleDirectedGraph<PertNode, PertEdge> graph) {
		String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
		xml += "<!--Created with Graph Editor-->\n";
		xml += "<graph title=\"" + title + "\" type=\"PERT\">\n";

		xml += "<nodes>\n";
		for (PertNode PertNode: graph.vertexSet()) {
			xml += "    <node id=\""+ PertNode.getId() +"\">\n";
			xml += "        <x>" + PertNode.getX() + "</x>\n";
			xml += "        <y>" + PertNode.getY() + "</y>\n";
			xml += "        <radius>" + PertNode.getRadius() + "</radius>\n";
			xml += "        <label>" + PertNode.getLabel() + "</label>\n";
			String colorHex = "#" + Integer.toHexString(PertNode.getColor().getRGB()).substring(2).toUpperCase();
			xml += "        <color>" + colorHex + "</color>\n";
			xml += "        <start>" + PertNode.getStart() + "</start>\n";
			xml += "        <end>" + PertNode.getEnd() + "</end>\n";
			xml += "    </node>\n";
		}
		xml += "</nodes>\n";

		xml += "<edges>\n";
		for (PertEdge edge: graph.edgeSet()) {
			xml += "    <edge>\n";
			xml += "        <source>" + graph.getEdgeSource(edge).getId() + "</source>\n";
			xml += "        <target>" + graph.getEdgeTarget(edge).getId() + "</target>\n";
			xml += "        <stroke>" + edge.getStroke() + "</stroke>\n";
			xml += "        <label>" + edge.getTask() + "/" + edge.getDuration() + "</label>\n";
			String colorHex = "#" + Integer.toHexString(edge.getColor().getRGB()).substring(2).toUpperCase();
			xml += "        <color>" + colorHex + "</color>\n";
			xml += "    </edge>\n";
		}
		xml += "</edges>\n";
		xml += "</graph>\n";
		return xml;
	}

	private static SimpleDirectedGraph<PertNode, PertEdge> parseGraphXml(File file) {
		DocumentBuilder builder;
		Document doc;
		SimpleDirectedGraph<PertNode, PertEdge> graph = new SimpleDirectedGraph<>(PertEdge.class);

		try {
			builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			doc = builder.parse(file);
			doc.getDocumentElement().normalize();

			// <graph title="xxx" type="Undirected|Directed|PERT">
			Element graphElement = doc.getDocumentElement();
			if (!"graph".equals(graphElement.getNodeName())) return null;
			String type = graphElement.getAttribute("type");
			if (!"PERT".equals(type)) return null;

			// <nodes> ... </nodes>
			NodeList nodes = graphElement.getElementsByTagName("node");
			Map<Integer, PertNode> graphNodes = new HashMap<>();
			int maxId = 1;
			for (int i = 0; i < nodes.getLength(); i++) {
				// <node> ... </node>
				org.w3c.dom.Node node = nodes.item(i);
				if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
					Element element = (Element) node;
					int id = Integer.valueOf(element.getAttribute("id"));
					maxId = Math.max(maxId, id);
					int x = getIntegerTag(element, "x");
					int y = getIntegerTag(element, "y");
					int radius = getIntegerTag(element, "radius");
					String label = getStringTag(element, "label");
					String color = getStringTag(element, "color");
					int start = getIntegerTag(element, "start");
					int end = getIntegerTag(element, "end");
					PertNode newNode = new PertNode(id, x, y, radius, label, hexToColor(color), start, end);
					graph.addVertex(newNode);
					graphNodes.put(id, newNode);
				}
			}

			// <edges> ... </edges>
			NodeList edges = graphElement.getElementsByTagName("edge");
			for (int i = 0; i < edges.getLength(); i++) {
				org.w3c.dom.Node node = edges.item(i);
				if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
					// <edge> ... </edge>
					Element element = (Element) node;
					int source = getIntegerTag(element, "source");
					int target = getIntegerTag(element, "target");
					int stroke = getIntegerTag(element, "stroke");
					String label = getStringTag(element, "label");
					int sep = label.indexOf("/");
					String task = (sep == -1) ? label : label.substring(0, sep);
					int duration = (sep == -1) ? 0 : Integer.valueOf(label.substring(sep+1));
					boolean critical = (stroke == 2);
					graph.addEdge(graphNodes.get(source), graphNodes.get(target), new PertEdge(task, duration, critical));
				}
			}
			return graph;
		} catch (ParserConfigurationException | SAXException | IOException | IllegalArgumentException e) {
			e.printStackTrace();
			return null;
		}
	}

	private static int getIntegerTag(Element element, String tagName) {
		NodeList nodes = element.getElementsByTagName(tagName);
		if (nodes.getLength() == 0) return 0;
		String content = nodes.item(0).getTextContent();
		if (Pattern.matches("^ *[+-]?[0-9]+ *$", content)) {
			return Integer.parseInt(content);
		} else {
			return 0;
		}
	}

	private static String getStringTag(Element element, String tagName) {
		NodeList nodes = element.getElementsByTagName(tagName);
		if (nodes.getLength() == 0) return "";
		return nodes.item(0).getTextContent();
	}

	private static Color hexToColor(String color) {
        try {
            if (color.charAt(0) == '#') {
                color = color.substring(1, 7);
            }

            int[] col = new int[3];

            for (int i = 0; i < 3; i++) {
                col[i] = Integer.parseInt(color.substring(i * 2, (i * 2) + 2), 16);
            }

            return new Color(col[0], col[1], col[2]);
        } catch (Exception e) {
            return Color.black;
        }
    }

    /*
     * Exemples d'utilisation
     */
	public static void main(String[] args) {
		// Load a PERT graph from a GraphEditor XML file
		TimingInterceptor ti = new TimingInterceptor();
		SimpleDirectedGraph<PertNode, PertEdge> pert1 = loadGraph("graphs/pert-ex73.xml");
		// Save PERT graph to GraphEditor XML file
		saveGraph("graphs/pert-ex73-saved.xml", "PERT : exemple 1", pert1);

		// Create ex73 PERT graph from scratch
        /*SimpleDirectedGraph<PertNode, PertEdge> pert2 = new SimpleDirectedGraph<>(PertEdge.class);
		PertNode[] nodes = {new PertNode(1, 161, 249, 20, "1", Color.WHITE, 0, 0),
				new PertNode(2, 343, 251, 20, "2", Color.WHITE, 9, 9),
				new PertNode(3, 253, 104, 20, "3", Color.WHITE, 3, 16),
				new PertNode(4, 447, 373, 20, "4", Color.WHITE, 16, 23),
				new PertNode(5, 471, 110, 20, "5", Color.WHITE, 13, 24),
				new PertNode(6, 617, 251, 20, "6", Color.WHITE, 29, 29)};
		for (PertNode node : nodes) {
			pert2.addVertex(node);
		}
		pert2.addEdge(nodes[0], nodes[2], new PertEdge("A", 3, false));
		pert2.addEdge(nodes[2], nodes[4], new PertEdge("D", 8, false));
		pert2.addEdge(nodes[0], nodes[1], new PertEdge("B", 9, true));
		pert2.addEdge(nodes[1], nodes[4], new PertEdge("E", 4, false));
		pert2.addEdge(nodes[0], nodes[3], new PertEdge("C", 5, false));
		pert2.addEdge(nodes[1], nodes[3], new PertEdge("F", 7, false));
		pert2.addEdge(nodes[4], nodes[5], new PertEdge("I", 5, false));
		pert2.addEdge(nodes[1], nodes[5], new PertEdge("G", 20, true));
		pert2.addEdge(nodes[3], nodes[5], new PertEdge("H", 6, false));*/

		/*
		N = 1000, T = 2000, F = 1000 → 0.65 sec
		N = 1000, T = 200000, F = 1000 → 1.29 sec
		N = 1000, T = 490000, F = 1000 → 2.52 sec
		 */
		//PertGenerator generator = new PertGenerator(1000, 2000, 1000, 20);
		//PertGenerator generator = new PertGenerator(1000, 200000, 1000, 20);
		//PertGenerator generator = new PertGenerator(1000, 490000, 1000, 20);
		PertGenerator generator = new PertGenerator(15, 21, 4, 20);
		ti.measureAndDisplay("Exercice generation", generator::generatePertExercise);
		// Save PERT graph to GraphEditor XML file
		saveGraph("graphs/first-graph-test.xml", "PERT : exemple 2", generator.getGraph());
		ti.measureAndDisplay("Precedences calculation", generator::getPrecedences);
		//Map<String, Set<String>> prec = generator.getPrecedences();
		//System.out.println(prec);
	}
}
