/**

 * @license Highcharts JS v4.1.4 (2015-03-10)

 *

 * Standalone Highcharts Framework

 *

 * License: MIT License

 */





/*global Highcharts */

var HighchartsAdapter = (function () {



var UNDEFINED,

	doc = document,

	emptyArray = [],

	timers = [],

	timerId,

	animSetters = {},

	Fx;



Math.easeInOutSine = function (t, b, c, d) {

	return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;

};







/**

 * Extend given object with custom events

 */

function augment(obj) {

	function removeOneEvent(el, type, fn) {

		el.removeEventListener(type, fn, false);

	}



	function IERemoveOneEvent(el, type, fn) {

		fn = el.HCProxiedMethods[fn.toString()];

		el.detachEvent('on' + type, fn);

	}



	function removeAllEvents(el, type) {

		var events = el.HCEvents,

			remove,

			types,

			len,

			n;



		if (el.removeEventListener) {

			remove = removeOneEvent;

		} else if (el.attachEvent) {

			remove = IERemoveOneEvent;

		} else {

			return; // break on non-DOM events

		}





		if (type) {

			types = {};

			types[type] = true;

		} else {

			types = events;

		}



		for (n in types) {

			if (events[n]) {

				len = events[n].length;

				while (len--) {

					remove(el, n, events[n][len]);

				}

			}

		}

	}



	if (!obj.HCExtended) {

		Highcharts.extend(obj, {

			HCExtended: true,



			HCEvents: {},



			bind: function (name, fn) {

				var el = this,

					events = this.HCEvents,

					wrappedFn;



				// handle DOM events in modern browsers

				if (el.addEventListener) {

					el.addEventListener(name, fn, false);



				// handle old IE implementation

				} else if (el.attachEvent) {

					

					wrappedFn = function (e) {

						e.target = e.srcElement || window; // #2820

						fn.call(el, e);

					};



					if (!el.HCProxiedMethods) {

						el.HCProxiedMethods = {};

					}



					// link wrapped fn with original fn, so we can get this in removeEvent

					el.HCProxiedMethods[fn.toString()] = wrappedFn;



					el.attachEvent('on' + name, wrappedFn);

				}





				if (events[name] === UNDEFINED) {

					events[name] = [];

				}



				events[name].push(fn);

			},



			unbind: function (name, fn) {

				var events,

					index;



				if (name) {

					events = this.HCEvents[name] || [];

					if (fn) {

						index = HighchartsAdapter.inArray(fn, events);

						if (index > -1) {

							events.splice(index, 1);

							this.HCEvents[name] = events;

						}

						if (this.removeEventListener) {

							removeOneEvent(this, name, fn);

						} else if (this.attachEvent) {

							IERemoveOneEvent(this, name, fn);

						}

					} else {

						removeAllEvents(this, name);

						this.HCEvents[name] = [];

					}

				} else {

					removeAllEvents(this);

					this.HCEvents = {};

				}

			},



			trigger: function (name, args) {

				var events = this.HCEvents[name] || [],

					target = this,

					len = events.length,

					i,

					preventDefault,

					fn;



				// Attach a simple preventDefault function to skip default handler if called

				preventDefault = function () {

					args.defaultPrevented = true;

				};

				

				for (i = 0; i < len; i++) {

					fn = events[i];



					// args is never null here

					if (args.stopped) {

						return;

					}



					args.preventDefault = preventDefault;

					args.target = target;



					// If the type is not set, we're running a custom event (#2297). If it is set,

					// we're running a browser event, and setting it will cause en error in

					// IE8 (#2465).

					if (!args.type) {

						args.type = name;

					}

					



					

					// If the event handler return false, prevent the default handler from executing

					if (fn.call(this, args) === false) {

						args.preventDefault();

					}

				}

			}

		});

	}



	return obj;

}





return {



	/**

	 * Initialize the adapter. This is run once as Highcharts is first run.

	 */

	init: function (pathAnim) {



		/**

		 * Compatibility section to add support for legacy IE. This can be removed if old IE 

		 * support is not needed.

		 */

		if (!doc.defaultView) {

			this._getStyle = function (el, prop) {

				var val;

				if (el.style[prop]) {

					return el.style[prop];

				} else {

					if (prop === 'opacity') {

						prop = 'filter';

					}

					/*jslint unparam: true*/

					val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b) { return b.toUpperCase(); })];

					if (prop === 'filter') {

						val = val.replace(

							/alpha\(opacity=([0-9]+)\)/, 

							function (a, b) { 

								return b / 100; 

							}

						);

					}

					/*jslint unparam: false*/

					return val === '' ? 1 : val;

				} 

			};

			this.adapterRun = function (elem, method) {

				var alias = { width: 'clientWidth', height: 'clientHeight' }[method];



				if (alias) {

					elem.style.zoom = 1;

					return elem[alias] - 2 * parseInt(HighchartsAdapter._getStyle(elem, 'padding'), 10);

				}

			};

		}



		if (!Array.prototype.forEach) {

			this.each = function (arr, fn) { // legacy

				var i = 0, 

					len = arr.length;

				for (; i < len; i++) {

					if (fn.call(arr[i], arr[i], i, arr) === false) {

						return i;

					}

				}

			};

		}



		if (!Array.prototype.indexOf) {

			this.inArray = function (item, arr) {

				var len, 

					i = 0;



				if (arr) {

					len = arr.length;

					

					for (; i < len; i++) {

						if (arr[i] === item) {

							return i;

						}

					}

				}



				return -1;

			};

		}



		if (!Array.prototype.filter) {

			this.grep = function (elements, callback) {

				var ret = [],

					i = 0,

					length = elements.length;



				for (; i < length; i++) {

					if (!!callback(elements[i], i)) {

						ret.push(elements[i]);

					}

				}



				return ret;

			};

		}



		//--- End compatibility section ---





		/**

		 * Start of animation specific code

		 */

		Fx = function (elem, options, prop) {

			this.options = options;

			this.elem = elem;

			this.prop = prop;

		};

		Fx.prototype = {

			

			update: function () {

				var styles,

					paths = this.paths,

					elem = this.elem,

					elemelem = elem.element; // if destroyed, it is null



				// Animation setter defined from outside

				if (animSetters[this.prop]) {

					animSetters[this.prop](this);



				// Animating a path definition on SVGElement

				} else if (paths && elemelem) {

					elem.attr('d', pathAnim.step(paths[0], paths[1], this.now, this.toD));



				// Other animations on SVGElement

				} else if (elem.attr) {

					if (elemelem) {

						elem.attr(this.prop, this.now);

					}



				// HTML styles

				} else {

					styles = {};

					styles[this.prop] = this.now + this.unit;

					Highcharts.css(elem, styles);

				}

				

				if (this.options.step) {

					this.options.step.call(this.elem, this.now, this);

				}



			},

			custom: function (from, to, unit) {

				var self = this,

					t = function (gotoEnd) {

						return self.step(gotoEnd);

					},

					i;



				this.startTime = +new Date();

				this.start = from;

				this.end = to;

				this.unit = unit;

				this.now = this.start;

				this.pos = this.state = 0;



				t.elem = this.elem;



				if (t() && timers.push(t) === 1) {

					timerId = setInterval(function () {

						

						for (i = 0; i < timers.length; i++) {

							if (!timers[i]()) {

								timers.splice(i--, 1);

							}

						}



						if (!timers.length) {

							clearInterval(timerId);

						}

					}, 13);

				}

			},

			

			step: function (gotoEnd) {

				var t = +new Date(),

					ret,

					done,

					options = this.options,

					elem = this.elem,

					i;

				

				if (elem.stopAnimation || (elem.attr && !elem.element)) { // #2616, element including flag is destroyed

					ret = false;



				} else if (gotoEnd || t >= options.duration + this.startTime) {

					this.now = this.end;

					this.pos = this.state = 1;

					this.update();



					this.options.curAnim[this.prop] = true;



					done = true;

					for (i in options.curAnim) {

						if (options.curAnim[i] !== true) {

							done = false;

						}

					}



					if (done) {

						if (options.complete) {

							options.complete.call(elem);

						}

					}

					ret = false;



				} else {

					var n = t - this.startTime;

					this.state = n / options.duration;

					this.pos = options.easing(n, 0, 1, options.duration);

					this.now = this.start + ((this.end - this.start) * this.pos);

					this.update();

					ret = true;

				}

				return ret;

			}

		};



		/**

		 * The adapter animate method

		 */

		this.animate = function (el, prop, opt) {

			var start,

				unit = '',

				end,

				fx,

				args,

				name;



			el.stopAnimation = false; // ready for new



			if (typeof opt !== 'object' || opt === null) {

				args = arguments;

				opt = {

					duration: args[2],

					easing: args[3],

					complete: args[4]

				};

			}

			if (typeof opt.duration !== 'number') {

				opt.duration = 400;

			}

			opt.easing = Math[opt.easing] || Math.easeInOutSine;

			opt.curAnim = Highcharts.extend({}, prop);

			

			for (name in prop) {

				fx = new Fx(el, opt, name);

				end = null;

				

				if (name === 'd') {

					fx.paths = pathAnim.init(

						el,

						el.d,

						prop.d

					);

					fx.toD = prop.d;

					start = 0;

					end = 1;

				} else if (el.attr) {

					start = el.attr(name);

				} else {

					start = parseFloat(HighchartsAdapter._getStyle(el, name)) || 0;

					if (name !== 'opacity') {

						unit = 'px';

					}

				}

	

				if (!end) {

					end = prop[name];

				}

				fx.custom(start, end, unit);

			}	

		};

	},



	/**

	 * Internal method to return CSS value for given element and property

	 */

	_getStyle: function (el, prop) {

		return window.getComputedStyle(el, undefined).getPropertyValue(prop);

	},



	/**

	 * Add an animation setter for a specific property

	 */

	addAnimSetter: function (prop, fn) {

		animSetters[prop] = fn;

	},



	/**

	 * Downloads a script and executes a callback when done.

	 * @param {String} scriptLocation

	 * @param {Function} callback

	 */

	getScript: function (scriptLocation, callback) {

		// We cannot assume that Assets class from mootools-more is available so instead insert a script tag to download script.

		var head = doc.getElementsByTagName('head')[0],

			script = doc.createElement('script');



		script.type = 'text/javascript';

		script.src = scriptLocation;

		script.onload = callback;



		head.appendChild(script);

	},



	/**

	 * Return the index of an item in an array, or -1 if not found

	 */

	inArray: function (item, arr) {

		return arr.indexOf ? arr.indexOf(item) : emptyArray.indexOf.call(arr, item);

	},





	/**

	 * A direct link to adapter methods

	 */

	adapterRun: function (elem, method) {

		return parseInt(HighchartsAdapter._getStyle(elem, method), 10);

	},



	/**

	 * Filter an array

	 */

	grep: function (elements, callback) {

		return emptyArray.filter.call(elements, callback);

	},



	/**

	 * Map an array

	 */

	map: function (arr, fn) {

		var results = [], i = 0, len = arr.length;



		for (; i < len; i++) {

			results[i] = fn.call(arr[i], arr[i], i, arr);

		}



		return results;

	},



	/**

	 * Get the element's offset position, corrected by overflow:auto. Loosely based on jQuery's offset method.

	 */

	offset: function (el) {

		var docElem = document.documentElement,

			box = el.getBoundingClientRect();



		return {

			top: box.top  + (window.pageYOffset || docElem.scrollTop)  - (docElem.clientTop  || 0),

			left: box.left + (window.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0)

		};

	},



	/**

	 * Add an event listener

	 */

	addEvent: function (el, type, fn) {

		augment(el).bind(type, fn);

	},



	/**

	 * Remove event added with addEvent

	 */

	removeEvent: function (el, type, fn) {

		augment(el).unbind(type, fn);

	},



	/**

	 * Fire an event on a custom object

	 */

	fireEvent: function (el, type, eventArguments, defaultFunction) {

		var e;



		if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {

			e = doc.createEvent('Events');

			e.initEvent(type, true, true);

			e.target = el;



			Highcharts.extend(e, eventArguments);



			if (el.dispatchEvent) {

				el.dispatchEvent(e);

			} else {

				el.fireEvent(type, e);

			}



		} else if (el.HCExtended === true) {

			eventArguments = eventArguments || {};

			el.trigger(type, eventArguments);

		}



		if (eventArguments && eventArguments.defaultPrevented) {

			defaultFunction = null;

		}



		if (defaultFunction) {

			defaultFunction(eventArguments);

		}

	},



	washMouseEvent: function (e) {

		return e;

	},





	/**

	 * Stop running animation

	 */

	stop: function (el) {

		el.stopAnimation = true;

	},



	/**

	 * Utility for iterating over an array. Parameters are reversed compared to jQuery.

	 * @param {Array} arr

	 * @param {Function} fn

	 */

	each: function (arr, fn) { // modern browsers

		return Array.prototype.forEach.call(arr, fn);

	}

};

}());

