/**
 * @license Highcharts JS v4.2.5 (2016-05-06)
 * Client side exporting module
 *
 * (c) 2015 Torstein Honsi / Oystein Moseng
 *
 * License: www.highcharts.com/license
 */

/*global MSBlobBuilder */
(function (factory) {
	if (typeof module === 'object' && module.exports) {
		module.exports = factory;
	} else {
		factory(Highcharts);
	}
}(function (Highcharts) {

	var win = Highcharts.win,
		nav = win.navigator,
		doc = win.document;

	// Dummy object so we can reuse our canvas-tools.js without errors
	Highcharts.CanVGRenderer = {};


	/**
	 * Downloads a script and executes a callback when done.
	 * @param {String} scriptLocation
	 * @param {Function} callback
	 */
	function getScript(scriptLocation, callback) {
		var head = doc.getElementsByTagName('head')[0],
			script = doc.createElement('script');

		script.type = 'text/javascript';
		script.src = scriptLocation;
		script.onload = callback;

		head.appendChild(script);
	}

	/**
	 * Add a new method to the Chart object to perform a local download
	 */
	Highcharts.Chart.prototype.exportChartLocal = function (exportingOptions, chartOptions) {
		var chart = this,
			options = Highcharts.merge(chart.options.exporting, exportingOptions),
			webKit = nav.userAgent.indexOf('WebKit') > -1 && nav.userAgent.indexOf('Chrome') < 0, // Webkit and not chrome
			scale = options.scale || 2,
			chartCopyContainer,
			domurl = win.URL || win.webkitURL || win,
			images,
			imagesEmbedded = 0,
			el,
			i,
			l,
			fallbackToExportServer = function () {
				if (options.fallbackToExportServer === false) {
					if (options.error) {
						options.error();
					} else {
						throw 'Fallback to export server disabled';
					}
				} else {
					chart.exportChart(options);
				}
			},
			// Get data:URL from image URL
			// Pass in callbacks to handle results. finallyCallback is always called at the end of the process. Supplying this callback is optional.
			// All callbacks receive three arguments: imageURL, imageType, and callbackArgs. callbackArgs is used only by callbacks and can contain whatever.
			imageToDataUrl = function (imageURL, imageType, callbackArgs, successCallback, taintedCallback, noCanvasSupportCallback, failedLoadCallback, finallyCallback) {
				var img = new win.Image(),
					taintedHandler,
					loadHandler = function () {
						var canvas = doc.createElement('canvas'),
							ctx = canvas.getContext && canvas.getContext('2d'),
							dataURL;
						try {
							if (!ctx) {
								noCanvasSupportCallback(imageURL, imageType, callbackArgs);
							} else {
								canvas.height = img.height * scale;
								canvas.width = img.width * scale;
								ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

								// Now we try to get the contents of the canvas.
								try {
									dataURL = canvas.toDataURL(imageType);
									successCallback(dataURL, imageType, callbackArgs);
								} catch (e) {
									// Failed - either tainted canvas or something else went horribly wrong
									if (e.name === 'SecurityError' || e.name === 'SECURITY_ERR' || e.message === 'SecurityError') {
										taintedHandler(imageURL, imageType, callbackArgs);
									} else {
										throw e;
									}
								}
							}
						} finally {
							if (finallyCallback) {
								finallyCallback(imageURL, imageType, callbackArgs);
							}
						}
					},
					// Image load failed (e.g. invalid URL)
					errorHandler = function () {
						failedLoadCallback(imageURL, imageType, callbackArgs);
						if (finallyCallback) {
							finallyCallback(imageURL, imageType, callbackArgs);
						}
					};
				
				// This is called on load if the image drawing to canvas failed with a security error.
				// We retry the drawing with crossOrigin set to Anonymous.
				taintedHandler = function () {
					img = new win.Image();
					taintedHandler = taintedCallback;
					img.crossOrigin = 'Anonymous'; // Must be set prior to loading image source
					img.onload = loadHandler;
					img.onerror = errorHandler;
					img.src = imageURL;
				};

				img.onload = loadHandler;
				img.onerror = errorHandler;
				img.src = imageURL;
			},
			// Get blob URL from SVG code. Falls back to normal data URI.
			svgToDataUrl = function (svg) {
				try {
					// Safari requires data URI since it doesn't allow navigation to blob URLs
					// Firefox has an issue with Blobs and internal references, leading to gradients not working using Blobs (#4550)
					if (!webKit && nav.userAgent.toLowerCase().indexOf('firefox') < 0) {
						return domurl.createObjectURL(new win.Blob([svg], { type: 'image/svg+xml;charset-utf-16' }));
					}
				} catch (e) {
					// Ignore
				}
				return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
			},
			// Download contents by dataURL/blob
			download = function (dataURL, extension) {
				var a = doc.createElement('a'),
					filename = (options.filename || 'chart') + '.' + extension,
					windowRef;

				// IE specific blob implementation
				if (nav.msSaveOrOpenBlob) {
					nav.msSaveOrOpenBlob(dataURL, filename);
					return;
				}

				// Try HTML5 download attr if supported
				if (a.download !== undefined) {
					a.href = dataURL;
					a.download = filename; // HTML5 download attribute
					a.target = '_blank';
					doc.body.appendChild(a);
					a.click();
					doc.body.removeChild(a);
				} else {
					// No download attr, just opening data URI
					try {
						windowRef = win.open(dataURL, 'chart');
						if (windowRef === undefined || windowRef === null) {
							throw 'Failed to open window';
						}
					} catch (e) {
						// window.open failed, trying location.href
						win.location.href = dataURL;
					}
				}
			},
			// Get data URL to an image of the chart and call download on it
			initiateDownload = function () {
				var svgurl,
					blob,
					imageType = options && options.type || 'image/png',
					imageExtension = imageType.split('/')[1],
					svg = chart.sanitizeSVG(chartCopyContainer.innerHTML); // SVG of chart copy

				// Initiate download depending on file type
				if (imageType === 'image/svg+xml') {
					// SVG download. In this case, we want to use Microsoft specific Blob if available
					try {
						if (nav.msSaveOrOpenBlob) {
							blob = new MSBlobBuilder();
							blob.append(svg);
							svgurl = blob.getBlob('image/svg+xml');
						} else {
							svgurl = svgToDataUrl(svg);
						}
						download(svgurl, 'svg');
					} catch (e) {
						fallbackToExportServer();
					}
				} else {
					// PNG/JPEG download - create bitmap from SVG
					
					// First, try to get PNG by rendering on canvas
					svgurl = svgToDataUrl(svg);
					imageToDataUrl(svgurl, imageType, { /* args */ }, function (imageURL) {
						// Success
						try {
							download(imageURL, imageExtension);
						} catch (e) {
							fallbackToExportServer();
						}
					}, function () {
						// Failed due to tainted canvas
						// Create new and untainted canvas
						var canvas = doc.createElement('canvas'),
							ctx = canvas.getContext('2d'),
							imageWidth = svg.match(/^<svg[^>]*width\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale,
							imageHeight = svg.match(/^<svg[^>]*height\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale,
							downloadWithCanVG = function () {
								ctx.drawSvg(svg, 0, 0, imageWidth, imageHeight);
								try {
									download(nav.msSaveOrOpenBlob ? canvas.msToBlob() : canvas.toDataURL(imageType), imageExtension);
								} catch (e) {
									fallbackToExportServer();
								}
							};

						canvas.width = imageWidth;
						canvas.height = imageHeight;
						if (win.canvg) {
							// Use preloaded canvg
							downloadWithCanVG();
						} else {
							// Must load canVG first
							chart.showLoading();
							getScript(Highcharts.getOptions().global.canvasToolsURL, function () {
								chart.hideLoading();
								downloadWithCanVG();
							});
						}
					},
					// No canvas support
					fallbackToExportServer,
					// Failed to load image
					fallbackToExportServer,
					// Finally
					function () {
						try {
							domurl.revokeObjectURL(svgurl);
						} catch (e) {
							// Ignore
						}
					});
				}
			},
			// Success handler, we converted image to base64!
			embeddedSuccess = function (imageURL, imageType, callbackArgs) {
				++imagesEmbedded;

				// Change image href in chart copy
				callbackArgs.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', imageURL);

				// Start download when done with the last image
				if (imagesEmbedded === images.length) {
					initiateDownload();
				}
			};

		// Hook into getSVG to get a copy of the chart copy's container
		Highcharts.wrap(Highcharts.Chart.prototype, 'getChartHTML', function (proceed) {
			chartCopyContainer = this.container.cloneNode(true);
			return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
		});

		// Trigger hook to get chart copy
		chart.getSVGForExport(options, chartOptions);
		images = chartCopyContainer.getElementsByTagName('image');

		try {
			// If there are no images to embed, just go ahead and start the download process
			if (!images.length) {
				initiateDownload();
			}

			// Go through the images we want to embed
			for (i = 0, l = images.length; i < l; ++i) {
				el = images[i];
				imageToDataUrl(el.getAttributeNS('http://www.w3.org/1999/xlink', 'href'), 'image/png', { imageElement: el },
					embeddedSuccess,
					// Tainted canvas
					fallbackToExportServer,
					// No canvas support 
					fallbackToExportServer,
					// Failed to load source
					fallbackToExportServer
				);
			}
		} catch (e) {
			fallbackToExportServer();
		}
	};

	// Extend the default options to use the local exporter logic
	Highcharts.getOptions().exporting.buttons.contextButton.menuItems = [{
		textKey: 'printChart',
		onclick: function () {
			this.print();
		}
	}, {
		separator: true
	}, {
		textKey: 'downloadPNG',
		onclick: function () {
			this.exportChartLocal();
		}
	}, {
		textKey: 'downloadJPEG',
		onclick: function () {
			this.exportChartLocal({
				type: 'image/jpeg'
			});
		}
	}, {
		textKey: 'downloadSVG',
		onclick: function () {
			this.exportChartLocal({
				type: 'image/svg+xml'
			});
		}
	}];

}));
