/**
 * CertainTrust SDK
 *
 * Implements the computational trust model "CertainTrust"
 * in JavaScript.
 * See <http://www.tk.informatik.tu-darmstadt.de/de/research/smart-security-and-trust/> for further details.
 *
 *
 * Telecooperation Department, Technische Universität Darmstadt
 * <http://www.tk.informatik.tu-darmstadt.de/>
 *
 * Prof. Dr. Max Mühlhäuser <max@informatik.tu-darmstadt.de>
 * Florian Volk <florian.volk@cased.de>
 *
 *
 * @author	Daniel Dieth
 * @author	David Kalnischkies
 * @author	Maria Pelevina
 * @version	1.0
 */

 /* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//##########################################################################################
// CertainTrustTViz constructor
//##########################################################################################
var CertainTrustTViz = function(data, config) {
	this.NR = CertainTrustTVizElement.length();
	CertainTrustTVizElement.push(this);
	this.certainTrusts = [];
	this.add(data);

	// set sane defaults for config if nothing is set
	if (config === undefined) config = {};
	if (config.id === undefined) config.id = 'certaintrust-tviz-' + this.NR;
	// design your widget
	if (config.canvas === undefined) config.canvas = {};
	if (config.canvas.height === undefined) config.canvas.height = 440;
	if (config.canvas.width === undefined) config.canvas.width = 440;
	if (config.canvas.inner === undefined) config.canvas.inner = 30;
	if (config.middle === undefined) config.middle = 'AVERAGE';
	if (config.onClick === undefined) config.onClick = undefined;
	if (config.onMiddleClick === undefined) config.onMiddleClick = undefined;

	this.ID = config.id;
	this.config = config;

	var element = document.createElement('div');
	element.setAttribute('id', this.ID);
	var dom = this.certainTrusts[0]._insertElement(config, element);

	this.update();

	if (dom !== undefined)
		return dom;
};
CertainTrustTViz.prototype.update = function() {
	var xmlns = 'http://www.w3.org/2000/svg';
	var dotx = this.config.canvas.height / 2;
	var doty = this.config.canvas.width / 2;
	var rotate = 360 - 45;
	var circle_y = this.config.canvas.inner;
	var circle_height = dotx - (2 * circle_y);
	var rotationstep = 360 / this.certainTrusts.length;

	var element = document.getElementById(this.ID);

	// see if we have to redraw thanks to an added/removed CertainTrust element
	if (element.hasChildNodes() === true) {
		var old_arcs = document.getElementById(this.ID + '-arcs');
		if (old_arcs.childNodes.length !== this.certainTrusts.length)
			element.removeChild(element.firstChild);
	}

	var arcs = document.createElementNS(xmlns, 'g');
	arcs.setAttribute('stroke', 'none');
	arcs.setAttribute('class', 'certaintrust-tviz-arcs');
	arcs.setAttribute('id', this.ID + '-arcs');

	// first time run or removed by the if above: full build
	if (element.hasChildNodes() === false)
	{
		// prepare the various elements we need
		var axes = document.createElementNS(xmlns, 'g');
		axes.setAttribute('fill', 'none');
		axes.setAttribute('stroke', 'grey');
		axes.setAttribute('class', 'certaintrust-tviz-axes');
		var desc = document.createElementNS(xmlns, 'g');
		for (var a = 0; a < this.certainTrusts.length; ++a) {
			var rotation = (rotate + (rotationstep * a)) % 360;
			// an axis for each arc to show of how big each arc could be
			var axis = this._generateAxis(xmlns, dotx, doty, circle_height, circle_y, rotation);
			axes.appendChild(axis);
			// the arc representing the CertainTrust element
			var arc = this._generateArc(xmlns, this.certainTrusts[a], dotx, doty, circle_height, circle_y, rotation, rotationstep);
			arcs.appendChild(arc);
			// label for each arc
			var label = this._generateArcLabel(xmlns, this.certainTrusts[a], dotx, doty, circle_height, circle_y, rotation, rotationstep);
			desc.appendChild(label);
		}
		if (this.config.middle !== 'NONE') {
			var total = this._generateTotalInTheMiddle(xmlns, dotx, doty);
			desc.appendChild(total);
		}
		// draw circles to indicate how large the arcs could be
		var circles = this._generateCircles(xmlns, circle_y, circle_height);
		// our new tviz
		var svg = document.createElementNS(xmlns, 'svg');
		svg.setAttribute('height', this.config.canvas.height);
		svg.setAttribute('width', this.config.canvas.width);
		svg.appendChild(circles);
		svg.appendChild(arcs);
		svg.appendChild(axes);
		svg.appendChild(desc);
		// finally, insert tviz
		element.appendChild(svg);
	} else {
		// we have most of the image, so just change what has changed
		element = element.firstChild;
		// get the new arcs set
		for (var a = 0; a < this.certainTrusts.length; ++a) {
			var rotation = (rotate + (rotationstep * a)) % 360;
			var arc = this._generateArc(xmlns, this.certainTrusts[a], dotx, doty, circle_height, circle_y, rotation, rotationstep);
			arcs.appendChild(arc);
		}
		// find the old arcs set and exchange it with the new set
		var old_arcs = document.getElementById(this.ID + '-arcs');
		element.insertBefore(arcs, old_arcs);
		element.removeChild(old_arcs);
		if (this.config.middle !== 'NONE') {
			// and now change the text of label in the middle
			var total = document.getElementById(this.ID + '-middle');
			total.firstChild.nodeValue = this._calcMiddleLabel(this.certainTrusts);
		}
	}
};
CertainTrustTViz.prototype.add = function() {
	for (var i = 0; i < arguments.length; ++i) {
		var ct = arguments[i];
		if (ct instanceof Array) {
			for (var j = 0; j < ct.length; ++j) {
				var c = ct[j];
				this.certainTrusts.push(c);
				c.addObserver(this);
			}
		} else {
			this.certainTrusts.push(ct);
			ct.addObserver(this);
		}
	}
	// don't trigger on first call from constructor
	if (this.ID !== undefined)
		this.update();
};
CertainTrustTViz.prototype.remove = function() {
	for (var i = 0; i < arguments.length; ++i) {
		var ct = arguments[i];
		if (ct instanceof CertainTrust) {
			for (var j = 0; j < ct.length; ++j) {
				for (var k = 0; k < this.certainTrusts.length; ++k) {
					if (this.certainTrusts[k] !== ct[j])
						continue;
					ct[j].deleteObserver(this);
					this.certainTrusts.splice(k, 1);
					break;
				}
			}
		} else {
			for (var k = 0; k < this.certainTrusts.length; ++k) {
				if (this.certainTrusts[k].getName() !== ct)
					continue;
				this.certainTrusts[k].deleteObserver(this);
				this.certainTrusts.splice(k, 1);
				break;
			}
		}
	}
	this.update();
};
CertainTrustTViz.prototype._generateAxis = function(xmlns, dotx, doty, circle_height, circle_y, rotation) {
		var point = this.certainTrusts[0]._pointOnCircle(dotx, doty, rotation, circle_y);
		var axis = document.createElementNS(xmlns, 'g');
		axis.setAttribute('fill', 'none');
		axis.setAttribute('stroke', 'grey');
		var placement;
		if (rotation >= 270 && rotation < 360 || rotation >= 0 && rotation < 90) {
			axis.setAttribute('transform', 'translate(' + point[1] + ',' + point[0] + ') rotate(' + (rotation) + ')');
			axis.setAttribute('class', 'certaintrust-tviz-axis-left');
			placement = -1;
		} else {
			axis.setAttribute('transform', 'translate(' + point[1] + ',' + point[0] + ') rotate(' + (rotation - 180) + ')');
			axis.setAttribute('class', 'certaintrust-tviz-axis-right');
			placement = 1;
		}
		var axis_line = document.createElementNS(xmlns, 'line');
		axis_line.setAttribute('fill', 'inherit');
		axis_line.setAttribute('stroke', 'inherit');
		axis_line.setAttribute('x1', 0);
		axis_line.setAttribute('y1', 0);
		axis_line.setAttribute('x2', 0);
		axis_line.setAttribute('y2', placement * circle_height);
		axis.appendChild(axis_line);
		for (var t = 0; t < 6; ++t) {
			var y = placement * (circle_height * (t / 5));
			var tick_line = document.createElementNS(xmlns, 'line');
			tick_line.setAttribute('fill', 'inherit');
			tick_line.setAttribute('stroke', 'inherit');
			tick_line.setAttribute('x1', 0);
			tick_line.setAttribute('y1', y);
			tick_line.setAttribute('x2', placement * 6);
			tick_line.setAttribute('y2', y);
			var tick_text = document.createElementNS(xmlns, 'text');
			tick_text.setAttribute('fill', 'grey');
			tick_text.setAttribute('x', placement * 9);
			tick_text.setAttribute('y', y);
			tick_text.setAttribute('dy', '.5em');
			tick_text.setAttribute('font-size', '.5em');
			var msg = document.createTextNode(t * 20);
			tick_text.appendChild(msg);
			var tick = document.createElementNS(xmlns, 'g');
			tick.setAttribute('fill', 'inherit');
			tick.setAttribute('stroke', 'inherit');
			tick.appendChild(tick_line);
			tick.appendChild(tick_text);
			axis.appendChild(tick);
		}
	return axis;
};
CertainTrustTViz.prototype._generateArc = function(xmlns, certainTrust, dotx, doty, circle_height, circle_y, rotation, rotationstep) {
	var arc_width = rotationstep * certainTrust.getC();
	var arc_free = (rotationstep - arc_width) / 2;
	var arc_low_left = certainTrust._pointOnCircle(dotx, doty, (rotation + arc_free), circle_y);
	var arc_low_right = certainTrust._pointOnCircle(dotx, doty, (rotation + rotationstep - arc_free), circle_y);
	var arc_height = (circle_height * certainTrust.getT()) + circle_y;
	var arc_high_left = certainTrust._pointOnCircle(dotx, doty, (rotation + arc_free), arc_height);
	var arc_high_right = certainTrust._pointOnCircle(dotx, doty, (rotation + rotationstep - arc_free), arc_height);
	var color = certainTrust._getColor(certainTrust.getC(), certainTrust.getT(), certainTrust.getF());
	var arc = document.createElementNS(xmlns, 'path');
	arc.setAttribute('fill', 'rgb(' + Math.round(color[0]) + ',' + Math.round(color[1]) + ',' + Math.round(color[2]) + ')');
	arc.setAttribute('stroke', 'inherit');
	arc.setAttribute('d', 'M' + arc_low_left[1] + ' ' + arc_low_left[0] +
		'A' + circle_y + ',' + circle_y + ' 0 0,1 ' + arc_low_right[1] + ',' + arc_low_right[0] +
		'L' + arc_high_right[1] + ' ' + arc_high_right[0] +
		'A' + arc_height + ',' + arc_height + ' 0 0,0 ' + arc_high_left[1] + ',' + arc_high_left[0] +
		'z');
	arc.setAttribute('title', 'Trust: ' + certainTrust.getT() + '\nCertainty: ' + certainTrust.getC());

	if (this.config.onClick !== undefined)
	{
		var onClick = this.config.onClick;
		arc.addEventListener("click", function(e) { onClick(certainTrust); }, false);
	}
	return arc;
};
CertainTrustTViz.prototype._generateArcLabel = function(xmlns, certainTrust, dotx, doty, circle_height, circle_y, rotation, rotationstep) {
	var label = document.createElementNS(xmlns, 'text');
	label.setAttribute('fill', 'black');
	var outerpoint = this.certainTrusts[0]._pointOnCircle(dotx, doty, (rotation + (rotationstep / 2)), (circle_height + circle_y));
	label.setAttribute('x', outerpoint[1]);
	label.setAttribute('y', outerpoint[0]);
	label.setAttribute('dy', '.5em');
	label.setAttribute('text-anchor', 'middle');
	var msg = document.createTextNode(certainTrust.getName());
	label.appendChild(msg);
	return label;
};
CertainTrustTViz.prototype._generateTotalInTheMiddle = function(xmlns, dotx, doty) {
	var total = document.createElementNS(xmlns, 'text');
	total.setAttribute('fill', 'grey');
	total.setAttribute('x', dotx);
	total.setAttribute('y', doty);
	total.setAttribute('dy', '.5em');
	total.setAttribute('text-anchor', 'middle');
	total.setAttribute('id', this.ID + '-middle');
	var msg = document.createTextNode(this._calcMiddleLabel(this.certainTrusts));
	total.appendChild(msg);
	if (this.config.onMiddleClick !== undefined)
	{
		var onMiddleClick = this.config.onMiddleClick;
		total.addEventListener("click", function(e) { onMiddleClick(); }, false);
	}
	return total;
};
CertainTrustTViz.prototype._generateCircles = function(xmlns, circle_y, circle_height) {
	var circles = document.createElementNS(xmlns, 'g');
	circles.setAttribute('fill', 'none');
	circles.setAttribute('stroke', 'lightgrey');
	circles.setAttribute('class', 'certaintrust-tviz-circles');
	for (var i = 0; i < 11; ++i) {
		var circle = document.createElementNS(xmlns, 'circle');
		circle.setAttribute('cx', '50%');
		circle.setAttribute('cy', '50%');
		var r = circle_y + (circle_height * (i / 10));
		circle.setAttribute('r', r);
		circle.setAttribute('fill', 'inherit');
		circle.setAttribute('stroke', 'inherit');
		circles.appendChild(circle);
	}
	return circles;
};
// Calculate the average over all included CertainTrusts
CertainTrustTViz.prototype._calcMiddleLabel = function(indata) {
	if (this.config.middle === "NONE")
		return "";
	else if (this.config.middle === "AVERAGE") {
		var avg = 0.0;
		for (var i = 0; i < indata.length; i++) {
			avg += indata[i].getExpectation();
		}
		return (Math.round(avg/indata.length*1000)/10) + "%";
	} else if (this.config.middle === "AND") {
		var result = indata[0];
		for (var i = 1; i < indata.length; i++) {
			result = result.AND(indata[i]);
		}
		return (Math.round(result.getExpectation()*1000)/10) + "%";
	} else
		return this.config.middle(indata);
};
// global var for storing and accessing all the widgets
var CertainTrustTVizElement = { _elements: [],
	ById: function(id) {
		for (var i = 0; i < CertainTrustTVizElement._elements.length; ++i) {
			if (CertainTrustTVizElement._elements[i].ID === id)
				return CertainTrustTVizElement._elements[i];
		}
		return null;
	},
	ByNr: function(nr) { return CertainTrustTVizElement._elements[nr]; },
	push: function(cte) { CertainTrustTVizElement._elements.push(cte); },
	length: function() { return CertainTrustTVizElement._elements.length; }
};
