text: 'use strict';
  
  var defaults = require('../core/core.defaults');
  var helpers = require('../helpers/index');
  var Ticks = require('../core/core.ticks');
  
  module.exports = function(Chart) {
  
  	var globalDefaults = defaults.global;
  
  	var defaultConfig = {
  		display: true,
  
  		// Boolean - Whether to animate scaling the chart from the centre
  		animate: true,
  		position: 'chartArea',
  
  		angleLines: {
  			display: true,
  			color: 'rgba(0, 0, 0, 0.1)',
  			lineWidth: 1
  		},
  
  		gridLines: {
  			circular: false
  		},
  
  		// label settings
  		ticks: {
  			// Boolean - Show a backdrop to the scale label
  			showLabelBackdrop: true,
  
  			// String - The colour of the label backdrop
  			backdropColor: 'rgba(255,255,255,0.75)',
  
  			// Number - The backdrop padding above & below the label in pixels
  			backdropPaddingY: 2,
  
  			// Number - The backdrop padding to the side of the label in pixels
  			backdropPaddingX: 2,
  
  			callback: Ticks.formatters.linear
  		},
  
  		pointLabels: {
  			// Boolean - if true, show point labels
  			display: true,
  
  			// Number - Point label font size in pixels
  			fontSize: 10,
  
  			// Function - Used to convert point labels
  			callback: function(label) {
  				return label;
  			}
  		}
  	};
  
  	function getValueCount(scale) {
  		var opts = scale.options;
  		return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
  	}
  
  	function getPointLabelFontOptions(scale) {
  		var pointLabelOptions = scale.options.pointLabels;
  		var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);
  		var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);
  		var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);
  		var font = helpers.fontString(fontSize, fontStyle, fontFamily);
  
  		return {
  			size: fontSize,
  			style: fontStyle,
  			family: fontFamily,
  			font: font
  		};
  	}
  
  	function measureLabelSize(ctx, fontSize, label) {
  		if (helpers.isArray(label)) {
  			return {
  				w: helpers.longestText(ctx, ctx.font, label),
  				h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)
  			};
  		}
  
  		return {
  			w: ctx.measureText(label).width,
  			h: fontSize
  		};
  	}
  
  	function determineLimits(angle, pos, size, min, max) {
  		if (angle === min || angle === max) {
  			return {
  				start: pos - (size / 2),
  				end: pos + (size / 2)
  			};
  		} else if (angle < min || angle > max) {
  			return {
  				start: pos - size - 5,
  				end: pos
  			};
  		}
  
  		return {
  			start: pos,
  			end: pos + size + 5
  		};
  	}
  
  	/**
  	 * Helper function to fit a radial linear scale with point labels
  	 */
  	function fitWithPointLabels(scale) {
  		/*
  		 * Right, this is really confusing and there is a lot of maths going on here
  		 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
  		 *
  		 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
  		 *
  		 * Solution:
  		 *
  		 * We assume the radius of the polygon is half the size of the canvas at first
  		 * at each index we check if the text overlaps.
  		 *
  		 * Where it does, we store that angle and that index.
  		 *
  		 * After finding the largest index and angle we calculate how much we need to remove
  		 * from the shape radius to move the point inwards by that x.
  		 *
  		 * We average the left and right distances to get the maximum shape radius that can fit in the box
  		 * along with labels.
  		 *
  		 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
  		 * on each side, removing that from the size, halving it and adding the left x protrusion width.
  		 *
  		 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
  		 * and position it in the most space efficient manner
  		 *
  		 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
  		 */
  
  		var plFont = getPointLabelFontOptions(scale);
  
  		// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
  		// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
  		var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
  		var furthestLimits = {
  			r: scale.width,
  			l: 0,
  			t: scale.height,
  			b: 0
  		};
  		var furthestAngles = {};
  		var i, textSize, pointPosition;
  
  		scale.ctx.font = plFont.font;
  		scale._pointLabelSizes = [];
  
  		var valueCount = getValueCount(scale);
  		for (i = 0; i < valueCount; i++) {
  			pointPosition = scale.getPointPosition(i, largestPossibleRadius);
  			textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');
  			scale._pointLabelSizes[i] = textSize;
  
  			// Add quarter circle to make degree 0 mean top of circle
  			var angleRadians = scale.getIndexAngle(i);
  			var angle = helpers.toDegrees(angleRadians) % 360;
  			var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
  			var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
  
  			if (hLimits.start < furthestLimits.l) {
  				furthestLimits.l = hLimits.start;
  				furthestAngles.l = angleRadians;
  			}
  
  			if (hLimits.end > furthestLimits.r) {
  				furthestLimits.r = hLimits.end;
  				furthestAngles.r = angleRadians;
  			}
  
  			if (vLimits.start < furthestLimits.t) {
  				furthestLimits.t = vLimits.start;
  				furthestAngles.t = angleRadians;
  			}
  
  			if (vLimits.end > furthestLimits.b) {
  				furthestLimits.b = vLimits.end;
  				furthestAngles.b = angleRadians;
  			}
  		}
  
  		scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);
  	}
  
  	/**
  	 * Helper function to fit a radial linear scale with no point labels
  	 */
  	function fit(scale) {
  		var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
  		scale.drawingArea = Math.round(largestPossibleRadius);
  		scale.setCenterPoint(0, 0, 0, 0);
  	}
  
  	function getTextAlignForAngle(angle) {
  		if (angle === 0 || angle === 180) {
  			return 'center';
  		} else if (angle < 180) {
  			return 'left';
  		}
  
  		return 'right';
  	}
  
  	function fillText(ctx, text, position, fontSize) {
  		if (helpers.isArray(text)) {
  			var y = position.y;
  			var spacing = 1.5 * fontSize;
  
  			for (var i = 0; i < text.length; ++i) {
  				ctx.fillText(text[i], position.x, y);
  				y += spacing;
  			}
  		} else {
  			ctx.fillText(text, position.x, position.y);
  		}
  	}
  
  	function adjustPointPositionForLabelHeight(angle, textSize, position) {
  		if (angle === 90 || angle === 270) {
  			position.y -= (textSize.h / 2);
  		} else if (angle > 270 || angle < 90) {
  			position.y -= textSize.h;
  		}
  	}
  
  	function drawPointLabels(scale) {
  		var ctx = scale.ctx;
  		var opts = scale.options;
  		var angleLineOpts = opts.angleLines;
  		var pointLabelOpts = opts.pointLabels;
  
  		ctx.lineWidth = angleLineOpts.lineWidth;
  		ctx.strokeStyle = angleLineOpts.color;
  
  		var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
  
  		// Point Label Font
  		var plFont = getPointLabelFontOptions(scale);
  
  		ctx.textBaseline = 'top';
  
  		for (var i = getValueCount(scale) - 1; i >= 0; i--) {
  			if (angleLineOpts.display) {
  				var outerPosition = scale.getPointPosition(i, outerDistance);
  				ctx.beginPath();
  				ctx.moveTo(scale.xCenter, scale.yCenter);
  				ctx.lineTo(outerPosition.x, outerPosition.y);
  				ctx.stroke();
  				ctx.closePath();
  			}
  
  			if (pointLabelOpts.display) {
  				// Extra 3px out for some label spacing
  				var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);
  
  				// Keep this in loop since we may support array properties here
  				var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, globalDefaults.defaultFontColor);
  				ctx.font = plFont.font;
  				ctx.fillStyle = pointLabelFontColor;
  
  				var angleRadians = scale.getIndexAngle(i);
  				var angle = helpers.toDegrees(angleRadians);
  				ctx.textAlign = getTextAlignForAngle(angle);
  				adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
  				fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);
  			}
  		}
  	}
  
  	function drawRadiusLine(scale, gridLineOpts, radius, index) {
  		var ctx = scale.ctx;
  		ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1);
  		ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
  
  		if (scale.options.gridLines.circular) {
  			// Draw circular arcs between the points
  			ctx.beginPath();
  			ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
  			ctx.closePath();
  			ctx.stroke();
  		} else {
  			// Draw straight lines connecting each index
  			var valueCount = getValueCount(scale);
  
  			if (valueCount === 0) {
  				return;
  			}
  
  			ctx.beginPath();
  			var pointPosition = scale.getPointPosition(0, radius);
  			ctx.moveTo(pointPosition.x, pointPosition.y);
  
  			for (var i = 1; i < valueCount; i++) {
  				pointPosition = scale.getPointPosition(i, radius);
  				ctx.lineTo(pointPosition.x, pointPosition.y);
  			}
  
  			ctx.closePath();
  			ctx.stroke();
  		}
  	}
  
  	function numberOrZero(param) {
  		return helpers.isNumber(param) ? param : 0;
  	}
  
  	var LinearRadialScale = Chart.LinearScaleBase.extend({
  		setDimensions: function() {
  			var me = this;
  			var opts = me.options;
  			var tickOpts = opts.ticks;
  			// Set the unconstrained dimension before label rotation
  			me.width = me.maxWidth;
  			me.height = me.maxHeight;
  			me.xCenter = Math.round(me.width / 2);
  			me.yCenter = Math.round(me.height / 2);
  
  			var minSize = helpers.min([me.height, me.width]);
  			var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
  			me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
  		},
  		determineDataLimits: function() {
  			var me = this;
  			var chart = me.chart;
  			var min = Number.POSITIVE_INFINITY;
  			var max = Number.NEGATIVE_INFINITY;
  
  			helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
  				if (chart.isDatasetVisible(datasetIndex)) {
  					var meta = chart.getDatasetMeta(datasetIndex);
  
  					helpers.each(dataset.data, function(rawValue, index) {
  						var value = +me.getRightValue(rawValue);
  						if (isNaN(value) || meta.data[index].hidden) {
  							return;
  						}
  
  						min = Math.min(value, min);
  						max = Math.max(value, max);
  					});
  				}
  			});
  
  			me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
  			me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
  
  			// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
  			me.handleTickRangeOptions();
  		},
  		getTickLimit: function() {
  			var tickOpts = this.options.ticks;
  			var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
  			return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
  		},
  		convertTicksToLabels: function() {
  			var me = this;
  
  			Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
  
  			// Point labels
  			me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
  		},
  		getLabelForIndex: function(index, datasetIndex) {
  			return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
  		},
  		fit: function() {
  			if (this.options.pointLabels.display) {
  				fitWithPointLabels(this);
  			} else {
  				fit(this);
  			}
  		},
  		/**
  		 * Set radius reductions and determine new radius and center point
  		 * @private
  		 */
  		setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
  			var me = this;
  			var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
  			var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
  			var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
  			var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);
  
  			radiusReductionLeft = numberOrZero(radiusReductionLeft);
  			radiusReductionRight = numberOrZero(radiusReductionRight);
  			radiusReductionTop = numberOrZero(radiusReductionTop);
  			radiusReductionBottom = numberOrZero(radiusReductionBottom);
  
  			me.drawingArea = Math.min(
  				Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
  				Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
  			me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
  		},
  		setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
  			var me = this;
  			var maxRight = me.width - rightMovement - me.drawingArea;
  			var maxLeft = leftMovement + me.drawingArea;
  			var maxTop = topMovement + me.drawingArea;
  			var maxBottom = me.height - bottomMovement - me.drawingArea;
  
  			me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
  			me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);
  		},
  
  		getIndexAngle: function(index) {
  			var angleMultiplier = (Math.PI * 2) / getValueCount(this);
  			var startAngle = this.chart.options && this.chart.options.startAngle ?
  				this.chart.options.startAngle :
  				0;
  
  			var startAngleRadians = startAngle * Math.PI * 2 / 360;
  
  			// Start from the top instead of right, so remove a quarter of the circle
  			return index * angleMultiplier + startAngleRadians;
  		},
  		getDistanceFromCenterForValue: function(value) {
  			var me = this;
  
  			if (value === null) {
  				return 0; // null always in center
  			}
  
  			// Take into account half font size + the yPadding of the top value
  			var scalingFactor = me.drawingArea / (me.max - me.min);
  			if (me.options.ticks.reverse) {
  				return (me.max - value) * scalingFactor;
  			}
  			return (value - me.min) * scalingFactor;
  		},
  		getPointPosition: function(index, distanceFromCenter) {
  			var me = this;
  			var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
  			return {
  				x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
  				y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
  			};
  		},
  		getPointPositionForValue: function(index, value) {
  			return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
  		},
  
  		getBasePosition: function() {
  			var me = this;
  			var min = me.min;
  			var max = me.max;
  
  			return me.getPointPositionForValue(0,
  				me.beginAtZero ? 0 :
  				min < 0 && max < 0 ? max :
  				min > 0 && max > 0 ? min :
  				0);
  		},
  
  		draw: function() {
  			var me = this;
  			var opts = me.options;
  			var gridLineOpts = opts.gridLines;
  			var tickOpts = opts.ticks;
  			var valueOrDefault = helpers.valueOrDefault;
  
  			if (opts.display) {
  				var ctx = me.ctx;
  				var startAngle = this.getIndexAngle(0);
  
  				// Tick Font
  				var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
  				var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
  				var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
  				var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
  
  				helpers.each(me.ticks, function(label, index) {
  					// Don't draw a centre value (if it is minimum)
  					if (index > 0 || tickOpts.reverse) {
  						var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
  
  						// Draw circular lines around the scale
  						if (gridLineOpts.display && index !== 0) {
  							drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
  						}
  
  						if (tickOpts.display) {
  							var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
  							ctx.font = tickLabelFont;
  
  							ctx.save();
  							ctx.translate(me.xCenter, me.yCenter);
  							ctx.rotate(startAngle);
  
  							if (tickOpts.showLabelBackdrop) {
  								var labelWidth = ctx.measureText(label).width;
  								ctx.fillStyle = tickOpts.backdropColor;
  								ctx.fillRect(
  									-labelWidth / 2 - tickOpts.backdropPaddingX,
  									-yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY,
  									labelWidth + tickOpts.backdropPaddingX * 2,
  									tickFontSize + tickOpts.backdropPaddingY * 2
  								);
  							}
  
  							ctx.textAlign = 'center';
  							ctx.textBaseline = 'middle';
  							ctx.fillStyle = tickFontColor;
  							ctx.fillText(label, 0, -yCenterOffset);
  							ctx.restore();
  						}
  					}
  				});
  
  				if (opts.angleLines.display || opts.pointLabels.display) {
  					drawPointLabels(me);
  				}
  			}
  		}
  	});
  	Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
  
  };
  