function NCurveAPI() {

	/********************************************
	 * NCurveCanvas
	 ********************************************/

	/**
	 *	Variable to store the one NCurveCanvas object used to 
	 *	work with. Should be initialised to an object of the
	 *	NCurveCanvasClass before using it.
	 */
	var NCurveCanvas;

	/**
	 *	Class used for representing NCurveCanvasses.
	 *
	 * 	@param	canvas	The canvas (HTMLElement) the NCurveCanvas should
	 *					paint to. The canvas should be 420px wide and 300px high.
	 */
	function NCurveCanvasClass(newCanvas) {
		this.canvas = newCanvas;
		this.ctx = this.canvas.getContext('2d');
		// insert an empty function in case the context does not support the strokeText function
		if(typeof this.ctx.strokeText == 'undefined') {
			this.ctx.strokeText = function() {};
		}

		/**
		 *	Defines the assumed dimensions of the canvas.
		 */
		this.dimensions = {
			x: 420,
			y: 200
		}

		/**
		 *	Defines the scaling factor for everything on the canvas.
		 *	For example: normally, the N curve has a top of about 0.4.
		 *	This top now comes at y=center.y+0.4*scale.y.
		 */
		this.scale = {
			x: 40,
			y: 300
		}

		/**
		 *	Defines the center point for the curve on the canvas.
		 */
		this.center = {
			x: this.dimensions.x/2+10,
			y: this.dimensions.y-40
		}

		/**
		 *	Defines the limits of the N curve being drawn (in number of
		 *	standard deviations).
		 */
		this.drawLimit = 4;

		/**
		 *	Draws the given curve to the canvas and highlights the area between
		 *	the given limits.
		 *	@param	curve	NCurve object representing the curve to be drawn.
		 */
		this.draw = function(curve) {
			this.ctx.save();
			this.ctx.clearRect(0,0,this.dimensions.x,this.dimensions.y);
			//this.ctx.strokeRect(0,0,this.dimensions.x,this.dimensions.y);
			// draw everything using normal axes, translate and flip afterwards.
			this.ctx.translate(this.center.x,this.center.y); 
			this.ctx.scale(1,-1); 
			this.drawGrid();
			this.drawNCurve();
			this.drawXAxis(curve.mean, curve.standev);
			this.drawYAxis();
			this.ctx.restore();
		}

		/**
		 *	Draws the given curve to the canvas and highlights the area between
		 *	the given limits.
		 *	@param	curve	NCurve object representing the curve to be drawn.
		 *	@param	from	The starting x-value for the area to highlight.
		 *	@param	to		The ending x-value for the area to highlight.
		 */
		this.drawFromTo = function(curve, from, to) {
			this.ctx.save();
			this.ctx.clearRect(0,0,this.dimensions.x,this.dimensions.y);
			//this.ctx.strokeRect(0,0,this.dimensions.x,this.dimensions.y);
			// draw everything using normal axes, translate and flip afterwards.
			this.ctx.translate(this.center.x,this.center.y); 
			this.ctx.scale(1,-1); 
			this.fillNCurve((from-curve.mean)/curve.standev, (to-curve.mean)/curve.standev);
			this.drawGrid();
			this.drawNCurve();
			this.drawXAxis(curve.mean, curve.standev);
			this.drawYAxis();
			this.ctx.restore();
		}

		/**
		 *	Draws the given curve to the canvas and highlights the area on both sides
		 *	with absolute distance value bigger than the given value.
		 *	@param	curve		NCurve object representing the curve to be drawn.
		 *	@param	absValue	The starting x-value for the area to highlight.
		 */
		this.drawAbsoluteValue = function(curve, absValue) {
			this.ctx.save();
			this.ctx.clearRect(0,0,this.dimensions.x,this.dimensions.y);
			//this.ctx.strokeRect(0,0,this.dimensions.x,this.dimensions.y);
			// draw everything using normal axes, translate and flip afterwards.
			this.ctx.translate(this.center.x,this.center.y); 
			this.ctx.scale(1,-1); 
			this.fillNCurve(absValue/curve.standev, 5);
			this.fillNCurve(-5,-absValue/curve.standev);
			this.drawGrid();
			this.drawNCurve();
			this.drawXAxis(curve.mean, curve.standev);
			this.drawYAxis();
			this.ctx.restore();
		}

		/**
		 *	Draws the N curve on the canvas. The curve drawn looks the same for every curve,
		 *	only the axis values change. Therefore, this method does not need a curve parameter.
		 */
		this.drawNCurve = function() {
			this.ctx.beginPath();
			this.ctx.moveTo(-this.drawLimit*this.scale.x,StandardNCurve.getValue(-this.drawLimit)*this.scale.y);
			for(var x = -this.drawLimit; x <= this.drawLimit; x+=0.05) {
				this.ctx.lineTo(x*this.scale.x,StandardNCurve.getValue(x)*this.scale.y);
			}
			this.ctx.stroke();
		}

		/**
		 *	Fills the area under the N curve on the canvas from given min to given max.
		 *	
		 *	@param min	The minimum value the curve should be filled from.
		 *	@param max	The max value the curve should be filled from.
		 */
		this.fillNCurve = function(min, max) {
			this.ctx.save();
			this.ctx.fillStyle = "#f49d20";
			this.ctx.moveTo(min,StandardNCurve.getValue(min)*this.scale.y);
			this.ctx.beginPath();
			for(var x = min; x <= max; x+=0.05) {
				this.ctx.lineTo(x*this.scale.x,StandardNCurve.getValue(x)*this.scale.y);
			}
			x = max; 
			this.ctx.lineTo(x*this.scale.x,StandardNCurve.getValue(x)*this.scale.y);
			this.ctx.lineTo(x*this.scale.x,0);
			this.ctx.lineTo(min*this.scale.x,0);
			this.ctx.closePath();
			this.ctx.fill();
			this.ctx.restore();
		}
	
		/**
		 *	Draws the X-axis and ticks on the canvas.
		 *	
		 *	@param	mean	The value of the mean of the curve being drawn.
		 *					This value is needed for the tick values.
		 *	@param	standev	The value of the standard deviation of the curve being drawn.
		 *					This value is needed for the tick values.
		 */
		this.drawXAxis = function(mean, standev) {
			// the axis line
			this.ctx.beginPath();
			this.ctx.moveTo(-this.drawLimit*this.scale.x-10,0);
			this.ctx.lineTo(this.drawLimit*this.scale.x+10,0);
			this.ctx.stroke();
			// the end arrow
			this.ctx.beginPath();
			this.ctx.moveTo(this.drawLimit*this.scale.x+10,3);
			this.ctx.lineTo(this.drawLimit*this.scale.x+20,0);
			this.ctx.lineTo(this.drawLimit*this.scale.x+10,-3);
			this.ctx.closePath();
			this.ctx.fill();
			// the ticks: put one at each multiple of the standard
			// deviation inside the draw limit
			this.ctx.save();
			this.ctx.scale(1,-1); // normal scaling to write the text correct, reverse afterwards
			this.ctx.textAlign = "center";
			this.ctx.strokeText(mean,0,15);
			this.ctx.beginPath();
			for(var tickX = 1; tickX < this.drawLimit; tickX++) {
				// negative tick
				this.ctx.moveTo(-tickX*this.scale.x,-2);
				this.ctx.lineTo(-tickX*this.scale.x,2);
				this.ctx.strokeText(mean-tickX*standev,-tickX*this.scale.x,15);
				// positive tick
				this.ctx.moveTo(tickX*this.scale.x,-2);
				this.ctx.lineTo(tickX*this.scale.x,2);
				this.ctx.strokeText(mean+tickX*standev,tickX*this.scale.x,15);
			}
			this.ctx.stroke();
			// draw the label here too
			this.ctx.strokeText("X",0,30); // TODO: should this be Z for a standard normal curve?
			this.ctx.restore();
		}

		/**
		 *	Draws the X-axis and ticks on the canvas.
		 */
		this.drawYAxis = function() {
			// the axis line
			this.ctx.beginPath();
			this.ctx.moveTo(-this.drawLimit*this.scale.x,-10);
			this.ctx.lineTo(-this.drawLimit*this.scale.x,0.4*this.scale.y+10);
			this.ctx.stroke();
			// the end arrow
			this.ctx.beginPath();
			this.ctx.moveTo(-this.drawLimit*this.scale.x-3,0.4*this.scale.y+10);
			this.ctx.lineTo(-this.drawLimit*this.scale.x,0.4*this.scale.y+20);
			this.ctx.lineTo(-this.drawLimit*this.scale.x+3,0.4*this.scale.y+10);
			this.ctx.closePath();
			this.ctx.fill();
			// the ticks: 0.1, 0.2, 0.3 and 0.4
			this.ctx.save();
			this.ctx.scale(1,-1); // normal scaling to write the text correct, reverse afterwards
			this.ctx.textAlign = "center";
			this.ctx.beginPath();
			for(var tickY = 1; tickY <= 4; tickY++) {
				this.ctx.moveTo(-this.drawLimit*this.scale.x-2,-0.1*tickY*this.scale.y);
				this.ctx.lineTo(-this.drawLimit*this.scale.x+2,-0.1*tickY*this.scale.y);
				this.ctx.strokeText("0." + tickY,-this.drawLimit*this.scale.x-15,-0.1*tickY*this.scale.y+3);
			}
			this.ctx.stroke();
			// draw the label here too
			this.ctx.strokeText("N(X)",-this.drawLimit*this.scale.x-40,-0.24*this.scale.y); // TODO: should this be Z for a standard normal curve?
			this.ctx.restore();
		}

		/**
		 *	Draws a grid on the canvas.
		 */
		this.drawGrid = function() {
			this.ctx.save();
			var dashlength = 5;
			var emptyspace = 3;
			this.ctx.strokeStyle = "lightgrey";
			this.ctx.beginPath();
			// horizontal lines
			for(var tickY = 1; tickY <= 4; tickY++) {
				for(var beginx = -this.drawLimit*this.scale.x-10; 
						beginx < this.drawLimit*this.scale.x; 
						beginx += (dashlength + emptyspace)) {
					this.ctx.moveTo(beginx,0.1*tickY*this.scale.y);
					this.ctx.lineTo(beginx+dashlength,0.1*tickY*this.scale.y);
				}
			}
			// vertical lines
			// positive tick
			for(var beginy = -10; 
					beginy < 0.4*this.scale.y+10; 
					beginy += (dashlength + emptyspace)) {	
				this.ctx.moveTo(0,beginy);
				this.ctx.lineTo(0,beginy+dashlength);
			}
			for(var tickX = 1; tickX < this.drawLimit; tickX++) {
				// negative tick
				for(var beginy = -10; 
						beginy < 0.4*this.scale.y+10; 
						beginy += (dashlength + emptyspace)) {	
					this.ctx.moveTo(-tickX*this.scale.x,beginy);
					this.ctx.lineTo(-tickX*this.scale.x,beginy+dashlength);
				}
				// positive tick
				for(var beginy = -10; 
						beginy < 0.4*this.scale.y+10; 
						beginy += (dashlength + emptyspace)) {	
					this.ctx.moveTo(tickX*this.scale.x,beginy);
					this.ctx.lineTo(tickX*this.scale.x,beginy+dashlength);
				}
			}
			this.ctx.stroke();
			this.ctx.restore();
		}
	}
	

	/********************************************
	 * N curve
	 * TODO: put this in a web worker (if the browser can handle this, check first so it *always* works)
	 ********************************************/

	/**
	 *	Singleton class used for bundling methods related to the standard N curve.
	 */
	var StandardNCurve = {
		/**
		 * 	Returns the value of the standard N curve at given x.
		 *	The returned value should be precise for at least 8 digits after
		 *	the decimal point (although little error on 9th and 10th too, see differences with matlab).
		 */		
		getValue: function(x) {
			//return  1/Math.sqrt(2*Math.PI) * Math.pow(Math.E,-1/2*x*x);
			return  0.398942280401433 * Math.pow(Math.E,-0.5*x*x);
		},

		/**
		 * 	Returns the value of the area below the standard N curve for
		 * 	x going from min to max.
		 *	The returned value should be precise for at least 6 digits after
		 *	the decimal point when rounding.
		 */
		getIntegral: function(min, max) {
			var sum = 0;
			var step = Math.min(0.00001,(max-min)/100000);
			for(var x = min; x < max; x+=step) { // notice the "x < max", not "x <= max"
				sum += this.getValue(x);
			}
			return sum*step;
		}
	}

	/**
	 *	Class used for representing non-standard N curves (the mean and standard deviation don't 
	 *	need to be the standard values of 0 and 1).
	 *
	 * 	@param	mean_		The mean of this N curve.
	 *	@param	standev_	The standard deviation of this N curve. This value should not be 0.
	 */
	function NCurve(mean_, standev_) {
		this.mean = mean_;
		this.standev = standev_;

		/**
		 * 	Returns the value of this N curve at given x.
		 *	The returned value should be precise for at least 8 digits after
		 *	the decimal point (although little error on 9th and 10th too, see differences with matlab).
		 */	
		this.getValue = function(x) {
			return StandardNCurve.getValue((x-this.mean)/this.standev);
		}

		/**
		 * 	Returns the value of the area below this N curve for
		 * 	x going from min to max.
		 *	The returned value should be precise for at least 6 digits after
		 *	the decimal point when rounding.
		 */
		this.getIntegral = function(min, max) {
			// practical calculation boundaries: 5*standev (5 in standard curve)
			var stanFrom = Math.max((min-this.mean)/this.standev,-5);
			var stanTo = Math.min((max-this.mean)/this.standev,5);
			return StandardNCurve.getIntegral(stanFrom, stanTo);
		}
	}		

	/*******************************************
	 *	PUBLIC INTERFACE
	 *******************************************/
	return {
		/**
		 *	Initialise the program with given sink.
		 *	
		 *	@param	canvasElement	The canvas element to paint on. Should have dimensions 420x200.
		 */
		init: function(canvasElement) {
			NCurveCanvas = new NCurveCanvasClass(canvasElement);
			// initial settings
			var curve = new NCurve(0,1);
			NCurveCanvas.draw(curve,15,27);
			
		},
		
		/**
		 *	Calculate and show the result for the non-standard N curve with given
		 *	mean and standard deviation from the given min to the given max.
		 */
		calculateFromTo: function(mean, standev, min, max) {
			var curve = new NCurve(mean, standev);
			NCurveCanvas.drawFromTo(curve, min, max);
			// return only 4 digits after decimal point
			return Math.round(curve.getIntegral(min, max)*10000)/10000;
		},
		
		/**
		 *	Calculate and show the result for the non-standard N curve with given
		 *	mean and standard deviation from the given min.
		 */
		calculateFrom: function(mean, standev, min) {
			var curve = new NCurve(mean, standev);
			NCurveCanvas.drawFromTo(curve, min, mean+5*standev);
			// return only 4 digits after decimal point
			return Math.round(curve.getIntegral(min, mean+5*standev)*10000)/10000;
		},
		
		/**
		 *	Calculate and show the result for the non-standard N curve with given
		 *	mean and standard deviation to the given max.
		 */
		calculateTo: function(mean, standev, max) {
			var curve = new NCurve(mean, standev);
			NCurveCanvas.drawFromTo(curve, mean-5*standev, max);
			// return only 4 digits after decimal point
			return Math.round(curve.getIntegral(mean-5*standev, max)*10000)/10000;
		},

		/**
		 *	Calculate and show the result for the non-standard N curve with given
		 *	mean and standard deviation for |X| > absMin.
		 */
		calculateAbsoluteValueFrom: function(mean, standev, absMin) {			
			var curve = new NCurve(mean, standev);
			if(absMin < 0) {
				NCurveCanvas.drawFromTo(curve, mean-5*standev, mean+5*standev);
				// return only 4 digits after decimal point
				return 1;
			} else {
				NCurveCanvas.drawAbsoluteValue(curve, absMin);
				// return only 4 digits after decimal point
				return 2*Math.round(curve.getIntegral(mean+absMin, mean+5*standev)*10000)/10000;
			}
		}
	};
}

/**
 *	The object introduced by this script.
 */
var ncurveapi = new NCurveAPI();

