/**
 * 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	Maria Pelevina
 * @author	David Kalnischkies
 * @version	1.1
 */
 
 /* 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/. */
 

/**
 * Available Constructors:
 * - CertainTrust(t, c, f, n)
 * - CertainTrust(r, s, n)
 * - CertainTrust(n)
 * optionally arguments can be preceded by name, e.g. CertainTrust(name, r, s, n)
 *
 * t - average rating value, [0; 1], from very negative to very positive
 * c - certainty value, [0; 1] from low certainty (no evidence) to the maximal maximal certainty.
 * f - initial trust value
 * w - weight
 * r - number of positive evidence
 * s - number of negative evidence
 * n - maximal number of expected evidence
 */

var CertainTrust = function() {
	this.weight = 2;
	this.observers = [];

	var offset = 0;
	this.name = "";

	if (this._isString(arguments[0])) {
		this.name = arguments[0];
		offset = 1;
	}
	if (arguments.length == 4 + offset || arguments.length == 5 + offset) {
		// CertainTrust(t, c, f, n, doc)
		// doc is a 'private' parameter
		this.t = arguments[0 + offset];
		this.c = arguments[1 + offset];
		this.f = arguments[2 + offset];
		this.n = arguments[3 + offset];
		this.doc = (arguments.length == 4 + offset) ? 0 : arguments[4 + offset];
		this.r = 0;
		this.s = 0;

		if (this.n <= 0)
			throw "N should be greater than 0. Entered n = " + this.n + "\n";
		if (this.f < 0 && this.f > 1)
			throw "f should lie within [0;1]. Entered f = " + this.f + "\n";
		if (this.c < 0 && this.c > 1)
			throw "c should lie within [0;1]. Entered c = " + this.c + "\n";
		if (this.t < 0 && this.t > 1)
			throw "t should lie within [0;1]. Entered t = " + this.t + "\n";

		this._calculateTCtoRS();
	} else if (arguments.length == 3 + offset) {
		// CertainTrust(r, s, n)
		this.n = arguments[2 + offset];
		this.c = 0;
		this.t = 0.5;
		this.f = 0.5;
		this.r = arguments[0 + offset];
		this.s = arguments[1 + offset];
		this.doc = 0;

		if (this.n <= 0)
			throw "N should be greater than 0. Entered n = " + this.n + "\n";
		if (this.r < 0)
			throw "r should be positive. Entered r = " + this.r + "\n";
		if (this.s < 0)
			throw "s should be positive. Entered s = " + this.s + "\n";

		this._normaliseRS();
		this._calculateRStoTC();
	} else {
		if (arguments.length == 1 + offset) {
			// CertainTrust(n)
			this.n = arguments[0 + offset];
			if (this.n <= 0)
				throw "N should be greater than 0. Entered n = " + this.n + "\n";
			this.c = 0;
			this.t = 0.5;
			this.f = 0.5;
			this.r = 0;
			this.s = 0;
			this.doc = 0;
		}
		else throw "Illegal number of arguments: " + arguments.length + "\n";
	}
};

//=========== Getters =================
CertainTrust.prototype.getName = function() {
	return this.name;
};

CertainTrust.prototype.getC = function() {
	return this.c;
};

CertainTrust.prototype.getT = function() {
	return this.t;
};

CertainTrust.prototype.getF = function() {
	return this.f;
};

CertainTrust.prototype.getR = function() {
	return this.r;
};

CertainTrust.prototype.getS = function() {
	return this.s;
};

CertainTrust.prototype.getN = function() {
	return this.n;
};

CertainTrust.prototype.getDoC = function() {
	return this.doc;
};

CertainTrust.prototype.getExpectation = function() {
	return (this.t * this.c) + ((1 - this.c) * this.f);
};

//=========== Setters =================
/**
 * Resets N value. Renormalises r and s values, recalculates c and t accordingly.
 * @param n - new maximal number of expected evidence
 */
CertainTrust.prototype.setN = function(n) {
	if (n > 0) {
		this.n = n;
		this._normaliseRS();
		this._calculateRStoTC();

		this.notifyObservers();
	}
	else throw "N should be greater than 0. Entered n = " + n + "\n";
};

/**
 * Sets f value.
 * @param f - initial trust value.
 */
CertainTrust.prototype.setF = function(f) {
	if (f >= 0 && f <= 1) {
		this.f = f;

		this.notifyObservers();
	}
	else throw "f should lie within [0;1]. Entered f = " + f + "\n";
};

/**
 * Sets Degree of Conflict value.
 * @param doc is the new value for DoC
 */
CertainTrust.prototype.setDoC = function(doc) {
	if (doc >= 0)
		this.doc = doc;
	else throw "DoC should be greater than 0. Entered DoC = " + doc + "\n";
};

/**
 * Sets c and t values. Recalculates r and s values accordingly.
 * @param t - new average trust value
 * @param c - new certainty value
 */
CertainTrust.prototype.setTC = function(t, c) {
	if (c >= 0 && c <= 1) {
		if (t >= 0 && t <= 1) {
			this.c = c;
			this.t = t;
			this._calculateTCtoRS();

			this.notifyObservers();
		}
		else throw "t should be greater than 0. Entered t = " + t + "\n";
	}
	else throw "c should lie within [0;1]. Entered c = " + c + "\n";
};

/**
 * Sets r and s values. Recalculates c and t values accordingly.
 * @param r - new number of positive evidence
 * @param s - new number of negative evidence
 */
CertainTrust.prototype.setRS = function(r, s) {
	if (r >= 0) {
		if (s >= 0) {
			this.r = r;
			this.s = s;
			this._normaliseRS();
			this._calculateRStoTC();

			this.notifyObservers();
		}
		else throw "s should be positive. Entered s = " + s + "\n";
	}
	else throw "r should be positive. Entered r = " + r + "\n";
};

/**
 * Add some positive evidence to r.
 * @param posEvidence - number of new positive evidences
 */
CertainTrust.prototype.addR = function(posEvidence) {
	if (posEvidence >= 0) {
		this.r += posEvidence;
		this._normaliseRS();
		this._calculateRStoTC();

		this.notifyObservers();
	}
	else throw "Number of positive evidences should be positive. Entered " + posEvidence + "\n";
};

/**
 * Add some negative evidence to s.
 * @param negEvidence - number of new negative evidences
 */
CertainTrust.prototype.addS = function(negEvidence) {
	if (negEvidence >= 0) {
		this.s += negEvidence;
		this._normaliseRS();
		this._calculateRStoTC();

		this.notifyObservers();
	}
	else throw "Number of negative evidences should be positive. Entered " + negEvidence + "\n";
};

//=========== Logic =================
/**
 * Computes OR function for this CertainTrust object and the specified argument. Result is returned as a new object,
 * argument and this CertainTrust object remain unchanged.
 * N values of both objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param arg - CertainTrust object
 * @return - result of OR computation for this object and an argument.
 */
CertainTrust.prototype._singleOR = function(arg) {
	var c1 = this.getC();
	var t1 = this.getT();
	var f1 = this.getF();

	var c2 = arg.getC();
	var t2 = arg.getT();
	var f2 = arg.getF();

	var resT = 0.5, resF = 0.5, resC = 0;

	if (!this._operationAllowed(this, arg))
		return undefined;

	resF = f1 + f2 - f1*f2;

	if (this._almostEqual(resF, 0)){
		f1 = 0.99999;
		f2 = 0.99999;
		resF = f1 + f2 - f1*f2;
		resC = c1 + c2 - c1*c2- (c1*f2*(1-c2)*(1-t1)+c2*f1*(1-c1)*(1-t2)) / resF;
	}
	else
		resC = c1 + c2 - c1*c2 - (c1*f2*(1-c2)*(1-t1)+c2*f1*(1-c1)*(1-t2)) / resF;

	if (this._almostEqual(resC, 0))
		resT = 0.5;
	else resT = (1/resC) * (c1*t1 + c2*t2 - c1*c2*t1*t2);

	resT = this._adjustValue(resT);
	resC = this._adjustValue(resC);
	resF = this._adjustValue(resF);

	var result = new CertainTrust(resT, resC, resF, this.n, 0);

	return result;
};

/**
 * Computes OR function for this CertainTrust object and the specified arguments.
 * Result is returned as a new object, arguments and this CertainTrust object remain unchanged.
 * Example: a.OR(b, c, d) returns new CertainTrust object that equals a OR b OR c OR d.
 * Multiple arguments allowed, but not less than one.
 * N values of all objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param args - arguments
 * @return - result of OR computation for this object and all arguments.
 */
CertainTrust.prototype.OR = function() {
	var result = this.clone();
	for (var i = 0; i < arguments.length; ++i) {
		var m = arguments[i];
		if (!this._operationAllowed(this, m))
			continue;
		result = result._singleOR(m);
	}
	return result;
};

/**
 * Computes AND function for this CertainTrust object and the specified argument. Result is returned as a new object,
 * argument and this CertainTrust object remain unchanged.
 * N values of both objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param arg - CertainTrust object
 * @return - result of AND computation for this object and an argument.
 */
CertainTrust.prototype._singleAND = function(arg){
	var c1 = this.getC();
	var f1 = this.getF();
	var t1 = this.getT();

	var c2 = arg.getC();
	var f2 = arg.getF();
	var t2 = arg.getT();

	var resC = 0, resT = 0.5, resF = 0.5;

	if (!this._operationAllowed(this, arg))
		return undefined;

	resF = f1*f2;
	if (this._almostEqual(resF, 1)){		//avoid division by 0
		f1 = 0.99999;
		f2 = 0.99999;
		resF = f1*f2;
		resC = c1 + c2 - c1*c2- (c2*t2*(1-c1)*(1-f1)+c1*t1*(1-c2)*(1-f2)) / (1 - resF);
	}
	else
		resC = c1 + c2 - c1*c2 - (c2*t2*(1-c1)*(1-f1)+c1*t1*(1-c2)*(1-f2)) / (1 - resF);

	if (this._almostEqual(resC, 0))
		resT = 0.5;
	else resT = (1/resC) *  ((c1*t1*c2*t2) + (c1*f2*t1*(1-c2)*(1-f1)+c2*f1*t2*(1-c1)*(1-f2)) / (1 - resF));

	resT = this._adjustValue(resT);
	resC = this._adjustValue(resC);
	resF = this._adjustValue(resF);

	return new CertainTrust(resT, resC, resF, this.n, 0);
};

CertainTrust.prototype._adjustValue = function(arg) {
	return Math.max(Math.min(arg, 1), 0);
};

CertainTrust.prototype._almostEqual = function(value, target) {
	return Math.abs(value - target) < 1E-10;
};

CertainTrust.prototype._operationAllowed = function(arg1, arg2) {
	//and all N's of TC's must be equal
	/*if (arg1.getN() != arg2.getN())							//Disabled by Debashis C. Ray for AND calculation
		throw "Different N values. Operation not allowed. \n"; */
	return true;
}

/**
 * Computes AND function for this CertainTrust object and the specified arguments.
 * Result is returned as a new object, arguments and this CertainTrust object remain unchanged.
 * Example: a.AND(b, c, d) returns new CertainTrust object that equals a AND b AND c AND d.
 * Multiple arguments allowed, but not less than one.
 * N values of all objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param args - arguments
 * @return - result of AND computation for this object and all arguments.
 */
CertainTrust.prototype.AND = function() {
	var result = this.clone();
	for (var i = 0; i < arguments.length; i++) {
		var m = arguments[i];
		if (!this._operationAllowed(this, m))
			continue;
		result = result._singleAND(m);
	}
	return result;
};

/**
 * Returns NOT of this CertainTrust object.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @return - NOT of this CertainTrust object.
 */
CertainTrust.prototype.NOT = function() {
	var result = this.clone();
	result.setTC(1 - this.getT(), this.getC());
	result.setF(1 - this.getF());
	result.setDoC(0);
	return result;
};

/**
 * an internal implementation of fusion function.
 * Is called by wFusion and cFusion
 * @param args - an array of CertainTrust objects
 * @param weights - an integer array of corresponding weights
 * @param doc - a degree of conflict (always 0 for wFusion)
 * @return - new CertainTrust object
 */
CertainTrust.prototype._internalFusion = function(args, weights, doc) {
	var resC, resT, resF;
	var allOne = true;
	var allZero = true;
	var allWeightsZero = true;
	var atLeastOne1 = false;
	var arrLength = args.length;

	// set the flags about C and Weight values
	for (var i = 0; i < arrLength; ++i)
		if (args[i].getC() !== 1) {
			allOne = false;
			i = arrLength;
		}
	for (i = 0; i < arrLength; ++i)
		if (args[i].getC() !== 0) {
			allZero = false;
			i = arrLength;
		}
	for (i = 0; i < arrLength; ++i)
		if (weights[i] !== 0) {
			allWeightsZero = false;
			i = arrLength;
		}
	for (i = 0; i < arrLength; ++i)
		if (args[i].getC() === 1) {
			atLeastOne1 = true;
			i = arrLength;
		}

	//Calculate T and C

	// 1. all C's  = 1
	var numeratorT = 0, denominatorT = 0;
	if (allOne) {
		// set C
		resC = 1 * (1 - doc);
		// set T
		if (allWeightsZero) {// save some calculation time
			resT = 0;
		}
		else {				// or use the function
			for (i = 0; i < arrLength; ++i) {
				numeratorT += weights[i] * args[i].getT();
				denominatorT += weights[i];
			}
			resT = numeratorT/denominatorT;
		}
	} else {
		if (atLeastOne1)
			throw "Illegal arguments. Either all C values must equal 1 or none of them. Operation not allowed\n";
		else {
			// 2. Any other combination
			if (allWeightsZero) { // save some calculation time
				resT = 0;
				resC = 0;
			}
			else {					// or use the function
				var numeratorC = 0, denominatorC = 0, mult;
				for (i = 0; i < arrLength; ++i) {
					mult = 1;
					for (var j = 0; j < arrLength; ++j) // Count the product for each sum element
						if (j !== i)
							mult *= 1 - args[j].getC();
					numeratorT += weights[i] * args[i].getT() * args[i].getC() * mult;
					denominatorT += weights[i] * args[i].getC() * mult;
					denominatorC += weights[i] * mult;
				}
				numeratorC = denominatorT;
				resC = (numeratorC/denominatorC) * (1 - doc);
				if (allZero)
					resT = 0.5;
				else
					resT = numeratorT/denominatorT;
			}
			// Special case for T
			if (allZero)
				resT = 0.5;
		}
	}

	// Calculate F
	if (allWeightsZero)
		resF = 0;
	else {
		var numerator = 0, denominator = 0;
		for (i = 0; i < arrLength; ++i) {
			numerator += weights[i] * args[i].getF();
			denominator += weights[i];
		}
		resF = numerator/denominator;
	}

	var result = args[0].clone();
	result.setTC(resT, resC);
	result.setF(resF);
	result.setDoC(doc);
	return result;
};

/**
 * Performs weighted fusion for an array of CertainTrust objects in correspondence with
 * an array of weights. Returns new CertainTrust object.
 * Requirements: N values of CertainTrust objects must be equal.
 * Number of weights should equal the number of CertainTrust objects.
 * Arrays must be non-empty
 * Either all of CertainTrust must be of certainty 1 or none of them.
 * @param args - an array of CertainTrust objects
 * @param weights - an integer array of corresponding weights
 * @return - new CertainTrust object
 */
CertainTrust.prototype.wFusion = function(args, weights) {
	//arrays should be equal
	if (args.length == weights.length) {
		//and not empty
		if (args.length !== 0) {
			for (var i = 1; i < args.length; ++i)
				if (!this._operationAllowed(args[0], args[i]))
					return undefined;

			return this._internalFusion(args, weights, 0);
		}
		else throw "Arrays are empty. Operation not allowed. \n";
	}
	else throw "Different lengths of arrays. Operation not allowed. \n";
};

/**
 * Conflicted Fusion is a variation of weighted fusion, which additionally computes the degree of conflict 
 * between given opinions (CertainTrust objects) and takes it into consideration while performing fusion.
 * The degree of conflict is then saved in the resulting CertainTrust object and may be checked with getDoC() function.
 * @param args - an array of CertainTrust objects
 * @param weights - an integer array of corresponding weights
 * @return - new CertainTrust object
 */
CertainTrust.prototype.cFusion = function(args, weights) {
	//arrays should be equal
	if (args.length == weights.length) {
		//and not empty
		if (args.length !== 0) {
			for (var i = 1; i < args.length; ++i)
				if (!this._operationAllowed(args[0], args[i]))
					return undefined;
			var denominator = args.length*(args.length - 1) / 2;
			var numerator = 0;
			for (i = 0; i < args.length; ++i)
				for (var j = i; j < args.length; ++j)
					numerator += Math.abs(args[i].getT() - args[j].getT()) *
						args[i].getC() * args[j].getC() *
						(1 - Math.abs((weights[i] - weights[j]) /
							      (weights[i] + weights[j])));
			var doc = numerator/denominator;
			return this._internalFusion(args, weights, doc);
		}
		else throw "Arrays are empty. Operation not allowed. \n";
	}
	else throw "Different lengths of arrays. Operation not allowed. \n";
};

//=========== Internal Calculations ==========
/**
 * Normalises r and s values according to n - maximal number of expected evidence
 * Important! Doesn't notify observers.
 */
CertainTrust.prototype._normaliseRS = function() {
	if ((this.r + this.s) > this.n) {
		var initR = this.r;
		this.r = (this.r * this.n) / (initR + this.s);
		this.s = (this.s * this.n) / (initR + this.s);
	}
};

/**
 * Calculates t and c values based on existing r and s values
 * Important! Doesn't notify observers.
 */
CertainTrust.prototype._calculateRStoTC = function() {
	var rs = this.r + this.s;
	var nrs = this.n * rs;
	this.c = nrs / ((2 * this.weight * (this.n - this.r - this.s)) + nrs);
	if (this._almostEqual(this.c, 0))
		this.t = 0.5;
	else
		this.t = this.r / rs;
};

/**
 * Calculates r and s values based on existing c and t values
 * Important! Doesn't notify observers.
 */
CertainTrust.prototype._calculateTCtoRS = function() {
	if (this._almostEqual(this.c, 0)) {
		this.r = 0;
		this.s = 0;
		this.t = 0.5;
	}
	else {
		var c2w = this.c * 2 * this.weight;
		var c2wn = c2w * this.n;
		var cn = this.c * this.n;
		this.r = (c2wn * this.t) / (c2w + this.n - cn);
		this.s = (c2wn - (c2wn * this.t)) / (c2w + this.n - cn);
	}
};

CertainTrust.prototype.clone = function() {
	var copy = new CertainTrust(this.getN());
	copy.c = this.c;
	copy.t = this.t;
	copy.f = this.f;
	copy.r = this.r;
	copy.s = this.s;
	copy.doc = this.doc;
	return copy;
};

CertainTrust.prototype._isString = function (obj) {
  return typeof(obj) === 'string';
};

//=========== Observer =================
CertainTrust.prototype.notifyObservers = function(message) {
	for (var i = 0; i < this.observers.length; ++i)
		this.observers[i].update(this.observers[i], message);
};
CertainTrust.prototype.addObserver = function(observer) {
	this.observers.push(observer);
};
CertainTrust.prototype.deleteObserver = function(observer) {
	var idx = this.observers.indexOf(observer);
	if(idx !== -1)
		this.observers.splice(idx, 1);
};

//=== shared functions for frontends ===
CertainTrust.prototype._insertElement = function(config, element) {
	var dom;
	if (config.domReturn === true) {
		return element;
	} else if (config.domParent !== undefined) {
		if (this._isString(config.domParent))
			document.getElementById(config.domParent).appendChild(element);
		else
			config.domParent.appendChild(element);
	} else if (config.domBefore !== undefined) {
		if (this._isString(config.domBefore))
			dom = document.getElementById(config.domBefore);
		else
			dom = config.domBefore;
		dom.parentNode.insertBefore(element, dom);
	} else {
		if (config.domAfter === undefined) {
			// the last script tag in DOM tree is the one creating this widget
			var scripts = document.getElementsByTagName('script');
			dom = scripts[scripts.length - 1];
		} else if (this._isString(config.domAfter))
			dom = document.getElementById(config.domAfter);
		else
			dom = config.domAfter;
		dom.parentNode.insertBefore(element, dom.nextSibling);
	}
	return undefined;
};
CertainTrust.prototype._getColor = function(certainty, trust, initf) {
	var resultp2 = ((1 - certainty) * initf);
	var result = (trust * certainty) + resultp2;
	var color;
	if (result < 0.5) {
		color = [
			255,
			Math.min(255, (255 * 2 * result)),
			0
		];
	} else {
		color = [
			Math.min(255, ((2 - (2 * result)) * 255)),
			255,
			0
		];
	}
	return color;
};
CertainTrust.prototype._pointOnCircle = function(centerx, centery, pointgrade, radius) {
	var pointrad = ((360 + pointgrade) % 360) * ((2 * Math.PI) / 360);
	var chord = 2 * radius * Math.sin((pointrad / 2));
	// height of our new point above the base-edge
	var y = Math.sqrt(2
			* (Math.pow(chord, 2) * Math.pow(radius, 2)
				+ Math.pow(radius, 4) + Math.pow(radius, 2)
				* Math.pow(chord, 2))
			- (Math.pow(chord, 4) + 2 * Math.pow(radius, 4)))
		/ (2 * radius);
	// distance to the cross-point of base-edge and height
	var a = Math.pow(radius, 2);
	var c = Math.pow(y, 2);
	// we do this to protect us from NaN cause by 1 - 1.00000004
	var x = (a < c) ? 0 : Math.sqrt(a - c);

	var directions = new Array("NE", "SE", "SW", "NW");
	var direction = 0;
	var alpharad = pointrad;
	while (alpharad > (0.5 * Math.PI)) {
		++direction;
		alpharad -= (0.5 * Math.PI);
	}
	if (directions[direction] == "NE" || directions[direction] == "NW")
		x *= -1;
	if (directions[direction] == "SW" || directions[direction] == "NW")
		y *= -1;
	return new Array((centerx + x), (centery + y));
};

/* optional implementation of CertainTrust without R and S calculations */
var CertainTrustSimple = function() {
	this.weight = 2;
	this.observers = [];

	var offset = 0;
	this.name = "";

	if (this._isString(arguments[0])) {
		this.name = arguments[0];
		offset = 1;
	}
	if (arguments.length == 3 + offset || arguments.length == 4 + offset) {
		// CertainTrustSimple(t, c, f, doc)
		// doc is a 'private' parameter
		this.t = arguments[0 + offset];
		this.c = arguments[1 + offset];
		this.f = arguments[2 + offset];
		this.doc = (arguments.length == 3 + offset) ? 0 : arguments[3 + offset];

		if (this.f < 0 && this.f > 1)
			throw "f should lie within [0;1]. Entered f = " + this.f + "\n";
		if (this.c < 0 && this.c > 1)
			throw "c should lie within [0;1]. Entered c = " + this.c + "\n";
		if (this.t < 0 && this.t > 1)
			throw "t should lie within [0;1]. Entered t = " + this.t + "\n";
	} else {
		this.c = 0;
		this.t = 0.5;
		this.f = 0.5;
		this.doc = 0;
	}
};
CertainTrustSimple.prototype = new CertainTrust(1);
CertainTrustSimple.prototype.constructor = CertainTrustSimple;
CertainTrustSimple.prototype._operationAllowed = function() { return true; }
CertainTrustSimple.prototype._calculateTCtoRS = function() { }
CertainTrustSimple.prototype._calculateRStoTC = function() { }
CertainTrustSimple.prototype._normaliseRS = function() { }
CertainTrustSimple.prototype.setRS = undefined;
CertainTrustSimple.prototype.addR = undefined;
CertainTrustSimple.prototype.addS = undefined;
CertainTrustSimple.prototype.setN = undefined;
CertainTrustSimple.prototype.getR = undefined;
CertainTrustSimple.prototype.getS = undefined;
CertainTrustSimple.prototype.getN = undefined;
CertainTrustSimple.prototype.clone = function() {
	var copy = new CertainTrustSimple();
	copy.c = this.c;
	copy.t = this.t;
	copy.f = this.f;
	copy.doc = this.doc;
	return copy;
};

/*Added by Debashis*/

/**
 * Computes OR function for this CertainTrustSimple object and the specified argument. Result is returned as a new object,
 * argument and this CertainTrust object remain unchanged.
 * N values of both objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param arg - CertainTrustSimple object
 * @return - result of OR computation for this object and an argument.
 */
CertainTrust.prototype._singlesimpleOR = function(arg) {
	var c1 = this.getC();
	var t1 = this.getT();
	var f1 = this.getF();

	var c2 = arg.getC();
	var t2 = arg.getT();
	var f2 = arg.getF();

	var resT = 0.5, resF = 0.5, resC = 0;

	if (!this._operationAllowed(this, arg))
		return undefined;

	resF = f1 + f2 - f1*f2;

	if (this._almostEqual(resF, 0)){
		f1 = 0.99999;
		f2 = 0.99999;
		resF = f1 + f2 - f1*f2;
		resC = c1 + c2 - c1*c2- (c1*f2*(1-c2)*(1-t1)+c2*f1*(1-c1)*(1-t2)) / resF;
	}
	else
		resC = c1 + c2 - c1*c2 - (c1*f2*(1-c2)*(1-t1)+c2*f1*(1-c1)*(1-t2)) / resF;

	if (this._almostEqual(resC, 0))
		resT = 0.5;
	else resT = (1/resC) * (c1*t1 + c2*t2 - c1*c2*t1*t2);

	resT = this._adjustValue(resT);
	resC = this._adjustValue(resC);
	resF = this._adjustValue(resF);

	var result = new CertainTrustSimple(resT, resC, resF, this.n, 0);

	return result;
};

/**
 * Computes OR function for this CertainTrustSimple object and the specified arguments.
 * Result is returned as a new object, arguments and this CertainTrustSimple object remain unchanged.
 * Example: a.OR(b, c, d) returns new CertainTrust object that equals a OR b OR c OR d.
 * Multiple arguments allowed, but not less than one.
 * N values of all objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param args - arguments
 * @return - result of OR computation for this object and all arguments.
 */
CertainTrustSimple.prototype.simpleOR = function() {
	var result = this.clone();
	for (var i = 0; i < arguments.length; ++i) {
		var m = arguments[i];
		if (!this._operationAllowed(this, m))
			continue;
		result = result._singlesimpleOR(m);
	}
	return result;
};

/**
 * Computes AND function for this CertainTrustSimple object and the specified argument. Result is returned as a new object,
 * argument and this CertainTrustSimple object remain unchanged.
 * N values of both objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param arg - CertainTrustSimple object
 * @return - result of AND computation for this object and an argument.
 */

CertainTrustSimple.prototype._singlesimpleAND = function(arg){
	var c1 = this.getC();
	var f1 = this.getF();
	var t1 = this.getT();

	var c2 = arg.getC();
	var f2 = arg.getF();
	var t2 = arg.getT();

	var resC = 0, resT = 0.5, resF = 0.5;

	if (!this._operationAllowed(this, arg))
		return undefined;

	resF = f1*f2;
	if (this._almostEqual(resF, 1)){		//avoid division by 0
		f1 = 0.99999;
		f2 = 0.99999;
		resF = f1*f2;
		resC = c1 + c2 - c1*c2 - (c2*t2*(1-c1)*(1-f1)+c1*t1*(1-c2)*(1-f2)) / (1 - resF);
	}
	else
		resC = c1 + c2 - c1*c2 - (c2*t2*(1-c1)*(1-f1)+c1*t1*(1-c2)*(1-f2)) / (1 - resF);

	if (this._almostEqual(resC, 0))
		resT = 0.5;
	else resT = (1/resC) *  ((c1*t1*c2*t2) + (c1*f2*t1*(1-c2)*(1-f1)+c2*f1*t2*(1-c1)*(1-f2)) / (1 - resF));

	resT = this._adjustValue(resT);
	resC = this._adjustValue(resC);
	resF = this._adjustValue(resF);

	return new CertainTrustSimple(resT, resC, resF);
};

/**
 * Computes AND function for this CertainTrustSimple object and the specified arguments.
 * Result is returned as a new object, arguments and this CertainTrustSimple object remain unchanged.
 * Example: a.AND(b, c, d) returns new CertainTrustSimple object that equals a AND b AND c AND d.
 * Multiple arguments allowed, but not less than one.
 * N values of all objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param args - arguments
 * @return - result of AND computation for this object and all arguments.
 */
CertainTrustSimple.prototype.simpleAND = function() {
	var result = this.clone();
	for (var i = 0; i < arguments.length; i++) {
		var m = arguments[i];
		if (!this._operationAllowed(this, m))
			continue;
		result = result._singlesimpleAND(m);
	}
	return result;
};

/**
 * Returns NOT of this CertainTrustSimple object.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @return - NOT of this CertainTrustSimple object.
 */
CertainTrustSimple.prototype.simpleNOT = function() {
	var result = this.clone();
	result.setTC(1 - this.getT(), this.getC());
	result.setF(1 - this.getF());
	result.setDoC(0);
	return result;
};

CertainTrust.prototype.setName = function(newname) {
	this.name = newname;
};

/**
 * Computes CONSENSUS function for this CertainTrust object and the specified argument. Result is returned as a new object,
 * argument and this CertainTrust object remain unchanged.
 * N values of both objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param arg - CertainTrust object
 * @return - result of CONSENSUS computation for this object and an argument.
 */
CertainTrust.prototype._singleCONSENSUS = function(arg){
	var c1 = this.getC();
	var f1 = this.getF();
	var t1 = this.getT();

	var c2 = arg.getC();
	var f2 = arg.getF();
	var t2 = arg.getT();

	var resC = 0, resT = 0.5, resF = 0.5;

	if (!this._operationAllowed(this, arg))
		return undefined;

	var tempC = c1*c2;
	if (this._almostEqual(tempC, 1)){		//avoid division by 0
		c1 = 0.99999;
		c2 = 0.99999;
	}

	resF = (f1*c1*(1-c2) + f2*c2*(1-c1))/(c1+c2-2*c1*c2);
	resC = (c1+c2-2*c1*c2)/(1-c1*c2);
	resT = (c1*t1*(1-c2)+c2*t2*(1-c1))/(c1+c2-2*c1*c2);

	resT = this._adjustValue(resT);
	resC = this._adjustValue(resC);
	resF = this._adjustValue(resF);

	return new CertainTrust(resT, resC, resF, this.n, 0);
};

CertainTrust.prototype.CONSENSUS = function() {
	var result = this.clone();
	for (var i = 0; i < arguments.length; i++) {
		var m = arguments[i];
		if (!this._operationAllowed(this, m))
			continue;
		result = result._singleCONSENSUS(m);
	}
	return result;
};

/**
 * Computes DISCOUNTING function for this CertainTrust object and the specified argument. Result is returned as a new object,
 * argument and this CertainTrust object remain unchanged.
 * N values of both objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param arg - CertainTrust object
 * @return - result of DISCOUNTING computation for this object and an argument.
 */
CertainTrust.prototype._singleDISCOUNTING = function(arg){
	var c1 = this.getC();
	var f1 = this.getF();
	var t1 = this.getT();

	var c2 = arg.getC();
	var f2 = arg.getF();
	var t2 = arg.getT();

	var resC = 0, resT = 0.5, resF = 0.5;

	if (!this._operationAllowed(this, arg))
		return undefined;

	//resF = f1*f2;
	if (this._almostEqual(resF, 1))		//avoid division by 0
		resC = t1*c1*c2;
	else
		resC = t1*c1*c2;

	if (this._almostEqual(resC, 0))
		resT = t2;
	else if (this._almostEqual(resF, 1))	//avoid division by 0
		resT = t2;
	else resT = t2;

	resT = this._adjustValue(resT);
	resC = this._adjustValue(resC);
	resF = this._adjustValue(resF);

	return new CertainTrust(resT, resC, resF, this.n, 0);
};

CertainTrust.prototype.DISCOUNTING = function() {
	var result = this.clone();
	for (var i = 0; i < arguments.length; i++) {
		var m = arguments[i];
		if (!this._operationAllowed(this, m))
			continue;
		result = result._singleDISCOUNTING(m);
	}
	return result;
};

/**
 * Computes CONSENSUS function for this CertainTrustSimple object and the specified argument. Result is returned as a new object,
 * argument and this CertainTrust object remain unchanged.
 * N values of both objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param arg - CertainTrustSimple object
 * @return - result of CONSENSUS computation for this object and an argument.
 */
CertainTrustSimple.prototype._singlesimpleCONSENSUS = function(arg){
	var c1 = this.getC();
	var f1 = this.getF();
	var t1 = this.getT();

	var c2 = arg.getC();
	var f2 = arg.getF();
	var t2 = arg.getT();

	var resC = 0, resT = 0.5, resF = 0.5;

	if (!this._operationAllowed(this, arg))
		return undefined;

	var tempC = c1*c2;
	if (this._almostEqual(tempC, 1)){		//avoid division by 0
		c1 = 0.99999;
		c2 = 0.99999;
	}

	resF = (f1*c1*(1-c2) + f2*c2*(1-c1))/(c1+c2-2*c1*c2);
	resC = (c1+c2-2*c1*c2)/(1-c1*c2);
	resT = (c1*t1*(1-c2)+c2*t2*(1-c1))/(c1+c2-2*c1*c2);

	resT = this._adjustValue(resT);
	resC = this._adjustValue(resC);
	resF = this._adjustValue(resF);

	return new CertainTrustSimple(resT, resC, resF);
};

CertainTrustSimple.prototype.simpleCONSENSUS = function() {
	var result = this.clone();
	for (var i = 0; i < arguments.length; i++) {
		var m = arguments[i];
		if (!this._operationAllowed(this, m))
			continue;
		result = result._singlesimpleCONSENSUS(m);
	}
	return result;
};

/**
 * Computes DISCOUNTING function for this CertainTrustSimple object and the specified argument. Result is returned as a new object,
 * argument and this CertainTrustSimple object remain unchanged.
 * N values of both objects should be equal.
 * For detailed information see CertainLogic: A Logic for Modeling Trust and Uncertainty
 * @param arg - CertainTrustSimple object
 * @return - result of DISCOUNTING computation for this object and an argument.
 */
CertainTrustSimple.prototype._singlesimpleDISCOUNTING = function(arg){
	var c1 = this.getC();
	var f1 = this.getF();
	var t1 = this.getT();

	var c2 = arg.getC();
	var f2 = arg.getF();
	var t2 = arg.getT();

	var resC = 0, resT = 0.5, resF = 0.5;

	if (!this._operationAllowed(this, arg))
		return undefined;

	//resF = f1*f2;
	if (this._almostEqual(resF, 1))		//avoid division by 0
		resC = t1*c1*c2;
	else
		resC = t1*c1*c2;

	if (this._almostEqual(resC, 0))
		resT = t2;
	else if (this._almostEqual(resF, 1))	//avoid division by 0
		resT = t2;
	else resT = t2;

	resT = this._adjustValue(resT);
	resC = this._adjustValue(resC);
	resF = this._adjustValue(resF);

	return new CertainTrustSimple(resT, resC, resF, this.n, 0);
};

CertainTrustSimple.prototype.simpleDISCOUNTING = function() {
	var result = this.clone();
	for (var i = 0; i < arguments.length; i++) {
		var m = arguments[i];
		if (!this._operationAllowed(this, m))
			continue;
		result = result._singlesimpleDISCOUNTING(m);
	}
	return result;
};

/**
 * an internal implementation of fusion function.
 * Is called by wFusion and cFusion
 * @param args - an array of CertainTrustSimple objects
 * @param weights - an integer array of corresponding weights
 * @param doc - a degree of conflict (always 0 for wFusion)
 * @return - new CertainTrustSimple object
 */
CertainTrustSimple.prototype._simpleinternalFusion = function(args, weights, doc) {
	var resC, resT, resF;
	var allOne = true;
	var allZero = true;
	var allWeightsZero = true;
	var atLeastOne1 = false;
	var arrLength = args.length;

	// set the flags about C and Weight values
	for (var i = 0; i < arrLength; ++i)
		if (args[i].getC() !== 1) {
			allOne = false;
			i = arrLength;
		}
	for (i = 0; i < arrLength; ++i)
		if (args[i].getC() !== 0) {
			allZero = false;
			i = arrLength;
		}
	for (i = 0; i < arrLength; ++i)
		if (weights[i] !== 0) {
			allWeightsZero = false;
			i = arrLength;
		}
	for (i = 0; i < arrLength; ++i)
		if (args[i].getC() === 1) {
			atLeastOne1 = true;
			i = arrLength;
		}

	//Calculate T and C

	// 1. all C's  = 1
	var numeratorT = 0, denominatorT = 0;
	if (allOne) {
		// set C
		resC = 1 * (1 - doc);
		// set T
		if (allWeightsZero) {// save some calculation time
			resT = 0;
		}
		else {				// or use the function
			for (i = 0; i < arrLength; ++i) {
				numeratorT += weights[i] * args[i].getT();
				denominatorT += weights[i];
			}
			resT = numeratorT/denominatorT;
		}
	} else {
		if (atLeastOne1)
			throw "Illegal arguments. Either all C values must equal 1 or none of them. Operation not allowed\n";
		else {
			// 2. Any other combination
			if (allWeightsZero) { // save some calculation time
				resT = 0;
				resC = 0;
			}
			else {					// or use the function
				var numeratorC = 0, denominatorC = 0, mult;
				for (i = 0; i < arrLength; ++i) {
					mult = 1;
					for (var j = 0; j < arrLength; ++j) // Count the product for each sum element
						if (j !== i)
							mult *= 1 - args[j].getC();
					numeratorT += weights[i] * args[i].getT() * args[i].getC() * mult;
					denominatorT += weights[i] * args[i].getC() * mult;
					denominatorC += weights[i] * mult;
				}
				numeratorC = denominatorT;
				resC = (numeratorC/denominatorC) * (1 - doc);
				if (allZero)
					resT = 0.5;
				else
					resT = numeratorT/denominatorT;
			}
			// Special case for T
			if (allZero)
				resT = 0.5;
		}
	}

	// Calculate F
	if (allWeightsZero)
		resF = 0;
	else {
		var numerator = 0, denominator = 0;
		for (i = 0; i < arrLength; ++i) {
			numerator += weights[i] * args[i].getF();
			denominator += weights[i];
		}
		resF = numerator/denominator;
	}

	var result = args[0].clone();
	result.setTC(resT, resC);
	result.setF(resF);
	result.setDoC(doc);
	return result;
};

/**
 * Performs weighted fusion for an array of CertainTrustSimple objects in correspondence with
 * an array of weights. Returns new CertainTrust object.
 * Requirements: N values of CertainTrustSimple objects must be equal.
 * Number of weights should equal the number of CertainTrust objects.
 * Arrays must be non-empty
 * Either all of CertainTrust must be of certainty 1 or none of them.
 * @param args - an array of CertainTrustSimple objects
 * @param weights - an integer array of corresponding weights
 * @return - new CertainTrustSimple object
 */
CertainTrustSimple.prototype.simplewFusion = function(args, weights) {
	//arrays should be equal
	if (args.length == weights.length) {
		//and not empty
		if (args.length !== 0) {
			for (var i = 1; i < args.length; ++i)
				if (!this._operationAllowed(args[0], args[i]))
					return undefined;

			return this._simpleinternalFusion(args, weights, 0);
		}
		else throw "Arrays are empty. Operation not allowed. \n";
	}
	else throw "Different lengths of arrays. Operation not allowed. \n";
};

/**
 * Conflicted Fusion is a variation of weighted fusion, which additionally computes the degree of conflict 
 * between given opinions (CertainTrustSimple objects) and takes it into consideration while performing fusion.
 * The degree of conflict is then saved in the resulting CertainTrust object and may be checked with getDoC() function.
 * @param args - an array of CertainTrustSimple objects
 * @param weights - an integer array of corresponding weights
 * @return - new CertainTrustSimple object
 */
CertainTrustSimple.prototype.simplecFusion = function(args, weights) { //
	//arrays should be equal
	if (args.length == weights.length) {
		//and not empty
		if (args.length !== 0) {
			for (var i = 1; i < args.length; ++i)
				if (!this._operationAllowed(args[0], args[i]))
					return undefined;
			var denominator = args.length*(args.length - 1) / 2;
			var numerator = 0;
			for (i = 0; i < args.length; ++i)
				for (var j = i; j < args.length; ++j)
					numerator += Math.abs(args[i].getT() - args[j].getT()) *
						args[i].getC() * args[j].getC() *
						(1 - Math.abs((weights[i] - weights[j]) /
							      (weights[i] + weights[j])));
			var doc = numerator/denominator;
			return this._simpleinternalFusion(args, weights, doc);
		}
		else throw "Arrays are empty. Operation not allowed. \n";
	}
	else throw "Different lengths of arrays. Operation not allowed. \n";
};
