package schema;
import model.Obj;

typedef MarkerDesc = {
	var id : String;
	var height : Int;
	var width : Int;
	var path : String;
	var style : String;
}

class Svg {
	public static var coordRE : EReg = new EReg("^translate\\(([0-9]+,[0-9]+)\\)$", "");

	public static inline var PADDING : Int = 3;
	public static inline var MARGIN : Int = 60;
	public static inline var RXY : Int = 20;

	public static inline var LETTER_HEIGHT : Int = 16;
	#if (sys || nodejs)
	public static inline var TEXT_STYLE : String = "font-family:'Dejavu Sans Mono', monospace;font-size:16px";
	public static inline var LETTER_WIDTH : Float = 10;
	#else
	public static inline var TEXT_STYLE : String = "font-family:'Roboto Mono';font-size:16px";
	public static inline var LETTER_WIDTH : Float = 9.5;
	#end

	public static inline var UNDERLINE_STYLE : String = "text-decoration:underline";
	public static inline var RECT_STYLE : String = "stroke:black";
	public static inline var LINE_STYLE : String = "fill:none;stroke:black";
	public static inline var WHITE_STYLE : String = "fill:white;stroke:black";
	public static inline var DOTTED_STYLE : String = "2,2";
	public static var MARKERS : Array<MarkerDesc> = [
		{	id : "arrowMarker", height : PADDING * 2, width : PADDING,
			path : 'M 0 0 L $PADDING $PADDING L 0 ${PADDING * 2} Z',
			style : "fill:black;stroke:black" },
		{	id : "inheritMarker", height : PADDING * 3, width : PADDING * 3,
			path : 'M 0 0 L ${PADDING * 3} ${PADDING * 1.5} L 0 ${PADDING * 3} Z',
			style : WHITE_STYLE },
		{	id : "aggregationMarker", height : PADDING * 3, width : PADDING * 4,
			path : 'M 0 ${PADDING * 1.5} L ${PADDING * 2} 0 L ${PADDING * 4} ${PADDING * 1.5} L ${PADDING * 2} ${PADDING * 3} Z',
			style : WHITE_STYLE },
		{	id : "compositionMarker", height : PADDING * 3, width : PADDING * 4,
			path : 'M 0 ${PADDING * 1.5} L ${PADDING * 2} 0 L ${PADDING * 4} ${PADDING * 1.5} L ${PADDING * 2} ${PADDING * 3} Z',
			style : "fill:black;stroke:black" }
	];
	public static inline var FILL_WHITE_STYLE : String = "fill:white";
	//public static inline var FILL_GREY_STYLE : String = "fill:lightgrey";

	public static function initSvg(doc : Xml) : Xml {
		var svg : Xml = Xml.createElement("svg");
		doc.addChild(svg);
		svg.set("xmlns", "http://www.w3.org/2000/svg");
		var defs : Xml = Xml.createElement("defs");
		svg.addChild(defs);
		#if (js && !nodejs)
		var fontStyle : Xml = Xml.createElement("style");
		defs.addChild(fontStyle);
		fontStyle.addChild(Xml.createPCData("@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap')"));
		#end
		var css : Xml = Xml.createElement("style");
		defs.addChild(css);
		css.addChild(Xml.createPCData("text,text>tspan{" + TEXT_STYLE + "} rect{" + RECT_STYLE + "} line,path{" + LINE_STYLE + "}"));

		for (desc in MARKERS) {
			var marker : Xml = Xml.createElement("marker");
			marker.set("id", desc.id);
			marker.set("orient", "auto");
			marker.set("refX", '${desc.width + 1}');
			marker.set("refY", '${desc.height / 2}');
			marker.set("markerHeight", '${desc.height}');
			marker.set("markerWidth", '${desc.width}');
			var path : Xml = Xml.createElement("path");
			path.set("style", desc.style);
			path.set("d", desc.path);
			marker.addChild(path);
			defs.addChild(marker);
		}
		return svg;
	}

	public static function placeObjects(objects : Iterable<Obj>, svg : Xml, height : Int, width : Int) {
		//calculate max lines heights and columns widths:
		var heights : Array<Int> = [ for (i in 0...height) 0 ];
		var widths : Array<Int> = [ for (i in 0...width) 0 ];
		for (o in objects) {
			if (o.svg != null) {
				var w : Int = Std.parseInt(o.svg.firstChild().get("width"));
				var h : Int = Std.parseInt(o.svg.firstChild().get("height"));
				if (w > widths[o.col]) widths[o.col] = w;
				if (h > heights[o.row]) heights[o.row] = h;
			}
		}
		//calculate lines and columns offsets
		var th : Int = PADDING;
		var lineOffsets : Array<Int> = new Array();
		for (i in 0...height) {
			lineOffsets[i] = th;
			th = th + heights[i] + (heights[i] != 0 ? MARGIN : 0);
		}
		var tw : Int = PADDING;
		var columnOffsets : Array<Int> = new Array();
		for (i in 0...width) {
			columnOffsets[i] = tw;
			tw = tw + widths[i] + (widths[i] != 0 ? MARGIN : 0);
		}

		//translate objects:
		for (o in objects) {
			if (o.svg != null) {
				var w : Int = Std.int(columnOffsets[o.col] + (widths[o.col] - Std.parseInt(o.svg.firstChild().get("width"))) / 2);
				var h : Int = Std.int(lineOffsets[o.row] + (heights[o.row] - Std.parseInt(o.svg.firstChild().get("height"))) / 2);
				o.svg.set("transform", 'translate($w,$h)');
			}
		}

		//th = th - MARGIN + PADDING;
		//tw = tw - MARGIN + PADDING;
		svg.set("width", Std.string(tw));
		svg.set("height", Std.string(th));
	}

	static function getTransform(svg : Xml) : Point {
		var transform = svg.get("transform");
		var p : Point = { x : 0, y : 0 };
		if (transform != null) {
			var xy : Array<String> = Svg.coordRE.replace(svg.get("transform"), "$1").split(","); //"translate(x,y)" -> [x, y]
			p.x = Std.parseInt(xy[0]);
			p.y = Std.parseInt(xy[1]);
		}
		return p;
	}

	public static function getBox(svg : Xml) : Box {
		var root = svg.firstChild();
		return new Box(getTransform(svg), Std.parseInt(root.get("width")), Std.parseInt(root.get("height")));
	}

	public static function getSvgLine(line : Line, startLabel : String, endLabel : String, ?endMarkerId : String) : Xml {
		var g : Xml = Xml.createElement("g");
		var l : Xml = Xml.createElement("line");
		g.addChild(l);
		l.set("x1", Std.string(line.start.x));
		l.set("x2", Std.string(line.end.x));
		l.set("y1", Std.string(line.start.y));
		l.set("y2", Std.string(line.end.y));
		addLineLabels(line, startLabel, endLabel, g);
		if (endMarkerId != null) {
			l.set("marker-end", 'url(#$endMarkerId)');
		}
		return g;
	}

	public static function getSvgCurve(curve : Curve, startLabel : String, endLabel : String, ?endMarkerId : String) : Xml {
		var g : Xml = Xml.createElement("g");
		var p : Xml = Xml.createElement("path");
		g.addChild(p);
		p.set("d", 'M ${curve.start.x} ${curve.start.y} Q ${curve.middle.x} ${curve.middle.y} ${curve.end.x} ${curve.end.y}');
		addLineLabels(curve, startLabel, endLabel, g);
		if (endMarkerId != null) {
			p.set("marker-end", 'url(#$endMarkerId)');
		}
		return g;
	}

	public static function getQuadCurve(qc : QuadCurve, startLabel : String, endLabel : String, ?endMarkerId : String) : Xml {
		var g : Xml = Xml.createElement("g");
		var p : Xml = Xml.createElement("path");
		g.addChild(p);
		p.set("d", 'M ${qc.start.x} ${qc.start.y} L ${qc.point1.x} ${qc.point1.y} L ${qc.point2.x} ${qc.point2.y} L ${qc.end.x} ${qc.end.y}');
		addLineLabels(qc, startLabel, endLabel, g);
		if (endMarkerId != null) {
			p.set("marker-end", 'url(#$endMarkerId)');
		}
		return g;
	}

	public static function addLineLabels(line : Line, startLabel : String, endLabel : String, g : Xml, pos : Int = 1) {
		if (startLabel != null) {
			var sLabel : Xml = Xml.createElement("text");
			g.addChild(sLabel);
			sLabel.addChild(Xml.createPCData(startLabel));
			var sLabelPos : Point = line.getLabelPos(startLabel, false, pos);
			sLabel.set("x", Std.string(sLabelPos.x));
			sLabel.set("y", Std.string(sLabelPos.y));
		}
		if (endLabel != null) {
			var eLabel : Xml = Xml.createElement("text");
			g.addChild(eLabel);
			eLabel.addChild(Xml.createPCData(endLabel));
			var eLabelPos : Point = line.getLabelPos(endLabel, true, pos);
			eLabel.set("x", Std.string(eLabelPos.x));
			eLabel.set("y", Std.string(eLabelPos.y));
		}
	}
}
