/**
 * @license Highcharts JS v4.1.9 (2015-10-07)
 *
 * Standalone Highcharts Framework
 *
 * License: MIT License
 */


/*global Highcharts */
var HighchartsAdapter = (function () {

var UNDEFINED,
	doc = document,
	emptyArray = [],
	timers = [],
	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) {
					t.timerId = setInterval(function () {
						
						for (i = 0; i < timers.length; i++) {
							if (!timers[i]()) {
								timers.splice(i--, 1);
							}
						}

						if (!timers.length) {
							clearInterval(t.timerId);
						}
					}, 13);
				}
			},
			
			step: function (gotoEnd) {
				var t = +new Date(),
					ret,
					done,
					options = this.options,
					elem = this.elem,
					i;
				
				if (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,
				PX = 'px';

			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];
				}
				if (end.match && end.match(PX)) {
					end = end.replace(/px/g, ''); // #4351
				}
				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) {

		var i = timers.length,
			timer;

		// Remove timers related to this element (#4519)
		while (i--) {
			timer = timers[i];
			if (timer.elem === el) {
				timers.splice(i, 1);
			}
		}
	},

	/**
	 * 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);
	}
};
}());
