text: "use strict";
  
  module.exports = function(Chart) {
  
  	var helpers = Chart.helpers;
  	var noop = helpers.noop;
  
  	Chart.defaults.global.legend = {
  
  		display: true,
  		position: 'top',
  		fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
  		reverse: false,
  
  		// a callback that will handle
  		onClick: function(e, legendItem) {
  			var index = legendItem.datasetIndex;
  			var ci = this.chart;
  			var meta = ci.getDatasetMeta(index);
  
  			// See controller.isDatasetVisible comment
  			meta.hidden = meta.hidden === null? !ci.data.datasets[index].hidden : null;
  
  			// We hid a dataset ... rerender the chart
  			ci.update();
  		},
  
  		labels: {
  			boxWidth: 40,
  			padding: 10,
  			// Generates labels shown in the legend
  			// Valid properties to return:
  			// text : text to display
  			// fillStyle : fill of coloured box
  			// strokeStyle: stroke of coloured box
  			// hidden : if this legend item refers to a hidden item
  			// lineCap : cap style for line
  			// lineDash
  			// lineDashOffset :
  			// lineJoin :
  			// lineWidth :
  			generateLabels: function(chart) {
  				var data = chart.data;
  				return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
  					return {
  						text: dataset.label,
  						fillStyle: dataset.backgroundColor,
  						hidden: !chart.isDatasetVisible(i),
  						lineCap: dataset.borderCapStyle,
  						lineDash: dataset.borderDash,
  						lineDashOffset: dataset.borderDashOffset,
  						lineJoin: dataset.borderJoinStyle,
  						lineWidth: dataset.borderWidth,
  						strokeStyle: dataset.borderColor,
  
  						// Below is extra data used for toggling the datasets
  						datasetIndex: i
  					};
  				}, this) : [];
  			}
  		}
  	};
  
  	Chart.Legend = Chart.Element.extend({
  
  		initialize: function(config) {
  			helpers.extend(this, config);
  
  			// Contains hit boxes for each dataset (in dataset order)
  			this.legendHitBoxes = [];
  
  			// Are we in doughnut mode which has a different data type
  			this.doughnutMode = false;
  		},
  
  		// These methods are ordered by lifecyle. Utilities then follow.
  		// Any function defined here is inherited by all legend types.
  		// Any function can be extended by the legend type
  
  		beforeUpdate: noop,
  		update: function(maxWidth, maxHeight, margins) {
  
  			// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
  			this.beforeUpdate();
  
  			// Absorb the master measurements
  			this.maxWidth = maxWidth;
  			this.maxHeight = maxHeight;
  			this.margins = margins;
  
  			// Dimensions
  			this.beforeSetDimensions();
  			this.setDimensions();
  			this.afterSetDimensions();
  			// Labels
  			this.beforeBuildLabels();
  			this.buildLabels();
  			this.afterBuildLabels();
  
  			// Fit
  			this.beforeFit();
  			this.fit();
  			this.afterFit();
  			//
  			this.afterUpdate();
  
  			return this.minSize;
  		},
  		afterUpdate: noop,
  
  		//
  
  		beforeSetDimensions: noop,
  		setDimensions: function() {
  			// Set the unconstrained dimension before label rotation
  			if (this.isHorizontal()) {
  				// Reset position before calculating rotation
  				this.width = this.maxWidth;
  				this.left = 0;
  				this.right = this.width;
  			} else {
  				this.height = this.maxHeight;
  
  				// Reset position before calculating rotation
  				this.top = 0;
  				this.bottom = this.height;
  			}
  
  			// Reset padding
  			this.paddingLeft = 0;
  			this.paddingTop = 0;
  			this.paddingRight = 0;
  			this.paddingBottom = 0;
  
  			// Reset minSize
  			this.minSize = {
  				width: 0,
  				height: 0
  			};
  		},
  		afterSetDimensions: noop,
  
  		//
  
  		beforeBuildLabels: noop,
  		buildLabels: function() {
  			this.legendItems = this.options.labels.generateLabels.call(this, this.chart);
  			if(this.options.reverse){
  				this.legendItems.reverse();
  			}
  		},
  		afterBuildLabels: noop,
  
  		//
  
  		beforeFit: noop,
  		fit: function() {
  			var opts = this.options;
  			var labelOpts = opts.labels;
  			var display = opts.display;
  
  			var ctx = this.ctx;
  
  			var globalDefault = Chart.defaults.global,
  				itemOrDefault = helpers.getValueOrDefault,
  				fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),
  				fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),
  				fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),
  				labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
  
  			// Reset hit boxes
  			var hitboxes = this.legendHitBoxes = [];
  
  			var minSize = this.minSize;
  			var isHorizontal = this.isHorizontal();
  
  			if (isHorizontal) {
  				minSize.width = this.maxWidth; // fill all the width
  				minSize.height = display ? 10 : 0;
  			} else {
  				minSize.width = display ? 10 : 0;
  				minSize.height = this.maxHeight; // fill all the height
  			}
  
  			// Increase sizes here
  			if (display) {
  				if (isHorizontal) {
  					// Labels
  
  					// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
  					var lineWidths = this.lineWidths = [0];
  					var totalHeight = this.legendItems.length ? fontSize + (labelOpts.padding) : 0;
  
  					ctx.textAlign = "left";
  					ctx.textBaseline = 'top';
  					ctx.font = labelFont;
  
  					helpers.each(this.legendItems, function(legendItem, i) {
  						var width = labelOpts.boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
  						if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= this.width) {
  							totalHeight += fontSize + (labelOpts.padding);
  							lineWidths[lineWidths.length] = this.left;
  						}
  
  						// Store the hitbox width and height here. Final position will be updated in `draw`
  						hitboxes[i] = {
  							left: 0,
  							top: 0,
  							width: width,
  							height: fontSize
  						};
  
  						lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
  					}, this);
  
  					minSize.height += totalHeight;
  
  				} else {
  					// TODO vertical
  				}
  			}
  
  			this.width = minSize.width;
  			this.height = minSize.height;
  		},
  		afterFit: noop,
  
  		// Shared Methods
  		isHorizontal: function() {
  			return this.options.position === "top" || this.options.position === "bottom";
  		},
  
  		// Actualy draw the legend on the canvas
  		draw: function() {
  			var opts = this.options;
  			var labelOpts = opts.labels;
  			var globalDefault = Chart.defaults.global,
  				lineDefault = globalDefault.elements.line,
  				legendWidth = this.width,
  				lineWidths = this.lineWidths;
  
  			if (opts.display) {
  				var ctx = this.ctx,
  					cursor = {
  						x: this.left + ((legendWidth - lineWidths[0]) / 2),
  						y: this.top + labelOpts.padding,
  						line: 0
  					},
  					itemOrDefault = helpers.getValueOrDefault,
  					fontColor = itemOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor),
  					fontSize = itemOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize),
  					fontStyle = itemOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle),
  					fontFamily = itemOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily),
  					labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
  
  				// Horizontal
  				if (this.isHorizontal()) {
  					// Labels
  					ctx.textAlign = "left";
  					ctx.textBaseline = 'top';
  					ctx.lineWidth = 0.5;
  					ctx.strokeStyle = fontColor; // for strikethrough effect
  					ctx.fillStyle = fontColor; // render in correct colour
  					ctx.font = labelFont;
  
  					var boxWidth = labelOpts.boxWidth,
  						hitboxes = this.legendHitBoxes;
  
  					helpers.each(this.legendItems, function(legendItem, i) {
  						var textWidth = ctx.measureText(legendItem.text).width,
  							width = boxWidth + (fontSize / 2) + textWidth,
  							x = cursor.x,
  							y = cursor.y;
  
  						if (x + width >= legendWidth) {
  							y = cursor.y += fontSize + (labelOpts.padding);
  							cursor.line++;
  							x = cursor.x = this.left + ((legendWidth - lineWidths[cursor.line]) / 2);
  						}
  
  						// Set the ctx for the box
  						ctx.save();
  
  						ctx.fillStyle = itemOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
  						ctx.lineCap = itemOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
  						ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
  						ctx.lineJoin = itemOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
  						ctx.lineWidth = itemOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
  						ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
  
  						if (ctx.setLineDash) {
  							// IE 9 and 10 do not support line dash
  							ctx.setLineDash(itemOrDefault(legendItem.lineDash, lineDefault.borderDash));
  						}
  
  						// Draw the box
  						ctx.strokeRect(x, y, boxWidth, fontSize);
  						ctx.fillRect(x, y, boxWidth, fontSize);
  
  						ctx.restore();
  
  						hitboxes[i].left = x;
  						hitboxes[i].top = y;
  
  						// Fill the actual label
  						ctx.fillText(legendItem.text, boxWidth + (fontSize / 2) + x, y);
  
  						if (legendItem.hidden) {
  							// Strikethrough the text if hidden
  							ctx.beginPath();
  							ctx.lineWidth = 2;
  							ctx.moveTo(boxWidth + (fontSize / 2) + x, y + (fontSize / 2));
  							ctx.lineTo(boxWidth + (fontSize / 2) + x + textWidth, y + (fontSize / 2));
  							ctx.stroke();
  						}
  
  						cursor.x += width + (labelOpts.padding);
  					}, this);
  				} else {
  
  				}
  			}
  		},
  
  		// Handle an event
  		handleEvent: function(e) {
  			var position = helpers.getRelativePosition(e, this.chart.chart),
  				x = position.x,
  				y = position.y,
  				opts = this.options;
  
  			if (x >= this.left && x <= this.right && y >= this.top && y <= this.bottom) {
  				// See if we are touching one of the dataset boxes
  				var lh = this.legendHitBoxes;
  				for (var i = 0; i < lh.length; ++i) {
  					var hitBox = lh[i];
  
  					if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
  						// Touching an element
  						if (opts.onClick) {
  							opts.onClick.call(this, e, this.legendItems[i]);
  						}
  						break;
  					}
  				}
  			}
  		}
  	});
  
  };
  