/** 
 * @license Highcharts JS v2.0.4 (2010-09-07)
 * Exporting module
 * 
 * (c) 2010 Torstein Hønsi
 * 
 * License: www.highcharts.com/license
 */

// JSLint options:
/*global Highcharts, document, window, Math, setTimeout */

(function() { // encapsulate

// create shortcuts
var HC = Highcharts,
	Chart = HC.Chart,
	addEvent = HC.addEvent,
	defaultOptions = HC.defaultOptions,
	createElement = HC.createElement,
	discardElement = HC.discardElement,
	css = HC.css,
	merge = HC.merge,
	each = HC.each,
	extend = HC.extend,
	math = Math,
	mathMax = math.max,
	doc = document,
	win = window,
	M = 'M',
	L = 'L',
	DIV = 'div',
	HIDDEN = 'hidden',
	NONE = 'none',
	PREFIX = 'highcharts-',
	ABSOLUTE = 'absolute',
	PX = 'px',



	// Add language and get the defaultOptions
	defaultOptions = HC.setOptions({
		lang: {
			downloadPNG: 'Download PNG image',
			downloadJPEG: 'Download JPEG image',
			downloadPDF: 'Download PDF document',
			downloadSVG: 'Download SVG vector image',
			exportButtonTitle: 'Export to raster or vector image',
			printButtonTitle: 'Print the chart'
		}
	});

// Buttons and menus are collected in a separate config option set called 'navigation'.
// This can be extended later to add control buttons like zoom and pan right click menus.
defaultOptions.navigation = {
	menuStyle: {
		border: '1px solid #A0A0A0',
		background: '#FFFFFF'
	},
	menuItemStyle: {
		padding: '0 5px',
		background: NONE,
		color: '#303030'
	},
	menuItemHoverStyle: {
		background: '#4572A5',
		color: '#FFFFFF'
	},
	
	buttonOptions: {
		align: 'right',
		backgroundColor: {
			linearGradient: [0, 0, 0, 20],
			stops: [
				[0.4, '#F7F7F7'],
				[0.6, '#E3E3E3']
			]
		},
		borderColor: '#B0B0B0',
		borderRadius: 3,
		borderWidth: 1,
		//enabled: true,
		height: 20,
		hoverBorderColor: '#909090',
		hoverSymbolFill: '#81A7CF',
		hoverSymbolStroke: '#4572A5',
		symbolFill: '#E0E0E0',
		//symbolSize: 12,
		symbolStroke: '#A0A0A0',
		//symbolStrokeWidth: 1,
		symbolX: 11.5,
		symbolY: 10.5,
		verticalAlign: 'top',
		width: 24,
		y: 10		
	}
};



// Add the export related options
defaultOptions.exporting = {
	//enabled: true,
	//filename: 'chart',
	type: 'image/png',
	url: 'http://export.highcharts.com/',
	width: 800,
	buttons: {
		exportButton: {
			//enabled: true,
			symbol: 'exportIcon',
			x: -10,
			symbolFill: '#A8BF77',
			hoverSymbolFill: '#768F3E',
			_titleKey: 'exportButtonTitle',
			menuItems: [{
				textKey: 'downloadPNG',
				onclick: function() {
					this.exportChart();
				}
			}, {
				textKey: 'downloadJPEG',
				onclick: function() {
					this.exportChart({
						type: 'image/jpeg'
					});
				}
			}, {
				textKey: 'downloadPDF',
				onclick: function() {
					this.exportChart({
						type: 'application/pdf'
					});
				}
			}, {
				textKey: 'downloadSVG',
				onclick: function() {
					this.exportChart({
						type: 'image/svg+xml'
					});
				}
			}/*, {
				text: 'View SVG',
				onclick: function() {
					var svg = this.getSVG()
						.replace(/</g, '\n&lt;')
						.replace(/>/g, '&gt;');
						
					doc.body.innerHTML = '<pre>'+ svg +'</pre>';
				}
			}*/]
			
		},
		printButton: {
			//enabled: true,
			symbol: 'printIcon',
			x: -36,
			symbolFill: '#B5C9DF',
			hoverSymbolFill: '#779ABF',
			_titleKey: 'printButtonTitle',
			onclick: function() {
				this.print();
			}
		}
	}
};



extend (Chart.prototype, {
	/**
	 * Return an SVG representation of the chart
	 * 
	 * @param additionalOptions {Object} Additional chart options for the generated SVG representation
	 */	
	 getSVG: function(additionalOptions) {
		var chart = this,
			chartCopy,
			sandbox,
			svg,
			seriesOptions,
			pointOptions,
			pointMarker,
			options = merge(chart.options, additionalOptions); // copy the options and add extra options
		
		// IE compatibility hack for generating SVG content that it doesn't really understand
		if (!doc.createElementNS) {
			doc.createElementNS = function(ns, tagName) {
				var elem = doc.createElement(tagName);
				elem.getBBox = function() {
					return chart.renderer.Element.prototype.getBBox.apply({ element: elem });
				};
				return elem;
			};
		}
		
		// create a sandbox where a new chart will be generated
		sandbox = createElement(DIV, null, {
			position: ABSOLUTE,
			top: '-9999em',
			width: chart.chartWidth + PX,
			height: chart.chartHeight + PX
		}, doc.body);
		
		// override some options
		extend(options.chart, {
			renderTo: sandbox,
			renderer: 'SVG'
		});
		options.exporting.enabled = false; // hide buttons in print
		options.chart.plotBackgroundImage = null; // the converter doesn't handle images
		
		// prepare for replicating the chart
		options.series = [];
		each (chart.series, function(serie) {
			seriesOptions = serie.options;			
			
			seriesOptions.animation = false; // turn off animation
			seriesOptions.showCheckbox = false;
			
			seriesOptions.data = [];
			each(serie.data, function(point) {
		        	pointOptions = point.config == null || typeof point.config == 'number'?
				       { y: point.y } :
				       point.config;
				pointOptions.x = point.x;
			        seriesOptions.data.push(pointOptions); // copy fresh updated data

				// remove image markers
			        if (point.config && point.config.marker && /^url\(/.test(point.config.marker.symbol)) {
					delete point.config.marker.symbol;
				}

			});	
			
			options.series.push(seriesOptions);
		});
		
		// generate the chart copy
		chartCopy = new Highcharts.Chart(options);
		
		// get the SVG from the container's innerHTML
		svg = chartCopy.container.innerHTML;
		
		// free up memory
		options = null;
		chartCopy.destroy();
		discardElement(sandbox);
		
		// sanitize
		svg = svg.
			replace(/zIndex="[^"]+"/g, ''). 
			replace(/isShadow="[^"]+"/g, '').
			replace(/symbolName="[^"]+"/g, '').
			replace(/jQuery[0-9]+="[^"]+"/g, '').
			replace(/isTracker="[^"]+"/g, '').
			replace(/url\([^#]+#/g, 'url(#').
			
			// IE specific
			replace(/id=([^" >]+)/g, 'id="$1"'). 
			replace(/class=([^" ]+)/g, 'class="$1"').
			replace(/ transform /g, ' ').
			replace(/:path/g, 'path').
			replace(/style="([^"]+)"/g, function(s) {
				return s.toLowerCase();
			});
			
		return svg;
	},
	
	/**
	 * Submit the SVG representation of the chart to the server
	 * @param {Object} options Exporting options. Possible members are url, type and width.
	 * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
	 */
	exportChart: function(options, chartOptions) {
		var form,
			chart = this,
			svg = chart.getSVG(chartOptions);
			
		// merge the options
		options = merge(chart.options.exporting, options);
		
		// create the form
		form = createElement('form', {
			method: 'post',
			action: options.url,
			target: '_blank'
		}, {
			display: NONE
		}, doc.body);
		
		// add the values
		each(['filename', 'type', 'width', 'svg'], function(name) {
			createElement('input', {
				type: HIDDEN,
				name: name,
				value: { 
					filename: options.filename || 'chart', 
					type: options.type, 
					width: options.width, 
					svg: svg 
				}[name]
			}, null, form);
		});
		
		// submit
		form.submit();
		
		// clean up
		discardElement(form);
	},
	
	/**
	 * Print the chart
	 */
	print: function() {
		
		var chart = this,
			container = chart.container,
			i,
			origDisplay = [],
			origParent = container.parentNode,
			body = doc.body,
			childNodes = body.childNodes;
			
		if (chart.isPrinting) { // block the button while in printing mode
			return;
		}
		
		chart.isPrinting = true;
		
		// hide all body content	
		each(childNodes, function(node, i) {
			if (node.nodeType == 1) {
				origDisplay[i] = node.style.display;
				node.style.display = NONE;
			}
		});
			
		// pull out the chart
		body.appendChild(container);
		 
		// print
		win.print();		
		
		// allow the browser to prepare before reverting
		setTimeout(function() {

			// put the chart back in
			origParent.appendChild(container);
			
			// restore all body content
			each (childNodes, function(node, i) {
				if (node.nodeType == 1) {
					node.style.display = origDisplay[i];
				}
			});
			
			chart.isPrinting = false;
			
		}, 1000);

	},
	
	/**
	 * Display a popup menu for choosing the export type 
	 * 
	 * @param {String} name An identifier for the menu
	 * @param {Array} items A collection with text and onclicks for the items
	 * @param {Number} x The x position of the opener button
	 * @param {Number} y The y position of the opener button
	 * @param {Number} width The width of the opener button
	 * @param {Number} height The height of the opener button
	 */
	contextMenu: function(name, items, x, y, width, height) {
		var chart = this,
			navOptions = chart.options.navigation,
			menuItemStyle = navOptions.menuItemStyle,
			chartWidth = chart.chartWidth,
			chartHeight = chart.chartHeight,
			cacheName = 'cache-'+ name,
			menu = chart[cacheName],
			menuPadding = mathMax(width, height), // for mouse leave detection
			boxShadow = '3px 3px 10px #888',
			innerMenu,
			hide,
			menuStyle; 
		
		// create the menu only the first time
		if (!menu) {
			
			// create a HTML element above the SVG		
			chart[cacheName] = menu = createElement(DIV, {
				className: PREFIX + name
			}, {
				position: ABSOLUTE,
				zIndex: 1000,
				padding: menuPadding + PX
			}, chart.container);
			
			innerMenu = createElement(DIV, null, 
				extend({
					MozBoxShadow: boxShadow,
					WebkitBoxShadow: boxShadow
				}, navOptions.menuStyle) , menu);
			
			hide = function() {
				css(menu, { display: NONE });
			};
			addEvent(menu, 'mouseleave', hide);
			
			// create the items
			each(items, function(item) {
				if (item) {
					createElement(DIV, {
						onclick: function() {
							hide();
							item.onclick.apply(chart, arguments);
						},
						onmouseover: function() {
							css(this, navOptions.menuItemHoverStyle);
						},
						onmouseout: function() {
							css(this, menuItemStyle);
						},
						innerHTML: item.text || HC.getOptions().lang[item.textKey]
					}, extend({
						cursor: 'pointer'
					}, menuItemStyle), innerMenu);
				}
			});
			
			chart.exportMenuWidth = menu.offsetWidth;
			chart.exportMenuHeigh = menu.offsetHeight;
		}
		
		menuStyle = { display: 'block' };
		
		// if outside right, right align it
		if (x + chart.exportMenuWidth > chartWidth) {
			menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
		} else {
			menuStyle.left = (x - menuPadding) + PX;
		}
		// if outside bottom, bottom align it
		if (y + height + chart.exportMenuWidth > chartHeight) {
			menuStyle.bottom = (chartHeight - y - menuPadding)  + PX;
		} else {
			menuStyle.top = (y + height - menuPadding) + PX;
		}
		
		css(menu, menuStyle);
	},
	
	/**
	 * Add the export button to the chart
	 */
	addButton: function(options) {
		var chart = this,
			renderer = chart.renderer,
			btnOptions = merge(chart.options.navigation.buttonOptions, options),
			onclick = btnOptions.onclick,
			menuItems = btnOptions.menuItems,
			position = chart.getAlignment(btnOptions),
			buttonLeft = position.x,
			buttonTop = position.y,
			buttonWidth = btnOptions.width,
			buttonHeight = btnOptions.height,
			box,
			symbol,
			button,	
			borderWidth = btnOptions.borderWidth,
			boxAttr = {
				stroke: btnOptions.borderColor
				
			},
			symbolAttr = {
				stroke: btnOptions.symbolStroke,
				fill: btnOptions.symbolFill
			};
			
		if (btnOptions.enabled === false) {
			return;
		}
			
		// element to capture the click
		function revert() {
			symbol.attr(symbolAttr);
			box.attr(boxAttr);
		}
		
		// the box border
		box = renderer.rect(
			0,
			0,
			buttonWidth, 
			buttonHeight,
			btnOptions.borderRadius,
			borderWidth
		)
		.translate(buttonLeft, buttonTop) // to allow gradients
		.attr(extend({
			fill: btnOptions.backgroundColor,
			'stroke-width': borderWidth,
			zIndex: 19
		}, boxAttr)).add();
		
		// the invisible element to track the clicks
		button = renderer.rect( 
			buttonLeft,
			buttonTop,
			buttonWidth,
			buttonHeight,
			0
		).attr({
			fill: 'rgba(255, 255, 255, 0.001)',
			title: HC.getOptions().lang[btnOptions._titleKey],
			zIndex: 21
		}).css({
			cursor: 'pointer'
		})
		.on('mouseover', function() {
			symbol.attr({
				stroke: btnOptions.hoverSymbolStroke,
				fill: btnOptions.hoverSymbolFill
			});
			box.attr({
				stroke: btnOptions.hoverBorderColor
			});
		})
		.on('mouseout', revert)		
		.add();
		
		addEvent(button.element, 'click', revert);
		
		// add the click event
		if (menuItems) {
			onclick = function(e) {
				chart.contextMenu('export-menu', menuItems, buttonLeft, buttonTop, buttonWidth, buttonHeight);
			};
		}
		addEvent(button.element, 'click', function() {
			onclick.apply(chart, arguments);
		});
		
		// the icon
		symbol = renderer.symbol(
				btnOptions.symbol, 
				buttonLeft + btnOptions.symbolX, 
				buttonTop + btnOptions.symbolY, 
				(btnOptions.symbolSize || 12) / 2
			)
			.attr(extend(symbolAttr, {
				'stroke-width': btnOptions.symbolStrokeWidth || 1,
				zIndex: 20		
			})).add();
		

		
	}
});

// Create the export icon
HC.Renderer.prototype.symbols.exportIcon = function(x, y, radius) {
	return [
		M, // the disk
		x - radius, y + radius,
		L,
		x + radius, y + radius,
		x + radius, y + radius * 0.5,
		x - radius, y + radius * 0.5,
		'Z',
		M, // the arrow
		x, y + radius * 0.5,
		L,
		x - radius * 0.5, y - radius / 3,
		x - radius / 6, y - radius / 3,
		x - radius / 6, y - radius,
		x + radius / 6, y - radius,
		x + radius / 6, y - radius / 3,
		x + radius * 0.5, y - radius / 3,
		'Z'
	];
};
// Create the print icon
HC.Renderer.prototype.symbols.printIcon = function(x, y, radius) {
	return [
		M, // the printer
		x - radius, y + radius * 0.5,
		L,
		x + radius, y + radius * 0.5,
		x + radius, y - radius / 3,
		x - radius, y - radius / 3,
		'Z',
		M, // the upper sheet
		x - radius * 0.5, y - radius / 3,
		L,
		x - radius * 0.5, y - radius,
		x + radius * 0.5, y - radius,
		x + radius * 0.5, y - radius / 3,
		'Z',
		M, // the lower sheet
		x - radius * 0.5, y + radius * 0.5,
		L,
		x - radius * 0.75, y + radius,
		x + radius * 0.75, y + radius,
		x + radius * 0.5, y + radius * 0.5,
		'Z'
	];
};

// Overwrite the HC.Chart object with added functionality for export buttons after render
HC.Chart = function(options) {
	var chart = new Chart(options),
		n,
		exportingOptions = chart.options.exporting,
		buttons = exportingOptions.buttons;
		
	if (exportingOptions.enabled !== false) {	
		for (n in buttons) {
			chart.addButton(buttons[n]);
		}
	}
	
	return chart;
};

})();

