text: 'use strict';
  
  var defaults = require('../core/core.defaults');
  var helpers = require('../helpers/index');
  var LinearScaleBase = require('./scale.linearbase');
  var Ticks = require('../core/core.ticks');
  
  var valueOrDefault = helpers.valueOrDefault;
  var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
  var resolve = helpers.options.resolve;
  
  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,
  		borderDash: [],
  		borderDashOffset: 0.0
  	},
  
  	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 getTickBackdropHeight(opts) {
  	var tickOpts = opts.ticks;
  
  	if (tickOpts.display && opts.display) {
  		return valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2;
  	}
  	return 0;
  }
  
  function measureLabelSize(ctx, lineHeight, label) {
  	if (helpers.isArray(label)) {
  		return {
  			w: helpers.longestText(ctx, ctx.font, label),
  			h: label.length * lineHeight
  		};
  	}
  
  	return {
  		w: ctx.measureText(label).width,
  		h: lineHeight
  	};
  }
  
  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,
  			end: pos
  		};
  	}
  
  	return {
  		start: pos,
  		end: pos + size
  	};
  }
  
  /**
   * 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 = helpers.options._parseFont(scale.options.pointLabels);
  
  	// 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 furthestLimits = {
  		l: 0,
  		r: scale.width,
  		t: 0,
  		b: scale.height - scale.paddingTop
  	};
  	var furthestAngles = {};
  	var i, textSize, pointPosition;
  
  	scale.ctx.font = plFont.string;
  	scale._pointLabelSizes = [];
  
  	var valueCount = scale.chart.data.labels.length;
  	for (i = 0; i < valueCount; i++) {
  		pointPosition = scale.getPointPosition(i, scale.drawingArea + 5);
  		textSize = measureLabelSize(scale.ctx, plFont.lineHeight, 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(scale.drawingArea, furthestLimits, furthestAngles);
  }
  
  function getTextAlignForAngle(angle) {
  	if (angle === 0 || angle === 180) {
  		return 'center';
  	} else if (angle < 180) {
  		return 'left';
  	}
  
  	return 'right';
  }
  
  function fillText(ctx, text, position, lineHeight) {
  	var y = position.y + lineHeight / 2;
  	var i, ilen;
  
  	if (helpers.isArray(text)) {
  		for (i = 0, ilen = text.length; i < ilen; ++i) {
  			ctx.fillText(text[i], position.x, y);
  			y += lineHeight;
  		}
  	} else {
  		ctx.fillText(text, position.x, 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 pointLabelOpts = opts.pointLabels;
  	var tickBackdropHeight = getTickBackdropHeight(opts);
  	var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
  	var plFont = helpers.options._parseFont(pointLabelOpts);
  
  	ctx.save();
  
  	ctx.font = plFont.string;
  	ctx.textBaseline = 'middle';
  
  	for (var i = scale.chart.data.labels.length - 1; i >= 0; i--) {
  		// Extra pixels out for some label spacing
  		var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
  		var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
  
  		// Keep this in loop since we may support array properties here
  		var pointLabelFontColor = valueAtIndexOrDefault(pointLabelOpts.fontColor, i, defaults.global.defaultFontColor);
  		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.lineHeight);
  	}
  	ctx.restore();
  }
  
  function drawRadiusLine(scale, gridLineOpts, radius, index) {
  	var ctx = scale.ctx;
  	var circular = gridLineOpts.circular;
  	var valueCount = scale.chart.data.labels.length;
  	var lineColor = valueAtIndexOrDefault(gridLineOpts.color, index - 1);
  	var lineWidth = valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
  	var pointPosition;
  
  	if ((!circular && !valueCount) || !lineColor || !lineWidth) {
  		return;
  	}
  
  	ctx.save();
  	ctx.strokeStyle = lineColor;
  	ctx.lineWidth = lineWidth;
  	if (ctx.setLineDash) {
  		ctx.setLineDash(gridLineOpts.borderDash || []);
  		ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0;
  	}
  
  	ctx.beginPath();
  	if (circular) {
  		// Draw circular arcs between the points
  		ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
  	} else {
  		// Draw straight lines connecting each index
  		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();
  	ctx.restore();
  }
  
  function numberOrZero(param) {
  	return helpers.isNumber(param) ? param : 0;
  }
  
  module.exports = LinearScaleBase.extend({
  	setDimensions: function() {
  		var me = this;
  
  		// Set the unconstrained dimension before label rotation
  		me.width = me.maxWidth;
  		me.height = me.maxHeight;
  		me.paddingTop = getTickBackdropHeight(me.options) / 2;
  		me.xCenter = Math.floor(me.width / 2);
  		me.yCenter = Math.floor((me.height - me.paddingTop) / 2);
  		me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2;
  	},
  
  	determineDataLimits: function() {
  		var me = this;
  		var minmax = me._getMinMax(false);
  		var min = minmax.min;
  		var max = minmax.max;
  
  		me.min = helpers.isFinite(min) && !isNaN(min) ? min : 0;
  		me.max = helpers.isFinite(max) && !isNaN(max) ? max : 0;
  
  		// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
  		me.handleTickRangeOptions();
  	},
  
  	// Returns the maximum number of ticks based on the scale dimension
  	_computeTickLimit: function() {
  		return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
  	},
  
  	generateTickLabels: function(ticks) {
  		var me = this;
  
  		LinearScaleBase.prototype.generateTickLabels.call(me, ticks);
  
  		// Point labels
  		me.pointLabels = me.chart.data.labels.map(function() {
  			var label = helpers.callback(me.options.pointLabels.callback, arguments, me);
  			return label || label === 0 ? label : '';
  		});
  	},
  
  	fit: function() {
  		var me = this;
  		var opts = me.options;
  
  		if (opts.display && opts.pointLabels.display) {
  			fitWithPointLabels(me);
  		} else {
  			me.setCenterPoint(0, 0, 0, 0);
  		}
  	},
  
  	/**
  	 * 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 - me.paddingTop), 0) / Math.cos(furthestAngles.b);
  
  		radiusReductionLeft = numberOrZero(radiusReductionLeft);
  		radiusReductionRight = numberOrZero(radiusReductionRight);
  		radiusReductionTop = numberOrZero(radiusReductionTop);
  		radiusReductionBottom = numberOrZero(radiusReductionBottom);
  
  		me.drawingArea = Math.min(
  			Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
  			Math.floor(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 - me.paddingTop) - bottomMovement - me.drawingArea;
  
  		me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left);
  		me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop);
  	},
  
  	getIndexAngle: function(index) {
  		var chart = this.chart;
  		var angleMultiplier = 360 / chart.data.labels.length;
  		var options = chart.options || {};
  		var startAngle = options.startAngle || 0;
  
  		// Start from the top instead of right, so remove a quarter of the circle
  		var angle = (index * angleMultiplier + startAngle) % 360;
  
  		return (angle < 0 ? angle + 360 : angle) * Math.PI * 2 / 360;
  	},
  
  	getDistanceFromCenterForValue: function(value) {
  		var me = this;
  
  		if (helpers.isNullOrUndef(value)) {
  			return NaN;
  		}
  
  		// 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.cos(thisAngle) * distanceFromCenter + me.xCenter,
  			y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter
  		};
  	},
  
  	getPointPositionForValue: function(index, value) {
  		return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
  	},
  
  	getBasePosition: function(index) {
  		var me = this;
  		var min = me.min;
  		var max = me.max;
  
  		return me.getPointPositionForValue(index || 0,
  			me.beginAtZero ? 0 :
  			min < 0 && max < 0 ? max :
  			min > 0 && max > 0 ? min :
  			0);
  	},
  
  	/**
  	 * @private
  	 */
  	_drawGrid: function() {
  		var me = this;
  		var ctx = me.ctx;
  		var opts = me.options;
  		var gridLineOpts = opts.gridLines;
  		var angleLineOpts = opts.angleLines;
  		var lineWidth = valueOrDefault(angleLineOpts.lineWidth, gridLineOpts.lineWidth);
  		var lineColor = valueOrDefault(angleLineOpts.color, gridLineOpts.color);
  		var i, offset, position;
  
  		if (opts.pointLabels.display) {
  			drawPointLabels(me);
  		}
  
  		if (gridLineOpts.display) {
  			helpers.each(me.ticks, function(tick, index) {
  				if (index !== 0) {
  					offset = me.getDistanceFromCenterForValue(me._tickValues[index]);
  					drawRadiusLine(me, gridLineOpts, offset, index);
  				}
  			});
  		}
  
  		if (angleLineOpts.display && lineWidth && lineColor) {
  			ctx.save();
  			ctx.lineWidth = lineWidth;
  			ctx.strokeStyle = lineColor;
  			if (ctx.setLineDash) {
  				ctx.setLineDash(resolve([angleLineOpts.borderDash, gridLineOpts.borderDash, []]));
  				ctx.lineDashOffset = resolve([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]);
  			}
  
  			for (i = me.chart.data.labels.length - 1; i >= 0; i--) {
  				offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max);
  				position = me.getPointPosition(i, offset);
  				ctx.beginPath();
  				ctx.moveTo(me.xCenter, me.yCenter);
  				ctx.lineTo(position.x, position.y);
  				ctx.stroke();
  			}
  
  			ctx.restore();
  		}
  	},
  
  	/**
  	 * @private
  	 */
  	_drawLabels: function() {
  		var me = this;
  		var ctx = me.ctx;
  		var opts = me.options;
  		var tickOpts = opts.ticks;
  
  		if (!tickOpts.display) {
  			return;
  		}
  
  		var startAngle = me.getIndexAngle(0);
  		var tickFont = helpers.options._parseFont(tickOpts);
  		var tickFontColor = valueOrDefault(tickOpts.fontColor, defaults.global.defaultFontColor);
  		var offset, width;
  
  		ctx.save();
  		ctx.font = tickFont.string;
  		ctx.translate(me.xCenter, me.yCenter);
  		ctx.rotate(startAngle);
  		ctx.textAlign = 'center';
  		ctx.textBaseline = 'middle';
  
  		helpers.each(me.ticks, function(tick, index) {
  			if (index === 0 && !tickOpts.reverse) {
  				return;
  			}
  
  			offset = me.getDistanceFromCenterForValue(me._tickValues[index]);
  
  			if (tickOpts.showLabelBackdrop) {
  				width = ctx.measureText(tick.label).width;
  				ctx.fillStyle = tickOpts.backdropColor;
  
  				ctx.fillRect(
  					-width / 2 - tickOpts.backdropPaddingX,
  					-offset - tickFont.size / 2 - tickOpts.backdropPaddingY,
  					width + tickOpts.backdropPaddingX * 2,
  					tickFont.size + tickOpts.backdropPaddingY * 2
  				);
  			}
  
  			ctx.fillStyle = tickFontColor;
  			ctx.fillText(tick.label, 0, -offset);
  		});
  
  		ctx.restore();
  	},
  
  	/**
  	 * @private
  	 */
  	_drawTitle: helpers.noop
  });
  
  // INTERNAL: static default options, registered in src/index.js
  module.exports._defaults = defaultConfig;
  