package schema;

import haxe.display.Protocol.HaxeNotificationMethod;
import model.*;

@:expose
class MCDToSVG {
	/**
	 * génère un diagramme au format SVG à partir d'un MCD
	 * @param diagram le MCD
	 * @return le diagramme SVG (sous la forme d'un objet XML)
	 */
	public static function generateSVG(diagram : Diagram) : Xml {
		var doc : Xml = Xml.createDocument();
		var svg : Xml = Svg.initSvg(doc);

		for (set in diagram) {
			if (Std.isOfType(set, Association)) {
				set.svg = generateAssociationSVG(cast(set, Association));
			} else {
				set.svg = generateSetSVG(set);
			}
			svg.addChild(set.svg);
		}

		Svg.placeObjects(diagram, svg, diagram.height, diagram.width);
		drawLines(diagram, svg);
		return doc;
	}

	static function generateAssociationSVG(set : Association) : Xml {
		var fieldsCount : Int = 0;
		var chrWidth : Int = set.name.length;
		for (field in set.fields) {
			fieldsCount++;
			if (field.name.length > chrWidth) {
				chrWidth = field.name.length;
			}
		}
		var g : Xml = Xml.createElement("g");
		if (set.isAgregation) {
			var rect : Xml = Xml.createElement("rect");
			g.addChild(rect);
			rect.set("height", Std.string((fieldsCount == 0 ? 2 : (fieldsCount + 1)) * Svg.LETTER_HEIGHT + Svg.PADDING * 2));
			rect.set("width", Std.string(chrWidth * Svg.LETTER_WIDTH + Svg.PADDING * 2 + Svg.RXY / 2));
			rect.set("style", Svg.FILL_WHITE_STYLE);
		}
		var rect : Xml = Xml.createElement("rect");
		g.addChild(rect);
		rect.set("height", Std.string((fieldsCount == 0 ? 2 : (fieldsCount + 1)) * Svg.LETTER_HEIGHT + Svg.PADDING * 2));
		rect.set("width", Std.string(chrWidth * Svg.LETTER_WIDTH + Svg.PADDING * 2 + Svg.RXY / 2));
		rect.set("rx", Std.string(Svg.RXY));
		rect.set("ry", Std.string(Svg.RXY));
		rect.set("style", /*Svg.FILL_GREY_STYLE*/Svg.FILL_WHITE_STYLE);

		var line : Xml = Xml.createElement("line");
		g.addChild(line);
		line.set("x1", "0");
		line.set("x2", Std.string(chrWidth * Svg.LETTER_WIDTH + Svg.PADDING * 2 + Svg.RXY / 2));
		line.set("y1", Std.string(Svg.LETTER_HEIGHT + Svg.PADDING));
		line.set("y2", Std.string(Svg.LETTER_HEIGHT + Svg.PADDING));

		var title : Xml = Xml.createElement("text");
		g.addChild(title);
		title.addChild(Xml.createPCData(set.name));
		title.set("x", Std.string(Svg.PADDING + Svg.RXY / 4 + (chrWidth - set.name.length) * Svg.LETTER_WIDTH / 2));
		title.set("y", Std.string(Svg.LETTER_HEIGHT + 1));

		var text : Xml = Xml.createElement("text");
		g.addChild(text);
		for (i in 0...set.fields.length) {
			var tspan : Xml = Xml.createElement("tspan");
			text.addChild(tspan);
			tspan.set("x", Std.string(Svg.PADDING + Svg.RXY / 4));
			tspan.set("y", Std.string(Svg.LETTER_HEIGHT * (i + 2) + Svg.PADDING));
			tspan.addChild(Xml.createPCData(set.fields[i].name));
		}
		return g;
	}

	static function generateSetSVG(set : Set) : Xml {
		var fieldsCount : Int = 0;
		var chrWidth : Int = set.name.length;
		for (field in set.fields) {
			if (!field.isStatic) {
				fieldsCount++;
				if (field.name.length > chrWidth) {
					chrWidth = field.name.length;
				}
			}
		}
		var g : Xml = Xml.createElement("g");
		var rect : Xml = Xml.createElement("rect");
		g.addChild(rect);
		rect.set("height", Std.string((fieldsCount == 0 ? 2 : (fieldsCount + 1)) * Svg.LETTER_HEIGHT + Svg.PADDING * 2));
		rect.set("width", Std.string(Std.int(chrWidth * Svg.LETTER_WIDTH + Svg.PADDING * 2)));
		rect.set("style", Svg.FILL_WHITE_STYLE);

		var titleBG : Xml = Xml.createElement("rect");
		g.addChild(titleBG);
		titleBG.set("height", Std.string(Svg.LETTER_HEIGHT + Svg.PADDING));
		titleBG.set("width", Std.string(chrWidth * Svg.LETTER_WIDTH + Svg.PADDING * 2));
		titleBG.set("style", /*Svg.FILL_GREY_STYLE*/Svg.FILL_WHITE_STYLE);

		var title : Xml = Xml.createElement("text");
		g.addChild(title);
		title.addChild(Xml.createPCData(set.name));
		title.set("x", Std.string(Svg.PADDING + (chrWidth - set.name.length) * Svg.LETTER_WIDTH / 2));
		title.set("y", Std.string(Svg.LETTER_HEIGHT));

		var text : Xml = Xml.createElement("text");
		g.addChild(text);
		for (i in 0...set.fields.length) {
			if (!set.fields[i].isStatic) {
				var tspan : Xml = Xml.createElement("tspan");
				text.addChild(tspan);
				tspan.set("x", Std.string(Svg.PADDING / 2));
				tspan.set("y", Std.string(Svg.LETTER_HEIGHT * (i + 2) + Svg.PADDING));
				if (set.id.indexOf(set.fields[i].name) != -1) {
					tspan.set("style", Svg.UNDERLINE_STYLE);
				}
				tspan.addChild(Xml.createPCData(set.fields[i].name));
			}
		}

		if (set.subsets.length != 0) {
			g.addChild(generateInheritanceSVG(set.subsetType, Std.parseInt(g.firstChild().get("width")), Std.parseInt(g.firstChild().get("height"))));
		}
		return g;
	}

	static function generateInheritanceSVG(type : String, setWidth : Int, setHeight : Int) : Xml {
		//~ var chrWidth = 2; //<path d="M125,85 a1,1 0 1,0 -215,0"
		var w : Int = Std.int(2 * Svg.LETTER_WIDTH + Svg.PADDING * 2 + Svg.RXY / 2);
		var h : Int = Svg.LETTER_HEIGHT + Svg.PADDING;
		var g : Xml = Xml.createElement("g");

		var line : Xml = Xml.createElement("line");
		g.addChild(line);
		line.set("x1", Std.string(Std.int(w / 2)));
		line.set("x2", Std.string(Std.int(w / 2)));
		line.set("y1", "0");
		line.set("y2", Std.string(Svg.PADDING)); //Svg.MARGIN for a box

		var path : Xml = Xml.createElement("path");
		g.addChild(path);
		path.set("d", 'M $w ${h + Svg.PADDING} a1,1 0 1,0 -$w 0 l $w 0'); //Svg.MARGIN for a box
		path.set("style", /*Svg.FILL_GREY_STYLE*/Svg.FILL_WHITE_STYLE);

		var title : Xml = Xml.createElement("text");
		g.addChild(title);
		title.addChild(Xml.createPCData(type));
		title.set("x", Std.string(Svg.PADDING + Svg.RXY / 4 + (2 - type.length) * Svg.LETTER_WIDTH / 2));
		title.set("y", Std.string(Svg.LETTER_HEIGHT + 1 + Svg.PADDING)); //Svg.MARGIN for a box

		g.set("transform", "translate(" + Std.string(Std.int((setWidth - w) / 2)) + "," + Std.string(setHeight) + ")");
		return g;
	}

	static function drawLines(diagram : Diagram, svg : Xml) {
		for (assoc in diagram.associations) {
			if (assoc.isReflexive()) {
				var curves = getDoubleCurves(assoc.links[0].set, assoc);
				svg.addChild(Svg.getSvgCurve(curves[0], assoc.links[1].getMCDCards(), null, null)); //inverted cards & arrow
				svg.addChild(Svg.getSvgCurve(curves[1], null, assoc.links[0].getMCDCards(), assoc.links[0].hasArrow ? Svg.MARKERS[0].id : null));
			} else {
				for (link in assoc.links) {
					var line = Box.getLine(Svg.getBox(assoc.svg), Svg.getBox(link.set.svg));
					svg.addChild(Svg.getSvgLine(line, null, link.getMCDCards(), link.hasArrow ? Svg.MARKERS[0].id : null));
				}
			}
		}
		for (p in diagram.sets) {
			if (p.subsets.length != 0) {
				for (c in p.subsets) {
					var parentBox = Svg.getBox(p.svg);
					var line = Box.getLine(parentBox, Svg.getBox(c.svg));
					line.start.x = parentBox.center.x;
					line.start.y = parentBox.topleft.y + parentBox.height + Svg.LETTER_HEIGHT + Std.int(Svg.PADDING * 2);
					svg.addChild(Svg.getSvgLine(line, null, null, null));
				}
			}
		}
	}

	public static function getDoubleCurves (set : Set, assoc : Association) : Array<Curve> {
		var box1 = Svg.getBox(set.svg);
		var box2 = Svg.getBox(assoc.svg);
		var curves : Array<Curve> = new Array();
		if (Box.areSameLine (box1, box2)) { //if on the same line
			var baseLine = Box.getLine (box1, box2);
			curves.push (new Curve ({ x : baseLine.start.x, y :box1.topleft.y + Params.LETTER_HEIGHT },
									{ x : box2.center.x, y : box2.topleft.y }, 0, Params.BEND_DST));
			curves.push (new Curve ({ x : box2.center.x, y : box2.topleft.y + box2.height },
									{ x : baseLine.start.x, y : box1.topleft.y + box1.height - Params.LETTER_HEIGHT },
									0, -Params.BEND_DST));
		} else if (Box.areSameColumn (box1, box2)) { //if on the same column
			var baseLine = Box.getLine (box1, box2);
			curves.push (new Curve ({ x : box1.topleft.x + Params.LETTER_HEIGHT, y : baseLine.start.y },
									{ x : box2.topleft.x, y : box2.center.y }, -Params.BEND_DST, 0));
			curves.push (new Curve ({ x : box2.topleft.x + box2.width, y : box2.center.y },
									{ x : box1.topleft.x + box1.width - Params.LETTER_HEIGHT, y : baseLine.start.y },
									Params.BEND_DST, 0));
		} else {
			var curve1 : Curve = null;
			var curve2 : Curve = null;
			if (Box.leftToRight (box1, box2)) {
				if (Box.topToBottom (box1, box2)) {
					curve1 = new Curve ({ x : box1.topleft.x + box1.width, y : box1.topleft.y + box1.height - 2 * Params.LETTER_HEIGHT },
										{ x : box2.center.x, y : box2.topleft.y }, Params.BEND_DST * 2, Params.BEND_DST * 2);
					curve2 = new Curve ({ x : box2.topleft.x, y : box2.center.y },
										{ x : box1.topleft.x + box1.width - 2 * Params.LETTER_HEIGHT, y : box1.topleft.y + box1.height },
										-Params.BEND_DST * 2, -Params.BEND_DST * 2);
				} else {
					curve1 = new Curve ({ x : box1.topleft.x + box1.width - 2 * Params.LETTER_HEIGHT, y : box1.topleft.y },
										{ x : box2.topleft.x, y : box2.center.y }, -Params.BEND_DST * 2, Params.BEND_DST * 2);
					curve2 = new Curve ({ x : box2.center.x, y : box2.topleft.y + box2.height },
										{ x : box1.topleft.x + box1.width, y : box1.topleft.y + 2 * Params.LETTER_HEIGHT },
										Params.BEND_DST * 2, -Params.BEND_DST * 2);
				}
			} else {
				if (Box.topToBottom (box1, box2)) {
					curve1 = new Curve ({ x : box1.topleft.x, y : box1.topleft.y + box1.height - 2 * Params.LETTER_HEIGHT },
										{ x : box2.center.x, y : box2.topleft.y }, -Params.BEND_DST * 2, Params.BEND_DST * 2);
					curve2 = new Curve ({ x : box2.topleft.x + box2.width, y : box2.center.y },
										{ x : box1.topleft.x + 2 * Params.LETTER_HEIGHT, y : box1.topleft.y + box1.height },
										Params.BEND_DST * 2, -Params.BEND_DST * 2);
				} else {
					curve1 = new Curve ({ x : box1.topleft.x + 2 * Params.LETTER_HEIGHT, y : box1.topleft.y },
										{ x : box2.topleft.x + box2.width, y : box2.center.y }, Params.BEND_DST * 2, Params.BEND_DST * 2);
					curve2 = new Curve ({ x : box2.center.x, y : box2.topleft.y + box2.height },
										{ x : box1.topleft.x, y : box1.topleft.y + 2 * Params.LETTER_HEIGHT },
										-Params.BEND_DST * 2, -Params.BEND_DST * 2);
				}
			}
			curves.push (curve1);
			curves.push (curve2);
		}
		return curves;
	}
}
