/* Minification failed. Returning unminified contents.
MinifyJavaScript(10957,9-10): error JS1028: Expected identifier or string: }
MinifyJavaScript(11261,5-6): error JS1028: Expected identifier or string: }
MinifyJavaScript(11385,5-6): error JS1028: Expected identifier or string: }
MinifyJavaScript(11443,5-6): error JS1028: Expected identifier or string: }
MinifyJavaScript(12173,9-10): error JS1028: Expected identifier or string: }
MinifyJavaScript(12196,9-10): error JS1028: Expected identifier or string: }
MinifyJavaScript(12247,5-6): error JS1028: Expected identifier or string: }
MinifyJavaScript(12852,5-6): error JS1028: Expected identifier or string: }
MinifyJavaScript(12989,25-26): error JS1028: Expected identifier or string: }
MinifyJavaScript(13178,9-10): error JS1028: Expected identifier or string: }
MinifyJavaScript(14289,13-14): error JS1028: Expected identifier or string: }
MinifyJavaScript(14306,13-14): error JS1028: Expected identifier or string: }
MinifyJavaScript(14317,13-14): error JS1028: Expected identifier or string: }
MinifyJavaScript(15070,9-10): error JS1028: Expected identifier or string: }
MinifyJavaScript(15097,9-10): error JS1028: Expected identifier or string: }
MinifyJavaScript(15741,49-52): error JS1010: Expected identifier: for
MinifyJavaScript(15742,22-25): error JS1010: Expected identifier: for
MinifyJavaScript(15960,13-14): error JS1028: Expected identifier or string: }
MinifyJavaScript(15968,13-14): error JS1028: Expected identifier or string: }
MinifyJavaScript(15985,5-6): error JS1028: Expected identifier or string: }
MinifyJavaScript(16220,9-10): error JS1028: Expected identifier or string: }
MinifyJavaScript(16494,5-6): error JS1028: Expected identifier or string: }
 */
/*!
 * jQuery JavaScript Library v1.9.1
 * http://jquery.com/
 *
 * Includes Sizzle.js
 * http://sizzlejs.com/
 *
 * Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2013-2-4
 */
(function( window, undefined ) {

// Can't do this because several apps including ASP.NET trace
// the stack via arguments.caller.callee and Firefox dies if
// you try to trace through "use strict" call chains. (#13335)
// Support: Firefox 18+
//"use strict";
var
	// The deferred used on DOM ready
	readyList,

	// A central reference to the root jQuery(document)
	rootjQuery,

	// Support: IE<9
	// For `typeof node.method` instead of `node.method !== undefined`
	core_strundefined = typeof undefined,

	// Use the correct document accordingly with window argument (sandbox)
	document = window.document,
	location = window.location,

	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,

	// Map over the $ in case of overwrite
	_$ = window.$,

	// [[Class]] -> type pairs
	class2type = {},

	// List of deleted data cache ids, so we can reuse them
	core_deletedIds = [],

	core_version = "1.9.1",

	// Save a reference to some core methods
	core_concat = core_deletedIds.concat,
	core_push = core_deletedIds.push,
	core_slice = core_deletedIds.slice,
	core_indexOf = core_deletedIds.indexOf,
	core_toString = class2type.toString,
	core_hasOwn = class2type.hasOwnProperty,
	core_trim = core_version.trim,

	// Define a local copy of jQuery
	jQuery = function( selector, context ) {
		// The jQuery object is actually just the init constructor 'enhanced'
		return new jQuery.fn.init( selector, context, rootjQuery );
	},

	// Used for matching numbers
	core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,

	// Used for splitting on whitespace
	core_rnotwhite = /\S+/g,

	// Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,

	// A simple way to check for HTML strings
	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
	// Strict HTML recognition (#11290: must start with <)
	rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,

	// Match a standalone tag
	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,

	// JSON RegExp
	rvalidchars = /^[\],:{}\s]*$/,
	rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
	rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
	rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,

	// Matches dashed string for camelizing
	rmsPrefix = /^-ms-/,
	rdashAlpha = /-([\da-z])/gi,

	// Used by jQuery.camelCase as callback to replace()
	fcamelCase = function( all, letter ) {
		return letter.toUpperCase();
	},

	// The ready event handler
	completed = function( event ) {

		// readyState === "complete" is good enough for us to call the dom ready in oldIE
		if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
			detach();
			jQuery.ready();
		}
	},
	// Clean-up method for dom ready events
	detach = function() {
		if ( document.addEventListener ) {
			document.removeEventListener( "DOMContentLoaded", completed, false );
			window.removeEventListener( "load", completed, false );

		} else {
			document.detachEvent( "onreadystatechange", completed );
			window.detachEvent( "onload", completed );
		}
	};

jQuery.fn = jQuery.prototype = {
	// The current version of jQuery being used
	jquery: core_version,

	constructor: jQuery,
	init: function( selector, context, rootjQuery ) {
		var match, elem;

		// HANDLE: $(""), $(null), $(undefined), $(false)
		if ( !selector ) {
			return this;
		}

		// Handle HTML strings
		if ( typeof selector === "string" ) {
			if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
				// Assume that strings that start and end with <> are HTML and skip the regex check
				match = [ null, selector, null ];

			} else {
				match = rquickExpr.exec( selector );
			}

			// Match html or make sure no context is specified for #id
			if ( match && (match[1] || !context) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[1] ) {
					context = context instanceof jQuery ? context[0] : context;

					// scripts is true for back-compat
					jQuery.merge( this, jQuery.parseHTML(
						match[1],
						context && context.nodeType ? context.ownerDocument || context : document,
						true
					) );

					// HANDLE: $(html, props)
					if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
						for ( match in context ) {
							// Properties of context are called as methods if possible
							if ( jQuery.isFunction( this[ match ] ) ) {
								this[ match ]( context[ match ] );

							// ...and otherwise set as attributes
							} else {
								this.attr( match, context[ match ] );
							}
						}
					}

					return this;

				// HANDLE: $(#id)
				} else {
					elem = document.getElementById( match[2] );

					// Check parentNode to catch when Blackberry 4.6 returns
					// nodes that are no longer in the document #6963
					if ( elem && elem.parentNode ) {
						// Handle the case where IE and Opera return items
						// by name instead of ID
						if ( elem.id !== match[2] ) {
							return rootjQuery.find( selector );
						}

						// Otherwise, we inject the element directly into the jQuery object
						this.length = 1;
						this[0] = elem;
					}

					this.context = document;
					this.selector = selector;
					return this;
				}

			// HANDLE: $(expr, $(...))
			} else if ( !context || context.jquery ) {
				return ( context || rootjQuery ).find( selector );

			// HANDLE: $(expr, context)
			// (which is just equivalent to: $(context).find(expr)
			} else {
				return this.constructor( context ).find( selector );
			}

		// HANDLE: $(DOMElement)
		} else if ( selector.nodeType ) {
			this.context = this[0] = selector;
			this.length = 1;
			return this;

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( jQuery.isFunction( selector ) ) {
			return rootjQuery.ready( selector );
		}

		if ( selector.selector !== undefined ) {
			this.selector = selector.selector;
			this.context = selector.context;
		}

		return jQuery.makeArray( selector, this );
	},

	// Start with an empty selector
	selector: "",

	// The default length of a jQuery object is 0
	length: 0,

	// The number of elements contained in the matched element set
	size: function() {
		return this.length;
	},

	toArray: function() {
		return core_slice.call( this );
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {
		return num == null ?

			// Return a 'clean' array
			this.toArray() :

			// Return just the object
			( num < 0 ? this[ this.length + num ] : this[ num ] );
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems ) {

		// Build a new jQuery matched element set
		var ret = jQuery.merge( this.constructor(), elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;
		ret.context = this.context;

		// Return the newly-formed element set
		return ret;
	},

	// Execute a callback for every element in the matched set.
	// (You can seed the arguments with an array of args, but this is
	// only used internally.)
	each: function( callback, args ) {
		return jQuery.each( this, callback, args );
	},

	ready: function( fn ) {
		// Add the callback
		jQuery.ready.promise().done( fn );

		return this;
	},

	slice: function() {
		return this.pushStack( core_slice.apply( this, arguments ) );
	},

	first: function() {
		return this.eq( 0 );
	},

	last: function() {
		return this.eq( -1 );
	},

	eq: function( i ) {
		var len = this.length,
			j = +i + ( i < 0 ? len : 0 );
		return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
	},

	map: function( callback ) {
		return this.pushStack( jQuery.map(this, function( elem, i ) {
			return callback.call( elem, i, elem );
		}));
	},

	end: function() {
		return this.prevObject || this.constructor(null);
	},

	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: core_push,
	sort: [].sort,
	splice: [].splice
};

// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

jQuery.extend = jQuery.fn.extend = function() {
	var src, copyIsArray, copy, name, options, clone,
		target = arguments[0] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;
		target = arguments[1] || {};
		// skip the boolean and the target
		i = 2;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
		target = {};
	}

	// extend jQuery itself if only one argument is passed
	if ( length === i ) {
		target = this;
		--i;
	}

	for ( ; i < length; i++ ) {
		// Only deal with non-null/undefined values
		if ( (options = arguments[ i ]) != null ) {
			// Extend the base object
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray(src) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject(src) ? src : {};
					}

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

jQuery.extend({
	noConflict: function( deep ) {
		if ( window.$ === jQuery ) {
			window.$ = _$;
		}

		if ( deep && window.jQuery === jQuery ) {
			window.jQuery = _jQuery;
		}

		return jQuery;
	},

	// Is the DOM ready to be used? Set to true once it occurs.
	isReady: false,

	// A counter to track how many items to wait for before
	// the ready event fires. See #6781
	readyWait: 1,

	// Hold (or release) the ready event
	holdReady: function( hold ) {
		if ( hold ) {
			jQuery.readyWait++;
		} else {
			jQuery.ready( true );
		}
	},

	// Handle when the DOM is ready
	ready: function( wait ) {

		// Abort if there are pending holds or we're already ready
		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
			return;
		}

		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
		if ( !document.body ) {
			return setTimeout( jQuery.ready );
		}

		// Remember that the DOM is ready
		jQuery.isReady = true;

		// If a normal DOM Ready event fired, decrement, and wait if need be
		if ( wait !== true && --jQuery.readyWait > 0 ) {
			return;
		}

		// If there are functions bound, to execute
		readyList.resolveWith( document, [ jQuery ] );

		// Trigger any bound ready events
		if ( jQuery.fn.trigger ) {
			jQuery( document ).trigger("ready").off("ready");
		}
	},

	// See test/unit/core.js for details concerning isFunction.
	// Since version 1.3, DOM methods and functions like alert
	// aren't supported. They return false on IE (#2968).
	isFunction: function( obj ) {
		return jQuery.type(obj) === "function";
	},

	isArray: Array.isArray || function( obj ) {
		return jQuery.type(obj) === "array";
	},

	isWindow: function( obj ) {
		return obj != null && obj == obj.window;
	},

	isNumeric: function( obj ) {
		return !isNaN( parseFloat(obj) ) && isFinite( obj );
	},

	type: function( obj ) {
		if ( obj == null ) {
			return String( obj );
		}
		return typeof obj === "object" || typeof obj === "function" ?
			class2type[ core_toString.call(obj) ] || "object" :
			typeof obj;
	},

	isPlainObject: function( obj ) {
		// Must be an Object.
		// Because of IE, we also have to check the presence of the constructor property.
		// Make sure that DOM nodes and window objects don't pass through, as well
		if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
			return false;
		}

		try {
			// Not own constructor property must be Object
			if ( obj.constructor &&
				!core_hasOwn.call(obj, "constructor") &&
				!core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
				return false;
			}
		} catch ( e ) {
			// IE8,9 Will throw exceptions on certain host objects #9897
			return false;
		}

		// Own properties are enumerated firstly, so to speed up,
		// if last one is own, then all properties are own.

		var key;
		for ( key in obj ) {}

		return key === undefined || core_hasOwn.call( obj, key );
	},

	isEmptyObject: function( obj ) {
		var name;
		for ( name in obj ) {
			return false;
		}
		return true;
	},

	error: function( msg ) {
		throw new Error( msg );
	},

	// data: string of html
	// context (optional): If specified, the fragment will be created in this context, defaults to document
	// keepScripts (optional): If true, will include scripts passed in the html string
	parseHTML: function( data, context, keepScripts ) {
		if ( !data || typeof data !== "string" ) {
			return null;
		}
		if ( typeof context === "boolean" ) {
			keepScripts = context;
			context = false;
		}
		context = context || document;

		var parsed = rsingleTag.exec( data ),
			scripts = !keepScripts && [];

		// Single tag
		if ( parsed ) {
			return [ context.createElement( parsed[1] ) ];
		}

		parsed = jQuery.buildFragment( [ data ], context, scripts );
		if ( scripts ) {
			jQuery( scripts ).remove();
		}
		return jQuery.merge( [], parsed.childNodes );
	},

	parseJSON: function( data ) {
		// Attempt to parse using the native JSON parser first
		if ( window.JSON && window.JSON.parse ) {
			return window.JSON.parse( data );
		}

		if ( data === null ) {
			return data;
		}

		if ( typeof data === "string" ) {

			// Make sure leading/trailing whitespace is removed (IE can't handle it)
			data = jQuery.trim( data );

			if ( data ) {
				// Make sure the incoming data is actual JSON
				// Logic borrowed from http://json.org/json2.js
				if ( rvalidchars.test( data.replace( rvalidescape, "@" )
					.replace( rvalidtokens, "]" )
					.replace( rvalidbraces, "")) ) {

					return ( new Function( "return " + data ) )();
				}
			}
		}

		jQuery.error( "Invalid JSON: " + data );
	},

	// Cross-browser xml parsing
	parseXML: function( data ) {
		var xml, tmp;
		if ( !data || typeof data !== "string" ) {
			return null;
		}
		try {
			if ( window.DOMParser ) { // Standard
				tmp = new DOMParser();
				xml = tmp.parseFromString( data , "text/xml" );
			} else { // IE
				xml = new ActiveXObject( "Microsoft.XMLDOM" );
				xml.async = "false";
				xml.loadXML( data );
			}
		} catch( e ) {
			xml = undefined;
		}
		if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
			jQuery.error( "Invalid XML: " + data );
		}
		return xml;
	},

	noop: function() {},

	// Evaluates a script in a global context
	// Workarounds based on findings by Jim Driscoll
	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
	globalEval: function( data ) {
		if ( data && jQuery.trim( data ) ) {
			// We use execScript on Internet Explorer
			// We use an anonymous function so that context is window
			// rather than jQuery in Firefox
			( window.execScript || function( data ) {
				window[ "eval" ].call( window, data );
			} )( data );
		}
	},

	// Convert dashed to camelCase; used by the css and data modules
	// Microsoft forgot to hump their vendor prefix (#9572)
	camelCase: function( string ) {
		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
	},

	nodeName: function( elem, name ) {
		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
	},

	// args is for internal usage only
	each: function( obj, callback, args ) {
		var value,
			i = 0,
			length = obj.length,
			isArray = isArraylike( obj );

		if ( args ) {
			if ( isArray ) {
				for ( ; i < length; i++ ) {
					value = callback.apply( obj[ i ], args );

					if ( value === false ) {
						break;
					}
				}
			} else {
				for ( i in obj ) {
					value = callback.apply( obj[ i ], args );

					if ( value === false ) {
						break;
					}
				}
			}

		// A special, fast, case for the most common use of each
		} else {
			if ( isArray ) {
				for ( ; i < length; i++ ) {
					value = callback.call( obj[ i ], i, obj[ i ] );

					if ( value === false ) {
						break;
					}
				}
			} else {
				for ( i in obj ) {
					value = callback.call( obj[ i ], i, obj[ i ] );

					if ( value === false ) {
						break;
					}
				}
			}
		}

		return obj;
	},

	// Use native String.trim function wherever possible
	trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
		function( text ) {
			return text == null ?
				"" :
				core_trim.call( text );
		} :

		// Otherwise use our own trimming functionality
		function( text ) {
			return text == null ?
				"" :
				( text + "" ).replace( rtrim, "" );
		},

	// results is for internal usage only
	makeArray: function( arr, results ) {
		var ret = results || [];

		if ( arr != null ) {
			if ( isArraylike( Object(arr) ) ) {
				jQuery.merge( ret,
					typeof arr === "string" ?
					[ arr ] : arr
				);
			} else {
				core_push.call( ret, arr );
			}
		}

		return ret;
	},

	inArray: function( elem, arr, i ) {
		var len;

		if ( arr ) {
			if ( core_indexOf ) {
				return core_indexOf.call( arr, elem, i );
			}

			len = arr.length;
			i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;

			for ( ; i < len; i++ ) {
				// Skip accessing in sparse arrays
				if ( i in arr && arr[ i ] === elem ) {
					return i;
				}
			}
		}

		return -1;
	},

	merge: function( first, second ) {
		var l = second.length,
			i = first.length,
			j = 0;

		if ( typeof l === "number" ) {
			for ( ; j < l; j++ ) {
				first[ i++ ] = second[ j ];
			}
		} else {
			while ( second[j] !== undefined ) {
				first[ i++ ] = second[ j++ ];
			}
		}

		first.length = i;

		return first;
	},

	grep: function( elems, callback, inv ) {
		var retVal,
			ret = [],
			i = 0,
			length = elems.length;
		inv = !!inv;

		// Go through the array, only saving the items
		// that pass the validator function
		for ( ; i < length; i++ ) {
			retVal = !!callback( elems[ i ], i );
			if ( inv !== retVal ) {
				ret.push( elems[ i ] );
			}
		}

		return ret;
	},

	// arg is for internal usage only
	map: function( elems, callback, arg ) {
		var value,
			i = 0,
			length = elems.length,
			isArray = isArraylike( elems ),
			ret = [];

		// Go through the array, translating each of the items to their
		if ( isArray ) {
			for ( ; i < length; i++ ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret[ ret.length ] = value;
				}
			}

		// Go through every key on the object,
		} else {
			for ( i in elems ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret[ ret.length ] = value;
				}
			}
		}

		// Flatten any nested arrays
		return core_concat.apply( [], ret );
	},

	// A global GUID counter for objects
	guid: 1,

	// Bind a function to a context, optionally partially applying any
	// arguments.
	proxy: function( fn, context ) {
		var args, proxy, tmp;

		if ( typeof context === "string" ) {
			tmp = fn[ context ];
			context = fn;
			fn = tmp;
		}

		// Quick check to determine if target is callable, in the spec
		// this throws a TypeError, but we will just return undefined.
		if ( !jQuery.isFunction( fn ) ) {
			return undefined;
		}

		// Simulated bind
		args = core_slice.call( arguments, 2 );
		proxy = function() {
			return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
		};

		// Set the guid of unique handler to the same of original handler, so it can be removed
		proxy.guid = fn.guid = fn.guid || jQuery.guid++;

		return proxy;
	},

	// Multifunctional method to get and set values of a collection
	// The value/s can optionally be executed if it's a function
	access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
		var i = 0,
			length = elems.length,
			bulk = key == null;

		// Sets many values
		if ( jQuery.type( key ) === "object" ) {
			chainable = true;
			for ( i in key ) {
				jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
			}

		// Sets one value
		} else if ( value !== undefined ) {
			chainable = true;

			if ( !jQuery.isFunction( value ) ) {
				raw = true;
			}

			if ( bulk ) {
				// Bulk operations run against the entire set
				if ( raw ) {
					fn.call( elems, value );
					fn = null;

				// ...except when executing function values
				} else {
					bulk = fn;
					fn = function( elem, key, value ) {
						return bulk.call( jQuery( elem ), value );
					};
				}
			}

			if ( fn ) {
				for ( ; i < length; i++ ) {
					fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
				}
			}
		}

		return chainable ?
			elems :

			// Gets
			bulk ?
				fn.call( elems ) :
				length ? fn( elems[0], key ) : emptyGet;
	},

	now: function() {
		return ( new Date() ).getTime();
	}
});

jQuery.ready.promise = function( obj ) {
	if ( !readyList ) {

		readyList = jQuery.Deferred();

		// Catch cases where $(document).ready() is called after the browser event has already occurred.
		// we once tried to use readyState "interactive" here, but it caused issues like the one
		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
		if ( document.readyState === "complete" ) {
			// Handle it asynchronously to allow scripts the opportunity to delay ready
			setTimeout( jQuery.ready );

		// Standards-based browsers support DOMContentLoaded
		} else if ( document.addEventListener ) {
			// Use the handy event callback
			document.addEventListener( "DOMContentLoaded", completed, false );

			// A fallback to window.onload, that will always work
			window.addEventListener( "load", completed, false );

		// If IE event model is used
		} else {
			// Ensure firing before onload, maybe late but safe also for iframes
			document.attachEvent( "onreadystatechange", completed );

			// A fallback to window.onload, that will always work
			window.attachEvent( "onload", completed );

			// If IE and not a frame
			// continually check to see if the document is ready
			var top = false;

			try {
				top = window.frameElement == null && document.documentElement;
			} catch(e) {}

			if ( top && top.doScroll ) {
				(function doScrollCheck() {
					if ( !jQuery.isReady ) {

						try {
							// Use the trick by Diego Perini
							// http://javascript.nwbox.com/IEContentLoaded/
							top.doScroll("left");
						} catch(e) {
							return setTimeout( doScrollCheck, 50 );
						}

						// detach all dom ready events
						detach();

						// and execute any waiting functions
						jQuery.ready();
					}
				})();
			}
		}
	}
	return readyList.promise( obj );
};

// Populate the class2type map
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
	class2type[ "[object " + name + "]" ] = name.toLowerCase();
});

function isArraylike( obj ) {
	var length = obj.length,
		type = jQuery.type( obj );

	if ( jQuery.isWindow( obj ) ) {
		return false;
	}

	if ( obj.nodeType === 1 && length ) {
		return true;
	}

	return type === "array" || type !== "function" &&
		( length === 0 ||
		typeof length === "number" && length > 0 && ( length - 1 ) in obj );
}

// All jQuery objects should point back to these
rootjQuery = jQuery(document);
// String to Object options format cache
var optionsCache = {};

// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
	var object = optionsCache[ options ] = {};
	jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
		object[ flag ] = true;
	});
	return object;
}

/*
 * Create a callback list using the following parameters:
 *
 *	options: an optional list of space-separated options that will change how
 *			the callback list behaves or a more traditional option object
 *
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options:
 *
 *	once:			will ensure the callback list can only be fired once (like a Deferred)
 *
 *	memory:			will keep track of previous values and will call any callback added
 *					after the list has been fired right away with the latest "memorized"
 *					values (like a Deferred)
 *
 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
 *
 *	stopOnFalse:	interrupt callings when a callback returns false
 *
 */
jQuery.Callbacks = function( options ) {

	// Convert options from String-formatted to Object-formatted if needed
	// (we check in cache first)
	options = typeof options === "string" ?
		( optionsCache[ options ] || createOptions( options ) ) :
		jQuery.extend( {}, options );

	var // Flag to know if list is currently firing
		firing,
		// Last fire value (for non-forgettable lists)
		memory,
		// Flag to know if list was already fired
		fired,
		// End of the loop when firing
		firingLength,
		// Index of currently firing callback (modified by remove if needed)
		firingIndex,
		// First callback to fire (used internally by add and fireWith)
		firingStart,
		// Actual callback list
		list = [],
		// Stack of fire calls for repeatable lists
		stack = !options.once && [],
		// Fire callbacks
		fire = function( data ) {
			memory = options.memory && data;
			fired = true;
			firingIndex = firingStart || 0;
			firingStart = 0;
			firingLength = list.length;
			firing = true;
			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
					memory = false; // To prevent further calls using add
					break;
				}
			}
			firing = false;
			if ( list ) {
				if ( stack ) {
					if ( stack.length ) {
						fire( stack.shift() );
					}
				} else if ( memory ) {
					list = [];
				} else {
					self.disable();
				}
			}
		},
		// Actual Callbacks object
		self = {
			// Add a callback or a collection of callbacks to the list
			add: function() {
				if ( list ) {
					// First, we save the current length
					var start = list.length;
					(function add( args ) {
						jQuery.each( args, function( _, arg ) {
							var type = jQuery.type( arg );
							if ( type === "function" ) {
								if ( !options.unique || !self.has( arg ) ) {
									list.push( arg );
								}
							} else if ( arg && arg.length && type !== "string" ) {
								// Inspect recursively
								add( arg );
							}
						});
					})( arguments );
					// Do we need to add the callbacks to the
					// current firing batch?
					if ( firing ) {
						firingLength = list.length;
					// With memory, if we're not firing then
					// we should call right away
					} else if ( memory ) {
						firingStart = start;
						fire( memory );
					}
				}
				return this;
			},
			// Remove a callback from the list
			remove: function() {
				if ( list ) {
					jQuery.each( arguments, function( _, arg ) {
						var index;
						while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
							list.splice( index, 1 );
							// Handle firing indexes
							if ( firing ) {
								if ( index <= firingLength ) {
									firingLength--;
								}
								if ( index <= firingIndex ) {
									firingIndex--;
								}
							}
						}
					});
				}
				return this;
			},
			// Check if a given callback is in the list.
			// If no argument is given, return whether or not list has callbacks attached.
			has: function( fn ) {
				return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
			},
			// Remove all callbacks from the list
			empty: function() {
				list = [];
				return this;
			},
			// Have the list do nothing anymore
			disable: function() {
				list = stack = memory = undefined;
				return this;
			},
			// Is it disabled?
			disabled: function() {
				return !list;
			},
			// Lock the list in its current state
			lock: function() {
				stack = undefined;
				if ( !memory ) {
					self.disable();
				}
				return this;
			},
			// Is it locked?
			locked: function() {
				return !stack;
			},
			// Call all callbacks with the given context and arguments
			fireWith: function( context, args ) {
				args = args || [];
				args = [ context, args.slice ? args.slice() : args ];
				if ( list && ( !fired || stack ) ) {
					if ( firing ) {
						stack.push( args );
					} else {
						fire( args );
					}
				}
				return this;
			},
			// Call all the callbacks with the given arguments
			fire: function() {
				self.fireWith( this, arguments );
				return this;
			},
			// To know if the callbacks have already been called at least once
			fired: function() {
				return !!fired;
			}
		};

	return self;
};
jQuery.extend({

	Deferred: function( func ) {
		var tuples = [
				// action, add listener, listener list, final state
				[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
				[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
				[ "notify", "progress", jQuery.Callbacks("memory") ]
			],
			state = "pending",
			promise = {
				state: function() {
					return state;
				},
				always: function() {
					deferred.done( arguments ).fail( arguments );
					return this;
				},
				then: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;
					return jQuery.Deferred(function( newDefer ) {
						jQuery.each( tuples, function( i, tuple ) {
							var action = tuple[ 0 ],
								fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
							// deferred[ done | fail | progress ] for forwarding actions to newDefer
							deferred[ tuple[1] ](function() {
								var returned = fn && fn.apply( this, arguments );
								if ( returned && jQuery.isFunction( returned.promise ) ) {
									returned.promise()
										.done( newDefer.resolve )
										.fail( newDefer.reject )
										.progress( newDefer.notify );
								} else {
									newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
								}
							});
						});
						fns = null;
					}).promise();
				},
				// Get a promise for this deferred
				// If obj is provided, the promise aspect is added to the object
				promise: function( obj ) {
					return obj != null ? jQuery.extend( obj, promise ) : promise;
				}
			},
			deferred = {};

		// Keep pipe for back-compat
		promise.pipe = promise.then;

		// Add list-specific methods
		jQuery.each( tuples, function( i, tuple ) {
			var list = tuple[ 2 ],
				stateString = tuple[ 3 ];

			// promise[ done | fail | progress ] = list.add
			promise[ tuple[1] ] = list.add;

			// Handle state
			if ( stateString ) {
				list.add(function() {
					// state = [ resolved | rejected ]
					state = stateString;

				// [ reject_list | resolve_list ].disable; progress_list.lock
				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
			}

			// deferred[ resolve | reject | notify ]
			deferred[ tuple[0] ] = function() {
				deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
				return this;
			};
			deferred[ tuple[0] + "With" ] = list.fireWith;
		});

		// Make the deferred a promise
		promise.promise( deferred );

		// Call given func if any
		if ( func ) {
			func.call( deferred, deferred );
		}

		// All done!
		return deferred;
	},

	// Deferred helper
	when: function( subordinate /* , ..., subordinateN */ ) {
		var i = 0,
			resolveValues = core_slice.call( arguments ),
			length = resolveValues.length,

			// the count of uncompleted subordinates
			remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

			// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

			// Update function for both resolve and progress values
			updateFunc = function( i, contexts, values ) {
				return function( value ) {
					contexts[ i ] = this;
					values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
					if( values === progressValues ) {
						deferred.notifyWith( contexts, values );
					} else if ( !( --remaining ) ) {
						deferred.resolveWith( contexts, values );
					}
				};
			},

			progressValues, progressContexts, resolveContexts;

		// add listeners to Deferred subordinates; treat others as resolved
		if ( length > 1 ) {
			progressValues = new Array( length );
			progressContexts = new Array( length );
			resolveContexts = new Array( length );
			for ( ; i < length; i++ ) {
				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
					resolveValues[ i ].promise()
						.done( updateFunc( i, resolveContexts, resolveValues ) )
						.fail( deferred.reject )
						.progress( updateFunc( i, progressContexts, progressValues ) );
				} else {
					--remaining;
				}
			}
		}

		// if we're not waiting on anything, resolve the master
		if ( !remaining ) {
			deferred.resolveWith( resolveContexts, resolveValues );
		}

		return deferred.promise();
	}
});
jQuery.support = (function() {

	var support, all, a,
		input, select, fragment,
		opt, eventName, isSupported, i,
		div = document.createElement("div");

	// Setup
	div.setAttribute( "className", "t" );
	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";

	// Support tests won't run in some limited or non-browser environments
	all = div.getElementsByTagName("*");
	a = div.getElementsByTagName("a")[ 0 ];
	if ( !all || !a || !all.length ) {
		return {};
	}

	// First batch of tests
	select = document.createElement("select");
	opt = select.appendChild( document.createElement("option") );
	input = div.getElementsByTagName("input")[ 0 ];

	a.style.cssText = "top:1px;float:left;opacity:.5";
	support = {
		// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
		getSetAttribute: div.className !== "t",

		// IE strips leading whitespace when .innerHTML is used
		leadingWhitespace: div.firstChild.nodeType === 3,

		// Make sure that tbody elements aren't automatically inserted
		// IE will insert them into empty tables
		tbody: !div.getElementsByTagName("tbody").length,

		// Make sure that link elements get serialized correctly by innerHTML
		// This requires a wrapper element in IE
		htmlSerialize: !!div.getElementsByTagName("link").length,

		// Get the style information from getAttribute
		// (IE uses .cssText instead)
		style: /top/.test( a.getAttribute("style") ),

		// Make sure that URLs aren't manipulated
		// (IE normalizes it by default)
		hrefNormalized: a.getAttribute("href") === "/a",

		// Make sure that element opacity exists
		// (IE uses filter instead)
		// Use a regex to work around a WebKit issue. See #5145
		opacity: /^0.5/.test( a.style.opacity ),

		// Verify style float existence
		// (IE uses styleFloat instead of cssFloat)
		cssFloat: !!a.style.cssFloat,

		// Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
		checkOn: !!input.value,

		// Make sure that a selected-by-default option has a working selected property.
		// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
		optSelected: opt.selected,

		// Tests for enctype support on a form (#6743)
		enctype: !!document.createElement("form").enctype,

		// Makes sure cloning an html5 element does not cause problems
		// Where outerHTML is undefined, this still works
		html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",

		// jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
		boxModel: document.compatMode === "CSS1Compat",

		// Will be defined later
		deleteExpando: true,
		noCloneEvent: true,
		inlineBlockNeedsLayout: false,
		shrinkWrapBlocks: false,
		reliableMarginRight: true,
		boxSizingReliable: true,
		pixelPosition: false
	};

	// Make sure checked status is properly cloned
	input.checked = true;
	support.noCloneChecked = input.cloneNode( true ).checked;

	// Make sure that the options inside disabled selects aren't marked as disabled
	// (WebKit marks them as disabled)
	select.disabled = true;
	support.optDisabled = !opt.disabled;

	// Support: IE<9
	try {
		delete div.test;
	} catch( e ) {
		support.deleteExpando = false;
	}

	// Check if we can trust getAttribute("value")
	input = document.createElement("input");
	input.setAttribute( "value", "" );
	support.input = input.getAttribute( "value" ) === "";

	// Check if an input maintains its value after becoming a radio
	input.value = "t";
	input.setAttribute( "type", "radio" );
	support.radioValue = input.value === "t";

	// #11217 - WebKit loses check when the name is after the checked attribute
	input.setAttribute( "checked", "t" );
	input.setAttribute( "name", "t" );

	fragment = document.createDocumentFragment();
	fragment.appendChild( input );

	// Check if a disconnected checkbox will retain its checked
	// value of true after appended to the DOM (IE6/7)
	support.appendChecked = input.checked;

	// WebKit doesn't clone checked state correctly in fragments
	support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;

	// Support: IE<9
	// Opera does not clone events (and typeof div.attachEvent === undefined).
	// IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
	if ( div.attachEvent ) {
		div.attachEvent( "onclick", function() {
			support.noCloneEvent = false;
		});

		div.cloneNode( true ).click();
	}

	// Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)
	// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php
	for ( i in { submit: true, change: true, focusin: true }) {
		div.setAttribute( eventName = "on" + i, "t" );

		support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false;
	}

	div.style.backgroundClip = "content-box";
	div.cloneNode( true ).style.backgroundClip = "";
	support.clearCloneStyle = div.style.backgroundClip === "content-box";

	// Run tests that need a body at doc ready
	jQuery(function() {
		var container, marginDiv, tds,
			divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",
			body = document.getElementsByTagName("body")[0];

		if ( !body ) {
			// Return for frameset docs that don't have a body
			return;
		}

		container = document.createElement("div");
		container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";

		body.appendChild( container ).appendChild( div );

		// Support: IE8
		// Check if table cells still have offsetWidth/Height when they are set
		// to display:none and there are still other visible table cells in a
		// table row; if so, offsetWidth/Height are not reliable for use when
		// determining if an element has been hidden directly using
		// display:none (it is still safe to use offsets if a parent element is
		// hidden; don safety goggles and see bug #4512 for more information).
		div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
		tds = div.getElementsByTagName("td");
		tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
		isSupported = ( tds[ 0 ].offsetHeight === 0 );

		tds[ 0 ].style.display = "";
		tds[ 1 ].style.display = "none";

		// Support: IE8
		// Check if empty table cells still have offsetWidth/Height
		support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );

		// Check box-sizing and margin behavior
		div.innerHTML = "";
		div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
		support.boxSizing = ( div.offsetWidth === 4 );
		support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );

		// Use window.getComputedStyle because jsdom on node.js will break without it.
		if ( window.getComputedStyle ) {
			support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
			support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";

			// Check if div with explicit width and no margin-right incorrectly
			// gets computed margin-right based on width of container. (#3333)
			// Fails in WebKit before Feb 2011 nightlies
			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
			marginDiv = div.appendChild( document.createElement("div") );
			marginDiv.style.cssText = div.style.cssText = divReset;
			marginDiv.style.marginRight = marginDiv.style.width = "0";
			div.style.width = "1px";

			support.reliableMarginRight =
				!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
		}

		if ( typeof div.style.zoom !== core_strundefined ) {
			// Support: IE<8
			// Check if natively block-level elements act like inline-block
			// elements when setting their display to 'inline' and giving
			// them layout
			div.innerHTML = "";
			div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
			support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );

			// Support: IE6
			// Check if elements with layout shrink-wrap their children
			div.style.display = "block";
			div.innerHTML = "<div></div>";
			div.firstChild.style.width = "5px";
			support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );

			if ( support.inlineBlockNeedsLayout ) {
				// Prevent IE 6 from affecting layout for positioned elements #11048
				// Prevent IE from shrinking the body in IE 7 mode #12869
				// Support: IE<8
				body.style.zoom = 1;
			}
		}

		body.removeChild( container );

		// Null elements to avoid leaks in IE
		container = div = tds = marginDiv = null;
	});

	// Null elements to avoid leaks in IE
	all = select = fragment = opt = a = input = null;

	return support;
})();

var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
	rmultiDash = /([A-Z])/g;

function internalData( elem, name, data, pvt /* Internal Use Only */ ){
	if ( !jQuery.acceptData( elem ) ) {
		return;
	}

	var thisCache, ret,
		internalKey = jQuery.expando,
		getByName = typeof name === "string",

		// We have to handle DOM nodes and JS objects differently because IE6-7
		// can't GC object references properly across the DOM-JS boundary
		isNode = elem.nodeType,

		// Only DOM nodes need the global jQuery cache; JS object data is
		// attached directly to the object so GC can occur automatically
		cache = isNode ? jQuery.cache : elem,

		// Only defining an ID for JS objects if its cache already exists allows
		// the code to shortcut on the same path as a DOM node with no cache
		id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;

	// Avoid doing any more work than we need to when trying to get data on an
	// object that has no data at all
	if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
		return;
	}

	if ( !id ) {
		// Only DOM nodes need a new unique ID for each element since their data
		// ends up in the global cache
		if ( isNode ) {
			elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++;
		} else {
			id = internalKey;
		}
	}

	if ( !cache[ id ] ) {
		cache[ id ] = {};

		// Avoids exposing jQuery metadata on plain JS objects when the object
		// is serialized using JSON.stringify
		if ( !isNode ) {
			cache[ id ].toJSON = jQuery.noop;
		}
	}

	// An object can be passed to jQuery.data instead of a key/value pair; this gets
	// shallow copied over onto the existing cache
	if ( typeof name === "object" || typeof name === "function" ) {
		if ( pvt ) {
			cache[ id ] = jQuery.extend( cache[ id ], name );
		} else {
			cache[ id ].data = jQuery.extend( cache[ id ].data, name );
		}
	}

	thisCache = cache[ id ];

	// jQuery data() is stored in a separate object inside the object's internal data
	// cache in order to avoid key collisions between internal data and user-defined
	// data.
	if ( !pvt ) {
		if ( !thisCache.data ) {
			thisCache.data = {};
		}

		thisCache = thisCache.data;
	}

	if ( data !== undefined ) {
		thisCache[ jQuery.camelCase( name ) ] = data;
	}

	// Check for both converted-to-camel and non-converted data property names
	// If a data property was specified
	if ( getByName ) {

		// First Try to find as-is property data
		ret = thisCache[ name ];

		// Test for null|undefined property data
		if ( ret == null ) {

			// Try to find the camelCased property
			ret = thisCache[ jQuery.camelCase( name ) ];
		}
	} else {
		ret = thisCache;
	}

	return ret;
}

function internalRemoveData( elem, name, pvt ) {
	if ( !jQuery.acceptData( elem ) ) {
		return;
	}

	var i, l, thisCache,
		isNode = elem.nodeType,

		// See jQuery.data for more information
		cache = isNode ? jQuery.cache : elem,
		id = isNode ? elem[ jQuery.expando ] : jQuery.expando;

	// If there is already no cache entry for this object, there is no
	// purpose in continuing
	if ( !cache[ id ] ) {
		return;
	}

	if ( name ) {

		thisCache = pvt ? cache[ id ] : cache[ id ].data;

		if ( thisCache ) {

			// Support array or space separated string names for data keys
			if ( !jQuery.isArray( name ) ) {

				// try the string as a key before any manipulation
				if ( name in thisCache ) {
					name = [ name ];
				} else {

					// split the camel cased version by spaces unless a key with the spaces exists
					name = jQuery.camelCase( name );
					if ( name in thisCache ) {
						name = [ name ];
					} else {
						name = name.split(" ");
					}
				}
			} else {
				// If "name" is an array of keys...
				// When data is initially created, via ("key", "val") signature,
				// keys will be converted to camelCase.
				// Since there is no way to tell _how_ a key was added, remove
				// both plain key and camelCase key. #12786
				// This will only penalize the array argument path.
				name = name.concat( jQuery.map( name, jQuery.camelCase ) );
			}

			for ( i = 0, l = name.length; i < l; i++ ) {
				delete thisCache[ name[i] ];
			}

			// If there is no data left in the cache, we want to continue
			// and let the cache object itself get destroyed
			if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
				return;
			}
		}
	}

	// See jQuery.data for more information
	if ( !pvt ) {
		delete cache[ id ].data;

		// Don't destroy the parent cache unless the internal data object
		// had been the only thing left in it
		if ( !isEmptyDataObject( cache[ id ] ) ) {
			return;
		}
	}

	// Destroy the cache
	if ( isNode ) {
		jQuery.cleanData( [ elem ], true );

	// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
	} else if ( jQuery.support.deleteExpando || cache != cache.window ) {
		delete cache[ id ];

	// When all else fails, null
	} else {
		cache[ id ] = null;
	}
}

jQuery.extend({
	cache: {},

	// Unique for each copy of jQuery on the page
	// Non-digits removed to match rinlinejQuery
	expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),

	// The following elements throw uncatchable exceptions if you
	// attempt to add expando properties to them.
	noData: {
		"embed": true,
		// Ban all objects except for Flash (which handle expandos)
		"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
		"applet": true
	},

	hasData: function( elem ) {
		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
		return !!elem && !isEmptyDataObject( elem );
	},

	data: function( elem, name, data ) {
		return internalData( elem, name, data );
	},

	removeData: function( elem, name ) {
		return internalRemoveData( elem, name );
	},

	// For internal use only.
	_data: function( elem, name, data ) {
		return internalData( elem, name, data, true );
	},

	_removeData: function( elem, name ) {
		return internalRemoveData( elem, name, true );
	},

	// A method for determining if a DOM node can handle the data expando
	acceptData: function( elem ) {
		// Do not set data on non-element because it will not be cleared (#8335).
		if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
			return false;
		}

		var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];

		// nodes accept data unless otherwise specified; rejection can be conditional
		return !noData || noData !== true && elem.getAttribute("classid") === noData;
	}
});

jQuery.fn.extend({
	data: function( key, value ) {
		var attrs, name,
			elem = this[0],
			i = 0,
			data = null;

		// Gets all values
		if ( key === undefined ) {
			if ( this.length ) {
				data = jQuery.data( elem );

				if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
					attrs = elem.attributes;
					for ( ; i < attrs.length; i++ ) {
						name = attrs[i].name;

						if ( !name.indexOf( "data-" ) ) {
							name = jQuery.camelCase( name.slice(5) );

							dataAttr( elem, name, data[ name ] );
						}
					}
					jQuery._data( elem, "parsedAttrs", true );
				}
			}

			return data;
		}

		// Sets multiple values
		if ( typeof key === "object" ) {
			return this.each(function() {
				jQuery.data( this, key );
			});
		}

		return jQuery.access( this, function( value ) {

			if ( value === undefined ) {
				// Try to fetch any internally stored data first
				return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
			}

			this.each(function() {
				jQuery.data( this, key, value );
			});
		}, null, value, arguments.length > 1, null, true );
	},

	removeData: function( key ) {
		return this.each(function() {
			jQuery.removeData( this, key );
		});
	}
});

function dataAttr( elem, key, data ) {
	// If nothing was found internally, try to fetch any
	// data from the HTML5 data-* attribute
	if ( data === undefined && elem.nodeType === 1 ) {

		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();

		data = elem.getAttribute( name );

		if ( typeof data === "string" ) {
			try {
				data = data === "true" ? true :
					data === "false" ? false :
					data === "null" ? null :
					// Only convert to a number if it doesn't change the string
					+data + "" === data ? +data :
					rbrace.test( data ) ? jQuery.parseJSON( data ) :
						data;
			} catch( e ) {}

			// Make sure we set the data so it isn't changed later
			jQuery.data( elem, key, data );

		} else {
			data = undefined;
		}
	}

	return data;
}

// checks a cache object for emptiness
function isEmptyDataObject( obj ) {
	var name;
	for ( name in obj ) {

		// if the public data object is empty, the private is still empty
		if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
			continue;
		}
		if ( name !== "toJSON" ) {
			return false;
		}
	}

	return true;
}
jQuery.extend({
	queue: function( elem, type, data ) {
		var queue;

		if ( elem ) {
			type = ( type || "fx" ) + "queue";
			queue = jQuery._data( elem, type );

			// Speed up dequeue by getting out quickly if this is just a lookup
			if ( data ) {
				if ( !queue || jQuery.isArray(data) ) {
					queue = jQuery._data( elem, type, jQuery.makeArray(data) );
				} else {
					queue.push( data );
				}
			}
			return queue || [];
		}
	},

	dequeue: function( elem, type ) {
		type = type || "fx";

		var queue = jQuery.queue( elem, type ),
			startLength = queue.length,
			fn = queue.shift(),
			hooks = jQuery._queueHooks( elem, type ),
			next = function() {
				jQuery.dequeue( elem, type );
			};

		// If the fx queue is dequeued, always remove the progress sentinel
		if ( fn === "inprogress" ) {
			fn = queue.shift();
			startLength--;
		}

		hooks.cur = fn;
		if ( fn ) {

			// Add a progress sentinel to prevent the fx queue from being
			// automatically dequeued
			if ( type === "fx" ) {
				queue.unshift( "inprogress" );
			}

			// clear up the last queue stop function
			delete hooks.stop;
			fn.call( elem, next, hooks );
		}

		if ( !startLength && hooks ) {
			hooks.empty.fire();
		}
	},

	// not intended for public consumption - generates a queueHooks object, or returns the current one
	_queueHooks: function( elem, type ) {
		var key = type + "queueHooks";
		return jQuery._data( elem, key ) || jQuery._data( elem, key, {
			empty: jQuery.Callbacks("once memory").add(function() {
				jQuery._removeData( elem, type + "queue" );
				jQuery._removeData( elem, key );
			})
		});
	}
});

jQuery.fn.extend({
	queue: function( type, data ) {
		var setter = 2;

		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
			setter--;
		}

		if ( arguments.length < setter ) {
			return jQuery.queue( this[0], type );
		}

		return data === undefined ?
			this :
			this.each(function() {
				var queue = jQuery.queue( this, type, data );

				// ensure a hooks for this queue
				jQuery._queueHooks( this, type );

				if ( type === "fx" && queue[0] !== "inprogress" ) {
					jQuery.dequeue( this, type );
				}
			});
	},
	dequeue: function( type ) {
		return this.each(function() {
			jQuery.dequeue( this, type );
		});
	},
	// Based off of the plugin by Clint Helfers, with permission.
	// http://blindsignals.com/index.php/2009/07/jquery-delay/
	delay: function( time, type ) {
		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
		type = type || "fx";

		return this.queue( type, function( next, hooks ) {
			var timeout = setTimeout( next, time );
			hooks.stop = function() {
				clearTimeout( timeout );
			};
		});
	},
	clearQueue: function( type ) {
		return this.queue( type || "fx", [] );
	},
	// Get a promise resolved when queues of a certain type
	// are emptied (fx is the type by default)
	promise: function( type, obj ) {
		var tmp,
			count = 1,
			defer = jQuery.Deferred(),
			elements = this,
			i = this.length,
			resolve = function() {
				if ( !( --count ) ) {
					defer.resolveWith( elements, [ elements ] );
				}
			};

		if ( typeof type !== "string" ) {
			obj = type;
			type = undefined;
		}
		type = type || "fx";

		while( i-- ) {
			tmp = jQuery._data( elements[ i ], type + "queueHooks" );
			if ( tmp && tmp.empty ) {
				count++;
				tmp.empty.add( resolve );
			}
		}
		resolve();
		return defer.promise( obj );
	}
});
var nodeHook, boolHook,
	rclass = /[\t\r\n]/g,
	rreturn = /\r/g,
	rfocusable = /^(?:input|select|textarea|button|object)$/i,
	rclickable = /^(?:a|area)$/i,
	rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,
	ruseDefault = /^(?:checked|selected)$/i,
	getSetAttribute = jQuery.support.getSetAttribute,
	getSetInput = jQuery.support.input;

jQuery.fn.extend({
	attr: function( name, value ) {
		return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
	},

	removeAttr: function( name ) {
		return this.each(function() {
			jQuery.removeAttr( this, name );
		});
	},

	prop: function( name, value ) {
		return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
	},

	removeProp: function( name ) {
		name = jQuery.propFix[ name ] || name;
		return this.each(function() {
			// try/catch handles cases where IE balks (such as removing a property on window)
			try {
				this[ name ] = undefined;
				delete this[ name ];
			} catch( e ) {}
		});
	},

	addClass: function( value ) {
		var classes, elem, cur, clazz, j,
			i = 0,
			len = this.length,
			proceed = typeof value === "string" && value;

		if ( jQuery.isFunction( value ) ) {
			return this.each(function( j ) {
				jQuery( this ).addClass( value.call( this, j, this.className ) );
			});
		}

		if ( proceed ) {
			// The disjunction here is for better compressibility (see removeClass)
			classes = ( value || "" ).match( core_rnotwhite ) || [];

			for ( ; i < len; i++ ) {
				elem = this[ i ];
				cur = elem.nodeType === 1 && ( elem.className ?
					( " " + elem.className + " " ).replace( rclass, " " ) :
					" "
				);

				if ( cur ) {
					j = 0;
					while ( (clazz = classes[j++]) ) {
						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
							cur += clazz + " ";
						}
					}
					elem.className = jQuery.trim( cur );

				}
			}
		}

		return this;
	},

	removeClass: function( value ) {
		var classes, elem, cur, clazz, j,
			i = 0,
			len = this.length,
			proceed = arguments.length === 0 || typeof value === "string" && value;

		if ( jQuery.isFunction( value ) ) {
			return this.each(function( j ) {
				jQuery( this ).removeClass( value.call( this, j, this.className ) );
			});
		}
		if ( proceed ) {
			classes = ( value || "" ).match( core_rnotwhite ) || [];

			for ( ; i < len; i++ ) {
				elem = this[ i ];
				// This expression is here for better compressibility (see addClass)
				cur = elem.nodeType === 1 && ( elem.className ?
					( " " + elem.className + " " ).replace( rclass, " " ) :
					""
				);

				if ( cur ) {
					j = 0;
					while ( (clazz = classes[j++]) ) {
						// Remove *all* instances
						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
							cur = cur.replace( " " + clazz + " ", " " );
						}
					}
					elem.className = value ? jQuery.trim( cur ) : "";
				}
			}
		}

		return this;
	},

	toggleClass: function( value, stateVal ) {
		var type = typeof value,
			isBool = typeof stateVal === "boolean";

		if ( jQuery.isFunction( value ) ) {
			return this.each(function( i ) {
				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
			});
		}

		return this.each(function() {
			if ( type === "string" ) {
				// toggle individual class names
				var className,
					i = 0,
					self = jQuery( this ),
					state = stateVal,
					classNames = value.match( core_rnotwhite ) || [];

				while ( (className = classNames[ i++ ]) ) {
					// check each className given, space separated list
					state = isBool ? state : !self.hasClass( className );
					self[ state ? "addClass" : "removeClass" ]( className );
				}

			// Toggle whole class name
			} else if ( type === core_strundefined || type === "boolean" ) {
				if ( this.className ) {
					// store className if set
					jQuery._data( this, "__className__", this.className );
				}

				// If the element has a class name or if we're passed "false",
				// then remove the whole classname (if there was one, the above saved it).
				// Otherwise bring back whatever was previously saved (if anything),
				// falling back to the empty string if nothing was stored.
				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
			}
		});
	},

	hasClass: function( selector ) {
		var className = " " + selector + " ",
			i = 0,
			l = this.length;
		for ( ; i < l; i++ ) {
			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
				return true;
			}
		}

		return false;
	},

	val: function( value ) {
		var ret, hooks, isFunction,
			elem = this[0];

		if ( !arguments.length ) {
			if ( elem ) {
				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];

				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
					return ret;
				}

				ret = elem.value;

				return typeof ret === "string" ?
					// handle most common string cases
					ret.replace(rreturn, "") :
					// handle cases where value is null/undef or number
					ret == null ? "" : ret;
			}

			return;
		}

		isFunction = jQuery.isFunction( value );

		return this.each(function( i ) {
			var val,
				self = jQuery(this);

			if ( this.nodeType !== 1 ) {
				return;
			}

			if ( isFunction ) {
				val = value.call( this, i, self.val() );
			} else {
				val = value;
			}

			// Treat null/undefined as ""; convert numbers to string
			if ( val == null ) {
				val = "";
			} else if ( typeof val === "number" ) {
				val += "";
			} else if ( jQuery.isArray( val ) ) {
				val = jQuery.map(val, function ( value ) {
					return value == null ? "" : value + "";
				});
			}

			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

			// If set returns undefined, fall back to normal setting
			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
				this.value = val;
			}
		});
	}
});

jQuery.extend({
	valHooks: {
		option: {
			get: function( elem ) {
				// attributes.value is undefined in Blackberry 4.7 but
				// uses .value. See #6932
				var val = elem.attributes.value;
				return !val || val.specified ? elem.value : elem.text;
			}
		},
		select: {
			get: function( elem ) {
				var value, option,
					options = elem.options,
					index = elem.selectedIndex,
					one = elem.type === "select-one" || index < 0,
					values = one ? null : [],
					max = one ? index + 1 : options.length,
					i = index < 0 ?
						max :
						one ? index : 0;

				// Loop through all the selected options
				for ( ; i < max; i++ ) {
					option = options[ i ];

					// oldIE doesn't update selected after form reset (#2551)
					if ( ( option.selected || i === index ) &&
							// Don't return options that are disabled or in a disabled optgroup
							( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
							( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {

						// Get the specific value for the option
						value = jQuery( option ).val();

						// We don't need an array for one selects
						if ( one ) {
							return value;
						}

						// Multi-Selects return an array
						values.push( value );
					}
				}

				return values;
			},

			set: function( elem, value ) {
				var values = jQuery.makeArray( value );

				jQuery(elem).find("option").each(function() {
					this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
				});

				if ( !values.length ) {
					elem.selectedIndex = -1;
				}
				return values;
			}
		}
	},

	attr: function( elem, name, value ) {
		var hooks, notxml, ret,
			nType = elem.nodeType;

		// don't get/set attributes on text, comment and attribute nodes
		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		// Fallback to prop when attributes are not supported
		if ( typeof elem.getAttribute === core_strundefined ) {
			return jQuery.prop( elem, name, value );
		}

		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

		// All attributes are lowercase
		// Grab necessary hook if one is defined
		if ( notxml ) {
			name = name.toLowerCase();
			hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
		}

		if ( value !== undefined ) {

			if ( value === null ) {
				jQuery.removeAttr( elem, name );

			} else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
				return ret;

			} else {
				elem.setAttribute( name, value + "" );
				return value;
			}

		} else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
			return ret;

		} else {

			// In IE9+, Flash objects don't have .getAttribute (#12945)
			// Support: IE9+
			if ( typeof elem.getAttribute !== core_strundefined ) {
				ret =  elem.getAttribute( name );
			}

			// Non-existent attributes return null, we normalize to undefined
			return ret == null ?
				undefined :
				ret;
		}
	},

	removeAttr: function( elem, value ) {
		var name, propName,
			i = 0,
			attrNames = value && value.match( core_rnotwhite );

		if ( attrNames && elem.nodeType === 1 ) {
			while ( (name = attrNames[i++]) ) {
				propName = jQuery.propFix[ name ] || name;

				// Boolean attributes get special treatment (#10870)
				if ( rboolean.test( name ) ) {
					// Set corresponding property to false for boolean attributes
					// Also clear defaultChecked/defaultSelected (if appropriate) for IE<8
					if ( !getSetAttribute && ruseDefault.test( name ) ) {
						elem[ jQuery.camelCase( "default-" + name ) ] =
							elem[ propName ] = false;
					} else {
						elem[ propName ] = false;
					}

				// See #9699 for explanation of this approach (setting first, then removal)
				} else {
					jQuery.attr( elem, name, "" );
				}

				elem.removeAttribute( getSetAttribute ? name : propName );
			}
		}
	},

	attrHooks: {
		type: {
			set: function( elem, value ) {
				if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
					// Setting the type on a radio button after the value resets the value in IE6-9
					// Reset value to default in case type is set after value during creation
					var val = elem.value;
					elem.setAttribute( "type", value );
					if ( val ) {
						elem.value = val;
					}
					return value;
				}
			}
		}
	},

	propFix: {
		tabindex: "tabIndex",
		readonly: "readOnly",
		"for": "htmlFor",
		"class": "className",
		maxlength: "maxLength",
		cellspacing: "cellSpacing",
		cellpadding: "cellPadding",
		rowspan: "rowSpan",
		colspan: "colSpan",
		usemap: "useMap",
		frameborder: "frameBorder",
		contenteditable: "contentEditable"
	},

	prop: function( elem, name, value ) {
		var ret, hooks, notxml,
			nType = elem.nodeType;

		// don't get/set properties on text, comment and attribute nodes
		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

		if ( notxml ) {
			// Fix name and attach hooks
			name = jQuery.propFix[ name ] || name;
			hooks = jQuery.propHooks[ name ];
		}

		if ( value !== undefined ) {
			if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
				return ret;

			} else {
				return ( elem[ name ] = value );
			}

		} else {
			if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
				return ret;

			} else {
				return elem[ name ];
			}
		}
	},

	propHooks: {
		tabIndex: {
			get: function( elem ) {
				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
				var attributeNode = elem.getAttributeNode("tabindex");

				return attributeNode && attributeNode.specified ?
					parseInt( attributeNode.value, 10 ) :
					rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
						0 :
						undefined;
			}
		}
	}
});

// Hook for boolean attributes
boolHook = {
	get: function( elem, name ) {
		var
			// Use .prop to determine if this attribute is understood as boolean
			prop = jQuery.prop( elem, name ),

			// Fetch it accordingly
			attr = typeof prop === "boolean" && elem.getAttribute( name ),
			detail = typeof prop === "boolean" ?

				getSetInput && getSetAttribute ?
					attr != null :
					// oldIE fabricates an empty string for missing boolean attributes
					// and conflates checked/selected into attroperties
					ruseDefault.test( name ) ?
						elem[ jQuery.camelCase( "default-" + name ) ] :
						!!attr :

				// fetch an attribute node for properties not recognized as boolean
				elem.getAttributeNode( name );

		return detail && detail.value !== false ?
			name.toLowerCase() :
			undefined;
	},
	set: function( elem, value, name ) {
		if ( value === false ) {
			// Remove boolean attributes when set to false
			jQuery.removeAttr( elem, name );
		} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
			// IE<8 needs the *property* name
			elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );

		// Use defaultChecked and defaultSelected for oldIE
		} else {
			elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
		}

		return name;
	}
};

// fix oldIE value attroperty
if ( !getSetInput || !getSetAttribute ) {
	jQuery.attrHooks.value = {
		get: function( elem, name ) {
			var ret = elem.getAttributeNode( name );
			return jQuery.nodeName( elem, "input" ) ?

				// Ignore the value *property* by using defaultValue
				elem.defaultValue :

				ret && ret.specified ? ret.value : undefined;
		},
		set: function( elem, value, name ) {
			if ( jQuery.nodeName( elem, "input" ) ) {
				// Does not return so that setAttribute is also used
				elem.defaultValue = value;
			} else {
				// Use nodeHook if defined (#1954); otherwise setAttribute is fine
				return nodeHook && nodeHook.set( elem, value, name );
			}
		}
	};
}

// IE6/7 do not support getting/setting some attributes with get/setAttribute
if ( !getSetAttribute ) {

	// Use this for any attribute in IE6/7
	// This fixes almost every IE6/7 issue
	nodeHook = jQuery.valHooks.button = {
		get: function( elem, name ) {
			var ret = elem.getAttributeNode( name );
			return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ?
				ret.value :
				undefined;
		},
		set: function( elem, value, name ) {
			// Set the existing or create a new attribute node
			var ret = elem.getAttributeNode( name );
			if ( !ret ) {
				elem.setAttributeNode(
					(ret = elem.ownerDocument.createAttribute( name ))
				);
			}

			ret.value = value += "";

			// Break association with cloned elements by also using setAttribute (#9646)
			return name === "value" || value === elem.getAttribute( name ) ?
				value :
				undefined;
		}
	};

	// Set contenteditable to false on removals(#10429)
	// Setting to empty string throws an error as an invalid value
	jQuery.attrHooks.contenteditable = {
		get: nodeHook.get,
		set: function( elem, value, name ) {
			nodeHook.set( elem, value === "" ? false : value, name );
		}
	};

	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
	// This is for removals
	jQuery.each([ "width", "height" ], function( i, name ) {
		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
			set: function( elem, value ) {
				if ( value === "" ) {
					elem.setAttribute( name, "auto" );
					return value;
				}
			}
		});
	});
}


// Some attributes require a special call on IE
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !jQuery.support.hrefNormalized ) {
	jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
			get: function( elem ) {
				var ret = elem.getAttribute( name, 2 );
				return ret == null ? undefined : ret;
			}
		});
	});

	// href/src property should get the full normalized URL (#10299/#12915)
	jQuery.each([ "href", "src" ], function( i, name ) {
		jQuery.propHooks[ name ] = {
			get: function( elem ) {
				return elem.getAttribute( name, 4 );
			}
		};
	});
}

if ( !jQuery.support.style ) {
	jQuery.attrHooks.style = {
		get: function( elem ) {
			// Return undefined in the case of empty string
			// Note: IE uppercases css property names, but if we were to .toLowerCase()
			// .cssText, that would destroy case senstitivity in URL's, like in "background"
			return elem.style.cssText || undefined;
		},
		set: function( elem, value ) {
			return ( elem.style.cssText = value + "" );
		}
	};
}

// Safari mis-reports the default selected property of an option
// Accessing the parent's selectedIndex property fixes it
if ( !jQuery.support.optSelected ) {
	jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
		get: function( elem ) {
			var parent = elem.parentNode;

			if ( parent ) {
				parent.selectedIndex;

				// Make sure that it also works with optgroups, see #5701
				if ( parent.parentNode ) {
					parent.parentNode.selectedIndex;
				}
			}
			return null;
		}
	});
}

// IE6/7 call enctype encoding
if ( !jQuery.support.enctype ) {
	jQuery.propFix.enctype = "encoding";
}

// Radios and checkboxes getter/setter
if ( !jQuery.support.checkOn ) {
	jQuery.each([ "radio", "checkbox" ], function() {
		jQuery.valHooks[ this ] = {
			get: function( elem ) {
				// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
				return elem.getAttribute("value") === null ? "on" : elem.value;
			}
		};
	});
}
jQuery.each([ "radio", "checkbox" ], function() {
	jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
		set: function( elem, value ) {
			if ( jQuery.isArray( value ) ) {
				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
			}
		}
	});
});
var rformElems = /^(?:input|select|textarea)$/i,
	rkeyEvent = /^key/,
	rmouseEvent = /^(?:mouse|contextmenu)|click/,
	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
	rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;

function returnTrue() {
	return true;
}

function returnFalse() {
	return false;
}

/*
 * Helper functions for managing events -- not part of the public interface.
 * Props to Dean Edwards' addEvent library for many of the ideas.
 */
jQuery.event = {

	global: {},

	add: function( elem, types, handler, data, selector ) {
		var tmp, events, t, handleObjIn,
			special, eventHandle, handleObj,
			handlers, type, namespaces, origType,
			elemData = jQuery._data( elem );

		// Don't attach events to noData or text/comment nodes (but allow plain objects)
		if ( !elemData ) {
			return;
		}

		// Caller can pass in an object of custom data in lieu of the handler
		if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
			selector = handleObjIn.selector;
		}

		// Make sure that the handler has a unique ID, used to find/remove it later
		if ( !handler.guid ) {
			handler.guid = jQuery.guid++;
		}

		// Init the element's event structure and main handler, if this is the first
		if ( !(events = elemData.events) ) {
			events = elemData.events = {};
		}
		if ( !(eventHandle = elemData.handle) ) {
			eventHandle = elemData.handle = function( e ) {
				// Discard the second event of a jQuery.event.trigger() and
				// when an event is called after a page has unloaded
				return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
					undefined;
			};
			// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
			eventHandle.elem = elem;
		}

		// Handle multiple events separated by a space
		// jQuery(...).bind("mouseover mouseout", fn);
		types = ( types || "" ).match( core_rnotwhite ) || [""];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[t] ) || [];
			type = origType = tmp[1];
			namespaces = ( tmp[2] || "" ).split( "." ).sort();

			// If event changes its type, use the special event handlers for the changed type
			special = jQuery.event.special[ type ] || {};

			// If selector defined, determine special event api type, otherwise given type
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// Update special based on newly reset type
			special = jQuery.event.special[ type ] || {};

			// handleObj is passed to all event handlers
			handleObj = jQuery.extend({
				type: type,
				origType: origType,
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
				namespace: namespaces.join(".")
			}, handleObjIn );

			// Init the event handler queue if we're the first
			if ( !(handlers = events[ type ]) ) {
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;

				// Only use addEventListener/attachEvent if the special events handler returns false
				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
					// Bind the global event handler to the element
					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle, false );

					} else if ( elem.attachEvent ) {
						elem.attachEvent( "on" + type, eventHandle );
					}
				}
			}

			if ( special.add ) {
				special.add.call( elem, handleObj );

				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// Add to the element's handler list, delegates in front
			if ( selector ) {
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// Keep track of which events have ever been used, for event optimization
			jQuery.event.global[ type ] = true;
		}

		// Nullify elem to prevent memory leaks in IE
		elem = null;
	},

	// Detach an event or set of events from an element
	remove: function( elem, types, handler, selector, mappedTypes ) {
		var j, handleObj, tmp,
			origCount, t, events,
			special, handlers, type,
			namespaces, origType,
			elemData = jQuery.hasData( elem ) && jQuery._data( elem );

		if ( !elemData || !(events = elemData.events) ) {
			return;
		}

		// Once for each type.namespace in types; type may be omitted
		types = ( types || "" ).match( core_rnotwhite ) || [""];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[t] ) || [];
			type = origType = tmp[1];
			namespaces = ( tmp[2] || "" ).split( "." ).sort();

			// Unbind all events (on this namespace, if provided) for the element
			if ( !type ) {
				for ( type in events ) {
					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
				}
				continue;
			}

			special = jQuery.event.special[ type ] || {};
			type = ( selector ? special.delegateType : special.bindType ) || type;
			handlers = events[ type ] || [];
			tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );

			// Remove matching events
			origCount = j = handlers.length;
			while ( j-- ) {
				handleObj = handlers[ j ];

				if ( ( mappedTypes || origType === handleObj.origType ) &&
					( !handler || handler.guid === handleObj.guid ) &&
					( !tmp || tmp.test( handleObj.namespace ) ) &&
					( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
					handlers.splice( j, 1 );

					if ( handleObj.selector ) {
						handlers.delegateCount--;
					}
					if ( special.remove ) {
						special.remove.call( elem, handleObj );
					}
				}
			}

			// Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
			if ( origCount && !handlers.length ) {
				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ];
			}
		}

		// Remove the expando if it's no longer used
		if ( jQuery.isEmptyObject( events ) ) {
			delete elemData.handle;

			// removeData also checks for emptiness and clears the expando if empty
			// so use it instead of delete
			jQuery._removeData( elem, "events" );
		}
	},

	trigger: function( event, data, elem, onlyHandlers ) {
		var handle, ontype, cur,
			bubbleType, special, tmp, i,
			eventPath = [ elem || document ],
			type = core_hasOwn.call( event, "type" ) ? event.type : event,
			namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];

		cur = tmp = elem = elem || document;

		// Don't do events on text and comment nodes
		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
			return;
		}

		// focus/blur morphs to focusin/out; ensure we're not firing them right now
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}

		if ( type.indexOf(".") >= 0 ) {
			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split(".");
			type = namespaces.shift();
			namespaces.sort();
		}
		ontype = type.indexOf(":") < 0 && "on" + type;

		// Caller can pass in a jQuery.Event object, Object, or just an event type string
		event = event[ jQuery.expando ] ?
			event :
			new jQuery.Event( type, typeof event === "object" && event );

		event.isTrigger = true;
		event.namespace = namespaces.join(".");
		event.namespace_re = event.namespace ?
			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
			null;

		// Clean up the event in case it is being reused
		event.result = undefined;
		if ( !event.target ) {
			event.target = elem;
		}

		// Clone any incoming data and prepend the event, creating the handler arg list
		data = data == null ?
			[ event ] :
			jQuery.makeArray( data, [ event ] );

		// Allow special events to draw outside the lines
		special = jQuery.event.special[ type ] || {};
		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}

		// Determine event propagation path in advance, per W3C events spec (#9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

			bubbleType = special.delegateType || type;
			if ( !rfocusMorph.test( bubbleType + type ) ) {
				cur = cur.parentNode;
			}
			for ( ; cur; cur = cur.parentNode ) {
				eventPath.push( cur );
				tmp = cur;
			}

			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( tmp === (elem.ownerDocument || document) ) {
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}

		// Fire handlers on the event path
		i = 0;
		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {

			event.type = i > 1 ?
				bubbleType :
				special.bindType || type;

			// jQuery handler
			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
			if ( handle ) {
				handle.apply( cur, data );
			}

			// Native handler
			handle = ontype && cur[ ontype ];
			if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
				event.preventDefault();
			}
		}
		event.type = type;

		// If nobody prevented the default action, do it now
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {

			if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
				!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {

				// Call a native DOM method on the target with the same name name as the event.
				// Can't use an .isFunction() check here because IE6/7 fails that test.
				// Don't do default actions on window, that's where global variables be (#6170)
				if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {

					// Don't re-trigger an onFOO event when we call its FOO() method
					tmp = elem[ ontype ];

					if ( tmp ) {
						elem[ ontype ] = null;
					}

					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;
					try {
						elem[ type ]();
					} catch ( e ) {
						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
					}
					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}

		return event.result;
	},

	dispatch: function( event ) {

		// Make a writable jQuery.Event from the native event object
		event = jQuery.event.fix( event );

		var i, ret, handleObj, matched, j,
			handlerQueue = [],
			args = core_slice.call( arguments ),
			handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
			special = jQuery.event.special[ event.type ] || {};

		// Use the fix-ed jQuery.Event rather than the (read-only) native event
		args[0] = event;
		event.delegateTarget = this;

		// Call the preDispatch hook for the mapped type, and let it bail if desired
		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
			return;
		}

		// Determine handlers
		handlerQueue = jQuery.event.handlers.call( this, event, handlers );

		// Run delegates first; they may want to stop propagation beneath us
		i = 0;
		while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
			event.currentTarget = matched.elem;

			j = 0;
			while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {

				// Triggered event must either 1) have no namespace, or
				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
				if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {

					event.handleObj = handleObj;
					event.data = handleObj.data;

					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
							.apply( matched.elem, args );

					if ( ret !== undefined ) {
						if ( (event.result = ret) === false ) {
							event.preventDefault();
							event.stopPropagation();
						}
					}
				}
			}
		}

		// Call the postDispatch hook for the mapped type
		if ( special.postDispatch ) {
			special.postDispatch.call( this, event );
		}

		return event.result;
	},

	handlers: function( event, handlers ) {
		var sel, handleObj, matches, i,
			handlerQueue = [],
			delegateCount = handlers.delegateCount,
			cur = event.target;

		// Find delegate handlers
		// Black-hole SVG <use> instance trees (#13180)
		// Avoid non-left-click bubbling in Firefox (#3861)
		if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {

			for ( ; cur != this; cur = cur.parentNode || this ) {

				// Don't check non-elements (#13208)
				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
				if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) {
					matches = [];
					for ( i = 0; i < delegateCount; i++ ) {
						handleObj = handlers[ i ];

						// Don't conflict with Object.prototype properties (#13203)
						sel = handleObj.selector + " ";

						if ( matches[ sel ] === undefined ) {
							matches[ sel ] = handleObj.needsContext ?
								jQuery( sel, this ).index( cur ) >= 0 :
								jQuery.find( sel, this, null, [ cur ] ).length;
						}
						if ( matches[ sel ] ) {
							matches.push( handleObj );
						}
					}
					if ( matches.length ) {
						handlerQueue.push({ elem: cur, handlers: matches });
					}
				}
			}
		}

		// Add the remaining (directly-bound) handlers
		if ( delegateCount < handlers.length ) {
			handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
		}

		return handlerQueue;
	},

	fix: function( event ) {
		if ( event[ jQuery.expando ] ) {
			return event;
		}

		// Create a writable copy of the event object and normalize some properties
		var i, prop, copy,
			type = event.type,
			originalEvent = event,
			fixHook = this.fixHooks[ type ];

		if ( !fixHook ) {
			this.fixHooks[ type ] = fixHook =
				rmouseEvent.test( type ) ? this.mouseHooks :
				rkeyEvent.test( type ) ? this.keyHooks :
				{};
		}
		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

		event = new jQuery.Event( originalEvent );

		i = copy.length;
		while ( i-- ) {
			prop = copy[ i ];
			event[ prop ] = originalEvent[ prop ];
		}

		// Support: IE<9
		// Fix target property (#1925)
		if ( !event.target ) {
			event.target = originalEvent.srcElement || document;
		}

		// Support: Chrome 23+, Safari?
		// Target should not be a text node (#504, #13143)
		if ( event.target.nodeType === 3 ) {
			event.target = event.target.parentNode;
		}

		// Support: IE<9
		// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
		event.metaKey = !!event.metaKey;

		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
	},

	// Includes some event props shared by KeyEvent and MouseEvent
	props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),

	fixHooks: {},

	keyHooks: {
		props: "char charCode key keyCode".split(" "),
		filter: function( event, original ) {

			// Add which for key events
			if ( event.which == null ) {
				event.which = original.charCode != null ? original.charCode : original.keyCode;
			}

			return event;
		}
	},

	mouseHooks: {
		props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
		filter: function( event, original ) {
			var body, eventDoc, doc,
				button = original.button,
				fromElement = original.fromElement;

			// Calculate pageX/Y if missing and clientX/Y available
			if ( event.pageX == null && original.clientX != null ) {
				eventDoc = event.target.ownerDocument || document;
				doc = eventDoc.documentElement;
				body = eventDoc.body;

				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
			}

			// Add relatedTarget, if necessary
			if ( !event.relatedTarget && fromElement ) {
				event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
			}

			// Add which for click: 1 === left; 2 === middle; 3 === right
			// Note: button is not normalized, so don't use it
			if ( !event.which && button !== undefined ) {
				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
			}

			return event;
		}
	},

	special: {
		load: {
			// Prevent triggered image.load events from bubbling to window.load
			noBubble: true
		},
		click: {
			// For checkbox, fire native event so checked state will be right
			trigger: function() {
				if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
					this.click();
					return false;
				}
			}
		},
		focus: {
			// Fire native event if possible so blur/focus sequence is correct
			trigger: function() {
				if ( this !== document.activeElement && this.focus ) {
					try {
						this.focus();
						return false;
					} catch ( e ) {
						// Support: IE<9
						// If we error on focus to hidden element (#1486, #12518),
						// let .trigger() run the handlers
					}
				}
			},
			delegateType: "focusin"
		},
		blur: {
			trigger: function() {
				if ( this === document.activeElement && this.blur ) {
					this.blur();
					return false;
				}
			},
			delegateType: "focusout"
		},

		beforeunload: {
			postDispatch: function( event ) {

				// Even when returnValue equals to undefined Firefox will still show alert
				if ( event.result !== undefined ) {
					event.originalEvent.returnValue = event.result;
				}
			}
		}
	},

	simulate: function( type, elem, event, bubble ) {
		// Piggyback on a donor event to simulate a different one.
		// Fake originalEvent to avoid donor's stopPropagation, but if the
		// simulated event prevents default then we do the same on the donor.
		var e = jQuery.extend(
			new jQuery.Event(),
			event,
			{ type: type,
				isSimulated: true,
				originalEvent: {}
			}
		);
		if ( bubble ) {
			jQuery.event.trigger( e, null, elem );
		} else {
			jQuery.event.dispatch.call( elem, e );
		}
		if ( e.isDefaultPrevented() ) {
			event.preventDefault();
		}
	}
};

jQuery.removeEvent = document.removeEventListener ?
	function( elem, type, handle ) {
		if ( elem.removeEventListener ) {
			elem.removeEventListener( type, handle, false );
		}
	} :
	function( elem, type, handle ) {
		var name = "on" + type;

		if ( elem.detachEvent ) {

			// #8545, #7054, preventing memory leaks for custom events in IE6-8
			// detachEvent needed property on element, by name of that event, to properly expose it to GC
			if ( typeof elem[ name ] === core_strundefined ) {
				elem[ name ] = null;
			}

			elem.detachEvent( name, handle );
		}
	};

jQuery.Event = function( src, props ) {
	// Allow instantiation without the 'new' keyword
	if ( !(this instanceof jQuery.Event) ) {
		return new jQuery.Event( src, props );
	}

	// Event object
	if ( src && src.type ) {
		this.originalEvent = src;
		this.type = src.type;

		// Events bubbling up the document may have been marked as prevented
		// by a handler lower down the tree; reflect the correct value.
		this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
			src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;

	// Event type
	} else {
		this.type = src;
	}

	// Put explicitly provided properties onto the event object
	if ( props ) {
		jQuery.extend( this, props );
	}

	// Create a timestamp if incoming event doesn't have one
	this.timeStamp = src && src.timeStamp || jQuery.now();

	// Mark it as fixed
	this[ jQuery.expando ] = true;
};

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse,

	preventDefault: function() {
		var e = this.originalEvent;

		this.isDefaultPrevented = returnTrue;
		if ( !e ) {
			return;
		}

		// If preventDefault exists, run it on the original event
		if ( e.preventDefault ) {
			e.preventDefault();

		// Support: IE
		// Otherwise set the returnValue property of the original event to false
		} else {
			e.returnValue = false;
		}
	},
	stopPropagation: function() {
		var e = this.originalEvent;

		this.isPropagationStopped = returnTrue;
		if ( !e ) {
			return;
		}
		// If stopPropagation exists, run it on the original event
		if ( e.stopPropagation ) {
			e.stopPropagation();
		}

		// Support: IE
		// Set the cancelBubble property of the original event to true
		e.cancelBubble = true;
	},
	stopImmediatePropagation: function() {
		this.isImmediatePropagationStopped = returnTrue;
		this.stopPropagation();
	}
};

// Create mouseenter/leave events using mouseover/out and event-time checks
jQuery.each({
	mouseenter: "mouseover",
	mouseleave: "mouseout"
}, function( orig, fix ) {
	jQuery.event.special[ orig ] = {
		delegateType: fix,
		bindType: fix,

		handle: function( event ) {
			var ret,
				target = this,
				related = event.relatedTarget,
				handleObj = event.handleObj;

			// For mousenter/leave call the handler if related is outside the target.
			// NB: No relatedTarget if the mouse left/entered the browser window
			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
				event.type = handleObj.origType;
				ret = handleObj.handler.apply( this, arguments );
				event.type = fix;
			}
			return ret;
		}
	};
});

// IE submit delegation
if ( !jQuery.support.submitBubbles ) {

	jQuery.event.special.submit = {
		setup: function() {
			// Only need this for delegated form submit events
			if ( jQuery.nodeName( this, "form" ) ) {
				return false;
			}

			// Lazy-add a submit handler when a descendant form may potentially be submitted
			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
				// Node name check avoids a VML-related crash in IE (#9807)
				var elem = e.target,
					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
				if ( form && !jQuery._data( form, "submitBubbles" ) ) {
					jQuery.event.add( form, "submit._submit", function( event ) {
						event._submit_bubble = true;
					});
					jQuery._data( form, "submitBubbles", true );
				}
			});
			// return undefined since we don't need an event listener
		},

		postDispatch: function( event ) {
			// If form was submitted by the user, bubble the event up the tree
			if ( event._submit_bubble ) {
				delete event._submit_bubble;
				if ( this.parentNode && !event.isTrigger ) {
					jQuery.event.simulate( "submit", this.parentNode, event, true );
				}
			}
		},

		teardown: function() {
			// Only need this for delegated form submit events
			if ( jQuery.nodeName( this, "form" ) ) {
				return false;
			}

			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
			jQuery.event.remove( this, "._submit" );
		}
	};
}

// IE change delegation and checkbox/radio fix
if ( !jQuery.support.changeBubbles ) {

	jQuery.event.special.change = {

		setup: function() {

			if ( rformElems.test( this.nodeName ) ) {
				// IE doesn't fire change on a check/radio until blur; trigger it on click
				// after a propertychange. Eat the blur-change in special.change.handle.
				// This still fires onchange a second time for check/radio after blur.
				if ( this.type === "checkbox" || this.type === "radio" ) {
					jQuery.event.add( this, "propertychange._change", function( event ) {
						if ( event.originalEvent.propertyName === "checked" ) {
							this._just_changed = true;
						}
					});
					jQuery.event.add( this, "click._change", function( event ) {
						if ( this._just_changed && !event.isTrigger ) {
							this._just_changed = false;
						}
						// Allow triggered, simulated change events (#11500)
						jQuery.event.simulate( "change", this, event, true );
					});
				}
				return false;
			}
			// Delegated event; lazy-add a change handler on descendant inputs
			jQuery.event.add( this, "beforeactivate._change", function( e ) {
				var elem = e.target;

				if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {
					jQuery.event.add( elem, "change._change", function( event ) {
						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
							jQuery.event.simulate( "change", this.parentNode, event, true );
						}
					});
					jQuery._data( elem, "changeBubbles", true );
				}
			});
		},

		handle: function( event ) {
			var elem = event.target;

			// Swallow native change events from checkbox/radio, we already triggered them above
			if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
				return event.handleObj.handler.apply( this, arguments );
			}
		},

		teardown: function() {
			jQuery.event.remove( this, "._change" );

			return !rformElems.test( this.nodeName );
		}
	};
}

// Create "bubbling" focus and blur events
if ( !jQuery.support.focusinBubbles ) {
	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {

		// Attach a single capturing handler while someone wants focusin/focusout
		var attaches = 0,
			handler = function( event ) {
				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
			};

		jQuery.event.special[ fix ] = {
			setup: function() {
				if ( attaches++ === 0 ) {
					document.addEventListener( orig, handler, true );
				}
			},
			teardown: function() {
				if ( --attaches === 0 ) {
					document.removeEventListener( orig, handler, true );
				}
			}
		};
	});
}

jQuery.fn.extend({

	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
		var type, origFn;

		// Types can be a map of types/handlers
		if ( typeof types === "object" ) {
			// ( types-Object, selector, data )
			if ( typeof selector !== "string" ) {
				// ( types-Object, data )
				data = data || selector;
				selector = undefined;
			}
			for ( type in types ) {
				this.on( type, selector, data, types[ type ], one );
			}
			return this;
		}

		if ( data == null && fn == null ) {
			// ( types, fn )
			fn = selector;
			data = selector = undefined;
		} else if ( fn == null ) {
			if ( typeof selector === "string" ) {
				// ( types, selector, fn )
				fn = data;
				data = undefined;
			} else {
				// ( types, data, fn )
				fn = data;
				data = selector;
				selector = undefined;
			}
		}
		if ( fn === false ) {
			fn = returnFalse;
		} else if ( !fn ) {
			return this;
		}

		if ( one === 1 ) {
			origFn = fn;
			fn = function( event ) {
				// Can use an empty set, since event contains the info
				jQuery().off( event );
				return origFn.apply( this, arguments );
			};
			// Use same guid so caller can remove using origFn
			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
		}
		return this.each( function() {
			jQuery.event.add( this, types, fn, data, selector );
		});
	},
	one: function( types, selector, data, fn ) {
		return this.on( types, selector, data, fn, 1 );
	},
	off: function( types, selector, fn ) {
		var handleObj, type;
		if ( types && types.preventDefault && types.handleObj ) {
			// ( event )  dispatched jQuery.Event
			handleObj = types.handleObj;
			jQuery( types.delegateTarget ).off(
				handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
				handleObj.selector,
				handleObj.handler
			);
			return this;
		}
		if ( typeof types === "object" ) {
			// ( types-object [, selector] )
			for ( type in types ) {
				this.off( type, selector, types[ type ] );
			}
			return this;
		}
		if ( selector === false || typeof selector === "function" ) {
			// ( types [, fn] )
			fn = selector;
			selector = undefined;
		}
		if ( fn === false ) {
			fn = returnFalse;
		}
		return this.each(function() {
			jQuery.event.remove( this, types, fn, selector );
		});
	},

	bind: function( types, data, fn ) {
		return this.on( types, null, data, fn );
	},
	unbind: function( types, fn ) {
		return this.off( types, null, fn );
	},

	delegate: function( selector, types, data, fn ) {
		return this.on( types, selector, data, fn );
	},
	undelegate: function( selector, types, fn ) {
		// ( namespace ) or ( selector, types [, fn] )
		return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
	},

	trigger: function( type, data ) {
		return this.each(function() {
			jQuery.event.trigger( type, data, this );
		});
	},
	triggerHandler: function( type, data ) {
		var elem = this[0];
		if ( elem ) {
			return jQuery.event.trigger( type, data, elem, true );
		}
	}
});
/*!
 * Sizzle CSS Selector Engine
 * Copyright 2012 jQuery Foundation and other contributors
 * Released under the MIT license
 * http://sizzlejs.com/
 */
(function( window, undefined ) {

var i,
	cachedruns,
	Expr,
	getText,
	isXML,
	compile,
	hasDuplicate,
	outermostContext,

	// Local document vars
	setDocument,
	document,
	docElem,
	documentIsXML,
	rbuggyQSA,
	rbuggyMatches,
	matches,
	contains,
	sortOrder,

	// Instance-specific data
	expando = "sizzle" + -(new Date()),
	preferredDoc = window.document,
	support = {},
	dirruns = 0,
	done = 0,
	classCache = createCache(),
	tokenCache = createCache(),
	compilerCache = createCache(),

	// General-purpose constants
	strundefined = typeof undefined,
	MAX_NEGATIVE = 1 << 31,

	// Array methods
	arr = [],
	pop = arr.pop,
	push = arr.push,
	slice = arr.slice,
	// Use a stripped-down indexOf if we can't use a native one
	indexOf = arr.indexOf || function( elem ) {
		var i = 0,
			len = this.length;
		for ( ; i < len; i++ ) {
			if ( this[i] === elem ) {
				return i;
			}
		}
		return -1;
	},


	// Regular expressions

	// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
	whitespace = "[\\x20\\t\\r\\n\\f]",
	// http://www.w3.org/TR/css3-syntax/#characters
	characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",

	// Loosely modeled on CSS identifier characters
	// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
	// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
	identifier = characterEncoding.replace( "w", "w#" ),

	// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
	operators = "([*^$|!~]?=)",
	attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
		"*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",

	// Prefer arguments quoted,
	//   then not containing pseudos/brackets,
	//   then attribute selectors/non-parenthetical expressions,
	//   then anything else
	// These preferences are here to reduce the number of selectors
	//   needing tokenize in the PSEUDO preFilter
	pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",

	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),

	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
	rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ),
	rpseudo = new RegExp( pseudos ),
	ridentifier = new RegExp( "^" + identifier + "$" ),

	matchExpr = {
		"ID": new RegExp( "^#(" + characterEncoding + ")" ),
		"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
		"NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
		"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
		"ATTR": new RegExp( "^" + attributes ),
		"PSEUDO": new RegExp( "^" + pseudos ),
		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
		// For use in libraries implementing .is()
		// We use this for POS matching in `select`
		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
	},

	rsibling = /[\x20\t\r\n\f]*[+~]/,

	rnative = /^[^{]+\{\s*\[native code/,

	// Easily-parseable/retrievable ID or TAG or CLASS selectors
	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

	rinputs = /^(?:input|select|textarea|button)$/i,
	rheader = /^h\d$/i,

	rescape = /'|\\/g,
	rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,

	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
	runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,
	funescape = function( _, escaped ) {
		var high = "0x" + escaped - 0x10000;
		// NaN means non-codepoint
		return high !== high ?
			escaped :
			// BMP codepoint
			high < 0 ?
				String.fromCharCode( high + 0x10000 ) :
				// Supplemental Plane codepoint (surrogate pair)
				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
	};

// Use a stripped-down slice if we can't use a native one
try {
	slice.call( preferredDoc.documentElement.childNodes, 0 )[0].nodeType;
} catch ( e ) {
	slice = function( i ) {
		var elem,
			results = [];
		while ( (elem = this[i++]) ) {
			results.push( elem );
		}
		return results;
	};
}

/**
 * For feature detection
 * @param {Function} fn The function to test for native support
 */
function isNative( fn ) {
	return rnative.test( fn + "" );
}

/**
 * Create key-value caches of limited size
 * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 *	deleting the oldest entry
 */
function createCache() {
	var cache,
		keys = [];

	return (cache = function( key, value ) {
		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
		if ( keys.push( key += " " ) > Expr.cacheLength ) {
			// Only keep the most recent entries
			delete cache[ keys.shift() ];
		}
		return (cache[ key ] = value);
	});
}

/**
 * Mark a function for special use by Sizzle
 * @param {Function} fn The function to mark
 */
function markFunction( fn ) {
	fn[ expando ] = true;
	return fn;
}

/**
 * Support testing using an element
 * @param {Function} fn Passed the created div and expects a boolean result
 */
function assert( fn ) {
	var div = document.createElement("div");

	try {
		return fn( div );
	} catch (e) {
		return false;
	} finally {
		// release memory in IE
		div = null;
	}
}

function Sizzle( selector, context, results, seed ) {
	var match, elem, m, nodeType,
		// QSA vars
		i, groups, old, nid, newContext, newSelector;

	if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
		setDocument( context );
	}

	context = context || document;
	results = results || [];

	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
		return [];
	}

	if ( !documentIsXML && !seed ) {

		// Shortcuts
		if ( (match = rquickExpr.exec( selector )) ) {
			// Speed-up: Sizzle("#ID")
			if ( (m = match[1]) ) {
				if ( nodeType === 9 ) {
					elem = context.getElementById( m );
					// Check parentNode to catch when Blackberry 4.6 returns
					// nodes that are no longer in the document #6963
					if ( elem && elem.parentNode ) {
						// Handle the case where IE, Opera, and Webkit return items
						// by name instead of ID
						if ( elem.id === m ) {
							results.push( elem );
							return results;
						}
					} else {
						return results;
					}
				} else {
					// Context is not a document
					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
						contains( context, elem ) && elem.id === m ) {
						results.push( elem );
						return results;
					}
				}

			// Speed-up: Sizzle("TAG")
			} else if ( match[2] ) {
				push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
				return results;

			// Speed-up: Sizzle(".CLASS")
			} else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) {
				push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
				return results;
			}
		}

		// QSA path
		if ( support.qsa && !rbuggyQSA.test(selector) ) {
			old = true;
			nid = expando;
			newContext = context;
			newSelector = nodeType === 9 && selector;

			// qSA works strangely on Element-rooted queries
			// We can work around this by specifying an extra ID on the root
			// and working up from there (Thanks to Andrew Dupont for the technique)
			// IE 8 doesn't work on object elements
			if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
				groups = tokenize( selector );

				if ( (old = context.getAttribute("id")) ) {
					nid = old.replace( rescape, "\\$&" );
				} else {
					context.setAttribute( "id", nid );
				}
				nid = "[id='" + nid + "'] ";

				i = groups.length;
				while ( i-- ) {
					groups[i] = nid + toSelector( groups[i] );
				}
				newContext = rsibling.test( selector ) && context.parentNode || context;
				newSelector = groups.join(",");
			}

			if ( newSelector ) {
				try {
					push.apply( results, slice.call( newContext.querySelectorAll(
						newSelector
					), 0 ) );
					return results;
				} catch(qsaError) {
				} finally {
					if ( !old ) {
						context.removeAttribute("id");
					}
				}
			}
		}
	}

	// All others
	return select( selector.replace( rtrim, "$1" ), context, results, seed );
}

/**
 * Detect xml
 * @param {Element|Object} elem An element or a document
 */
isXML = Sizzle.isXML = function( elem ) {
	// documentElement is verified for cases where it doesn't yet exist
	// (such as loading iframes in IE - #4833)
	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
	return documentElement ? documentElement.nodeName !== "HTML" : false;
};

/**
 * Sets document-related variables once based on the current document
 * @param {Element|Object} [doc] An element or document object to use to set the document
 * @returns {Object} Returns the current document
 */
setDocument = Sizzle.setDocument = function( node ) {
	var doc = node ? node.ownerDocument || node : preferredDoc;

	// If no document and documentElement is available, return
	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
		return document;
	}

	// Set our document
	document = doc;
	docElem = doc.documentElement;

	// Support tests
	documentIsXML = isXML( doc );

	// Check if getElementsByTagName("*") returns only elements
	support.tagNameNoComments = assert(function( div ) {
		div.appendChild( doc.createComment("") );
		return !div.getElementsByTagName("*").length;
	});

	// Check if attributes should be retrieved by attribute nodes
	support.attributes = assert(function( div ) {
		div.innerHTML = "<select></select>";
		var type = typeof div.lastChild.getAttribute("multiple");
		// IE8 returns a string for some attributes even when not present
		return type !== "boolean" && type !== "string";
	});

	// Check if getElementsByClassName can be trusted
	support.getByClassName = assert(function( div ) {
		// Opera can't find a second classname (in 9.6)
		div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
		if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {
			return false;
		}

		// Safari 3.2 caches class attributes and doesn't catch changes
		div.lastChild.className = "e";
		return div.getElementsByClassName("e").length === 2;
	});

	// Check if getElementById returns elements by name
	// Check if getElementsByName privileges form controls or returns elements by ID
	support.getByName = assert(function( div ) {
		// Inject content
		div.id = expando + 0;
		div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";
		docElem.insertBefore( div, docElem.firstChild );

		// Test
		var pass = doc.getElementsByName &&
			// buggy browsers will return fewer than the correct 2
			doc.getElementsByName( expando ).length === 2 +
			// buggy browsers will return more than the correct 0
			doc.getElementsByName( expando + 0 ).length;
		support.getIdNotName = !doc.getElementById( expando );

		// Cleanup
		docElem.removeChild( div );

		return pass;
	});

	// IE6/7 return modified attributes
	Expr.attrHandle = assert(function( div ) {
		div.innerHTML = "<a href='#'></a>";
		return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
			div.firstChild.getAttribute("href") === "#";
	}) ?
		{} :
		{
			"href": function( elem ) {
				return elem.getAttribute( "href", 2 );
			},
			"type": function( elem ) {
				return elem.getAttribute("type");
			}
		};

	// ID find and filter
	if ( support.getIdNotName ) {
		Expr.find["ID"] = function( id, context ) {
			if ( typeof context.getElementById !== strundefined && !documentIsXML ) {
				var m = context.getElementById( id );
				// Check parentNode to catch when Blackberry 4.6 returns
				// nodes that are no longer in the document #6963
				return m && m.parentNode ? [m] : [];
			}
		};
		Expr.filter["ID"] = function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				return elem.getAttribute("id") === attrId;
			};
		};
	} else {
		Expr.find["ID"] = function( id, context ) {
			if ( typeof context.getElementById !== strundefined && !documentIsXML ) {
				var m = context.getElementById( id );

				return m ?
					m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
						[m] :
						undefined :
					[];
			}
		};
		Expr.filter["ID"] =  function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
				return node && node.value === attrId;
			};
		};
	}

	// Tag
	Expr.find["TAG"] = support.tagNameNoComments ?
		function( tag, context ) {
			if ( typeof context.getElementsByTagName !== strundefined ) {
				return context.getElementsByTagName( tag );
			}
		} :
		function( tag, context ) {
			var elem,
				tmp = [],
				i = 0,
				results = context.getElementsByTagName( tag );

			// Filter out possible comments
			if ( tag === "*" ) {
				while ( (elem = results[i++]) ) {
					if ( elem.nodeType === 1 ) {
						tmp.push( elem );
					}
				}

				return tmp;
			}
			return results;
		};

	// Name
	Expr.find["NAME"] = support.getByName && function( tag, context ) {
		if ( typeof context.getElementsByName !== strundefined ) {
			return context.getElementsByName( name );
		}
	};

	// Class
	Expr.find["CLASS"] = support.getByClassName && function( className, context ) {
		if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) {
			return context.getElementsByClassName( className );
		}
	};

	// QSA and matchesSelector support

	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
	rbuggyMatches = [];

	// qSa(:focus) reports false when true (Chrome 21),
	// no need to also add to buggyMatches since matches checks buggyQSA
	// A support test would require too much code (would include document ready)
	rbuggyQSA = [ ":focus" ];

	if ( (support.qsa = isNative(doc.querySelectorAll)) ) {
		// Build QSA regex
		// Regex strategy adopted from Diego Perini
		assert(function( div ) {
			// Select is set to empty string on purpose
			// This is to test IE's treatment of not explictly
			// setting a boolean content attribute,
			// since its presence should be enough
			// http://bugs.jquery.com/ticket/12359
			div.innerHTML = "<select><option selected=''></option></select>";

			// IE8 - Some boolean attributes are not treated correctly
			if ( !div.querySelectorAll("[selected]").length ) {
				rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
			}

			// Webkit/Opera - :checked should return selected option elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			// IE8 throws error here and will not see later tests
			if ( !div.querySelectorAll(":checked").length ) {
				rbuggyQSA.push(":checked");
			}
		});

		assert(function( div ) {

			// Opera 10-12/IE8 - ^= $= *= and empty values
			// Should not select anything
			div.innerHTML = "<input type='hidden' i=''/>";
			if ( div.querySelectorAll("[i^='']").length ) {
				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
			}

			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
			// IE8 throws error here and will not see later tests
			if ( !div.querySelectorAll(":enabled").length ) {
				rbuggyQSA.push( ":enabled", ":disabled" );
			}

			// Opera 10-11 does not throw on post-comma invalid pseudos
			div.querySelectorAll("*,:x");
			rbuggyQSA.push(",.*:");
		});
	}

	if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector ||
		docElem.mozMatchesSelector ||
		docElem.webkitMatchesSelector ||
		docElem.oMatchesSelector ||
		docElem.msMatchesSelector) )) ) {

		assert(function( div ) {
			// Check to see if it's possible to do matchesSelector
			// on a disconnected node (IE 9)
			support.disconnectedMatch = matches.call( div, "div" );

			// This should fail with an exception
			// Gecko does not error, returns false instead
			matches.call( div, "[s!='']:x" );
			rbuggyMatches.push( "!=", pseudos );
		});
	}

	rbuggyQSA = new RegExp( rbuggyQSA.join("|") );
	rbuggyMatches = new RegExp( rbuggyMatches.join("|") );

	// Element contains another
	// Purposefully does not implement inclusive descendent
	// As in, an element does not contain itself
	contains = isNative(docElem.contains) || docElem.compareDocumentPosition ?
		function( a, b ) {
			var adown = a.nodeType === 9 ? a.documentElement : a,
				bup = b && b.parentNode;
			return a === bup || !!( bup && bup.nodeType === 1 && (
				adown.contains ?
					adown.contains( bup ) :
					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
			));
		} :
		function( a, b ) {
			if ( b ) {
				while ( (b = b.parentNode) ) {
					if ( b === a ) {
						return true;
					}
				}
			}
			return false;
		};

	// Document order sorting
	sortOrder = docElem.compareDocumentPosition ?
	function( a, b ) {
		var compare;

		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) {
			if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) {
				if ( a === doc || contains( preferredDoc, a ) ) {
					return -1;
				}
				if ( b === doc || contains( preferredDoc, b ) ) {
					return 1;
				}
				return 0;
			}
			return compare & 4 ? -1 : 1;
		}

		return a.compareDocumentPosition ? -1 : 1;
	} :
	function( a, b ) {
		var cur,
			i = 0,
			aup = a.parentNode,
			bup = b.parentNode,
			ap = [ a ],
			bp = [ b ];

		// Exit early if the nodes are identical
		if ( a === b ) {
			hasDuplicate = true;
			return 0;

		// Parentless nodes are either documents or disconnected
		} else if ( !aup || !bup ) {
			return a === doc ? -1 :
				b === doc ? 1 :
				aup ? -1 :
				bup ? 1 :
				0;

		// If the nodes are siblings, we can do a quick check
		} else if ( aup === bup ) {
			return siblingCheck( a, b );
		}

		// Otherwise we need full lists of their ancestors for comparison
		cur = a;
		while ( (cur = cur.parentNode) ) {
			ap.unshift( cur );
		}
		cur = b;
		while ( (cur = cur.parentNode) ) {
			bp.unshift( cur );
		}

		// Walk down the tree looking for a discrepancy
		while ( ap[i] === bp[i] ) {
			i++;
		}

		return i ?
			// Do a sibling check if the nodes have a common ancestor
			siblingCheck( ap[i], bp[i] ) :

			// Otherwise nodes in our document sort first
			ap[i] === preferredDoc ? -1 :
			bp[i] === preferredDoc ? 1 :
			0;
	};

	// Always assume the presence of duplicates if sort doesn't
	// pass them to our comparison function (as in Google Chrome).
	hasDuplicate = false;
	[0, 0].sort( sortOrder );
	support.detectDuplicates = hasDuplicate;

	return document;
};

Sizzle.matches = function( expr, elements ) {
	return Sizzle( expr, null, null, elements );
};

Sizzle.matchesSelector = function( elem, expr ) {
	// Set document vars if needed
	if ( ( elem.ownerDocument || elem ) !== document ) {
		setDocument( elem );
	}

	// Make sure that attribute selectors are quoted
	expr = expr.replace( rattributeQuotes, "='$1']" );

	// rbuggyQSA always contains :focus, so no need for an existence check
	if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) {
		try {
			var ret = matches.call( elem, expr );

			// IE 9's matchesSelector returns false on disconnected nodes
			if ( ret || support.disconnectedMatch ||
					// As well, disconnected nodes are said to be in a document
					// fragment in IE 9
					elem.document && elem.document.nodeType !== 11 ) {
				return ret;
			}
		} catch(e) {}
	}

	return Sizzle( expr, document, null, [elem] ).length > 0;
};

Sizzle.contains = function( context, elem ) {
	// Set document vars if needed
	if ( ( context.ownerDocument || context ) !== document ) {
		setDocument( context );
	}
	return contains( context, elem );
};

Sizzle.attr = function( elem, name ) {
	var val;

	// Set document vars if needed
	if ( ( elem.ownerDocument || elem ) !== document ) {
		setDocument( elem );
	}

	if ( !documentIsXML ) {
		name = name.toLowerCase();
	}
	if ( (val = Expr.attrHandle[ name ]) ) {
		return val( elem );
	}
	if ( documentIsXML || support.attributes ) {
		return elem.getAttribute( name );
	}
	return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ?
		name :
		val && val.specified ? val.value : null;
};

Sizzle.error = function( msg ) {
	throw new Error( "Syntax error, unrecognized expression: " + msg );
};

// Document sorting and removing duplicates
Sizzle.uniqueSort = function( results ) {
	var elem,
		duplicates = [],
		i = 1,
		j = 0;

	// Unless we *know* we can detect duplicates, assume their presence
	hasDuplicate = !support.detectDuplicates;
	results.sort( sortOrder );

	if ( hasDuplicate ) {
		for ( ; (elem = results[i]); i++ ) {
			if ( elem === results[ i - 1 ] ) {
				j = duplicates.push( i );
			}
		}
		while ( j-- ) {
			results.splice( duplicates[ j ], 1 );
		}
	}

	return results;
};

function siblingCheck( a, b ) {
	var cur = b && a,
		diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE );

	// Use IE sourceIndex if available on both nodes
	if ( diff ) {
		return diff;
	}

	// Check if b follows a
	if ( cur ) {
		while ( (cur = cur.nextSibling) ) {
			if ( cur === b ) {
				return -1;
			}
		}
	}

	return a ? 1 : -1;
}

// Returns a function to use in pseudos for input types
function createInputPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return name === "input" && elem.type === type;
	};
}

// Returns a function to use in pseudos for buttons
function createButtonPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return (name === "input" || name === "button") && elem.type === type;
	};
}

// Returns a function to use in pseudos for positionals
function createPositionalPseudo( fn ) {
	return markFunction(function( argument ) {
		argument = +argument;
		return markFunction(function( seed, matches ) {
			var j,
				matchIndexes = fn( [], seed.length, argument ),
				i = matchIndexes.length;

			// Match elements found at the specified indexes
			while ( i-- ) {
				if ( seed[ (j = matchIndexes[i]) ] ) {
					seed[j] = !(matches[j] = seed[j]);
				}
			}
		});
	});
}

/**
 * Utility function for retrieving the text value of an array of DOM nodes
 * @param {Array|Element} elem
 */
getText = Sizzle.getText = function( elem ) {
	var node,
		ret = "",
		i = 0,
		nodeType = elem.nodeType;

	if ( !nodeType ) {
		// If no nodeType, this is expected to be an array
		for ( ; (node = elem[i]); i++ ) {
			// Do not traverse comment nodes
			ret += getText( node );
		}
	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
		// Use textContent for elements
		// innerText usage removed for consistency of new lines (see #11153)
		if ( typeof elem.textContent === "string" ) {
			return elem.textContent;
		} else {
			// Traverse its children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				ret += getText( elem );
			}
		}
	} else if ( nodeType === 3 || nodeType === 4 ) {
		return elem.nodeValue;
	}
	// Do not include comment or processing instruction nodes

	return ret;
};

Expr = Sizzle.selectors = {

	// Can be adjusted by the user
	cacheLength: 50,

	createPseudo: markFunction,

	match: matchExpr,

	find: {},

	relative: {
		">": { dir: "parentNode", first: true },
		" ": { dir: "parentNode" },
		"+": { dir: "previousSibling", first: true },
		"~": { dir: "previousSibling" }
	},

	preFilter: {
		"ATTR": function( match ) {
			match[1] = match[1].replace( runescape, funescape );

			// Move the given value to match[3] whether quoted or unquoted
			match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );

			if ( match[2] === "~=" ) {
				match[3] = " " + match[3] + " ";
			}

			return match.slice( 0, 4 );
		},

		"CHILD": function( match ) {
			/* matches from matchExpr["CHILD"]
				1 type (only|nth|...)
				2 what (child|of-type)
				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
				4 xn-component of xn+y argument ([+-]?\d*n|)
				5 sign of xn-component
				6 x of xn-component
				7 sign of y-component
				8 y of y-component
			*/
			match[1] = match[1].toLowerCase();

			if ( match[1].slice( 0, 3 ) === "nth" ) {
				// nth-* requires argument
				if ( !match[3] ) {
					Sizzle.error( match[0] );
				}

				// numeric x and y parameters for Expr.filter.CHILD
				// remember that false/true cast respectively to 0/1
				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );

			// other types prohibit arguments
			} else if ( match[3] ) {
				Sizzle.error( match[0] );
			}

			return match;
		},

		"PSEUDO": function( match ) {
			var excess,
				unquoted = !match[5] && match[2];

			if ( matchExpr["CHILD"].test( match[0] ) ) {
				return null;
			}

			// Accept quoted arguments as-is
			if ( match[4] ) {
				match[2] = match[4];

			// Strip excess characters from unquoted arguments
			} else if ( unquoted && rpseudo.test( unquoted ) &&
				// Get excess from tokenize (recursively)
				(excess = tokenize( unquoted, true )) &&
				// advance to the next closing parenthesis
				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {

				// excess is a negative index
				match[0] = match[0].slice( 0, excess );
				match[2] = unquoted.slice( 0, excess );
			}

			// Return only captures needed by the pseudo filter method (type and argument)
			return match.slice( 0, 3 );
		}
	},

	filter: {

		"TAG": function( nodeName ) {
			if ( nodeName === "*" ) {
				return function() { return true; };
			}

			nodeName = nodeName.replace( runescape, funescape ).toLowerCase();
			return function( elem ) {
				return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
			};
		},

		"CLASS": function( className ) {
			var pattern = classCache[ className + " " ];

			return pattern ||
				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
				classCache( className, function( elem ) {
					return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
				});
		},

		"ATTR": function( name, operator, check ) {
			return function( elem ) {
				var result = Sizzle.attr( elem, name );

				if ( result == null ) {
					return operator === "!=";
				}
				if ( !operator ) {
					return true;
				}

				result += "";

				return operator === "=" ? result === check :
					operator === "!=" ? result !== check :
					operator === "^=" ? check && result.indexOf( check ) === 0 :
					operator === "*=" ? check && result.indexOf( check ) > -1 :
					operator === "$=" ? check && result.slice( -check.length ) === check :
					operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
					false;
			};
		},

		"CHILD": function( type, what, argument, first, last ) {
			var simple = type.slice( 0, 3 ) !== "nth",
				forward = type.slice( -4 ) !== "last",
				ofType = what === "of-type";

			return first === 1 && last === 0 ?

				// Shortcut for :nth-*(n)
				function( elem ) {
					return !!elem.parentNode;
				} :

				function( elem, context, xml ) {
					var cache, outerCache, node, diff, nodeIndex, start,
						dir = simple !== forward ? "nextSibling" : "previousSibling",
						parent = elem.parentNode,
						name = ofType && elem.nodeName.toLowerCase(),
						useCache = !xml && !ofType;

					if ( parent ) {

						// :(first|last|only)-(child|of-type)
						if ( simple ) {
							while ( dir ) {
								node = elem;
								while ( (node = node[ dir ]) ) {
									if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
										return false;
									}
								}
								// Reverse direction for :only-* (if we haven't yet done so)
								start = dir = type === "only" && !start && "nextSibling";
							}
							return true;
						}

						start = [ forward ? parent.firstChild : parent.lastChild ];

						// non-xml :nth-child(...) stores cache data on `parent`
						if ( forward && useCache ) {
							// Seek `elem` from a previously-cached index
							outerCache = parent[ expando ] || (parent[ expando ] = {});
							cache = outerCache[ type ] || [];
							nodeIndex = cache[0] === dirruns && cache[1];
							diff = cache[0] === dirruns && cache[2];
							node = nodeIndex && parent.childNodes[ nodeIndex ];

							while ( (node = ++nodeIndex && node && node[ dir ] ||

								// Fallback to seeking `elem` from the start
								(diff = nodeIndex = 0) || start.pop()) ) {

								// When found, cache indexes on `parent` and break
								if ( node.nodeType === 1 && ++diff && node === elem ) {
									outerCache[ type ] = [ dirruns, nodeIndex, diff ];
									break;
								}
							}

						// Use previously-cached element index if available
						} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
							diff = cache[1];

						// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
						} else {
							// Use the same loop as above to seek `elem` from the start
							while ( (node = ++nodeIndex && node && node[ dir ] ||
								(diff = nodeIndex = 0) || start.pop()) ) {

								if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
									// Cache the index of each encountered element
									if ( useCache ) {
										(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
									}

									if ( node === elem ) {
										break;
									}
								}
							}
						}

						// Incorporate the offset, then check against cycle size
						diff -= last;
						return diff === first || ( diff % first === 0 && diff / first >= 0 );
					}
				};
		},

		"PSEUDO": function( pseudo, argument ) {
			// pseudo-class names are case-insensitive
			// http://www.w3.org/TR/selectors/#pseudo-classes
			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
			// Remember that setFilters inherits from pseudos
			var args,
				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
					Sizzle.error( "unsupported pseudo: " + pseudo );

			// The user may use createPseudo to indicate that
			// arguments are needed to create the filter function
			// just as Sizzle does
			if ( fn[ expando ] ) {
				return fn( argument );
			}

			// But maintain support for old signatures
			if ( fn.length > 1 ) {
				args = [ pseudo, pseudo, "", argument ];
				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
					markFunction(function( seed, matches ) {
						var idx,
							matched = fn( seed, argument ),
							i = matched.length;
						while ( i-- ) {
							idx = indexOf.call( seed, matched[i] );
							seed[ idx ] = !( matches[ idx ] = matched[i] );
						}
					}) :
					function( elem ) {
						return fn( elem, 0, args );
					};
			}

			return fn;
		}
	},

	pseudos: {
		// Potentially complex pseudos
		"not": markFunction(function( selector ) {
			// Trim the selector passed to compile
			// to avoid treating leading and trailing
			// spaces as combinators
			var input = [],
				results = [],
				matcher = compile( selector.replace( rtrim, "$1" ) );

			return matcher[ expando ] ?
				markFunction(function( seed, matches, context, xml ) {
					var elem,
						unmatched = matcher( seed, null, xml, [] ),
						i = seed.length;

					// Match elements unmatched by `matcher`
					while ( i-- ) {
						if ( (elem = unmatched[i]) ) {
							seed[i] = !(matches[i] = elem);
						}
					}
				}) :
				function( elem, context, xml ) {
					input[0] = elem;
					matcher( input, null, xml, results );
					return !results.pop();
				};
		}),

		"has": markFunction(function( selector ) {
			return function( elem ) {
				return Sizzle( selector, elem ).length > 0;
			};
		}),

		"contains": markFunction(function( text ) {
			return function( elem ) {
				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
			};
		}),

		// "Whether an element is represented by a :lang() selector
		// is based solely on the element's language value
		// being equal to the identifier C,
		// or beginning with the identifier C immediately followed by "-".
		// The matching of C against the element's language value is performed case-insensitively.
		// The identifier C does not have to be a valid language name."
		// http://www.w3.org/TR/selectors/#lang-pseudo
		"lang": markFunction( function( lang ) {
			// lang value must be a valid identifider
			if ( !ridentifier.test(lang || "") ) {
				Sizzle.error( "unsupported lang: " + lang );
			}
			lang = lang.replace( runescape, funescape ).toLowerCase();
			return function( elem ) {
				var elemLang;
				do {
					if ( (elemLang = documentIsXML ?
						elem.getAttribute("xml:lang") || elem.getAttribute("lang") :
						elem.lang) ) {

						elemLang = elemLang.toLowerCase();
						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
					}
				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
				return false;
			};
		}),

		// Miscellaneous
		"target": function( elem ) {
			var hash = window.location && window.location.hash;
			return hash && hash.slice( 1 ) === elem.id;
		},

		"root": function( elem ) {
			return elem === docElem;
		},

		"focus": function( elem ) {
			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
		},

		// Boolean properties
		"enabled": function( elem ) {
			return elem.disabled === false;
		},

		"disabled": function( elem ) {
			return elem.disabled === true;
		},

		"checked": function( elem ) {
			// In CSS3, :checked should return both checked and selected elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			var nodeName = elem.nodeName.toLowerCase();
			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
		},

		"selected": function( elem ) {
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			if ( elem.parentNode ) {
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		// Contents
		"empty": function( elem ) {
			// http://www.w3.org/TR/selectors/#empty-pseudo
			// :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
			//   not comment, processing instructions, or others
			// Thanks to Diego Perini for the nodeName shortcut
			//   Greater than "@" means alpha characters (specifically not starting with "#" or "?")
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) {
					return false;
				}
			}
			return true;
		},

		"parent": function( elem ) {
			return !Expr.pseudos["empty"]( elem );
		},

		// Element/input types
		"header": function( elem ) {
			return rheader.test( elem.nodeName );
		},

		"input": function( elem ) {
			return rinputs.test( elem.nodeName );
		},

		"button": function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return name === "input" && elem.type === "button" || name === "button";
		},

		"text": function( elem ) {
			var attr;
			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
			// use getAttribute instead to test this case
			return elem.nodeName.toLowerCase() === "input" &&
				elem.type === "text" &&
				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type );
		},

		// Position-in-collection
		"first": createPositionalPseudo(function() {
			return [ 0 ];
		}),

		"last": createPositionalPseudo(function( matchIndexes, length ) {
			return [ length - 1 ];
		}),

		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
			return [ argument < 0 ? argument + length : argument ];
		}),

		"even": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 0;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"odd": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 1;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; --i >= 0; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; ++i < length; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		})
	}
};

// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
	Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
	Expr.pseudos[ i ] = createButtonPseudo( i );
}

function tokenize( selector, parseOnly ) {
	var matched, match, tokens, type,
		soFar, groups, preFilters,
		cached = tokenCache[ selector + " " ];

	if ( cached ) {
		return parseOnly ? 0 : cached.slice( 0 );
	}

	soFar = selector;
	groups = [];
	preFilters = Expr.preFilter;

	while ( soFar ) {

		// Comma and first run
		if ( !matched || (match = rcomma.exec( soFar )) ) {
			if ( match ) {
				// Don't consume trailing commas as valid
				soFar = soFar.slice( match[0].length ) || soFar;
			}
			groups.push( tokens = [] );
		}

		matched = false;

		// Combinators
		if ( (match = rcombinators.exec( soFar )) ) {
			matched = match.shift();
			tokens.push( {
				value: matched,
				// Cast descendant combinators to space
				type: match[0].replace( rtrim, " " )
			} );
			soFar = soFar.slice( matched.length );
		}

		// Filters
		for ( type in Expr.filter ) {
			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
				(match = preFilters[ type ]( match ))) ) {
				matched = match.shift();
				tokens.push( {
					value: matched,
					type: type,
					matches: match
				} );
				soFar = soFar.slice( matched.length );
			}
		}

		if ( !matched ) {
			break;
		}
	}

	// Return the length of the invalid excess
	// if we're just parsing
	// Otherwise, throw an error or return tokens
	return parseOnly ?
		soFar.length :
		soFar ?
			Sizzle.error( selector ) :
			// Cache the tokens
			tokenCache( selector, groups ).slice( 0 );
}

function toSelector( tokens ) {
	var i = 0,
		len = tokens.length,
		selector = "";
	for ( ; i < len; i++ ) {
		selector += tokens[i].value;
	}
	return selector;
}

function addCombinator( matcher, combinator, base ) {
	var dir = combinator.dir,
		checkNonElements = base && dir === "parentNode",
		doneName = done++;

	return combinator.first ?
		// Check against closest ancestor/preceding element
		function( elem, context, xml ) {
			while ( (elem = elem[ dir ]) ) {
				if ( elem.nodeType === 1 || checkNonElements ) {
					return matcher( elem, context, xml );
				}
			}
		} :

		// Check against all ancestor/preceding elements
		function( elem, context, xml ) {
			var data, cache, outerCache,
				dirkey = dirruns + " " + doneName;

			// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
			if ( xml ) {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						if ( matcher( elem, context, xml ) ) {
							return true;
						}
					}
				}
			} else {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						outerCache = elem[ expando ] || (elem[ expando ] = {});
						if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
							if ( (data = cache[1]) === true || data === cachedruns ) {
								return data === true;
							}
						} else {
							cache = outerCache[ dir ] = [ dirkey ];
							cache[1] = matcher( elem, context, xml ) || cachedruns;
							if ( cache[1] === true ) {
								return true;
							}
						}
					}
				}
			}
		};
}

function elementMatcher( matchers ) {
	return matchers.length > 1 ?
		function( elem, context, xml ) {
			var i = matchers.length;
			while ( i-- ) {
				if ( !matchers[i]( elem, context, xml ) ) {
					return false;
				}
			}
			return true;
		} :
		matchers[0];
}

function condense( unmatched, map, filter, context, xml ) {
	var elem,
		newUnmatched = [],
		i = 0,
		len = unmatched.length,
		mapped = map != null;

	for ( ; i < len; i++ ) {
		if ( (elem = unmatched[i]) ) {
			if ( !filter || filter( elem, context, xml ) ) {
				newUnmatched.push( elem );
				if ( mapped ) {
					map.push( i );
				}
			}
		}
	}

	return newUnmatched;
}

function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
	if ( postFilter && !postFilter[ expando ] ) {
		postFilter = setMatcher( postFilter );
	}
	if ( postFinder && !postFinder[ expando ] ) {
		postFinder = setMatcher( postFinder, postSelector );
	}
	return markFunction(function( seed, results, context, xml ) {
		var temp, i, elem,
			preMap = [],
			postMap = [],
			preexisting = results.length,

			// Get initial elements from seed or context
			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),

			// Prefilter to get matcher input, preserving a map for seed-results synchronization
			matcherIn = preFilter && ( seed || !selector ) ?
				condense( elems, preMap, preFilter, context, xml ) :
				elems,

			matcherOut = matcher ?
				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?

					// ...intermediate processing is necessary
					[] :

					// ...otherwise use results directly
					results :
				matcherIn;

		// Find primary matches
		if ( matcher ) {
			matcher( matcherIn, matcherOut, context, xml );
		}

		// Apply postFilter
		if ( postFilter ) {
			temp = condense( matcherOut, postMap );
			postFilter( temp, [], context, xml );

			// Un-match failing elements by moving them back to matcherIn
			i = temp.length;
			while ( i-- ) {
				if ( (elem = temp[i]) ) {
					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
				}
			}
		}

		if ( seed ) {
			if ( postFinder || preFilter ) {
				if ( postFinder ) {
					// Get the final matcherOut by condensing this intermediate into postFinder contexts
					temp = [];
					i = matcherOut.length;
					while ( i-- ) {
						if ( (elem = matcherOut[i]) ) {
							// Restore matcherIn since elem is not yet a final match
							temp.push( (matcherIn[i] = elem) );
						}
					}
					postFinder( null, (matcherOut = []), temp, xml );
				}

				// Move matched elements from seed to results to keep them synchronized
				i = matcherOut.length;
				while ( i-- ) {
					if ( (elem = matcherOut[i]) &&
						(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {

						seed[temp] = !(results[temp] = elem);
					}
				}
			}

		// Add elements to results, through postFinder if defined
		} else {
			matcherOut = condense(
				matcherOut === results ?
					matcherOut.splice( preexisting, matcherOut.length ) :
					matcherOut
			);
			if ( postFinder ) {
				postFinder( null, results, matcherOut, xml );
			} else {
				push.apply( results, matcherOut );
			}
		}
	});
}

function matcherFromTokens( tokens ) {
	var checkContext, matcher, j,
		len = tokens.length,
		leadingRelative = Expr.relative[ tokens[0].type ],
		implicitRelative = leadingRelative || Expr.relative[" "],
		i = leadingRelative ? 1 : 0,

		// The foundational matcher ensures that elements are reachable from top-level context(s)
		matchContext = addCombinator( function( elem ) {
			return elem === checkContext;
		}, implicitRelative, true ),
		matchAnyContext = addCombinator( function( elem ) {
			return indexOf.call( checkContext, elem ) > -1;
		}, implicitRelative, true ),
		matchers = [ function( elem, context, xml ) {
			return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
				(checkContext = context).nodeType ?
					matchContext( elem, context, xml ) :
					matchAnyContext( elem, context, xml ) );
		} ];

	for ( ; i < len; i++ ) {
		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
		} else {
			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );

			// Return special upon seeing a positional matcher
			if ( matcher[ expando ] ) {
				// Find the next relative operator (if any) for proper handling
				j = ++i;
				for ( ; j < len; j++ ) {
					if ( Expr.relative[ tokens[j].type ] ) {
						break;
					}
				}
				return setMatcher(
					i > 1 && elementMatcher( matchers ),
					i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ),
					matcher,
					i < j && matcherFromTokens( tokens.slice( i, j ) ),
					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
					j < len && toSelector( tokens )
				);
			}
			matchers.push( matcher );
		}
	}

	return elementMatcher( matchers );
}

function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
	// A counter to specify which element is currently being matched
	var matcherCachedRuns = 0,
		bySet = setMatchers.length > 0,
		byElement = elementMatchers.length > 0,
		superMatcher = function( seed, context, xml, results, expandContext ) {
			var elem, j, matcher,
				setMatched = [],
				matchedCount = 0,
				i = "0",
				unmatched = seed && [],
				outermost = expandContext != null,
				contextBackup = outermostContext,
				// We must always have either seed elements or context
				elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
				// Use integer dirruns iff this is the outermost matcher
				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);

			if ( outermost ) {
				outermostContext = context !== document && context;
				cachedruns = matcherCachedRuns;
			}

			// Add elements passing elementMatchers directly to results
			// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
			for ( ; (elem = elems[i]) != null; i++ ) {
				if ( byElement && elem ) {
					j = 0;
					while ( (matcher = elementMatchers[j++]) ) {
						if ( matcher( elem, context, xml ) ) {
							results.push( elem );
							break;
						}
					}
					if ( outermost ) {
						dirruns = dirrunsUnique;
						cachedruns = ++matcherCachedRuns;
					}
				}

				// Track unmatched elements for set filters
				if ( bySet ) {
					// They will have gone through all possible matchers
					if ( (elem = !matcher && elem) ) {
						matchedCount--;
					}

					// Lengthen the array for every element, matched or not
					if ( seed ) {
						unmatched.push( elem );
					}
				}
			}

			// Apply set filters to unmatched elements
			matchedCount += i;
			if ( bySet && i !== matchedCount ) {
				j = 0;
				while ( (matcher = setMatchers[j++]) ) {
					matcher( unmatched, setMatched, context, xml );
				}

				if ( seed ) {
					// Reintegrate element matches to eliminate the need for sorting
					if ( matchedCount > 0 ) {
						while ( i-- ) {
							if ( !(unmatched[i] || setMatched[i]) ) {
								setMatched[i] = pop.call( results );
							}
						}
					}

					// Discard index placeholder values to get only actual matches
					setMatched = condense( setMatched );
				}

				// Add matches to results
				push.apply( results, setMatched );

				// Seedless set matches succeeding multiple successful matchers stipulate sorting
				if ( outermost && !seed && setMatched.length > 0 &&
					( matchedCount + setMatchers.length ) > 1 ) {

					Sizzle.uniqueSort( results );
				}
			}

			// Override manipulation of globals by nested matchers
			if ( outermost ) {
				dirruns = dirrunsUnique;
				outermostContext = contextBackup;
			}

			return unmatched;
		};

	return bySet ?
		markFunction( superMatcher ) :
		superMatcher;
}

compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
	var i,
		setMatchers = [],
		elementMatchers = [],
		cached = compilerCache[ selector + " " ];

	if ( !cached ) {
		// Generate a function of recursive functions that can be used to check each element
		if ( !group ) {
			group = tokenize( selector );
		}
		i = group.length;
		while ( i-- ) {
			cached = matcherFromTokens( group[i] );
			if ( cached[ expando ] ) {
				setMatchers.push( cached );
			} else {
				elementMatchers.push( cached );
			}
		}

		// Cache the compiled function
		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
	}
	return cached;
};

function multipleContexts( selector, contexts, results ) {
	var i = 0,
		len = contexts.length;
	for ( ; i < len; i++ ) {
		Sizzle( selector, contexts[i], results );
	}
	return results;
}

function select( selector, context, results, seed ) {
	var i, tokens, token, type, find,
		match = tokenize( selector );

	if ( !seed ) {
		// Try to minimize operations if there is only one group
		if ( match.length === 1 ) {

			// Take a shortcut and set the context if the root selector is an ID
			tokens = match[0] = match[0].slice( 0 );
			if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
					context.nodeType === 9 && !documentIsXML &&
					Expr.relative[ tokens[1].type ] ) {

				context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0];
				if ( !context ) {
					return results;
				}

				selector = selector.slice( tokens.shift().value.length );
			}

			// Fetch a seed set for right-to-left matching
			i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
			while ( i-- ) {
				token = tokens[i];

				// Abort if we hit a combinator
				if ( Expr.relative[ (type = token.type) ] ) {
					break;
				}
				if ( (find = Expr.find[ type ]) ) {
					// Search, expanding context for leading sibling combinators
					if ( (seed = find(
						token.matches[0].replace( runescape, funescape ),
						rsibling.test( tokens[0].type ) && context.parentNode || context
					)) ) {

						// If seed is empty or no tokens remain, we can return early
						tokens.splice( i, 1 );
						selector = seed.length && toSelector( tokens );
						if ( !selector ) {
							push.apply( results, slice.call( seed, 0 ) );
							return results;
						}

						break;
					}
				}
			}
		}
	}

	// Compile and execute a filtering function
	// Provide `match` to avoid retokenization if we modified the selector above
	compile( selector, match )(
		seed,
		context,
		documentIsXML,
		results,
		rsibling.test( selector )
	);
	return results;
}

// Deprecated
Expr.pseudos["nth"] = Expr.pseudos["eq"];

// Easy API for creating new setFilters
function setFilters() {}
Expr.filters = setFilters.prototype = Expr.pseudos;
Expr.setFilters = new setFilters();

// Initialize with the default document
setDocument();

// Override sizzle attribute retrieval
Sizzle.attr = jQuery.attr;
jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.pseudos;
jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;


})( window );
var runtil = /Until$/,
	rparentsprev = /^(?:parents|prev(?:Until|All))/,
	isSimple = /^.[^:#\[\.,]*$/,
	rneedsContext = jQuery.expr.match.needsContext,
	// methods guaranteed to produce a unique set when starting from a unique set
	guaranteedUnique = {
		children: true,
		contents: true,
		next: true,
		prev: true
	};

jQuery.fn.extend({
	find: function( selector ) {
		var i, ret, self,
			len = this.length;

		if ( typeof selector !== "string" ) {
			self = this;
			return this.pushStack( jQuery( selector ).filter(function() {
				for ( i = 0; i < len; i++ ) {
					if ( jQuery.contains( self[ i ], this ) ) {
						return true;
					}
				}
			}) );
		}

		ret = [];
		for ( i = 0; i < len; i++ ) {
			jQuery.find( selector, this[ i ], ret );
		}

		// Needed because $( selector, context ) becomes $( context ).find( selector )
		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
		ret.selector = ( this.selector ? this.selector + " " : "" ) + selector;
		return ret;
	},

	has: function( target ) {
		var i,
			targets = jQuery( target, this ),
			len = targets.length;

		return this.filter(function() {
			for ( i = 0; i < len; i++ ) {
				if ( jQuery.contains( this, targets[i] ) ) {
					return true;
				}
			}
		});
	},

	not: function( selector ) {
		return this.pushStack( winnow(this, selector, false) );
	},

	filter: function( selector ) {
		return this.pushStack( winnow(this, selector, true) );
	},

	is: function( selector ) {
		return !!selector && (
			typeof selector === "string" ?
				// If this is a positional/relative selector, check membership in the returned set
				// so $("p:first").is("p:last") won't return true for a doc with two "p".
				rneedsContext.test( selector ) ?
					jQuery( selector, this.context ).index( this[0] ) >= 0 :
					jQuery.filter( selector, this ).length > 0 :
				this.filter( selector ).length > 0 );
	},

	closest: function( selectors, context ) {
		var cur,
			i = 0,
			l = this.length,
			ret = [],
			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
				jQuery( selectors, context || this.context ) :
				0;

		for ( ; i < l; i++ ) {
			cur = this[i];

			while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
				if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
					ret.push( cur );
					break;
				}
				cur = cur.parentNode;
			}
		}

		return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret );
	},

	// Determine the position of an element within
	// the matched set of elements
	index: function( elem ) {

		// No argument, return index in parent
		if ( !elem ) {
			return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;
		}

		// index in selector
		if ( typeof elem === "string" ) {
			return jQuery.inArray( this[0], jQuery( elem ) );
		}

		// Locate the position of the desired element
		return jQuery.inArray(
			// If it receives a jQuery object, the first element is used
			elem.jquery ? elem[0] : elem, this );
	},

	add: function( selector, context ) {
		var set = typeof selector === "string" ?
				jQuery( selector, context ) :
				jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
			all = jQuery.merge( this.get(), set );

		return this.pushStack( jQuery.unique(all) );
	},

	addBack: function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter(selector)
		);
	}
});

jQuery.fn.andSelf = jQuery.fn.addBack;

function sibling( cur, dir ) {
	do {
		cur = cur[ dir ];
	} while ( cur && cur.nodeType !== 1 );

	return cur;
}

jQuery.each({
	parent: function( elem ) {
		var parent = elem.parentNode;
		return parent && parent.nodeType !== 11 ? parent : null;
	},
	parents: function( elem ) {
		return jQuery.dir( elem, "parentNode" );
	},
	parentsUntil: function( elem, i, until ) {
		return jQuery.dir( elem, "parentNode", until );
	},
	next: function( elem ) {
		return sibling( elem, "nextSibling" );
	},
	prev: function( elem ) {
		return sibling( elem, "previousSibling" );
	},
	nextAll: function( elem ) {
		return jQuery.dir( elem, "nextSibling" );
	},
	prevAll: function( elem ) {
		return jQuery.dir( elem, "previousSibling" );
	},
	nextUntil: function( elem, i, until ) {
		return jQuery.dir( elem, "nextSibling", until );
	},
	prevUntil: function( elem, i, until ) {
		return jQuery.dir( elem, "previousSibling", until );
	},
	siblings: function( elem ) {
		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
	},
	children: function( elem ) {
		return jQuery.sibling( elem.firstChild );
	},
	contents: function( elem ) {
		return jQuery.nodeName( elem, "iframe" ) ?
			elem.contentDocument || elem.contentWindow.document :
			jQuery.merge( [], elem.childNodes );
	}
}, function( name, fn ) {
	jQuery.fn[ name ] = function( until, selector ) {
		var ret = jQuery.map( this, fn, until );

		if ( !runtil.test( name ) ) {
			selector = until;
		}

		if ( selector && typeof selector === "string" ) {
			ret = jQuery.filter( selector, ret );
		}

		ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;

		if ( this.length > 1 && rparentsprev.test( name ) ) {
			ret = ret.reverse();
		}

		return this.pushStack( ret );
	};
});

jQuery.extend({
	filter: function( expr, elems, not ) {
		if ( not ) {
			expr = ":not(" + expr + ")";
		}

		return elems.length === 1 ?
			jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
			jQuery.find.matches(expr, elems);
	},

	dir: function( elem, dir, until ) {
		var matched = [],
			cur = elem[ dir ];

		while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
			if ( cur.nodeType === 1 ) {
				matched.push( cur );
			}
			cur = cur[dir];
		}
		return matched;
	},

	sibling: function( n, elem ) {
		var r = [];

		for ( ; n; n = n.nextSibling ) {
			if ( n.nodeType === 1 && n !== elem ) {
				r.push( n );
			}
		}

		return r;
	}
});

// Implement the identical functionality for filter and not
function winnow( elements, qualifier, keep ) {

	// Can't pass null or undefined to indexOf in Firefox 4
	// Set to 0 to skip string check
	qualifier = qualifier || 0;

	if ( jQuery.isFunction( qualifier ) ) {
		return jQuery.grep(elements, function( elem, i ) {
			var retVal = !!qualifier.call( elem, i, elem );
			return retVal === keep;
		});

	} else if ( qualifier.nodeType ) {
		return jQuery.grep(elements, function( elem ) {
			return ( elem === qualifier ) === keep;
		});

	} else if ( typeof qualifier === "string" ) {
		var filtered = jQuery.grep(elements, function( elem ) {
			return elem.nodeType === 1;
		});

		if ( isSimple.test( qualifier ) ) {
			return jQuery.filter(qualifier, filtered, !keep);
		} else {
			qualifier = jQuery.filter( qualifier, filtered );
		}
	}

	return jQuery.grep(elements, function( elem ) {
		return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
	});
}
function createSafeFragment( document ) {
	var list = nodeNames.split( "|" ),
		safeFrag = document.createDocumentFragment();

	if ( safeFrag.createElement ) {
		while ( list.length ) {
			safeFrag.createElement(
				list.pop()
			);
		}
	}
	return safeFrag;
}

var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
		"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
	rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
	rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
	rleadingWhitespace = /^\s+/,
	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
	rtagName = /<([\w:]+)/,
	rtbody = /<tbody/i,
	rhtml = /<|&#?\w+;/,
	rnoInnerhtml = /<(?:script|style|link)/i,
	manipulation_rcheckableType = /^(?:checkbox|radio)$/i,
	// checked="checked" or checked
	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
	rscriptType = /^$|\/(?:java|ecma)script/i,
	rscriptTypeMasked = /^true\/(.*)/,
	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,

	// We have to close these tags to support XHTML (#13200)
	wrapMap = {
		option: [ 1, "<select multiple='multiple'>", "</select>" ],
		legend: [ 1, "<fieldset>", "</fieldset>" ],
		area: [ 1, "<map>", "</map>" ],
		param: [ 1, "<object>", "</object>" ],
		thead: [ 1, "<table>", "</table>" ],
		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],

		// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
		// unless wrapped in a div with non-breaking characters in front of it.
		_default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>"  ]
	},
	safeFragment = createSafeFragment( document ),
	fragmentDiv = safeFragment.appendChild( document.createElement("div") );

wrapMap.optgroup = wrapMap.option;
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;

jQuery.fn.extend({
	text: function( value ) {
		return jQuery.access( this, function( value ) {
			return value === undefined ?
				jQuery.text( this ) :
				this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
		}, null, value, arguments.length );
	},

	wrapAll: function( html ) {
		if ( jQuery.isFunction( html ) ) {
			return this.each(function(i) {
				jQuery(this).wrapAll( html.call(this, i) );
			});
		}

		if ( this[0] ) {
			// The elements to wrap the target around
			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);

			if ( this[0].parentNode ) {
				wrap.insertBefore( this[0] );
			}

			wrap.map(function() {
				var elem = this;

				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
					elem = elem.firstChild;
				}

				return elem;
			}).append( this );
		}

		return this;
	},

	wrapInner: function( html ) {
		if ( jQuery.isFunction( html ) ) {
			return this.each(function(i) {
				jQuery(this).wrapInner( html.call(this, i) );
			});
		}

		return this.each(function() {
			var self = jQuery( this ),
				contents = self.contents();

			if ( contents.length ) {
				contents.wrapAll( html );

			} else {
				self.append( html );
			}
		});
	},

	wrap: function( html ) {
		var isFunction = jQuery.isFunction( html );

		return this.each(function(i) {
			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
		});
	},

	unwrap: function() {
		return this.parent().each(function() {
			if ( !jQuery.nodeName( this, "body" ) ) {
				jQuery( this ).replaceWith( this.childNodes );
			}
		}).end();
	},

	append: function() {
		return this.domManip(arguments, true, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				this.appendChild( elem );
			}
		});
	},

	prepend: function() {
		return this.domManip(arguments, true, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				this.insertBefore( elem, this.firstChild );
			}
		});
	},

	before: function() {
		return this.domManip( arguments, false, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this );
			}
		});
	},

	after: function() {
		return this.domManip( arguments, false, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this.nextSibling );
			}
		});
	},

	// keepData is for internal use only--do not document
	remove: function( selector, keepData ) {
		var elem,
			i = 0;

		for ( ; (elem = this[i]) != null; i++ ) {
			if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) {
				if ( !keepData && elem.nodeType === 1 ) {
					jQuery.cleanData( getAll( elem ) );
				}

				if ( elem.parentNode ) {
					if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
						setGlobalEval( getAll( elem, "script" ) );
					}
					elem.parentNode.removeChild( elem );
				}
			}
		}

		return this;
	},

	empty: function() {
		var elem,
			i = 0;

		for ( ; (elem = this[i]) != null; i++ ) {
			// Remove element nodes and prevent memory leaks
			if ( elem.nodeType === 1 ) {
				jQuery.cleanData( getAll( elem, false ) );
			}

			// Remove any remaining nodes
			while ( elem.firstChild ) {
				elem.removeChild( elem.firstChild );
			}

			// If this is a select, ensure that it displays empty (#12336)
			// Support: IE<9
			if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
				elem.options.length = 0;
			}
		}

		return this;
	},

	clone: function( dataAndEvents, deepDataAndEvents ) {
		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

		return this.map( function () {
			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
		});
	},

	html: function( value ) {
		return jQuery.access( this, function( value ) {
			var elem = this[0] || {},
				i = 0,
				l = this.length;

			if ( value === undefined ) {
				return elem.nodeType === 1 ?
					elem.innerHTML.replace( rinlinejQuery, "" ) :
					undefined;
			}

			// See if we can take a shortcut and just use innerHTML
			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
				( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&
				( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
				!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {

				value = value.replace( rxhtmlTag, "<$1></$2>" );

				try {
					for (; i < l; i++ ) {
						// Remove element nodes and prevent memory leaks
						elem = this[i] || {};
						if ( elem.nodeType === 1 ) {
							jQuery.cleanData( getAll( elem, false ) );
							elem.innerHTML = value;
						}
					}

					elem = 0;

				// If using innerHTML throws an exception, use the fallback method
				} catch(e) {}
			}

			if ( elem ) {
				this.empty().append( value );
			}
		}, null, value, arguments.length );
	},

	replaceWith: function( value ) {
		var isFunc = jQuery.isFunction( value );

		// Make sure that the elements are removed from the DOM before they are inserted
		// this can help fix replacing a parent with child elements
		if ( !isFunc && typeof value !== "string" ) {
			value = jQuery( value ).not( this ).detach();
		}

		return this.domManip( [ value ], true, function( elem ) {
			var next = this.nextSibling,
				parent = this.parentNode;

			if ( parent ) {
				jQuery( this ).remove();
				parent.insertBefore( elem, next );
			}
		});
	},

	detach: function( selector ) {
		return this.remove( selector, true );
	},

	domManip: function( args, table, callback ) {

		// Flatten any nested arrays
		args = core_concat.apply( [], args );

		var first, node, hasScripts,
			scripts, doc, fragment,
			i = 0,
			l = this.length,
			set = this,
			iNoClone = l - 1,
			value = args[0],
			isFunction = jQuery.isFunction( value );

		// We can't cloneNode fragments that contain checked, in WebKit
		if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {
			return this.each(function( index ) {
				var self = set.eq( index );
				if ( isFunction ) {
					args[0] = value.call( this, index, table ? self.html() : undefined );
				}
				self.domManip( args, table, callback );
			});
		}

		if ( l ) {
			fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
			first = fragment.firstChild;

			if ( fragment.childNodes.length === 1 ) {
				fragment = first;
			}

			if ( first ) {
				table = table && jQuery.nodeName( first, "tr" );
				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
				hasScripts = scripts.length;

				// Use the original fragment for the last item instead of the first because it can end up
				// being emptied incorrectly in certain situations (#8070).
				for ( ; i < l; i++ ) {
					node = fragment;

					if ( i !== iNoClone ) {
						node = jQuery.clone( node, true, true );

						// Keep references to cloned scripts for later restoration
						if ( hasScripts ) {
							jQuery.merge( scripts, getAll( node, "script" ) );
						}
					}

					callback.call(
						table && jQuery.nodeName( this[i], "table" ) ?
							findOrAppend( this[i], "tbody" ) :
							this[i],
						node,
						i
					);
				}

				if ( hasScripts ) {
					doc = scripts[ scripts.length - 1 ].ownerDocument;

					// Reenable scripts
					jQuery.map( scripts, restoreScript );

					// Evaluate executable scripts on first document insertion
					for ( i = 0; i < hasScripts; i++ ) {
						node = scripts[ i ];
						if ( rscriptType.test( node.type || "" ) &&
							!jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {

							if ( node.src ) {
								// Hope ajax is available...
								jQuery.ajax({
									url: node.src,
									type: "GET",
									dataType: "script",
									async: false,
									global: false,
									"throws": true
								});
							} else {
								jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
							}
						}
					}
				}

				// Fix #11809: Avoid leaking memory
				fragment = first = null;
			}
		}

		return this;
	}
});

function findOrAppend( elem, tag ) {
	return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
}

// Replace/restore the type attribute of script elements for safe DOM manipulation
function disableScript( elem ) {
	var attr = elem.getAttributeNode("type");
	elem.type = ( attr && attr.specified ) + "/" + elem.type;
	return elem;
}
function restoreScript( elem ) {
	var match = rscriptTypeMasked.exec( elem.type );
	if ( match ) {
		elem.type = match[1];
	} else {
		elem.removeAttribute("type");
	}
	return elem;
}

// Mark scripts as having already been evaluated
function setGlobalEval( elems, refElements ) {
	var elem,
		i = 0;
	for ( ; (elem = elems[i]) != null; i++ ) {
		jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) );
	}
}

function cloneCopyEvent( src, dest ) {

	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
		return;
	}

	var type, i, l,
		oldData = jQuery._data( src ),
		curData = jQuery._data( dest, oldData ),
		events = oldData.events;

	if ( events ) {
		delete curData.handle;
		curData.events = {};

		for ( type in events ) {
			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
				jQuery.event.add( dest, type, events[ type ][ i ] );
			}
		}
	}

	// make the cloned public data object a copy from the original
	if ( curData.data ) {
		curData.data = jQuery.extend( {}, curData.data );
	}
}

function fixCloneNodeIssues( src, dest ) {
	var nodeName, e, data;

	// We do not need to do anything for non-Elements
	if ( dest.nodeType !== 1 ) {
		return;
	}

	nodeName = dest.nodeName.toLowerCase();

	// IE6-8 copies events bound via attachEvent when using cloneNode.
	if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {
		data = jQuery._data( dest );

		for ( e in data.events ) {
			jQuery.removeEvent( dest, e, data.handle );
		}

		// Event data gets referenced instead of copied if the expando gets copied too
		dest.removeAttribute( jQuery.expando );
	}

	// IE blanks contents when cloning scripts, and tries to evaluate newly-set text
	if ( nodeName === "script" && dest.text !== src.text ) {
		disableScript( dest ).text = src.text;
		restoreScript( dest );

	// IE6-10 improperly clones children of object elements using classid.
	// IE10 throws NoModificationAllowedError if parent is null, #12132.
	} else if ( nodeName === "object" ) {
		if ( dest.parentNode ) {
			dest.outerHTML = src.outerHTML;
		}

		// This path appears unavoidable for IE9. When cloning an object
		// element in IE9, the outerHTML strategy above is not sufficient.
		// If the src has innerHTML and the destination does not,
		// copy the src.innerHTML into the dest.innerHTML. #10324
		if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {
			dest.innerHTML = src.innerHTML;
		}

	} else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) {
		// IE6-8 fails to persist the checked state of a cloned checkbox
		// or radio button. Worse, IE6-7 fail to give the cloned element
		// a checked appearance if the defaultChecked value isn't also set

		dest.defaultChecked = dest.checked = src.checked;

		// IE6-7 get confused and end up setting the value of a cloned
		// checkbox/radio button to an empty string instead of "on"
		if ( dest.value !== src.value ) {
			dest.value = src.value;
		}

	// IE6-8 fails to return the selected option to the default selected
	// state when cloning options
	} else if ( nodeName === "option" ) {
		dest.defaultSelected = dest.selected = src.defaultSelected;

	// IE6-8 fails to set the defaultValue to the correct value when
	// cloning other types of input fields
	} else if ( nodeName === "input" || nodeName === "textarea" ) {
		dest.defaultValue = src.defaultValue;
	}
}

jQuery.each({
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after",
	replaceAll: "replaceWith"
}, function( name, original ) {
	jQuery.fn[ name ] = function( selector ) {
		var elems,
			i = 0,
			ret = [],
			insert = jQuery( selector ),
			last = insert.length - 1;

		for ( ; i <= last; i++ ) {
			elems = i === last ? this : this.clone(true);
			jQuery( insert[i] )[ original ]( elems );

			// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
			core_push.apply( ret, elems.get() );
		}

		return this.pushStack( ret );
	};
});

function getAll( context, tag ) {
	var elems, elem,
		i = 0,
		found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) :
			typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) :
			undefined;

	if ( !found ) {
		for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {
			if ( !tag || jQuery.nodeName( elem, tag ) ) {
				found.push( elem );
			} else {
				jQuery.merge( found, getAll( elem, tag ) );
			}
		}
	}

	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
		jQuery.merge( [ context ], found ) :
		found;
}

// Used in buildFragment, fixes the defaultChecked property
function fixDefaultChecked( elem ) {
	if ( manipulation_rcheckableType.test( elem.type ) ) {
		elem.defaultChecked = elem.checked;
	}
}

jQuery.extend({
	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
		var destElements, node, clone, i, srcElements,
			inPage = jQuery.contains( elem.ownerDocument, elem );

		if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
			clone = elem.cloneNode( true );

		// IE<=8 does not properly clone detached, unknown element nodes
		} else {
			fragmentDiv.innerHTML = elem.outerHTML;
			fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
		}

		if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {

			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
			destElements = getAll( clone );
			srcElements = getAll( elem );

			// Fix all IE cloning issues
			for ( i = 0; (node = srcElements[i]) != null; ++i ) {
				// Ensure that the destination node is not null; Fixes #9587
				if ( destElements[i] ) {
					fixCloneNodeIssues( node, destElements[i] );
				}
			}
		}

		// Copy the events from the original to the clone
		if ( dataAndEvents ) {
			if ( deepDataAndEvents ) {
				srcElements = srcElements || getAll( elem );
				destElements = destElements || getAll( clone );

				for ( i = 0; (node = srcElements[i]) != null; i++ ) {
					cloneCopyEvent( node, destElements[i] );
				}
			} else {
				cloneCopyEvent( elem, clone );
			}
		}

		// Preserve script evaluation history
		destElements = getAll( clone, "script" );
		if ( destElements.length > 0 ) {
			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
		}

		destElements = srcElements = node = null;

		// Return the cloned set
		return clone;
	},

	buildFragment: function( elems, context, scripts, selection ) {
		var j, elem, contains,
			tmp, tag, tbody, wrap,
			l = elems.length,

			// Ensure a safe fragment
			safe = createSafeFragment( context ),

			nodes = [],
			i = 0;

		for ( ; i < l; i++ ) {
			elem = elems[ i ];

			if ( elem || elem === 0 ) {

				// Add nodes directly
				if ( jQuery.type( elem ) === "object" ) {
					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );

				// Convert non-html into a text node
				} else if ( !rhtml.test( elem ) ) {
					nodes.push( context.createTextNode( elem ) );

				// Convert html into DOM nodes
				} else {
					tmp = tmp || safe.appendChild( context.createElement("div") );

					// Deserialize a standard representation
					tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
					wrap = wrapMap[ tag ] || wrapMap._default;

					tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2];

					// Descend through wrappers to the right content
					j = wrap[0];
					while ( j-- ) {
						tmp = tmp.lastChild;
					}

					// Manually add leading whitespace removed by IE
					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
						nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );
					}

					// Remove IE's autoinserted <tbody> from table fragments
					if ( !jQuery.support.tbody ) {

						// String was a <table>, *may* have spurious <tbody>
						elem = tag === "table" && !rtbody.test( elem ) ?
							tmp.firstChild :

							// String was a bare <thead> or <tfoot>
							wrap[1] === "<table>" && !rtbody.test( elem ) ?
								tmp :
								0;

						j = elem && elem.childNodes.length;
						while ( j-- ) {
							if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) {
								elem.removeChild( tbody );
							}
						}
					}

					jQuery.merge( nodes, tmp.childNodes );

					// Fix #12392 for WebKit and IE > 9
					tmp.textContent = "";

					// Fix #12392 for oldIE
					while ( tmp.firstChild ) {
						tmp.removeChild( tmp.firstChild );
					}

					// Remember the top-level container for proper cleanup
					tmp = safe.lastChild;
				}
			}
		}

		// Fix #11356: Clear elements from fragment
		if ( tmp ) {
			safe.removeChild( tmp );
		}

		// Reset defaultChecked for any radios and checkboxes
		// about to be appended to the DOM in IE 6/7 (#8060)
		if ( !jQuery.support.appendChecked ) {
			jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
		}

		i = 0;
		while ( (elem = nodes[ i++ ]) ) {

			// #4087 - If origin and destination elements are the same, and this is
			// that element, do not do anything
			if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
				continue;
			}

			contains = jQuery.contains( elem.ownerDocument, elem );

			// Append to fragment
			tmp = getAll( safe.appendChild( elem ), "script" );

			// Preserve script evaluation history
			if ( contains ) {
				setGlobalEval( tmp );
			}

			// Capture executables
			if ( scripts ) {
				j = 0;
				while ( (elem = tmp[ j++ ]) ) {
					if ( rscriptType.test( elem.type || "" ) ) {
						scripts.push( elem );
					}
				}
			}
		}

		tmp = null;

		return safe;
	},

	cleanData: function( elems, /* internal */ acceptData ) {
		var elem, type, id, data,
			i = 0,
			internalKey = jQuery.expando,
			cache = jQuery.cache,
			deleteExpando = jQuery.support.deleteExpando,
			special = jQuery.event.special;

		for ( ; (elem = elems[i]) != null; i++ ) {

			if ( acceptData || jQuery.acceptData( elem ) ) {

				id = elem[ internalKey ];
				data = id && cache[ id ];

				if ( data ) {
					if ( data.events ) {
						for ( type in data.events ) {
							if ( special[ type ] ) {
								jQuery.event.remove( elem, type );

							// This is a shortcut to avoid jQuery.event.remove's overhead
							} else {
								jQuery.removeEvent( elem, type, data.handle );
							}
						}
					}

					// Remove cache only if it was not already removed by jQuery.event.remove
					if ( cache[ id ] ) {

						delete cache[ id ];

						// IE does not allow us to delete expando properties from nodes,
						// nor does it have a removeAttribute function on Document nodes;
						// we must handle all of these cases
						if ( deleteExpando ) {
							delete elem[ internalKey ];

						} else if ( typeof elem.removeAttribute !== core_strundefined ) {
							elem.removeAttribute( internalKey );

						} else {
							elem[ internalKey ] = null;
						}

						core_deletedIds.push( id );
					}
				}
			}
		}
	}
});
var iframe, getStyles, curCSS,
	ralpha = /alpha\([^)]*\)/i,
	ropacity = /opacity\s*=\s*([^)]*)/,
	rposition = /^(top|right|bottom|left)$/,
	// swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
	// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
	rmargin = /^margin/,
	rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
	rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
	rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ),
	elemdisplay = { BODY: "block" },

	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
	cssNormalTransform = {
		letterSpacing: 0,
		fontWeight: 400
	},

	cssExpand = [ "Top", "Right", "Bottom", "Left" ],
	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];

// return a css property mapped to a potentially vendor prefixed property
function vendorPropName( style, name ) {

	// shortcut for names that are not vendor prefixed
	if ( name in style ) {
		return name;
	}

	// check for vendor prefixed names
	var capName = name.charAt(0).toUpperCase() + name.slice(1),
		origName = name,
		i = cssPrefixes.length;

	while ( i-- ) {
		name = cssPrefixes[ i ] + capName;
		if ( name in style ) {
			return name;
		}
	}

	return origName;
}

function isHidden( elem, el ) {
	// isHidden might be called from jQuery#filter function;
	// in that case, element will be second argument
	elem = el || elem;
	return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
}

function showHide( elements, show ) {
	var display, elem, hidden,
		values = [],
		index = 0,
		length = elements.length;

	for ( ; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}

		values[ index ] = jQuery._data( elem, "olddisplay" );
		display = elem.style.display;
		if ( show ) {
			// Reset the inline display of this element to learn if it is
			// being hidden by cascaded rules or not
			if ( !values[ index ] && display === "none" ) {
				elem.style.display = "";
			}

			// Set elements which have been overridden with display: none
			// in a stylesheet to whatever the default browser style is
			// for such an element
			if ( elem.style.display === "" && isHidden( elem ) ) {
				values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
			}
		} else {

			if ( !values[ index ] ) {
				hidden = isHidden( elem );

				if ( display && display !== "none" || !hidden ) {
					jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
				}
			}
		}
	}

	// Set the display of most of the elements in a second loop
	// to avoid the constant reflow
	for ( index = 0; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}
		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
			elem.style.display = show ? values[ index ] || "" : "none";
		}
	}

	return elements;
}

jQuery.fn.extend({
	css: function( name, value ) {
		return jQuery.access( this, function( elem, name, value ) {
			var len, styles,
				map = {},
				i = 0;

			if ( jQuery.isArray( name ) ) {
				styles = getStyles( elem );
				len = name.length;

				for ( ; i < len; i++ ) {
					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
				}

				return map;
			}

			return value !== undefined ?
				jQuery.style( elem, name, value ) :
				jQuery.css( elem, name );
		}, name, value, arguments.length > 1 );
	},
	show: function() {
		return showHide( this, true );
	},
	hide: function() {
		return showHide( this );
	},
	toggle: function( state ) {
		var bool = typeof state === "boolean";

		return this.each(function() {
			if ( bool ? state : isHidden( this ) ) {
				jQuery( this ).show();
			} else {
				jQuery( this ).hide();
			}
		});
	}
});

jQuery.extend({
	// Add in style property hooks for overriding the default
	// behavior of getting and setting a style property
	cssHooks: {
		opacity: {
			get: function( elem, computed ) {
				if ( computed ) {
					// We should always get a number back from opacity
					var ret = curCSS( elem, "opacity" );
					return ret === "" ? "1" : ret;
				}
			}
		}
	},

	// Exclude the following css properties to add px
	cssNumber: {
		"columnCount": true,
		"fillOpacity": true,
		"fontWeight": true,
		"lineHeight": true,
		"opacity": true,
		"orphans": true,
		"widows": true,
		"zIndex": true,
		"zoom": true
	},

	// Add in properties whose names you wish to fix before
	// setting or getting the value
	cssProps: {
		// normalize float css property
		"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
	},

	// Get and set the style property on a DOM Node
	style: function( elem, name, value, extra ) {
		// Don't set styles on text and comment nodes
		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
			return;
		}

		// Make sure that we're working with the right name
		var ret, type, hooks,
			origName = jQuery.camelCase( name ),
			style = elem.style;

		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );

		// gets hook for the prefixed version
		// followed by the unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// Check if we're setting a value
		if ( value !== undefined ) {
			type = typeof value;

			// convert relative number strings (+= or -=) to relative numbers. #7345
			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
				value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
				// Fixes bug #9237
				type = "number";
			}

			// Make sure that NaN and null values aren't set. See: #7116
			if ( value == null || type === "number" && isNaN( value ) ) {
				return;
			}

			// If a number was passed in, add 'px' to the (except for certain CSS properties)
			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
				value += "px";
			}

			// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
			// but it would mean to define eight (for every problematic property) identical functions
			if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
				style[ name ] = "inherit";
			}

			// If a hook was provided, use that value, otherwise just set the specified value
			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {

				// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
				// Fixes bug #5509
				try {
					style[ name ] = value;
				} catch(e) {}
			}

		} else {
			// If a hook was provided get the non-computed value from there
			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
				return ret;
			}

			// Otherwise just get the value from the style object
			return style[ name ];
		}
	},

	css: function( elem, name, extra, styles ) {
		var num, val, hooks,
			origName = jQuery.camelCase( name );

		// Make sure that we're working with the right name
		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );

		// gets hook for the prefixed version
		// followed by the unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// If a hook was provided get the computed value from there
		if ( hooks && "get" in hooks ) {
			val = hooks.get( elem, true, extra );
		}

		// Otherwise, if a way to get the computed value exists, use that
		if ( val === undefined ) {
			val = curCSS( elem, name, styles );
		}

		//convert "normal" to computed value
		if ( val === "normal" && name in cssNormalTransform ) {
			val = cssNormalTransform[ name ];
		}

		// Return, converting to number if forced or a qualifier was provided and val looks numeric
		if ( extra === "" || extra ) {
			num = parseFloat( val );
			return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
		}
		return val;
	},

	// A method for quickly swapping in/out CSS properties to get correct calculations
	swap: function( elem, options, callback, args ) {
		var ret, name,
			old = {};

		// Remember the old values, and insert the new ones
		for ( name in options ) {
			old[ name ] = elem.style[ name ];
			elem.style[ name ] = options[ name ];
		}

		ret = callback.apply( elem, args || [] );

		// Revert the old values
		for ( name in options ) {
			elem.style[ name ] = old[ name ];
		}

		return ret;
	}
});

// NOTE: we've included the "window" in window.getComputedStyle
// because jsdom on node.js will break without it.
if ( window.getComputedStyle ) {
	getStyles = function( elem ) {
		return window.getComputedStyle( elem, null );
	};

	curCSS = function( elem, name, _computed ) {
		var width, minWidth, maxWidth,
			computed = _computed || getStyles( elem ),

			// getPropertyValue is only needed for .css('filter') in IE9, see #12537
			ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
			style = elem.style;

		if ( computed ) {

			if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
				ret = jQuery.style( elem, name );
			}

			// A tribute to the "awesome hack by Dean Edwards"
			// Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
			// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
			// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
			if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {

				// Remember the original values
				width = style.width;
				minWidth = style.minWidth;
				maxWidth = style.maxWidth;

				// Put in the new values to get a computed value out
				style.minWidth = style.maxWidth = style.width = ret;
				ret = computed.width;

				// Revert the changed values
				style.width = width;
				style.minWidth = minWidth;
				style.maxWidth = maxWidth;
			}
		}

		return ret;
	};
} else if ( document.documentElement.currentStyle ) {
	getStyles = function( elem ) {
		return elem.currentStyle;
	};

	curCSS = function( elem, name, _computed ) {
		var left, rs, rsLeft,
			computed = _computed || getStyles( elem ),
			ret = computed ? computed[ name ] : undefined,
			style = elem.style;

		// Avoid setting ret to empty string here
		// so we don't default to auto
		if ( ret == null && style && style[ name ] ) {
			ret = style[ name ];
		}

		// From the awesome hack by Dean Edwards
		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

		// If we're not dealing with a regular pixel number
		// but a number that has a weird ending, we need to convert it to pixels
		// but not position css attributes, as those are proportional to the parent element instead
		// and we can't measure the parent instead because it might trigger a "stacking dolls" problem
		if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {

			// Remember the original values
			left = style.left;
			rs = elem.runtimeStyle;
			rsLeft = rs && rs.left;

			// Put in the new values to get a computed value out
			if ( rsLeft ) {
				rs.left = elem.currentStyle.left;
			}
			style.left = name === "fontSize" ? "1em" : ret;
			ret = style.pixelLeft + "px";

			// Revert the changed values
			style.left = left;
			if ( rsLeft ) {
				rs.left = rsLeft;
			}
		}

		return ret === "" ? "auto" : ret;
	};
}

function setPositiveNumber( elem, value, subtract ) {
	var matches = rnumsplit.exec( value );
	return matches ?
		// Guard against undefined "subtract", e.g., when used as in cssHooks
		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
		value;
}

function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
	var i = extra === ( isBorderBox ? "border" : "content" ) ?
		// If we already have the right measurement, avoid augmentation
		4 :
		// Otherwise initialize for horizontal or vertical properties
		name === "width" ? 1 : 0,

		val = 0;

	for ( ; i < 4; i += 2 ) {
		// both box models exclude margin, so add it if we want it
		if ( extra === "margin" ) {
			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
		}

		if ( isBorderBox ) {
			// border-box includes padding, so remove it if we want content
			if ( extra === "content" ) {
				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
			}

			// at this point, extra isn't border nor margin, so remove border
			if ( extra !== "margin" ) {
				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		} else {
			// at this point, extra isn't content, so add padding
			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );

			// at this point, extra isn't content nor padding, so add border
			if ( extra !== "padding" ) {
				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		}
	}

	return val;
}

function getWidthOrHeight( elem, name, extra ) {

	// Start with offset property, which is equivalent to the border-box value
	var valueIsBorderBox = true,
		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
		styles = getStyles( elem ),
		isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

	// some non-html elements return undefined for offsetWidth, so check for null/undefined
	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
	if ( val <= 0 || val == null ) {
		// Fall back to computed then uncomputed css if necessary
		val = curCSS( elem, name, styles );
		if ( val < 0 || val == null ) {
			val = elem.style[ name ];
		}

		// Computed unit is not pixels. Stop here and return.
		if ( rnumnonpx.test(val) ) {
			return val;
		}

		// we need the check for style in case a browser which returns unreliable values
		// for getComputedStyle silently falls back to the reliable elem.style
		valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );

		// Normalize "", auto, and prepare for extra
		val = parseFloat( val ) || 0;
	}

	// use the active box-sizing model to add/subtract irrelevant styles
	return ( val +
		augmentWidthOrHeight(
			elem,
			name,
			extra || ( isBorderBox ? "border" : "content" ),
			valueIsBorderBox,
			styles
		)
	) + "px";
}

// Try to determine the default display value of an element
function css_defaultDisplay( nodeName ) {
	var doc = document,
		display = elemdisplay[ nodeName ];

	if ( !display ) {
		display = actualDisplay( nodeName, doc );

		// If the simple way fails, read from inside an iframe
		if ( display === "none" || !display ) {
			// Use the already-created iframe if possible
			iframe = ( iframe ||
				jQuery("<iframe frameborder='0' width='0' height='0'/>")
				.css( "cssText", "display:block !important" )
			).appendTo( doc.documentElement );

			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
			doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;
			doc.write("<!doctype html><html><body>");
			doc.close();

			display = actualDisplay( nodeName, doc );
			iframe.detach();
		}

		// Store the correct default display
		elemdisplay[ nodeName ] = display;
	}

	return display;
}

// Called ONLY from within css_defaultDisplay
function actualDisplay( name, doc ) {
	var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
		display = jQuery.css( elem[0], "display" );
	elem.remove();
	return display;
}

jQuery.each([ "height", "width" ], function( i, name ) {
	jQuery.cssHooks[ name ] = {
		get: function( elem, computed, extra ) {
			if ( computed ) {
				// certain elements can have dimension info if we invisibly show them
				// however, it must have a current display style that would benefit from this
				return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
					jQuery.swap( elem, cssShow, function() {
						return getWidthOrHeight( elem, name, extra );
					}) :
					getWidthOrHeight( elem, name, extra );
			}
		},

		set: function( elem, value, extra ) {
			var styles = extra && getStyles( elem );
			return setPositiveNumber( elem, value, extra ?
				augmentWidthOrHeight(
					elem,
					name,
					extra,
					jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
					styles
				) : 0
			);
		}
	};
});

if ( !jQuery.support.opacity ) {
	jQuery.cssHooks.opacity = {
		get: function( elem, computed ) {
			// IE uses filters for opacity
			return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
				( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
				computed ? "1" : "";
		},

		set: function( elem, value ) {
			var style = elem.style,
				currentStyle = elem.currentStyle,
				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
				filter = currentStyle && currentStyle.filter || style.filter || "";

			// IE has trouble with opacity if it does not have layout
			// Force it by setting the zoom level
			style.zoom = 1;

			// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
			// if value === "", then remove inline opacity #12685
			if ( ( value >= 1 || value === "" ) &&
					jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
					style.removeAttribute ) {

				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
				// if "filter:" is present at all, clearType is disabled, we want to avoid this
				// style.removeAttribute is IE Only, but so apparently is this code path...
				style.removeAttribute( "filter" );

				// if there is no filter style applied in a css rule or unset inline opacity, we are done
				if ( value === "" || currentStyle && !currentStyle.filter ) {
					return;
				}
			}

			// otherwise, set new filter values
			style.filter = ralpha.test( filter ) ?
				filter.replace( ralpha, opacity ) :
				filter + " " + opacity;
		}
	};
}

// These hooks cannot be added until DOM ready because the support test
// for it is not run until after DOM ready
jQuery(function() {
	if ( !jQuery.support.reliableMarginRight ) {
		jQuery.cssHooks.marginRight = {
			get: function( elem, computed ) {
				if ( computed ) {
					// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
					// Work around by temporarily setting element display to inline-block
					return jQuery.swap( elem, { "display": "inline-block" },
						curCSS, [ elem, "marginRight" ] );
				}
			}
		};
	}

	// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
	// getComputedStyle returns percent when specified for top/left/bottom/right
	// rather than make the css module depend on the offset module, we just check for it here
	if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
		jQuery.each( [ "top", "left" ], function( i, prop ) {
			jQuery.cssHooks[ prop ] = {
				get: function( elem, computed ) {
					if ( computed ) {
						computed = curCSS( elem, prop );
						// if curCSS returns percentage, fallback to offset
						return rnumnonpx.test( computed ) ?
							jQuery( elem ).position()[ prop ] + "px" :
							computed;
					}
				}
			};
		});
	}

});

if ( jQuery.expr && jQuery.expr.filters ) {
	jQuery.expr.filters.hidden = function( elem ) {
		// Support: Opera <= 12.12
		// Opera reports offsetWidths and offsetHeights less than zero on some elements
		return elem.offsetWidth <= 0 && elem.offsetHeight <= 0 ||
			(!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
	};

	jQuery.expr.filters.visible = function( elem ) {
		return !jQuery.expr.filters.hidden( elem );
	};
}

// These hooks are used by animate to expand properties
jQuery.each({
	margin: "",
	padding: "",
	border: "Width"
}, function( prefix, suffix ) {
	jQuery.cssHooks[ prefix + suffix ] = {
		expand: function( value ) {
			var i = 0,
				expanded = {},

				// assumes a single number if not a string
				parts = typeof value === "string" ? value.split(" ") : [ value ];

			for ( ; i < 4; i++ ) {
				expanded[ prefix + cssExpand[ i ] + suffix ] =
					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
			}

			return expanded;
		}
	};

	if ( !rmargin.test( prefix ) ) {
		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
	}
});
var r20 = /%20/g,
	rbracket = /\[\]$/,
	rCRLF = /\r?\n/g,
	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
	rsubmittable = /^(?:input|select|textarea|keygen)/i;

jQuery.fn.extend({
	serialize: function() {
		return jQuery.param( this.serializeArray() );
	},
	serializeArray: function() {
		return this.map(function(){
			// Can add propHook for "elements" to filter or add form elements
			var elements = jQuery.prop( this, "elements" );
			return elements ? jQuery.makeArray( elements ) : this;
		})
		.filter(function(){
			var type = this.type;
			// Use .is(":disabled") so that fieldset[disabled] works
			return this.name && !jQuery( this ).is( ":disabled" ) &&
				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
				( this.checked || !manipulation_rcheckableType.test( type ) );
		})
		.map(function( i, elem ){
			var val = jQuery( this ).val();

			return val == null ?
				null :
				jQuery.isArray( val ) ?
					jQuery.map( val, function( val ){
						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
					}) :
					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
		}).get();
	}
});

//Serialize an array of form elements or a set of
//key/values into a query string
jQuery.param = function( a, traditional ) {
	var prefix,
		s = [],
		add = function( key, value ) {
			// If value is a function, invoke it and return its value
			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
		};

	// Set traditional to true for jQuery <= 1.3.2 behavior.
	if ( traditional === undefined ) {
		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
	}

	// If an array was passed in, assume that it is an array of form elements.
	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
		// Serialize the form elements
		jQuery.each( a, function() {
			add( this.name, this.value );
		});

	} else {
		// If traditional, encode the "old" way (the way 1.3.2 or older
		// did it), otherwise encode params recursively.
		for ( prefix in a ) {
			buildParams( prefix, a[ prefix ], traditional, add );
		}
	}

	// Return the resulting serialization
	return s.join( "&" ).replace( r20, "+" );
};

function buildParams( prefix, obj, traditional, add ) {
	var name;

	if ( jQuery.isArray( obj ) ) {
		// Serialize array item.
		jQuery.each( obj, function( i, v ) {
			if ( traditional || rbracket.test( prefix ) ) {
				// Treat each array item as a scalar.
				add( prefix, v );

			} else {
				// Item is non-scalar (array or object), encode its numeric index.
				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
			}
		});

	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
		// Serialize object item.
		for ( name in obj ) {
			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
		}

	} else {
		// Serialize scalar item.
		add( prefix, obj );
	}
}
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {

	// Handle event binding
	jQuery.fn[ name ] = function( data, fn ) {
		return arguments.length > 0 ?
			this.on( name, null, data, fn ) :
			this.trigger( name );
	};
});

jQuery.fn.hover = function( fnOver, fnOut ) {
	return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
};
var
	// Document location
	ajaxLocParts,
	ajaxLocation,
	ajax_nonce = jQuery.now(),

	ajax_rquery = /\?/,
	rhash = /#.*$/,
	rts = /([?&])_=[^&]*/,
	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
	// #7653, #8125, #8152: local protocol detection
	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
	rnoContent = /^(?:GET|HEAD)$/,
	rprotocol = /^\/\//,
	rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,

	// Keep a copy of the old load method
	_load = jQuery.fn.load,

	/* Prefilters
	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
	 * 2) These are called:
	 *    - BEFORE asking for a transport
	 *    - AFTER param serialization (s.data is a string if s.processData is true)
	 * 3) key is the dataType
	 * 4) the catchall symbol "*" can be used
	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
	 */
	prefilters = {},

	/* Transports bindings
	 * 1) key is the dataType
	 * 2) the catchall symbol "*" can be used
	 * 3) selection will start with transport dataType and THEN go to "*" if needed
	 */
	transports = {},

	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
	allTypes = "*/".concat("*");

// #8138, IE may throw an exception when accessing
// a field from window.location if document.domain has been set
try {
	ajaxLocation = location.href;
} catch( e ) {
	// Use the href attribute of an A element
	// since IE will modify it given document.location
	ajaxLocation = document.createElement( "a" );
	ajaxLocation.href = "";
	ajaxLocation = ajaxLocation.href;
}

// Segment location into parts
ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];

// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {

	// dataTypeExpression is optional and defaults to "*"
	return function( dataTypeExpression, func ) {

		if ( typeof dataTypeExpression !== "string" ) {
			func = dataTypeExpression;
			dataTypeExpression = "*";
		}

		var dataType,
			i = 0,
			dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];

		if ( jQuery.isFunction( func ) ) {
			// For each dataType in the dataTypeExpression
			while ( (dataType = dataTypes[i++]) ) {
				// Prepend if requested
				if ( dataType[0] === "+" ) {
					dataType = dataType.slice( 1 ) || "*";
					(structure[ dataType ] = structure[ dataType ] || []).unshift( func );

				// Otherwise append
				} else {
					(structure[ dataType ] = structure[ dataType ] || []).push( func );
				}
			}
		}
	};
}

// Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {

	var inspected = {},
		seekingTransport = ( structure === transports );

	function inspect( dataType ) {
		var selected;
		inspected[ dataType ] = true;
		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
			if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
				options.dataTypes.unshift( dataTypeOrTransport );
				inspect( dataTypeOrTransport );
				return false;
			} else if ( seekingTransport ) {
				return !( selected = dataTypeOrTransport );
			}
		});
		return selected;
	}

	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}

// A special extend for ajax options
// that takes "flat" options (not to be deep extended)
// Fixes #9887
function ajaxExtend( target, src ) {
	var deep, key,
		flatOptions = jQuery.ajaxSettings.flatOptions || {};

	for ( key in src ) {
		if ( src[ key ] !== undefined ) {
			( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
		}
	}
	if ( deep ) {
		jQuery.extend( true, target, deep );
	}

	return target;
}

jQuery.fn.load = function( url, params, callback ) {
	if ( typeof url !== "string" && _load ) {
		return _load.apply( this, arguments );
	}

	var selector, response, type,
		self = this,
		off = url.indexOf(" ");

	if ( off >= 0 ) {
		selector = url.slice( off, url.length );
		url = url.slice( 0, off );
	}

	// If it's a function
	if ( jQuery.isFunction( params ) ) {

		// We assume that it's the callback
		callback = params;
		params = undefined;

	// Otherwise, build a param string
	} else if ( params && typeof params === "object" ) {
		type = "POST";
	}

	// If we have elements to modify, make the request
	if ( self.length > 0 ) {
		jQuery.ajax({
			url: url,

			// if "type" variable is undefined, then "GET" method will be used
			type: type,
			dataType: "html",
			data: params
		}).done(function( responseText ) {

			// Save response for use in complete callback
			response = arguments;

			self.html( selector ?

				// If a selector was specified, locate the right elements in a dummy div
				// Exclude scripts to avoid IE 'Permission Denied' errors
				jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :

				// Otherwise use the full result
				responseText );

		}).complete( callback && function( jqXHR, status ) {
			self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
		});
	}

	return this;
};

// Attach a bunch of functions for handling common AJAX events
jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
	jQuery.fn[ type ] = function( fn ){
		return this.on( type, fn );
	};
});

jQuery.each( [ "get", "post" ], function( i, method ) {
	jQuery[ method ] = function( url, data, callback, type ) {
		// shift arguments if data argument was omitted
		if ( jQuery.isFunction( data ) ) {
			type = type || callback;
			callback = data;
			data = undefined;
		}

		return jQuery.ajax({
			url: url,
			type: method,
			dataType: type,
			data: data,
			success: callback
		});
	};
});

jQuery.extend({

	// Counter for holding the number of active queries
	active: 0,

	// Last-Modified header cache for next request
	lastModified: {},
	etag: {},

	ajaxSettings: {
		url: ajaxLocation,
		type: "GET",
		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
		global: true,
		processData: true,
		async: true,
		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
		/*
		timeout: 0,
		data: null,
		dataType: null,
		username: null,
		password: null,
		cache: null,
		throws: false,
		traditional: false,
		headers: {},
		*/

		accepts: {
			"*": allTypes,
			text: "text/plain",
			html: "text/html",
			xml: "application/xml, text/xml",
			json: "application/json, text/javascript"
		},

		contents: {
			xml: /xml/,
			html: /html/,
			json: /json/
		},

		responseFields: {
			xml: "responseXML",
			text: "responseText"
		},

		// Data converters
		// Keys separate source (or catchall "*") and destination types with a single space
		converters: {

			// Convert anything to text
			"* text": window.String,

			// Text to html (true = no transformation)
			"text html": true,

			// Evaluate text as a json expression
			"text json": jQuery.parseJSON,

			// Parse text as xml
			"text xml": jQuery.parseXML
		},

		// For options that shouldn't be deep extended:
		// you can add your own custom options here if
		// and when you create one that shouldn't be
		// deep extended (see ajaxExtend)
		flatOptions: {
			url: true,
			context: true
		}
	},

	// Creates a full fledged settings object into target
	// with both ajaxSettings and settings fields.
	// If target is omitted, writes into ajaxSettings.
	ajaxSetup: function( target, settings ) {
		return settings ?

			// Building a settings object
			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :

			// Extending ajaxSettings
			ajaxExtend( jQuery.ajaxSettings, target );
	},

	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
	ajaxTransport: addToPrefiltersOrTransports( transports ),

	// Main method
	ajax: function( url, options ) {

		// If url is an object, simulate pre-1.5 signature
		if ( typeof url === "object" ) {
			options = url;
			url = undefined;
		}

		// Force options to be an object
		options = options || {};

		var // Cross-domain detection vars
			parts,
			// Loop variable
			i,
			// URL without anti-cache param
			cacheURL,
			// Response headers as string
			responseHeadersString,
			// timeout handle
			timeoutTimer,

			// To know if global events are to be dispatched
			fireGlobals,

			transport,
			// Response headers
			responseHeaders,
			// Create the final options object
			s = jQuery.ajaxSetup( {}, options ),
			// Callbacks context
			callbackContext = s.context || s,
			// Context for global events is callbackContext if it is a DOM node or jQuery collection
			globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
				jQuery( callbackContext ) :
				jQuery.event,
			// Deferreds
			deferred = jQuery.Deferred(),
			completeDeferred = jQuery.Callbacks("once memory"),
			// Status-dependent callbacks
			statusCode = s.statusCode || {},
			// Headers (they are sent all at once)
			requestHeaders = {},
			requestHeadersNames = {},
			// The jqXHR state
			state = 0,
			// Default abort message
			strAbort = "canceled",
			// Fake xhr
			jqXHR = {
				readyState: 0,

				// Builds headers hashtable if needed
				getResponseHeader: function( key ) {
					var match;
					if ( state === 2 ) {
						if ( !responseHeaders ) {
							responseHeaders = {};
							while ( (match = rheaders.exec( responseHeadersString )) ) {
								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
							}
						}
						match = responseHeaders[ key.toLowerCase() ];
					}
					return match == null ? null : match;
				},

				// Raw string
				getAllResponseHeaders: function() {
					return state === 2 ? responseHeadersString : null;
				},

				// Caches the header
				setRequestHeader: function( name, value ) {
					var lname = name.toLowerCase();
					if ( !state ) {
						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
						requestHeaders[ name ] = value;
					}
					return this;
				},

				// Overrides response content-type header
				overrideMimeType: function( type ) {
					if ( !state ) {
						s.mimeType = type;
					}
					return this;
				},

				// Status-dependent callbacks
				statusCode: function( map ) {
					var code;
					if ( map ) {
						if ( state < 2 ) {
							for ( code in map ) {
								// Lazy-add the new callback in a way that preserves old ones
								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
							}
						} else {
							// Execute the appropriate callbacks
							jqXHR.always( map[ jqXHR.status ] );
						}
					}
					return this;
				},

				// Cancel the request
				abort: function( statusText ) {
					var finalText = statusText || strAbort;
					if ( transport ) {
						transport.abort( finalText );
					}
					done( 0, finalText );
					return this;
				}
			};

		// Attach deferreds
		deferred.promise( jqXHR ).complete = completeDeferred.add;
		jqXHR.success = jqXHR.done;
		jqXHR.error = jqXHR.fail;

		// Remove hash character (#7531: and string promotion)
		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
		// Handle falsy url in the settings object (#10093: consistency with old signature)
		// We also use the url parameter if available
		s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );

		// Alias method option to type as per ticket #12004
		s.type = options.method || options.type || s.method || s.type;

		// Extract dataTypes list
		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];

		// A cross-domain request is in order when we have a protocol:host:port mismatch
		if ( s.crossDomain == null ) {
			parts = rurl.exec( s.url.toLowerCase() );
			s.crossDomain = !!( parts &&
				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
			);
		}

		// Convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" ) {
			s.data = jQuery.param( s.data, s.traditional );
		}

		// Apply prefilters
		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

		// If request was aborted inside a prefilter, stop there
		if ( state === 2 ) {
			return jqXHR;
		}

		// We can fire global events as of now if asked to
		fireGlobals = s.global;

		// Watch for a new set of requests
		if ( fireGlobals && jQuery.active++ === 0 ) {
			jQuery.event.trigger("ajaxStart");
		}

		// Uppercase the type
		s.type = s.type.toUpperCase();

		// Determine if request has content
		s.hasContent = !rnoContent.test( s.type );

		// Save the URL in case we're toying with the If-Modified-Since
		// and/or If-None-Match header later on
		cacheURL = s.url;

		// More options handling for requests with no content
		if ( !s.hasContent ) {

			// If data is available, append data to url
			if ( s.data ) {
				cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
				// #9682: remove data so that it's not used in an eventual retry
				delete s.data;
			}

			// Add anti-cache in url if needed
			if ( s.cache === false ) {
				s.url = rts.test( cacheURL ) ?

					// If there is already a '_' parameter, set its value
					cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :

					// Otherwise add one to the end
					cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
			}
		}

		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
		if ( s.ifModified ) {
			if ( jQuery.lastModified[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
			}
			if ( jQuery.etag[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
			}
		}

		// Set the correct header, if data is being sent
		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
			jqXHR.setRequestHeader( "Content-Type", s.contentType );
		}

		// Set the Accepts header for the server, depending on the dataType
		jqXHR.setRequestHeader(
			"Accept",
			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
				s.accepts[ "*" ]
		);

		// Check for headers option
		for ( i in s.headers ) {
			jqXHR.setRequestHeader( i, s.headers[ i ] );
		}

		// Allow custom headers/mimetypes and early abort
		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
			// Abort if not done already and return
			return jqXHR.abort();
		}

		// aborting is no longer a cancellation
		strAbort = "abort";

		// Install callbacks on deferreds
		for ( i in { success: 1, error: 1, complete: 1 } ) {
			jqXHR[ i ]( s[ i ] );
		}

		// Get transport
		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

		// If no transport, we auto-abort
		if ( !transport ) {
			done( -1, "No Transport" );
		} else {
			jqXHR.readyState = 1;

			// Send global event
			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
			}
			// Timeout
			if ( s.async && s.timeout > 0 ) {
				timeoutTimer = setTimeout(function() {
					jqXHR.abort("timeout");
				}, s.timeout );
			}

			try {
				state = 1;
				transport.send( requestHeaders, done );
			} catch ( e ) {
				// Propagate exception as error if not done
				if ( state < 2 ) {
					done( -1, e );
				// Simply rethrow otherwise
				} else {
					throw e;
				}
			}
		}

		// Callback for when everything is done
		function done( status, nativeStatusText, responses, headers ) {
			var isSuccess, success, error, response, modified,
				statusText = nativeStatusText;

			// Called once
			if ( state === 2 ) {
				return;
			}

			// State is "done" now
			state = 2;

			// Clear timeout if it exists
			if ( timeoutTimer ) {
				clearTimeout( timeoutTimer );
			}

			// Dereference transport for early garbage collection
			// (no matter how long the jqXHR object will be used)
			transport = undefined;

			// Cache response headers
			responseHeadersString = headers || "";

			// Set readyState
			jqXHR.readyState = status > 0 ? 4 : 0;

			// Get response data
			if ( responses ) {
				response = ajaxHandleResponses( s, jqXHR, responses );
			}

			// If successful, handle type chaining
			if ( status >= 200 && status < 300 || status === 304 ) {

				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
				if ( s.ifModified ) {
					modified = jqXHR.getResponseHeader("Last-Modified");
					if ( modified ) {
						jQuery.lastModified[ cacheURL ] = modified;
					}
					modified = jqXHR.getResponseHeader("etag");
					if ( modified ) {
						jQuery.etag[ cacheURL ] = modified;
					}
				}

				// if no content
				if ( status === 204 ) {
					isSuccess = true;
					statusText = "nocontent";

				// if not modified
				} else if ( status === 304 ) {
					isSuccess = true;
					statusText = "notmodified";

				// If we have data, let's convert it
				} else {
					isSuccess = ajaxConvert( s, response );
					statusText = isSuccess.state;
					success = isSuccess.data;
					error = isSuccess.error;
					isSuccess = !error;
				}
			} else {
				// We extract error from statusText
				// then normalize statusText and status for non-aborts
				error = statusText;
				if ( status || !statusText ) {
					statusText = "error";
					if ( status < 0 ) {
						status = 0;
					}
				}
			}

			// Set data for the fake xhr object
			jqXHR.status = status;
			jqXHR.statusText = ( nativeStatusText || statusText ) + "";

			// Success/Error
			if ( isSuccess ) {
				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
			} else {
				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
			}

			// Status-dependent callbacks
			jqXHR.statusCode( statusCode );
			statusCode = undefined;

			if ( fireGlobals ) {
				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
					[ jqXHR, s, isSuccess ? success : error ] );
			}

			// Complete
			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
				// Handle the global AJAX counter
				if ( !( --jQuery.active ) ) {
					jQuery.event.trigger("ajaxStop");
				}
			}
		}

		return jqXHR;
	},

	getScript: function( url, callback ) {
		return jQuery.get( url, undefined, callback, "script" );
	},

	getJSON: function( url, data, callback ) {
		return jQuery.get( url, data, callback, "json" );
	}
});

/* Handles responses to an ajax request:
 * - sets all responseXXX fields accordingly
 * - finds the right dataType (mediates between content-type and expected dataType)
 * - returns the corresponding response
 */
function ajaxHandleResponses( s, jqXHR, responses ) {
	var firstDataType, ct, finalDataType, type,
		contents = s.contents,
		dataTypes = s.dataTypes,
		responseFields = s.responseFields;

	// Fill responseXXX fields
	for ( type in responseFields ) {
		if ( type in responses ) {
			jqXHR[ responseFields[type] ] = responses[ type ];
		}
	}

	// Remove auto dataType and get content-type in the process
	while( dataTypes[ 0 ] === "*" ) {
		dataTypes.shift();
		if ( ct === undefined ) {
			ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
		}
	}

	// Check if we're dealing with a known content-type
	if ( ct ) {
		for ( type in contents ) {
			if ( contents[ type ] && contents[ type ].test( ct ) ) {
				dataTypes.unshift( type );
				break;
			}
		}
	}

	// Check to see if we have a response for the expected dataType
	if ( dataTypes[ 0 ] in responses ) {
		finalDataType = dataTypes[ 0 ];
	} else {
		// Try convertible dataTypes
		for ( type in responses ) {
			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
				finalDataType = type;
				break;
			}
			if ( !firstDataType ) {
				firstDataType = type;
			}
		}
		// Or just use first one
		finalDataType = finalDataType || firstDataType;
	}

	// If we found a dataType
	// We add the dataType to the list if needed
	// and return the corresponding response
	if ( finalDataType ) {
		if ( finalDataType !== dataTypes[ 0 ] ) {
			dataTypes.unshift( finalDataType );
		}
		return responses[ finalDataType ];
	}
}

// Chain conversions given the request and the original response
function ajaxConvert( s, response ) {
	var conv2, current, conv, tmp,
		converters = {},
		i = 0,
		// Work with a copy of dataTypes in case we need to modify it for conversion
		dataTypes = s.dataTypes.slice(),
		prev = dataTypes[ 0 ];

	// Apply the dataFilter if provided
	if ( s.dataFilter ) {
		response = s.dataFilter( response, s.dataType );
	}

	// Create converters map with lowercased keys
	if ( dataTypes[ 1 ] ) {
		for ( conv in s.converters ) {
			converters[ conv.toLowerCase() ] = s.converters[ conv ];
		}
	}

	// Convert to each sequential dataType, tolerating list modification
	for ( ; (current = dataTypes[++i]); ) {

		// There's only work to do if current dataType is non-auto
		if ( current !== "*" ) {

			// Convert response if prev dataType is non-auto and differs from current
			if ( prev !== "*" && prev !== current ) {

				// Seek a direct converter
				conv = converters[ prev + " " + current ] || converters[ "* " + current ];

				// If none found, seek a pair
				if ( !conv ) {
					for ( conv2 in converters ) {

						// If conv2 outputs current
						tmp = conv2.split(" ");
						if ( tmp[ 1 ] === current ) {

							// If prev can be converted to accepted input
							conv = converters[ prev + " " + tmp[ 0 ] ] ||
								converters[ "* " + tmp[ 0 ] ];
							if ( conv ) {
								// Condense equivalence converters
								if ( conv === true ) {
									conv = converters[ conv2 ];

								// Otherwise, insert the intermediate dataType
								} else if ( converters[ conv2 ] !== true ) {
									current = tmp[ 0 ];
									dataTypes.splice( i--, 0, current );
								}

								break;
							}
						}
					}
				}

				// Apply converter (if not an equivalence)
				if ( conv !== true ) {

					// Unless errors are allowed to bubble, catch and return them
					if ( conv && s["throws"] ) {
						response = conv( response );
					} else {
						try {
							response = conv( response );
						} catch ( e ) {
							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
						}
					}
				}
			}

			// Update prev for next iteration
			prev = current;
		}
	}

	return { state: "success", data: response };
}
// Install script dataType
jQuery.ajaxSetup({
	accepts: {
		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
	},
	contents: {
		script: /(?:java|ecma)script/
	},
	converters: {
		"text script": function( text ) {
			jQuery.globalEval( text );
			return text;
		}
	}
});

// Handle cache's special case and global
jQuery.ajaxPrefilter( "script", function( s ) {
	if ( s.cache === undefined ) {
		s.cache = false;
	}
	if ( s.crossDomain ) {
		s.type = "GET";
		s.global = false;
	}
});

// Bind script tag hack transport
jQuery.ajaxTransport( "script", function(s) {

	// This transport only deals with cross domain requests
	if ( s.crossDomain ) {

		var script,
			head = document.head || jQuery("head")[0] || document.documentElement;

		return {

			send: function( _, callback ) {

				script = document.createElement("script");

				script.async = true;

				if ( s.scriptCharset ) {
					script.charset = s.scriptCharset;
				}

				script.src = s.url;

				// Attach handlers for all browsers
				script.onload = script.onreadystatechange = function( _, isAbort ) {

					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {

						// Handle memory leak in IE
						script.onload = script.onreadystatechange = null;

						// Remove the script
						if ( script.parentNode ) {
							script.parentNode.removeChild( script );
						}

						// Dereference the script
						script = null;

						// Callback if not abort
						if ( !isAbort ) {
							callback( 200, "success" );
						}
					}
				};

				// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
				// Use native DOM manipulation to avoid our domManip AJAX trickery
				head.insertBefore( script, head.firstChild );
			},

			abort: function() {
				if ( script ) {
					script.onload( undefined, true );
				}
			}
		};
	}
});
var oldCallbacks = [],
	rjsonp = /(=)\?(?=&|$)|\?\?/;

// Default jsonp settings
jQuery.ajaxSetup({
	jsonp: "callback",
	jsonpCallback: function() {
		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
		this[ callback ] = true;
		return callback;
	}
});

// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

	var callbackName, overwritten, responseContainer,
		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
			"url" :
			typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
		);

	// Handle iff the expected data type is "jsonp" or we have a parameter to set
	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

		// Get callback name, remembering preexisting value associated with it
		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
			s.jsonpCallback() :
			s.jsonpCallback;

		// Insert callback into url or form data
		if ( jsonProp ) {
			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
		} else if ( s.jsonp !== false ) {
			s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
		}

		// Use data converter to retrieve json after script execution
		s.converters["script json"] = function() {
			if ( !responseContainer ) {
				jQuery.error( callbackName + " was not called" );
			}
			return responseContainer[ 0 ];
		};

		// force json dataType
		s.dataTypes[ 0 ] = "json";

		// Install callback
		overwritten = window[ callbackName ];
		window[ callbackName ] = function() {
			responseContainer = arguments;
		};

		// Clean-up function (fires after converters)
		jqXHR.always(function() {
			// Restore preexisting value
			window[ callbackName ] = overwritten;

			// Save back as free
			if ( s[ callbackName ] ) {
				// make sure that re-using the options doesn't screw things around
				s.jsonpCallback = originalSettings.jsonpCallback;

				// save the callback name for future use
				oldCallbacks.push( callbackName );
			}

			// Call if it was a function and we have a response
			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
				overwritten( responseContainer[ 0 ] );
			}

			responseContainer = overwritten = undefined;
		});

		// Delegate to script
		return "script";
	}
});
var xhrCallbacks, xhrSupported,
	xhrId = 0,
	// #5280: Internet Explorer will keep connections alive if we don't abort on unload
	xhrOnUnloadAbort = window.ActiveXObject && function() {
		// Abort all pending requests
		var key;
		for ( key in xhrCallbacks ) {
			xhrCallbacks[ key ]( undefined, true );
		}
	};

// Functions to create xhrs
function createStandardXHR() {
	try {
		return new window.XMLHttpRequest();
	} catch( e ) {}
}

function createActiveXHR() {
	try {
		return new window.ActiveXObject("Microsoft.XMLHTTP");
	} catch( e ) {}
}

// Create the request object
// (This is still attached to ajaxSettings for backward compatibility)
jQuery.ajaxSettings.xhr = window.ActiveXObject ?
	/* Microsoft failed to properly
	 * implement the XMLHttpRequest in IE7 (can't request local files),
	 * so we use the ActiveXObject when it is available
	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
	 * we need a fallback.
	 */
	function() {
		return !this.isLocal && createStandardXHR() || createActiveXHR();
	} :
	// For all other browsers, use the standard XMLHttpRequest object
	createStandardXHR;

// Determine support properties
xhrSupported = jQuery.ajaxSettings.xhr();
jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
xhrSupported = jQuery.support.ajax = !!xhrSupported;

// Create transport if the browser can provide an xhr
if ( xhrSupported ) {

	jQuery.ajaxTransport(function( s ) {
		// Cross domain only allowed if supported through XMLHttpRequest
		if ( !s.crossDomain || jQuery.support.cors ) {

			var callback;

			return {
				send: function( headers, complete ) {

					// Get a new xhr
					var handle, i,
						xhr = s.xhr();

					// Open the socket
					// Passing null username, generates a login popup on Opera (#2865)
					if ( s.username ) {
						xhr.open( s.type, s.url, s.async, s.username, s.password );
					} else {
						xhr.open( s.type, s.url, s.async );
					}

					// Apply custom fields if provided
					if ( s.xhrFields ) {
						for ( i in s.xhrFields ) {
							xhr[ i ] = s.xhrFields[ i ];
						}
					}

					// Override mime type if needed
					if ( s.mimeType && xhr.overrideMimeType ) {
						xhr.overrideMimeType( s.mimeType );
					}

					// X-Requested-With header
					// For cross-domain requests, seeing as conditions for a preflight are
					// akin to a jigsaw puzzle, we simply never set it to be sure.
					// (it can always be set on a per-request basis or even using ajaxSetup)
					// For same-domain requests, won't change header if already provided.
					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
						headers["X-Requested-With"] = "XMLHttpRequest";
					}

					// Need an extra try/catch for cross domain requests in Firefox 3
					try {
						for ( i in headers ) {
							xhr.setRequestHeader( i, headers[ i ] );
						}
					} catch( err ) {}

					// Do send the request
					// This may raise an exception which is actually
					// handled in jQuery.ajax (so no try/catch here)
					xhr.send( ( s.hasContent && s.data ) || null );

					// Listener
					callback = function( _, isAbort ) {
						var status, responseHeaders, statusText, responses;

						// Firefox throws exceptions when accessing properties
						// of an xhr when a network error occurred
						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
						try {

							// Was never called and is aborted or complete
							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {

								// Only called once
								callback = undefined;

								// Do not keep as active anymore
								if ( handle ) {
									xhr.onreadystatechange = jQuery.noop;
									if ( xhrOnUnloadAbort ) {
										delete xhrCallbacks[ handle ];
									}
								}

								// If it's an abort
								if ( isAbort ) {
									// Abort it manually if needed
									if ( xhr.readyState !== 4 ) {
										xhr.abort();
									}
								} else {
									responses = {};
									status = xhr.status;
									responseHeaders = xhr.getAllResponseHeaders();

									// When requesting binary data, IE6-9 will throw an exception
									// on any attempt to access responseText (#11426)
									if ( typeof xhr.responseText === "string" ) {
										responses.text = xhr.responseText;
									}

									// Firefox throws an exception when accessing
									// statusText for faulty cross-domain requests
									try {
										statusText = xhr.statusText;
									} catch( e ) {
										// We normalize with Webkit giving an empty statusText
										statusText = "";
									}

									// Filter status for non standard behaviors

									// If the request is local and we have data: assume a success
									// (success with no data won't get notified, that's the best we
									// can do given current implementations)
									if ( !status && s.isLocal && !s.crossDomain ) {
										status = responses.text ? 200 : 404;
									// IE - #1450: sometimes returns 1223 when it should be 204
									} else if ( status === 1223 ) {
										status = 204;
									}
								}
							}
						} catch( firefoxAccessException ) {
							if ( !isAbort ) {
								complete( -1, firefoxAccessException );
							}
						}

						// Call complete if needed
						if ( responses ) {
							complete( status, statusText, responses, responseHeaders );
						}
					};

					if ( !s.async ) {
						// if we're in sync mode we fire the callback
						callback();
					} else if ( xhr.readyState === 4 ) {
						// (IE6 & IE7) if it's in cache and has been
						// retrieved directly we need to fire the callback
						setTimeout( callback );
					} else {
						handle = ++xhrId;
						if ( xhrOnUnloadAbort ) {
							// Create the active xhrs callbacks list if needed
							// and attach the unload handler
							if ( !xhrCallbacks ) {
								xhrCallbacks = {};
								jQuery( window ).unload( xhrOnUnloadAbort );
							}
							// Add to list of active xhrs callbacks
							xhrCallbacks[ handle ] = callback;
						}
						xhr.onreadystatechange = callback;
					}
				},

				abort: function() {
					if ( callback ) {
						callback( undefined, true );
					}
				}
			};
		}
	});
}
var fxNow, timerId,
	rfxtypes = /^(?:toggle|show|hide)$/,
	rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
	rrun = /queueHooks$/,
	animationPrefilters = [ defaultPrefilter ],
	tweeners = {
		"*": [function( prop, value ) {
			var end, unit,
				tween = this.createTween( prop, value ),
				parts = rfxnum.exec( value ),
				target = tween.cur(),
				start = +target || 0,
				scale = 1,
				maxIterations = 20;

			if ( parts ) {
				end = +parts[2];
				unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );

				// We need to compute starting value
				if ( unit !== "px" && start ) {
					// Iteratively approximate from a nonzero starting point
					// Prefer the current property, because this process will be trivial if it uses the same units
					// Fallback to end or a simple constant
					start = jQuery.css( tween.elem, prop, true ) || end || 1;

					do {
						// If previous iteration zeroed out, double until we get *something*
						// Use a string for doubling factor so we don't accidentally see scale as unchanged below
						scale = scale || ".5";

						// Adjust and apply
						start = start / scale;
						jQuery.style( tween.elem, prop, start + unit );

					// Update scale, tolerating zero or NaN from tween.cur()
					// And breaking the loop if scale is unchanged or perfect, or if we've just had enough
					} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
				}

				tween.unit = unit;
				tween.start = start;
				// If a +=/-= token was provided, we're doing a relative animation
				tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
			}
			return tween;
		}]
	};

// Animations created synchronously will run synchronously
function createFxNow() {
	setTimeout(function() {
		fxNow = undefined;
	});
	return ( fxNow = jQuery.now() );
}

function createTweens( animation, props ) {
	jQuery.each( props, function( prop, value ) {
		var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
			index = 0,
			length = collection.length;
		for ( ; index < length; index++ ) {
			if ( collection[ index ].call( animation, prop, value ) ) {

				// we're done with this property
				return;
			}
		}
	});
}

function Animation( elem, properties, options ) {
	var result,
		stopped,
		index = 0,
		length = animationPrefilters.length,
		deferred = jQuery.Deferred().always( function() {
			// don't match elem in the :animated selector
			delete tick.elem;
		}),
		tick = function() {
			if ( stopped ) {
				return false;
			}
			var currentTime = fxNow || createFxNow(),
				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
				// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
				temp = remaining / animation.duration || 0,
				percent = 1 - temp,
				index = 0,
				length = animation.tweens.length;

			for ( ; index < length ; index++ ) {
				animation.tweens[ index ].run( percent );
			}

			deferred.notifyWith( elem, [ animation, percent, remaining ]);

			if ( percent < 1 && length ) {
				return remaining;
			} else {
				deferred.resolveWith( elem, [ animation ] );
				return false;
			}
		},
		animation = deferred.promise({
			elem: elem,
			props: jQuery.extend( {}, properties ),
			opts: jQuery.extend( true, { specialEasing: {} }, options ),
			originalProperties: properties,
			originalOptions: options,
			startTime: fxNow || createFxNow(),
			duration: options.duration,
			tweens: [],
			createTween: function( prop, end ) {
				var tween = jQuery.Tween( elem, animation.opts, prop, end,
						animation.opts.specialEasing[ prop ] || animation.opts.easing );
				animation.tweens.push( tween );
				return tween;
			},
			stop: function( gotoEnd ) {
				var index = 0,
					// if we are going to the end, we want to run all the tweens
					// otherwise we skip this part
					length = gotoEnd ? animation.tweens.length : 0;
				if ( stopped ) {
					return this;
				}
				stopped = true;
				for ( ; index < length ; index++ ) {
					animation.tweens[ index ].run( 1 );
				}

				// resolve when we played the last frame
				// otherwise, reject
				if ( gotoEnd ) {
					deferred.resolveWith( elem, [ animation, gotoEnd ] );
				} else {
					deferred.rejectWith( elem, [ animation, gotoEnd ] );
				}
				return this;
			}
		}),
		props = animation.props;

	propFilter( props, animation.opts.specialEasing );

	for ( ; index < length ; index++ ) {
		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
		if ( result ) {
			return result;
		}
	}

	createTweens( animation, props );

	if ( jQuery.isFunction( animation.opts.start ) ) {
		animation.opts.start.call( elem, animation );
	}

	jQuery.fx.timer(
		jQuery.extend( tick, {
			elem: elem,
			anim: animation,
			queue: animation.opts.queue
		})
	);

	// attach callbacks from options
	return animation.progress( animation.opts.progress )
		.done( animation.opts.done, animation.opts.complete )
		.fail( animation.opts.fail )
		.always( animation.opts.always );
}

function propFilter( props, specialEasing ) {
	var value, name, index, easing, hooks;

	// camelCase, specialEasing and expand cssHook pass
	for ( index in props ) {
		name = jQuery.camelCase( index );
		easing = specialEasing[ name ];
		value = props[ index ];
		if ( jQuery.isArray( value ) ) {
			easing = value[ 1 ];
			value = props[ index ] = value[ 0 ];
		}

		if ( index !== name ) {
			props[ name ] = value;
			delete props[ index ];
		}

		hooks = jQuery.cssHooks[ name ];
		if ( hooks && "expand" in hooks ) {
			value = hooks.expand( value );
			delete props[ name ];

			// not quite $.extend, this wont overwrite keys already present.
			// also - reusing 'index' from above because we have the correct "name"
			for ( index in value ) {
				if ( !( index in props ) ) {
					props[ index ] = value[ index ];
					specialEasing[ index ] = easing;
				}
			}
		} else {
			specialEasing[ name ] = easing;
		}
	}
}

jQuery.Animation = jQuery.extend( Animation, {

	tweener: function( props, callback ) {
		if ( jQuery.isFunction( props ) ) {
			callback = props;
			props = [ "*" ];
		} else {
			props = props.split(" ");
		}

		var prop,
			index = 0,
			length = props.length;

		for ( ; index < length ; index++ ) {
			prop = props[ index ];
			tweeners[ prop ] = tweeners[ prop ] || [];
			tweeners[ prop ].unshift( callback );
		}
	},

	prefilter: function( callback, prepend ) {
		if ( prepend ) {
			animationPrefilters.unshift( callback );
		} else {
			animationPrefilters.push( callback );
		}
	}
});

function defaultPrefilter( elem, props, opts ) {
	/*jshint validthis:true */
	var prop, index, length,
		value, dataShow, toggle,
		tween, hooks, oldfire,
		anim = this,
		style = elem.style,
		orig = {},
		handled = [],
		hidden = elem.nodeType && isHidden( elem );

	// handle queue: false promises
	if ( !opts.queue ) {
		hooks = jQuery._queueHooks( elem, "fx" );
		if ( hooks.unqueued == null ) {
			hooks.unqueued = 0;
			oldfire = hooks.empty.fire;
			hooks.empty.fire = function() {
				if ( !hooks.unqueued ) {
					oldfire();
				}
			};
		}
		hooks.unqueued++;

		anim.always(function() {
			// doing this makes sure that the complete handler will be called
			// before this completes
			anim.always(function() {
				hooks.unqueued--;
				if ( !jQuery.queue( elem, "fx" ).length ) {
					hooks.empty.fire();
				}
			});
		});
	}

	// height/width overflow pass
	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
		// Make sure that nothing sneaks out
		// Record all 3 overflow attributes because IE does not
		// change the overflow attribute when overflowX and
		// overflowY are set to the same value
		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];

		// Set display property to inline-block for height/width
		// animations on inline elements that are having width/height animated
		if ( jQuery.css( elem, "display" ) === "inline" &&
				jQuery.css( elem, "float" ) === "none" ) {

			// inline-level elements accept inline-block;
			// block-level elements need to be inline with layout
			if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
				style.display = "inline-block";

			} else {
				style.zoom = 1;
			}
		}
	}

	if ( opts.overflow ) {
		style.overflow = "hidden";
		if ( !jQuery.support.shrinkWrapBlocks ) {
			anim.always(function() {
				style.overflow = opts.overflow[ 0 ];
				style.overflowX = opts.overflow[ 1 ];
				style.overflowY = opts.overflow[ 2 ];
			});
		}
	}


	// show/hide pass
	for ( index in props ) {
		value = props[ index ];
		if ( rfxtypes.exec( value ) ) {
			delete props[ index ];
			toggle = toggle || value === "toggle";
			if ( value === ( hidden ? "hide" : "show" ) ) {
				continue;
			}
			handled.push( index );
		}
	}

	length = handled.length;
	if ( length ) {
		dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
		if ( "hidden" in dataShow ) {
			hidden = dataShow.hidden;
		}

		// store state if its toggle - enables .stop().toggle() to "reverse"
		if ( toggle ) {
			dataShow.hidden = !hidden;
		}
		if ( hidden ) {
			jQuery( elem ).show();
		} else {
			anim.done(function() {
				jQuery( elem ).hide();
			});
		}
		anim.done(function() {
			var prop;
			jQuery._removeData( elem, "fxshow" );
			for ( prop in orig ) {
				jQuery.style( elem, prop, orig[ prop ] );
			}
		});
		for ( index = 0 ; index < length ; index++ ) {
			prop = handled[ index ];
			tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
			orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );

			if ( !( prop in dataShow ) ) {
				dataShow[ prop ] = tween.start;
				if ( hidden ) {
					tween.end = tween.start;
					tween.start = prop === "width" || prop === "height" ? 1 : 0;
				}
			}
		}
	}
}

function Tween( elem, options, prop, end, easing ) {
	return new Tween.prototype.init( elem, options, prop, end, easing );
}
jQuery.Tween = Tween;

Tween.prototype = {
	constructor: Tween,
	init: function( elem, options, prop, end, easing, unit ) {
		this.elem = elem;
		this.prop = prop;
		this.easing = easing || "swing";
		this.options = options;
		this.start = this.now = this.cur();
		this.end = end;
		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
	},
	cur: function() {
		var hooks = Tween.propHooks[ this.prop ];

		return hooks && hooks.get ?
			hooks.get( this ) :
			Tween.propHooks._default.get( this );
	},
	run: function( percent ) {
		var eased,
			hooks = Tween.propHooks[ this.prop ];

		if ( this.options.duration ) {
			this.pos = eased = jQuery.easing[ this.easing ](
				percent, this.options.duration * percent, 0, 1, this.options.duration
			);
		} else {
			this.pos = eased = percent;
		}
		this.now = ( this.end - this.start ) * eased + this.start;

		if ( this.options.step ) {
			this.options.step.call( this.elem, this.now, this );
		}

		if ( hooks && hooks.set ) {
			hooks.set( this );
		} else {
			Tween.propHooks._default.set( this );
		}
		return this;
	}
};

Tween.prototype.init.prototype = Tween.prototype;

Tween.propHooks = {
	_default: {
		get: function( tween ) {
			var result;

			if ( tween.elem[ tween.prop ] != null &&
				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
				return tween.elem[ tween.prop ];
			}

			// passing an empty string as a 3rd parameter to .css will automatically
			// attempt a parseFloat and fallback to a string if the parse fails
			// so, simple values such as "10px" are parsed to Float.
			// complex values such as "rotate(1rad)" are returned as is.
			result = jQuery.css( tween.elem, tween.prop, "" );
			// Empty strings, null, undefined and "auto" are converted to 0.
			return !result || result === "auto" ? 0 : result;
		},
		set: function( tween ) {
			// use step hook for back compat - use cssHook if its there - use .style if its
			// available and use plain properties where available
			if ( jQuery.fx.step[ tween.prop ] ) {
				jQuery.fx.step[ tween.prop ]( tween );
			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
			} else {
				tween.elem[ tween.prop ] = tween.now;
			}
		}
	}
};

// Remove in 2.0 - this supports IE8's panic based approach
// to setting things on disconnected nodes

Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
	set: function( tween ) {
		if ( tween.elem.nodeType && tween.elem.parentNode ) {
			tween.elem[ tween.prop ] = tween.now;
		}
	}
};

jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
	var cssFn = jQuery.fn[ name ];
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return speed == null || typeof speed === "boolean" ?
			cssFn.apply( this, arguments ) :
			this.animate( genFx( name, true ), speed, easing, callback );
	};
});

jQuery.fn.extend({
	fadeTo: function( speed, to, easing, callback ) {

		// show any hidden elements after setting opacity to 0
		return this.filter( isHidden ).css( "opacity", 0 ).show()

			// animate to the value specified
			.end().animate({ opacity: to }, speed, easing, callback );
	},
	animate: function( prop, speed, easing, callback ) {
		var empty = jQuery.isEmptyObject( prop ),
			optall = jQuery.speed( speed, easing, callback ),
			doAnimation = function() {
				// Operate on a copy of prop so per-property easing won't be lost
				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
				doAnimation.finish = function() {
					anim.stop( true );
				};
				// Empty animations, or finishing resolves immediately
				if ( empty || jQuery._data( this, "finish" ) ) {
					anim.stop( true );
				}
			};
			doAnimation.finish = doAnimation;

		return empty || optall.queue === false ?
			this.each( doAnimation ) :
			this.queue( optall.queue, doAnimation );
	},
	stop: function( type, clearQueue, gotoEnd ) {
		var stopQueue = function( hooks ) {
			var stop = hooks.stop;
			delete hooks.stop;
			stop( gotoEnd );
		};

		if ( typeof type !== "string" ) {
			gotoEnd = clearQueue;
			clearQueue = type;
			type = undefined;
		}
		if ( clearQueue && type !== false ) {
			this.queue( type || "fx", [] );
		}

		return this.each(function() {
			var dequeue = true,
				index = type != null && type + "queueHooks",
				timers = jQuery.timers,
				data = jQuery._data( this );

			if ( index ) {
				if ( data[ index ] && data[ index ].stop ) {
					stopQueue( data[ index ] );
				}
			} else {
				for ( index in data ) {
					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
						stopQueue( data[ index ] );
					}
				}
			}

			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
					timers[ index ].anim.stop( gotoEnd );
					dequeue = false;
					timers.splice( index, 1 );
				}
			}

			// start the next in the queue if the last step wasn't forced
			// timers currently will call their complete callbacks, which will dequeue
			// but only if they were gotoEnd
			if ( dequeue || !gotoEnd ) {
				jQuery.dequeue( this, type );
			}
		});
	},
	finish: function( type ) {
		if ( type !== false ) {
			type = type || "fx";
		}
		return this.each(function() {
			var index,
				data = jQuery._data( this ),
				queue = data[ type + "queue" ],
				hooks = data[ type + "queueHooks" ],
				timers = jQuery.timers,
				length = queue ? queue.length : 0;

			// enable finishing flag on private data
			data.finish = true;

			// empty the queue first
			jQuery.queue( this, type, [] );

			if ( hooks && hooks.cur && hooks.cur.finish ) {
				hooks.cur.finish.call( this );
			}

			// look for any active animations, and finish them
			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
					timers[ index ].anim.stop( true );
					timers.splice( index, 1 );
				}
			}

			// look for any animations in the old queue and finish them
			for ( index = 0; index < length; index++ ) {
				if ( queue[ index ] && queue[ index ].finish ) {
					queue[ index ].finish.call( this );
				}
			}

			// turn off finishing flag
			delete data.finish;
		});
	}
});

// Generate parameters to create a standard animation
function genFx( type, includeWidth ) {
	var which,
		attrs = { height: type },
		i = 0;

	// if we include width, step value is 1 to do all cssExpand values,
	// if we don't include width, step value is 2 to skip over Left and Right
	includeWidth = includeWidth? 1 : 0;
	for( ; i < 4 ; i += 2 - includeWidth ) {
		which = cssExpand[ i ];
		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
	}

	if ( includeWidth ) {
		attrs.opacity = attrs.width = type;
	}

	return attrs;
}

// Generate shortcuts for custom animations
jQuery.each({
	slideDown: genFx("show"),
	slideUp: genFx("hide"),
	slideToggle: genFx("toggle"),
	fadeIn: { opacity: "show" },
	fadeOut: { opacity: "hide" },
	fadeToggle: { opacity: "toggle" }
}, function( name, props ) {
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return this.animate( props, speed, easing, callback );
	};
});

jQuery.speed = function( speed, easing, fn ) {
	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
		complete: fn || !fn && easing ||
			jQuery.isFunction( speed ) && speed,
		duration: speed,
		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
	};

	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;

	// normalize opt.queue - true/undefined/null -> "fx"
	if ( opt.queue == null || opt.queue === true ) {
		opt.queue = "fx";
	}

	// Queueing
	opt.old = opt.complete;

	opt.complete = function() {
		if ( jQuery.isFunction( opt.old ) ) {
			opt.old.call( this );
		}

		if ( opt.queue ) {
			jQuery.dequeue( this, opt.queue );
		}
	};

	return opt;
};

jQuery.easing = {
	linear: function( p ) {
		return p;
	},
	swing: function( p ) {
		return 0.5 - Math.cos( p*Math.PI ) / 2;
	}
};

jQuery.timers = [];
jQuery.fx = Tween.prototype.init;
jQuery.fx.tick = function() {
	var timer,
		timers = jQuery.timers,
		i = 0;

	fxNow = jQuery.now();

	for ( ; i < timers.length; i++ ) {
		timer = timers[ i ];
		// Checks the timer has not already been removed
		if ( !timer() && timers[ i ] === timer ) {
			timers.splice( i--, 1 );
		}
	}

	if ( !timers.length ) {
		jQuery.fx.stop();
	}
	fxNow = undefined;
};

jQuery.fx.timer = function( timer ) {
	if ( timer() && jQuery.timers.push( timer ) ) {
		jQuery.fx.start();
	}
};

jQuery.fx.interval = 13;

jQuery.fx.start = function() {
	if ( !timerId ) {
		timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
	}
};

jQuery.fx.stop = function() {
	clearInterval( timerId );
	timerId = null;
};

jQuery.fx.speeds = {
	slow: 600,
	fast: 200,
	// Default speed
	_default: 400
};

// Back Compat <1.8 extension point
jQuery.fx.step = {};

if ( jQuery.expr && jQuery.expr.filters ) {
	jQuery.expr.filters.animated = function( elem ) {
		return jQuery.grep(jQuery.timers, function( fn ) {
			return elem === fn.elem;
		}).length;
	};
}
jQuery.fn.offset = function( options ) {
	if ( arguments.length ) {
		return options === undefined ?
			this :
			this.each(function( i ) {
				jQuery.offset.setOffset( this, options, i );
			});
	}

	var docElem, win,
		box = { top: 0, left: 0 },
		elem = this[ 0 ],
		doc = elem && elem.ownerDocument;

	if ( !doc ) {
		return;
	}

	docElem = doc.documentElement;

	// Make sure it's not a disconnected DOM node
	if ( !jQuery.contains( docElem, elem ) ) {
		return box;
	}

	// If we don't have gBCR, just use 0,0 rather than error
	// BlackBerry 5, iOS 3 (original iPhone)
	if ( typeof elem.getBoundingClientRect !== core_strundefined ) {
		box = elem.getBoundingClientRect();
	}
	win = getWindow( doc );
	return {
		top: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),
		left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
	};
};

jQuery.offset = {

	setOffset: function( elem, options, i ) {
		var position = jQuery.css( elem, "position" );

		// set position first, in-case top/left are set even on static elem
		if ( position === "static" ) {
			elem.style.position = "relative";
		}

		var curElem = jQuery( elem ),
			curOffset = curElem.offset(),
			curCSSTop = jQuery.css( elem, "top" ),
			curCSSLeft = jQuery.css( elem, "left" ),
			calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
			props = {}, curPosition = {}, curTop, curLeft;

		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
		if ( calculatePosition ) {
			curPosition = curElem.position();
			curTop = curPosition.top;
			curLeft = curPosition.left;
		} else {
			curTop = parseFloat( curCSSTop ) || 0;
			curLeft = parseFloat( curCSSLeft ) || 0;
		}

		if ( jQuery.isFunction( options ) ) {
			options = options.call( elem, i, curOffset );
		}

		if ( options.top != null ) {
			props.top = ( options.top - curOffset.top ) + curTop;
		}
		if ( options.left != null ) {
			props.left = ( options.left - curOffset.left ) + curLeft;
		}

		if ( "using" in options ) {
			options.using.call( elem, props );
		} else {
			curElem.css( props );
		}
	}
};


jQuery.fn.extend({

	position: function() {
		if ( !this[ 0 ] ) {
			return;
		}

		var offsetParent, offset,
			parentOffset = { top: 0, left: 0 },
			elem = this[ 0 ];

		// fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
		if ( jQuery.css( elem, "position" ) === "fixed" ) {
			// we assume that getBoundingClientRect is available when computed position is fixed
			offset = elem.getBoundingClientRect();
		} else {
			// Get *real* offsetParent
			offsetParent = this.offsetParent();

			// Get correct offsets
			offset = this.offset();
			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
				parentOffset = offsetParent.offset();
			}

			// Add offsetParent borders
			parentOffset.top  += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
		}

		// Subtract parent offsets and element margins
		// note: when an element has margin: auto the offsetLeft and marginLeft
		// are the same in Safari causing offset.left to incorrectly be 0
		return {
			top:  offset.top  - parentOffset.top - jQuery.css( elem, "marginTop", true ),
			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
		};
	},

	offsetParent: function() {
		return this.map(function() {
			var offsetParent = this.offsetParent || document.documentElement;
			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
				offsetParent = offsetParent.offsetParent;
			}
			return offsetParent || document.documentElement;
		});
	}
});


// Create scrollLeft and scrollTop methods
jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
	var top = /Y/.test( prop );

	jQuery.fn[ method ] = function( val ) {
		return jQuery.access( this, function( elem, method, val ) {
			var win = getWindow( elem );

			if ( val === undefined ) {
				return win ? (prop in win) ? win[ prop ] :
					win.document.documentElement[ method ] :
					elem[ method ];
			}

			if ( win ) {
				win.scrollTo(
					!top ? val : jQuery( win ).scrollLeft(),
					top ? val : jQuery( win ).scrollTop()
				);

			} else {
				elem[ method ] = val;
			}
		}, method, val, arguments.length, null );
	};
});

function getWindow( elem ) {
	return jQuery.isWindow( elem ) ?
		elem :
		elem.nodeType === 9 ?
			elem.defaultView || elem.parentWindow :
			false;
}
// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
		// margin is only for outerHeight, outerWidth
		jQuery.fn[ funcName ] = function( margin, value ) {
			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );

			return jQuery.access( this, function( elem, type, value ) {
				var doc;

				if ( jQuery.isWindow( elem ) ) {
					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
					// isn't a whole lot we can do. See pull request at this URL for discussion:
					// https://github.com/jquery/jquery/pull/764
					return elem.document.documentElement[ "client" + name ];
				}

				// Get document width or height
				if ( elem.nodeType === 9 ) {
					doc = elem.documentElement;

					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
					// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
					return Math.max(
						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
						elem.body[ "offset" + name ], doc[ "offset" + name ],
						doc[ "client" + name ]
					);
				}

				return value === undefined ?
					// Get width or height on the element, requesting but not forcing parseFloat
					jQuery.css( elem, type, extra ) :

					// Set width or height on the element
					jQuery.style( elem, type, value, extra );
			}, type, chainable ? margin : undefined, chainable, null );
		};
	});
});
// Limit scope pollution from any deprecated API
// (function() {

// })();
// Expose jQuery to the global object
window.jQuery = window.$ = jQuery;

// Expose jQuery as an AMD module, but only for AMD loaders that
// understand the issues with loading multiple versions of jQuery
// in a page that all might call define(). The loader will indicate
// they have special allowances for multiple jQuery versions by
// specifying define.amd.jQuery = true. Register as a named module,
// since jQuery can be concatenated with other files that may use define,
// but not use a proper concatenation script that understands anonymous
// AMD modules. A named AMD is safest and most robust way to register.
// Lowercase jquery is used because AMD module names are derived from
// file names, and jQuery is normally delivered in a lowercase file name.
// Do this after creating the global so that if an AMD module wants to call
// noConflict to hide this version of jQuery, it will work.
if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
	define( "jquery", [], function () { return jQuery; } );
}

})( window );

/*!
 * jQuery Validation Plugin 1.11.1
 *
 * http://bassistance.de/jquery-plugins/jquery-plugin-validation/
 * http://docs.jquery.com/Plugins/Validation
 *
 * Copyright 2013 Jörn Zaefferer
 * Released under the MIT license:
 *   http://www.opensource.org/licenses/mit-license.php
 */

(function($) {

$.extend($.fn, {
	// http://docs.jquery.com/Plugins/Validation/validate
	validate: function( options ) {

		// if nothing is selected, return nothing; can't chain anyway
		if ( !this.length ) {
			if ( options && options.debug && window.console ) {
				console.warn( "Nothing selected, can't validate, returning nothing." );
			}
			return;
		}

		// check if a validator for this form was already created
		var validator = $.data( this[0], "validator" );
		if ( validator ) {
			return validator;
		}

		// Add novalidate tag if HTML5.
		this.attr( "novalidate", "novalidate" );

		validator = new $.validator( options, this[0] );
		$.data( this[0], "validator", validator );

		if ( validator.settings.onsubmit ) {

			this.validateDelegate( ":submit", "click", function( event ) {
				if ( validator.settings.submitHandler ) {
					validator.submitButton = event.target;
				}
				// allow suppressing validation by adding a cancel class to the submit button
				if ( $(event.target).hasClass("cancel") ) {
					validator.cancelSubmit = true;
				}

				// allow suppressing validation by adding the html5 formnovalidate attribute to the submit button
				if ( $(event.target).attr("formnovalidate") !== undefined ) {
					validator.cancelSubmit = true;
				}
			});

			// validate the form on submit
			this.submit( function( event ) {
				if ( validator.settings.debug ) {
					// prevent form submit to be able to see console output
					event.preventDefault();
				}
				function handle() {
					var hidden;
					if ( validator.settings.submitHandler ) {
						if ( validator.submitButton ) {
							// insert a hidden input as a replacement for the missing submit button
							hidden = $("<input type='hidden'/>").attr("name", validator.submitButton.name).val( $(validator.submitButton).val() ).appendTo(validator.currentForm);
						}
						validator.settings.submitHandler.call( validator, validator.currentForm, event );
						if ( validator.submitButton ) {
							// and clean up afterwards; thanks to no-block-scope, hidden can be referenced
							hidden.remove();
						}
						return false;
					}
					return true;
				}

				// prevent submit for invalid forms or custom submit handlers
				if ( validator.cancelSubmit ) {
					validator.cancelSubmit = false;
					return handle();
				}
				if ( validator.form() ) {
					if ( validator.pendingRequest ) {
						validator.formSubmitted = true;
						return false;
					}
					return handle();
				} else {
					validator.focusInvalid();
					return false;
				}
			});
		}

		return validator;
	},
	// http://docs.jquery.com/Plugins/Validation/valid
	valid: function() {
		if ( $(this[0]).is("form")) {
			return this.validate().form();
		} else {
			var valid = true;
			var validator = $(this[0].form).validate();
			this.each(function() {
				valid = valid && validator.element(this);
			});
			return valid;
		}
	},
	// attributes: space seperated list of attributes to retrieve and remove
	removeAttrs: function( attributes ) {
		var result = {},
			$element = this;
		$.each(attributes.split(/\s/), function( index, value ) {
			result[value] = $element.attr(value);
			$element.removeAttr(value);
		});
		return result;
	},
	// http://docs.jquery.com/Plugins/Validation/rules
	rules: function( command, argument ) {
		var element = this[0];

		if ( command ) {
			var settings = $.data(element.form, "validator").settings;
			var staticRules = settings.rules;
			var existingRules = $.validator.staticRules(element);
			switch(command) {
			case "add":
				$.extend(existingRules, $.validator.normalizeRule(argument));
				// remove messages from rules, but allow them to be set separetely
				delete existingRules.messages;
				staticRules[element.name] = existingRules;
				if ( argument.messages ) {
					settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages );
				}
				break;
			case "remove":
				if ( !argument ) {
					delete staticRules[element.name];
					return existingRules;
				}
				var filtered = {};
				$.each(argument.split(/\s/), function( index, method ) {
					filtered[method] = existingRules[method];
					delete existingRules[method];
				});
				return filtered;
			}
		}

		var data = $.validator.normalizeRules(
		$.extend(
			{},
			$.validator.classRules(element),
			$.validator.attributeRules(element),
			$.validator.dataRules(element),
			$.validator.staticRules(element)
		), element);

		// make sure required is at front
		if ( data.required ) {
			var param = data.required;
			delete data.required;
			data = $.extend({required: param}, data);
		}

		return data;
	}
});

// Custom selectors
$.extend($.expr[":"], {
	// http://docs.jquery.com/Plugins/Validation/blank
	blank: function( a ) { return !$.trim("" + $(a).val()); },
	// http://docs.jquery.com/Plugins/Validation/filled
	filled: function( a ) { return !!$.trim("" + $(a).val()); },
	// http://docs.jquery.com/Plugins/Validation/unchecked
	unchecked: function( a ) { return !$(a).prop("checked"); }
});

// constructor for validator
$.validator = function( options, form ) {
	this.settings = $.extend( true, {}, $.validator.defaults, options );
	this.currentForm = form;
	this.init();
};

$.validator.format = function( source, params ) {
	if ( arguments.length === 1 ) {
		return function() {
			var args = $.makeArray(arguments);
			args.unshift(source);
			return $.validator.format.apply( this, args );
		};
	}
	if ( arguments.length > 2 && params.constructor !== Array  ) {
		params = $.makeArray(arguments).slice(1);
	}
	if ( params.constructor !== Array ) {
		params = [ params ];
	}
	$.each(params, function( i, n ) {
		source = source.replace( new RegExp("\\{" + i + "\\}", "g"), function() {
			return n;
		});
	});
	return source;
};

$.extend($.validator, {

	defaults: {
		messages: {},
		groups: {},
		rules: {},
		errorClass: "error",
		validClass: "valid",
		errorElement: "label",
		focusInvalid: true,
		errorContainer: $([]),
		errorLabelContainer: $([]),
		onsubmit: true,
		ignore: ":hidden",
		ignoreTitle: false,
		onfocusin: function( element, event ) {
			this.lastActive = element;

			// hide error label and remove error class on focus if enabled
			if ( this.settings.focusCleanup && !this.blockFocusCleanup ) {
				if ( this.settings.unhighlight ) {
					this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass );
				}
				this.addWrapper(this.errorsFor(element)).hide();
			}
		},
		onfocusout: function( element, event ) {
			if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) {
				this.element(element);
			}
		},
		onkeyup: function( element, event ) {
			if ( event.which === 9 && this.elementValue(element) === "" ) {
				return;
			} else if ( element.name in this.submitted || element === this.lastElement ) {
				this.element(element);
			}
		},
		onclick: function( element, event ) {
			// click on selects, radiobuttons and checkboxes
			if ( element.name in this.submitted ) {
				this.element(element);
			}
			// or option elements, check parent select in that case
			else if ( element.parentNode.name in this.submitted ) {
				this.element(element.parentNode);
			}
		},
		highlight: function( element, errorClass, validClass ) {
			if ( element.type === "radio" ) {
				this.findByName(element.name).addClass(errorClass).removeClass(validClass);
			} else {
				$(element).addClass(errorClass).removeClass(validClass);
			}
		},
		unhighlight: function( element, errorClass, validClass ) {
			if ( element.type === "radio" ) {
				this.findByName(element.name).removeClass(errorClass).addClass(validClass);
			} else {
				$(element).removeClass(errorClass).addClass(validClass);
			}
		}
	},

	// http://docs.jquery.com/Plugins/Validation/Validator/setDefaults
	setDefaults: function( settings ) {
		$.extend( $.validator.defaults, settings );
	},

	messages: {
		required: "This field is required.",
		remote: "Please fix this field.",
		email: "Please enter a valid email address.",
		url: "Please enter a valid URL.",
		date: "Please enter a valid date.",
		dateISO: "Please enter a valid date (ISO).",
		number: "Please enter a valid number.",
		digits: "Please enter only digits.",
		creditcard: "Please enter a valid credit card number.",
		equalTo: "Please enter the same value again.",
		maxlength: $.validator.format("Please enter no more than {0} characters."),
		minlength: $.validator.format("Please enter at least {0} characters."),
		rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
		range: $.validator.format("Please enter a value between {0} and {1}."),
		max: $.validator.format("Please enter a value less than or equal to {0}."),
		min: $.validator.format("Please enter a value greater than or equal to {0}.")
	},

	autoCreateRanges: false,

	prototype: {

		init: function() {
			this.labelContainer = $(this.settings.errorLabelContainer);
			this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
			this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer );
			this.submitted = {};
			this.valueCache = {};
			this.pendingRequest = 0;
			this.pending = {};
			this.invalid = {};
			this.reset();

			var groups = (this.groups = {});
			$.each(this.settings.groups, function( key, value ) {
				if ( typeof value === "string" ) {
					value = value.split(/\s/);
				}
				$.each(value, function( index, name ) {
					groups[name] = key;
				});
			});
			var rules = this.settings.rules;
			$.each(rules, function( key, value ) {
				rules[key] = $.validator.normalizeRule(value);
			});

			function delegate(event) {
				var validator = $.data(this[0].form, "validator"),
					eventType = "on" + event.type.replace(/^validate/, "");
				if ( validator.settings[eventType] ) {
					validator.settings[eventType].call(validator, this[0], event);
				}
			}
			$(this.currentForm)
				.validateDelegate(":text, [type='password'], [type='file'], select, textarea, " +
					"[type='number'], [type='search'] ,[type='tel'], [type='url'], " +
					"[type='email'], [type='datetime'], [type='date'], [type='month'], " +
					"[type='week'], [type='time'], [type='datetime-local'], " +
					"[type='range'], [type='color'] ",
					"focusin focusout keyup", delegate)
				.validateDelegate("[type='radio'], [type='checkbox'], select, option", "click", delegate);

			if ( this.settings.invalidHandler ) {
				$(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler);
			}
		},

		// http://docs.jquery.com/Plugins/Validation/Validator/form
		form: function() {
			this.checkForm();
			$.extend(this.submitted, this.errorMap);
			this.invalid = $.extend({}, this.errorMap);
			if ( !this.valid() ) {
				$(this.currentForm).triggerHandler("invalid-form", [this]);
			}
			this.showErrors();
			return this.valid();
		},

		checkForm: function() {
			this.prepareForm();
			for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) {
				this.check( elements[i] );
			}
			return this.valid();
		},

		// http://docs.jquery.com/Plugins/Validation/Validator/element
		element: function( element ) {
			element = this.validationTargetFor( this.clean( element ) );
			this.lastElement = element;
			this.prepareElement( element );
			this.currentElements = $(element);
			var result = this.check( element ) !== false;
			if ( result ) {
				delete this.invalid[element.name];
			} else {
				this.invalid[element.name] = true;
			}
			if ( !this.numberOfInvalids() ) {
				// Hide error containers on last error
				this.toHide = this.toHide.add( this.containers );
			}
			this.showErrors();
			return result;
		},

		// http://docs.jquery.com/Plugins/Validation/Validator/showErrors
		showErrors: function( errors ) {
			if ( errors ) {
				// add items to error list and map
				$.extend( this.errorMap, errors );
				this.errorList = [];
				for ( var name in errors ) {
					this.errorList.push({
						message: errors[name],
						element: this.findByName(name)[0]
					});
				}
				// remove items from success list
				this.successList = $.grep( this.successList, function( element ) {
					return !(element.name in errors);
				});
			}
			if ( this.settings.showErrors ) {
				this.settings.showErrors.call( this, this.errorMap, this.errorList );
			} else {
				this.defaultShowErrors();
			}
		},

		// http://docs.jquery.com/Plugins/Validation/Validator/resetForm
		resetForm: function() {
			if ( $.fn.resetForm ) {
				$(this.currentForm).resetForm();
			}
			this.submitted = {};
			this.lastElement = null;
			this.prepareForm();
			this.hideErrors();
			this.elements().removeClass( this.settings.errorClass ).removeData( "previousValue" );
		},

		numberOfInvalids: function() {
			return this.objectLength(this.invalid);
		},

		objectLength: function( obj ) {
			var count = 0;
			for ( var i in obj ) {
				count++;
			}
			return count;
		},

		hideErrors: function() {
			this.addWrapper( this.toHide ).hide();
		},

		valid: function() {
			return this.size() === 0;
		},

		size: function() {
			return this.errorList.length;
		},

		focusInvalid: function() {
			if ( this.settings.focusInvalid ) {
				try {
					$(this.findLastActive() || this.errorList.length && this.errorList[0].element || [])
					.filter(":visible")
					.focus()
					// manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
					.trigger("focusin");
				} catch(e) {
					// ignore IE throwing errors when focusing hidden elements
				}
			}
		},

		findLastActive: function() {
			var lastActive = this.lastActive;
			return lastActive && $.grep(this.errorList, function( n ) {
				return n.element.name === lastActive.name;
			}).length === 1 && lastActive;
		},

		elements: function() {
			var validator = this,
				rulesCache = {};

			// select all valid inputs inside the form (no submit or reset buttons)
			return $(this.currentForm)
			.find("input, select, textarea")
			.not(":submit, :reset, :image, [disabled]")
			.not( this.settings.ignore )
			.filter(function() {
				if ( !this.name && validator.settings.debug && window.console ) {
					console.error( "%o has no name assigned", this);
				}

				// select only the first element for each name, and only those with rules specified
				if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) {
					return false;
				}

				rulesCache[this.name] = true;
				return true;
			});
		},

		clean: function( selector ) {
			return $(selector)[0];
		},

		errors: function() {
			var errorClass = this.settings.errorClass.replace(" ", ".");
			return $(this.settings.errorElement + "." + errorClass, this.errorContext);
		},

		reset: function() {
			this.successList = [];
			this.errorList = [];
			this.errorMap = {};
			this.toShow = $([]);
			this.toHide = $([]);
			this.currentElements = $([]);
		},

		prepareForm: function() {
			this.reset();
			this.toHide = this.errors().add( this.containers );
		},

		prepareElement: function( element ) {
			this.reset();
			this.toHide = this.errorsFor(element);
		},

		elementValue: function( element ) {
			var type = $(element).attr("type"),
				val = $(element).val();

			if ( type === "radio" || type === "checkbox" ) {
				return $("input[name='" + $(element).attr("name") + "']:checked").val();
			}

			if ( typeof val === "string" ) {
				return val.replace(/\r/g, "");
			}
			return val;
		},

		check: function( element ) {
			element = this.validationTargetFor( this.clean( element ) );

			var rules = $(element).rules();
			var dependencyMismatch = false;
			var val = this.elementValue(element);
			var result;

			for (var method in rules ) {
				var rule = { method: method, parameters: rules[method] };
				try {

					result = $.validator.methods[method].call( this, val, element, rule.parameters );

					// if a method indicates that the field is optional and therefore valid,
					// don't mark it as valid when there are no other rules
					if ( result === "dependency-mismatch" ) {
						dependencyMismatch = true;
						continue;
					}
					dependencyMismatch = false;

					if ( result === "pending" ) {
						this.toHide = this.toHide.not( this.errorsFor(element) );
						return;
					}

					if ( !result ) {
						this.formatAndAdd( element, rule );
						return false;
					}
				} catch(e) {
					if ( this.settings.debug && window.console ) {
						console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e );
					}
					throw e;
				}
			}
			if ( dependencyMismatch ) {
				return;
			}
			if ( this.objectLength(rules) ) {
				this.successList.push(element);
			}
			return true;
		},

		// return the custom message for the given element and validation method
		// specified in the element's HTML5 data attribute
		customDataMessage: function( element, method ) {
			return $(element).data("msg-" + method.toLowerCase()) || (element.attributes && $(element).attr("data-msg-" + method.toLowerCase()));
		},

		// return the custom message for the given element name and validation method
		customMessage: function( name, method ) {
			var m = this.settings.messages[name];
			return m && (m.constructor === String ? m : m[method]);
		},

		// return the first defined argument, allowing empty strings
		findDefined: function() {
			for(var i = 0; i < arguments.length; i++) {
				if ( arguments[i] !== undefined ) {
					return arguments[i];
				}
			}
			return undefined;
		},

		defaultMessage: function( element, method ) {
			return this.findDefined(
				this.customMessage( element.name, method ),
				this.customDataMessage( element, method ),
				// title is never undefined, so handle empty string as undefined
				!this.settings.ignoreTitle && element.title || undefined,
				$.validator.messages[method],
				"<strong>Warning: No message defined for " + element.name + "</strong>"
			);
		},

		formatAndAdd: function( element, rule ) {
			var message = this.defaultMessage( element, rule.method ),
				theregex = /\$?\{(\d+)\}/g;
			if ( typeof message === "function" ) {
				message = message.call(this, rule.parameters, element);
			} else if (theregex.test(message)) {
				message = $.validator.format(message.replace(theregex, "{$1}"), rule.parameters);
			}
			this.errorList.push({
				message: message,
				element: element
			});

			this.errorMap[element.name] = message;
			this.submitted[element.name] = message;
		},

		addWrapper: function( toToggle ) {
			if ( this.settings.wrapper ) {
				toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) );
			}
			return toToggle;
		},

		defaultShowErrors: function() {
			var i, elements;
			for ( i = 0; this.errorList[i]; i++ ) {
				var error = this.errorList[i];
				if ( this.settings.highlight ) {
					this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
				}
				this.showLabel( error.element, error.message );
			}
			if ( this.errorList.length ) {
				this.toShow = this.toShow.add( this.containers );
			}
			if ( this.settings.success ) {
				for ( i = 0; this.successList[i]; i++ ) {
					this.showLabel( this.successList[i] );
				}
			}
			if ( this.settings.unhighlight ) {
				for ( i = 0, elements = this.validElements(); elements[i]; i++ ) {
					this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
				}
			}
			this.toHide = this.toHide.not( this.toShow );
			this.hideErrors();
			this.addWrapper( this.toShow ).show();
		},

		validElements: function() {
			return this.currentElements.not(this.invalidElements());
		},

		invalidElements: function() {
			return $(this.errorList).map(function() {
				return this.element;
			});
		},

		showLabel: function( element, message ) {
			var label = this.errorsFor( element );
			if ( label.length ) {
				// refresh error/success class
				label.removeClass( this.settings.validClass ).addClass( this.settings.errorClass );
				// replace message on existing label
				label.html(message);
			} else {
				// create label
				label = $("<" + this.settings.errorElement + ">")
					.attr("for", this.idOrName(element))
					.addClass(this.settings.errorClass)
					.html(message || "");
				if ( this.settings.wrapper ) {
					// make sure the element is visible, even in IE
					// actually showing the wrapped element is handled elsewhere
					label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
				}
				if ( !this.labelContainer.append(label).length ) {
					if ( this.settings.errorPlacement ) {
						this.settings.errorPlacement(label, $(element) );
					} else {
						label.insertAfter(element);
					}
				}
			}
			if ( !message && this.settings.success ) {
				label.text("");
				if ( typeof this.settings.success === "string" ) {
					label.addClass( this.settings.success );
				} else {
					this.settings.success( label, element );
				}
			}
			this.toShow = this.toShow.add(label);
		},

		errorsFor: function( element ) {
			var name = this.idOrName(element);
			return this.errors().filter(function() {
				return $(this).attr("for") === name;
			});
		},

		idOrName: function( element ) {
			return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
		},

		validationTargetFor: function( element ) {
			// if radio/checkbox, validate first element in group instead
			if ( this.checkable(element) ) {
				element = this.findByName( element.name ).not(this.settings.ignore)[0];
			}
			return element;
		},

		checkable: function( element ) {
			return (/radio|checkbox/i).test(element.type);
		},

		findByName: function( name ) {
			return $(this.currentForm).find("[name='" + name + "']");
		},

		getLength: function( value, element ) {
			switch( element.nodeName.toLowerCase() ) {
			case "select":
				return $("option:selected", element).length;
			case "input":
				if ( this.checkable( element) ) {
					return this.findByName(element.name).filter(":checked").length;
				}
			}
			return value.length;
		},

		depend: function( param, element ) {
			return this.dependTypes[typeof param] ? this.dependTypes[typeof param](param, element) : true;
		},

		dependTypes: {
			"boolean": function( param, element ) {
				return param;
			},
			"string": function( param, element ) {
				return !!$(param, element.form).length;
			},
			"function": function( param, element ) {
				return param(element);
			}
		},

		optional: function( element ) {
			var val = this.elementValue(element);
			return !$.validator.methods.required.call(this, val, element) && "dependency-mismatch";
		},

		startRequest: function( element ) {
			if ( !this.pending[element.name] ) {
				this.pendingRequest++;
				this.pending[element.name] = true;
			}
		},

		stopRequest: function( element, valid ) {
			this.pendingRequest--;
			// sometimes synchronization fails, make sure pendingRequest is never < 0
			if ( this.pendingRequest < 0 ) {
				this.pendingRequest = 0;
			}
			delete this.pending[element.name];
			if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) {
				$(this.currentForm).submit();
				this.formSubmitted = false;
			} else if (!valid && this.pendingRequest === 0 && this.formSubmitted) {
				$(this.currentForm).triggerHandler("invalid-form", [this]);
				this.formSubmitted = false;
			}
		},

		previousValue: function( element ) {
			return $.data(element, "previousValue") || $.data(element, "previousValue", {
				old: null,
				valid: true,
				message: this.defaultMessage( element, "remote" )
			});
		}

	},

	classRuleSettings: {
		required: {required: true},
		email: {email: true},
		url: {url: true},
		date: {date: true},
		dateISO: {dateISO: true},
		number: {number: true},
		digits: {digits: true},
		creditcard: {creditcard: true}
	},

	addClassRules: function( className, rules ) {
		if ( className.constructor === String ) {
			this.classRuleSettings[className] = rules;
		} else {
			$.extend(this.classRuleSettings, className);
		}
	},

	classRules: function( element ) {
		var rules = {};
		var classes = $(element).attr("class");
		if ( classes ) {
			$.each(classes.split(" "), function() {
				if ( this in $.validator.classRuleSettings ) {
					$.extend(rules, $.validator.classRuleSettings[this]);
				}
			});
		}
		return rules;
	},

	attributeRules: function( element ) {
		var rules = {};
		var $element = $(element);
		var type = $element[0].getAttribute("type");

		for (var method in $.validator.methods) {
			var value;

			// support for <input required> in both html5 and older browsers
			if ( method === "required" ) {
				value = $element.get(0).getAttribute(method);
				// Some browsers return an empty string for the required attribute
				// and non-HTML5 browsers might have required="" markup
				if ( value === "" ) {
					value = true;
				}
				// force non-HTML5 browsers to return bool
				value = !!value;
			} else {
				value = $element.attr(method);
			}

			// convert the value to a number for number inputs, and for text for backwards compability
			// allows type="date" and others to be compared as strings
			if ( /min|max/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) {
				value = Number(value);
			}

			if ( value ) {
				rules[method] = value;
			} else if ( type === method && type !== 'range' ) {
				// exception: the jquery validate 'range' method
				// does not test for the html5 'range' type
				rules[method] = true;
			}
		}

		// maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs
		if ( rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength) ) {
			delete rules.maxlength;
		}

		return rules;
	},

	dataRules: function( element ) {
		var method, value,
			rules = {}, $element = $(element);
		for (method in $.validator.methods) {
			value = $element.data("rule-" + method.toLowerCase());
			if ( value !== undefined ) {
				rules[method] = value;
			}
		}
		return rules;
	},

	staticRules: function( element ) {
		var rules = {};
		var validator = $.data(element.form, "validator");
		if ( validator.settings.rules ) {
			rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {};
		}
		return rules;
	},

	normalizeRules: function( rules, element ) {
		// handle dependency check
		$.each(rules, function( prop, val ) {
			// ignore rule when param is explicitly false, eg. required:false
			if ( val === false ) {
				delete rules[prop];
				return;
			}
			if ( val.param || val.depends ) {
				var keepRule = true;
				switch (typeof val.depends) {
				case "string":
					keepRule = !!$(val.depends, element.form).length;
					break;
				case "function":
					keepRule = val.depends.call(element, element);
					break;
				}
				if ( keepRule ) {
					rules[prop] = val.param !== undefined ? val.param : true;
				} else {
					delete rules[prop];
				}
			}
		});

		// evaluate parameters
		$.each(rules, function( rule, parameter ) {
			rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter;
		});

		// clean number parameters
		$.each(['minlength', 'maxlength'], function() {
			if ( rules[this] ) {
				rules[this] = Number(rules[this]);
			}
		});
		$.each(['rangelength', 'range'], function() {
			var parts;
			if ( rules[this] ) {
				if ( $.isArray(rules[this]) ) {
					rules[this] = [Number(rules[this][0]), Number(rules[this][1])];
				} else if ( typeof rules[this] === "string" ) {
					parts = rules[this].split(/[\s,]+/);
					rules[this] = [Number(parts[0]), Number(parts[1])];
				}
			}
		});

		if ( $.validator.autoCreateRanges ) {
			// auto-create ranges
			if ( rules.min && rules.max ) {
				rules.range = [rules.min, rules.max];
				delete rules.min;
				delete rules.max;
			}
			if ( rules.minlength && rules.maxlength ) {
				rules.rangelength = [rules.minlength, rules.maxlength];
				delete rules.minlength;
				delete rules.maxlength;
			}
		}

		return rules;
	},

	// Converts a simple string to a {string: true} rule, e.g., "required" to {required:true}
	normalizeRule: function( data ) {
		if ( typeof data === "string" ) {
			var transformed = {};
			$.each(data.split(/\s/), function() {
				transformed[this] = true;
			});
			data = transformed;
		}
		return data;
	},

	// http://docs.jquery.com/Plugins/Validation/Validator/addMethod
	addMethod: function( name, method, message ) {
		$.validator.methods[name] = method;
		$.validator.messages[name] = message !== undefined ? message : $.validator.messages[name];
		if ( method.length < 3 ) {
			$.validator.addClassRules(name, $.validator.normalizeRule(name));
		}
	},

	methods: {

		// http://docs.jquery.com/Plugins/Validation/Methods/required
		required: function( value, element, param ) {
			// check if dependency is met
			if ( !this.depend(param, element) ) {
				return "dependency-mismatch";
			}
			if ( element.nodeName.toLowerCase() === "select" ) {
				// could be an array for select-multiple or a string, both are fine this way
				var val = $(element).val();
				return val && val.length > 0;
			}
			if ( this.checkable(element) ) {
				return this.getLength(value, element) > 0;
			}
			return $.trim(value).length > 0;
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/email
		email: function( value, element ) {
			// contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
			return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(value);
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/url
		url: function( value, element ) {
			// contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
			return this.optional(element) || /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/date
		date: function( value, element ) {
			return this.optional(element) || !/Invalid|NaN/.test(new Date(value).toString());
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/dateISO
		dateISO: function( value, element ) {
			return this.optional(element) || /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value);
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/number
		number: function( value, element ) {
			return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value);
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/digits
		digits: function( value, element ) {
			return this.optional(element) || /^\d+$/.test(value);
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/creditcard
		// based on http://en.wikipedia.org/wiki/Luhn
		creditcard: function( value, element ) {
			if ( this.optional(element) ) {
				return "dependency-mismatch";
			}
			// accept only spaces, digits and dashes
			if ( /[^0-9 \-]+/.test(value) ) {
				return false;
			}
			var nCheck = 0,
				nDigit = 0,
				bEven = false;

			value = value.replace(/\D/g, "");

			for (var n = value.length - 1; n >= 0; n--) {
				var cDigit = value.charAt(n);
				nDigit = parseInt(cDigit, 10);
				if ( bEven ) {
					if ( (nDigit *= 2) > 9 ) {
						nDigit -= 9;
					}
				}
				nCheck += nDigit;
				bEven = !bEven;
			}

			return (nCheck % 10) === 0;
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/minlength
		minlength: function( value, element, param ) {
			var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element);
			return this.optional(element) || length >= param;
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/maxlength
		maxlength: function( value, element, param ) {
			var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element);
			return this.optional(element) || length <= param;
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/rangelength
		rangelength: function( value, element, param ) {
			var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element);
			return this.optional(element) || ( length >= param[0] && length <= param[1] );
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/min
		min: function( value, element, param ) {
			return this.optional(element) || value >= param;
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/max
		max: function( value, element, param ) {
			return this.optional(element) || value <= param;
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/range
		range: function( value, element, param ) {
			return this.optional(element) || ( value >= param[0] && value <= param[1] );
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/equalTo
		equalTo: function( value, element, param ) {
			// bind to the blur event of the target in order to revalidate whenever the target field is updated
			// TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
			var target = $(param);
			if ( this.settings.onfocusout ) {
				target.unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
					$(element).valid();
				});
			}
			return value === target.val();
		},

		// http://docs.jquery.com/Plugins/Validation/Methods/remote
		remote: function( value, element, param ) {
			if ( this.optional(element) ) {
				return "dependency-mismatch";
			}

			var previous = this.previousValue(element);
			if (!this.settings.messages[element.name] ) {
				this.settings.messages[element.name] = {};
			}
			previous.originalMessage = this.settings.messages[element.name].remote;
			this.settings.messages[element.name].remote = previous.message;

			param = typeof param === "string" && {url:param} || param;

			if ( previous.old === value ) {
				return previous.valid;
			}

			previous.old = value;
			var validator = this;
			this.startRequest(element);
			var data = {};
			data[element.name] = value;
			$.ajax($.extend(true, {
				url: param,
				mode: "abort",
				port: "validate" + element.name,
				dataType: "json",
				data: data,
				success: function( response ) {
					validator.settings.messages[element.name].remote = previous.originalMessage;
					var valid = response === true || response === "true";
					if ( valid ) {
						var submitted = validator.formSubmitted;
						validator.prepareElement(element);
						validator.formSubmitted = submitted;
						validator.successList.push(element);
						delete validator.invalid[element.name];
						validator.showErrors();
					} else {
						var errors = {};
						var message = response || validator.defaultMessage( element, "remote" );
						errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
						validator.invalid[element.name] = true;
						validator.showErrors(errors);
					}
					previous.valid = valid;
					validator.stopRequest(element, valid);
				}
			}, param));
			return "pending";
		}

	}

});

// deprecated, use $.validator.format instead
$.format = $.validator.format;

}(jQuery));

// ajax mode: abort
// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
(function($) {
	var pendingRequests = {};
	// Use a prefilter if available (1.5+)
	if ( $.ajaxPrefilter ) {
		$.ajaxPrefilter(function( settings, _, xhr ) {
			var port = settings.port;
			if ( settings.mode === "abort" ) {
				if ( pendingRequests[port] ) {
					pendingRequests[port].abort();
				}
				pendingRequests[port] = xhr;
			}
		});
	} else {
		// Proxy ajax
		var ajax = $.ajax;
		$.ajax = function( settings ) {
			var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode,
				port = ( "port" in settings ? settings : $.ajaxSettings ).port;
			if ( mode === "abort" ) {
				if ( pendingRequests[port] ) {
					pendingRequests[port].abort();
				}
				pendingRequests[port] = ajax.apply(this, arguments);
				return pendingRequests[port];
			}
			return ajax.apply(this, arguments);
		};
	}
}(jQuery));

// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target
(function($) {
	$.extend($.fn, {
		validateDelegate: function( delegate, type, handler ) {
			return this.bind(type, function( event ) {
				var target = $(event.target);
				if ( target.is(delegate) ) {
					return handler.apply(target, arguments);
				}
			});
		}
	});
}(jQuery));

//! moment.js
//! version : 2.27.0
//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
//! license : MIT
//! momentjs.com

;(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    global.moment = factory()
}(this, (function () { 'use strict';

    var hookCallback;

    function hooks() {
        return hookCallback.apply(null, arguments);
    }

    // This is done to register the method called with moment()
    // without creating circular dependencies.
    function setHookCallback(callback) {
        hookCallback = callback;
    }

    function isArray(input) {
        return (
            input instanceof Array ||
            Object.prototype.toString.call(input) === '[object Array]'
        );
    }

    function isObject(input) {
        // IE8 will treat undefined and null as object if it wasn't for
        // input != null
        return (
            input != null &&
            Object.prototype.toString.call(input) === '[object Object]'
        );
    }

    function hasOwnProp(a, b) {
        return Object.prototype.hasOwnProperty.call(a, b);
    }

    function isObjectEmpty(obj) {
        if (Object.getOwnPropertyNames) {
            return Object.getOwnPropertyNames(obj).length === 0;
        } else {
            var k;
            for (k in obj) {
                if (hasOwnProp(obj, k)) {
                    return false;
                }
            }
            return true;
        }
    }

    function isUndefined(input) {
        return input === void 0;
    }

    function isNumber(input) {
        return (
            typeof input === 'number' ||
            Object.prototype.toString.call(input) === '[object Number]'
        );
    }

    function isDate(input) {
        return (
            input instanceof Date ||
            Object.prototype.toString.call(input) === '[object Date]'
        );
    }

    function map(arr, fn) {
        var res = [],
            i;
        for (i = 0; i < arr.length; ++i) {
            res.push(fn(arr[i], i));
        }
        return res;
    }

    function extend(a, b) {
        for (var i in b) {
            if (hasOwnProp(b, i)) {
                a[i] = b[i];
            }
        }

        if (hasOwnProp(b, 'toString')) {
            a.toString = b.toString;
        }

        if (hasOwnProp(b, 'valueOf')) {
            a.valueOf = b.valueOf;
        }

        return a;
    }

    function createUTC(input, format, locale, strict) {
        return createLocalOrUTC(input, format, locale, strict, true).utc();
    }

    function defaultParsingFlags() {
        // We need to deep clone this object.
        return {
            empty: false,
            unusedTokens: [],
            unusedInput: [],
            overflow: -2,
            charsLeftOver: 0,
            nullInput: false,
            invalidEra: null,
            invalidMonth: null,
            invalidFormat: false,
            userInvalidated: false,
            iso: false,
            parsedDateParts: [],
            era: null,
            meridiem: null,
            rfc2822: false,
            weekdayMismatch: false,
        };
    }

    function getParsingFlags(m) {
        if (m._pf == null) {
            m._pf = defaultParsingFlags();
        }
        return m._pf;
    }

    var some;
    if (Array.prototype.some) {
        some = Array.prototype.some;
    } else {
        some = function (fun) {
            var t = Object(this),
                len = t.length >>> 0,
                i;

            for (i = 0; i < len; i++) {
                if (i in t && fun.call(this, t[i], i, t)) {
                    return true;
                }
            }

            return false;
        };
    }

    function isValid(m) {
        if (m._isValid == null) {
            var flags = getParsingFlags(m),
                parsedParts = some.call(flags.parsedDateParts, function (i) {
                    return i != null;
                }),
                isNowValid =
                    !isNaN(m._d.getTime()) &&
                    flags.overflow < 0 &&
                    !flags.empty &&
                    !flags.invalidEra &&
                    !flags.invalidMonth &&
                    !flags.invalidWeekday &&
                    !flags.weekdayMismatch &&
                    !flags.nullInput &&
                    !flags.invalidFormat &&
                    !flags.userInvalidated &&
                    (!flags.meridiem || (flags.meridiem && parsedParts));

            if (m._strict) {
                isNowValid =
                    isNowValid &&
                    flags.charsLeftOver === 0 &&
                    flags.unusedTokens.length === 0 &&
                    flags.bigHour === undefined;
            }

            if (Object.isFrozen == null || !Object.isFrozen(m)) {
                m._isValid = isNowValid;
            } else {
                return isNowValid;
            }
        }
        return m._isValid;
    }

    function createInvalid(flags) {
        var m = createUTC(NaN);
        if (flags != null) {
            extend(getParsingFlags(m), flags);
        } else {
            getParsingFlags(m).userInvalidated = true;
        }

        return m;
    }

    // Plugins that add properties should also add the key here (null value),
    // so we can properly clone ourselves.
    var momentProperties = (hooks.momentProperties = []),
        updateInProgress = false;

    function copyConfig(to, from) {
        var i, prop, val;

        if (!isUndefined(from._isAMomentObject)) {
            to._isAMomentObject = from._isAMomentObject;
        }
        if (!isUndefined(from._i)) {
            to._i = from._i;
        }
        if (!isUndefined(from._f)) {
            to._f = from._f;
        }
        if (!isUndefined(from._l)) {
            to._l = from._l;
        }
        if (!isUndefined(from._strict)) {
            to._strict = from._strict;
        }
        if (!isUndefined(from._tzm)) {
            to._tzm = from._tzm;
        }
        if (!isUndefined(from._isUTC)) {
            to._isUTC = from._isUTC;
        }
        if (!isUndefined(from._offset)) {
            to._offset = from._offset;
        }
        if (!isUndefined(from._pf)) {
            to._pf = getParsingFlags(from);
        }
        if (!isUndefined(from._locale)) {
            to._locale = from._locale;
        }

        if (momentProperties.length > 0) {
            for (i = 0; i < momentProperties.length; i++) {
                prop = momentProperties[i];
                val = from[prop];
                if (!isUndefined(val)) {
                    to[prop] = val;
                }
            }
        }

        return to;
    }

    // Moment prototype object
    function Moment(config) {
        copyConfig(this, config);
        this._d = new Date(config._d != null ? config._d.getTime() : NaN);
        if (!this.isValid()) {
            this._d = new Date(NaN);
        }
        // Prevent infinite loop in case updateOffset creates new moment
        // objects.
        if (updateInProgress === false) {
            updateInProgress = true;
            hooks.updateOffset(this);
            updateInProgress = false;
        }
    }

    function isMoment(obj) {
        return (
            obj instanceof Moment || (obj != null && obj._isAMomentObject != null)
        );
    }

    function warn(msg) {
        if (
            hooks.suppressDeprecationWarnings === false &&
            typeof console !== 'undefined' &&
            console.warn
        ) {
            console.warn('Deprecation warning: ' + msg);
        }
    }

    function deprecate(msg, fn) {
        var firstTime = true;

        return extend(function () {
            if (hooks.deprecationHandler != null) {
                hooks.deprecationHandler(null, msg);
            }
            if (firstTime) {
                var args = [],
                    arg,
                    i,
                    key;
                for (i = 0; i < arguments.length; i++) {
                    arg = '';
                    if (typeof arguments[i] === 'object') {
                        arg += '\n[' + i + '] ';
                        for (key in arguments[0]) {
                            if (hasOwnProp(arguments[0], key)) {
                                arg += key + ': ' + arguments[0][key] + ', ';
                            }
                        }
                        arg = arg.slice(0, -2); // Remove trailing comma and space
                    } else {
                        arg = arguments[i];
                    }
                    args.push(arg);
                }
                warn(
                    msg +
                        '\nArguments: ' +
                        Array.prototype.slice.call(args).join('') +
                        '\n' +
                        new Error().stack
                );
                firstTime = false;
            }
            return fn.apply(this, arguments);
        }, fn);
    }

    var deprecations = {};

    function deprecateSimple(name, msg) {
        if (hooks.deprecationHandler != null) {
            hooks.deprecationHandler(name, msg);
        }
        if (!deprecations[name]) {
            warn(msg);
            deprecations[name] = true;
        }
    }

    hooks.suppressDeprecationWarnings = false;
    hooks.deprecationHandler = null;

    function isFunction(input) {
        return (
            (typeof Function !== 'undefined' && input instanceof Function) ||
            Object.prototype.toString.call(input) === '[object Function]'
        );
    }

    function set(config) {
        var prop, i;
        for (i in config) {
            if (hasOwnProp(config, i)) {
                prop = config[i];
                if (isFunction(prop)) {
                    this[i] = prop;
                } else {
                    this['_' + i] = prop;
                }
            }
        }
        this._config = config;
        // Lenient ordinal parsing accepts just a number in addition to
        // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
        // TODO: Remove "ordinalParse" fallback in next major release.
        this._dayOfMonthOrdinalParseLenient = new RegExp(
            (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
                '|' +
                /\d{1,2}/.source
        );
    }

    function mergeConfigs(parentConfig, childConfig) {
        var res = extend({}, parentConfig),
            prop;
        for (prop in childConfig) {
            if (hasOwnProp(childConfig, prop)) {
                if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
                    res[prop] = {};
                    extend(res[prop], parentConfig[prop]);
                    extend(res[prop], childConfig[prop]);
                } else if (childConfig[prop] != null) {
                    res[prop] = childConfig[prop];
                } else {
                    delete res[prop];
                }
            }
        }
        for (prop in parentConfig) {
            if (
                hasOwnProp(parentConfig, prop) &&
                !hasOwnProp(childConfig, prop) &&
                isObject(parentConfig[prop])
            ) {
                // make sure changes to properties don't modify parent config
                res[prop] = extend({}, res[prop]);
            }
        }
        return res;
    }

    function Locale(config) {
        if (config != null) {
            this.set(config);
        }
    }

    var keys;

    if (Object.keys) {
        keys = Object.keys;
    } else {
        keys = function (obj) {
            var i,
                res = [];
            for (i in obj) {
                if (hasOwnProp(obj, i)) {
                    res.push(i);
                }
            }
            return res;
        };
    }

    var defaultCalendar = {
        sameDay: '[Today at] LT',
        nextDay: '[Tomorrow at] LT',
        nextWeek: 'dddd [at] LT',
        lastDay: '[Yesterday at] LT',
        lastWeek: '[Last] dddd [at] LT',
        sameElse: 'L',
    };

    function calendar(key, mom, now) {
        var output = this._calendar[key] || this._calendar['sameElse'];
        return isFunction(output) ? output.call(mom, now) : output;
    }

    function zeroFill(number, targetLength, forceSign) {
        var absNumber = '' + Math.abs(number),
            zerosToFill = targetLength - absNumber.length,
            sign = number >= 0;
        return (
            (sign ? (forceSign ? '+' : '') : '-') +
            Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) +
            absNumber
        );
    }

    var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,
        localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,
        formatFunctions = {},
        formatTokenFunctions = {};

    // token:    'M'
    // padded:   ['MM', 2]
    // ordinal:  'Mo'
    // callback: function () { this.month() + 1 }
    function addFormatToken(token, padded, ordinal, callback) {
        var func = callback;
        if (typeof callback === 'string') {
            func = function () {
                return this[callback]();
            };
        }
        if (token) {
            formatTokenFunctions[token] = func;
        }
        if (padded) {
            formatTokenFunctions[padded[0]] = function () {
                return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
            };
        }
        if (ordinal) {
            formatTokenFunctions[ordinal] = function () {
                return this.localeData().ordinal(
                    func.apply(this, arguments),
                    token
                );
            };
        }
    }

    function removeFormattingTokens(input) {
        if (input.match(/\[[\s\S]/)) {
            return input.replace(/^\[|\]$/g, '');
        }
        return input.replace(/\\/g, '');
    }

    function makeFormatFunction(format) {
        var array = format.match(formattingTokens),
            i,
            length;

        for (i = 0, length = array.length; i < length; i++) {
            if (formatTokenFunctions[array[i]]) {
                array[i] = formatTokenFunctions[array[i]];
            } else {
                array[i] = removeFormattingTokens(array[i]);
            }
        }

        return function (mom) {
            var output = '',
                i;
            for (i = 0; i < length; i++) {
                output += isFunction(array[i])
                    ? array[i].call(mom, format)
                    : array[i];
            }
            return output;
        };
    }

    // format date using native date object
    function formatMoment(m, format) {
        if (!m.isValid()) {
            return m.localeData().invalidDate();
        }

        format = expandFormat(format, m.localeData());
        formatFunctions[format] =
            formatFunctions[format] || makeFormatFunction(format);

        return formatFunctions[format](m);
    }

    function expandFormat(format, locale) {
        var i = 5;

        function replaceLongDateFormatTokens(input) {
            return locale.longDateFormat(input) || input;
        }

        localFormattingTokens.lastIndex = 0;
        while (i >= 0 && localFormattingTokens.test(format)) {
            format = format.replace(
                localFormattingTokens,
                replaceLongDateFormatTokens
            );
            localFormattingTokens.lastIndex = 0;
            i -= 1;
        }

        return format;
    }

    var defaultLongDateFormat = {
        LTS: 'h:mm:ss A',
        LT: 'h:mm A',
        L: 'MM/DD/YYYY',
        LL: 'MMMM D, YYYY',
        LLL: 'MMMM D, YYYY h:mm A',
        LLLL: 'dddd, MMMM D, YYYY h:mm A',
    };

    function longDateFormat(key) {
        var format = this._longDateFormat[key],
            formatUpper = this._longDateFormat[key.toUpperCase()];

        if (format || !formatUpper) {
            return format;
        }

        this._longDateFormat[key] = formatUpper
            .match(formattingTokens)
            .map(function (tok) {
                if (
                    tok === 'MMMM' ||
                    tok === 'MM' ||
                    tok === 'DD' ||
                    tok === 'dddd'
                ) {
                    return tok.slice(1);
                }
                return tok;
            })
            .join('');

        return this._longDateFormat[key];
    }

    var defaultInvalidDate = 'Invalid date';

    function invalidDate() {
        return this._invalidDate;
    }

    var defaultOrdinal = '%d',
        defaultDayOfMonthOrdinalParse = /\d{1,2}/;

    function ordinal(number) {
        return this._ordinal.replace('%d', number);
    }

    var defaultRelativeTime = {
        future: 'in %s',
        past: '%s ago',
        s: 'a few seconds',
        ss: '%d seconds',
        m: 'a minute',
        mm: '%d minutes',
        h: 'an hour',
        hh: '%d hours',
        d: 'a day',
        dd: '%d days',
        w: 'a week',
        ww: '%d weeks',
        M: 'a month',
        MM: '%d months',
        y: 'a year',
        yy: '%d years',
    };

    function relativeTime(number, withoutSuffix, string, isFuture) {
        var output = this._relativeTime[string];
        return isFunction(output)
            ? output(number, withoutSuffix, string, isFuture)
            : output.replace(/%d/i, number);
    }

    function pastFuture(diff, output) {
        var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
        return isFunction(format) ? format(output) : format.replace(/%s/i, output);
    }

    var aliases = {};

    function addUnitAlias(unit, shorthand) {
        var lowerCase = unit.toLowerCase();
        aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
    }

    function normalizeUnits(units) {
        return typeof units === 'string'
            ? aliases[units] || aliases[units.toLowerCase()]
            : undefined;
    }

    function normalizeObjectUnits(inputObject) {
        var normalizedInput = {},
            normalizedProp,
            prop;

        for (prop in inputObject) {
            if (hasOwnProp(inputObject, prop)) {
                normalizedProp = normalizeUnits(prop);
                if (normalizedProp) {
                    normalizedInput[normalizedProp] = inputObject[prop];
                }
            }
        }

        return normalizedInput;
    }

    var priorities = {};

    function addUnitPriority(unit, priority) {
        priorities[unit] = priority;
    }

    function getPrioritizedUnits(unitsObj) {
        var units = [],
            u;
        for (u in unitsObj) {
            if (hasOwnProp(unitsObj, u)) {
                units.push({ unit: u, priority: priorities[u] });
            }
        }
        units.sort(function (a, b) {
            return a.priority - b.priority;
        });
        return units;
    }

    function isLeapYear(year) {
        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
    }

    function absFloor(number) {
        if (number < 0) {
            // -0 -> 0
            return Math.ceil(number) || 0;
        } else {
            return Math.floor(number);
        }
    }

    function toInt(argumentForCoercion) {
        var coercedNumber = +argumentForCoercion,
            value = 0;

        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
            value = absFloor(coercedNumber);
        }

        return value;
    }

    function makeGetSet(unit, keepTime) {
        return function (value) {
            if (value != null) {
                set$1(this, unit, value);
                hooks.updateOffset(this, keepTime);
                return this;
            } else {
                return get(this, unit);
            }
        };
    }

    function get(mom, unit) {
        return mom.isValid()
            ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()
            : NaN;
    }

    function set$1(mom, unit, value) {
        if (mom.isValid() && !isNaN(value)) {
            if (
                unit === 'FullYear' &&
                isLeapYear(mom.year()) &&
                mom.month() === 1 &&
                mom.date() === 29
            ) {
                value = toInt(value);
                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](
                    value,
                    mom.month(),
                    daysInMonth(value, mom.month())
                );
            } else {
                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
            }
        }
    }

    // MOMENTS

    function stringGet(units) {
        units = normalizeUnits(units);
        if (isFunction(this[units])) {
            return this[units]();
        }
        return this;
    }

    function stringSet(units, value) {
        if (typeof units === 'object') {
            units = normalizeObjectUnits(units);
            var prioritized = getPrioritizedUnits(units),
                i;
            for (i = 0; i < prioritized.length; i++) {
                this[prioritized[i].unit](units[prioritized[i].unit]);
            }
        } else {
            units = normalizeUnits(units);
            if (isFunction(this[units])) {
                return this[units](value);
            }
        }
        return this;
    }

    var match1 = /\d/, //       0 - 9
        match2 = /\d\d/, //      00 - 99
        match3 = /\d{3}/, //     000 - 999
        match4 = /\d{4}/, //    0000 - 9999
        match6 = /[+-]?\d{6}/, // -999999 - 999999
        match1to2 = /\d\d?/, //       0 - 99
        match3to4 = /\d\d\d\d?/, //     999 - 9999
        match5to6 = /\d\d\d\d\d\d?/, //   99999 - 999999
        match1to3 = /\d{1,3}/, //       0 - 999
        match1to4 = /\d{1,4}/, //       0 - 9999
        match1to6 = /[+-]?\d{1,6}/, // -999999 - 999999
        matchUnsigned = /\d+/, //       0 - inf
        matchSigned = /[+-]?\d+/, //    -inf - inf
        matchOffset = /Z|[+-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
        matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z
        matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
        // any word (or two) characters or numbers including two/three word month in arabic.
        // includes scottish gaelic two word and hyphenated months
        matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,
        regexes;

    regexes = {};

    function addRegexToken(token, regex, strictRegex) {
        regexes[token] = isFunction(regex)
            ? regex
            : function (isStrict, localeData) {
                  return isStrict && strictRegex ? strictRegex : regex;
              };
    }

    function getParseRegexForToken(token, config) {
        if (!hasOwnProp(regexes, token)) {
            return new RegExp(unescapeFormat(token));
        }

        return regexes[token](config._strict, config._locale);
    }

    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
    function unescapeFormat(s) {
        return regexEscape(
            s
                .replace('\\', '')
                .replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (
                    matched,
                    p1,
                    p2,
                    p3,
                    p4
                ) {
                    return p1 || p2 || p3 || p4;
                })
        );
    }

    function regexEscape(s) {
        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
    }

    var tokens = {};

    function addParseToken(token, callback) {
        var i,
            func = callback;
        if (typeof token === 'string') {
            token = [token];
        }
        if (isNumber(callback)) {
            func = function (input, array) {
                array[callback] = toInt(input);
            };
        }
        for (i = 0; i < token.length; i++) {
            tokens[token[i]] = func;
        }
    }

    function addWeekParseToken(token, callback) {
        addParseToken(token, function (input, array, config, token) {
            config._w = config._w || {};
            callback(input, config._w, config, token);
        });
    }

    function addTimeToArrayFromToken(token, input, config) {
        if (input != null && hasOwnProp(tokens, token)) {
            tokens[token](input, config._a, config, token);
        }
    }

    var YEAR = 0,
        MONTH = 1,
        DATE = 2,
        HOUR = 3,
        MINUTE = 4,
        SECOND = 5,
        MILLISECOND = 6,
        WEEK = 7,
        WEEKDAY = 8;

    function mod(n, x) {
        return ((n % x) + x) % x;
    }

    var indexOf;

    if (Array.prototype.indexOf) {
        indexOf = Array.prototype.indexOf;
    } else {
        indexOf = function (o) {
            // I know
            var i;
            for (i = 0; i < this.length; ++i) {
                if (this[i] === o) {
                    return i;
                }
            }
            return -1;
        };
    }

    function daysInMonth(year, month) {
        if (isNaN(year) || isNaN(month)) {
            return NaN;
        }
        var modMonth = mod(month, 12);
        year += (month - modMonth) / 12;
        return modMonth === 1
            ? isLeapYear(year)
                ? 29
                : 28
            : 31 - ((modMonth % 7) % 2);
    }

    // FORMATTING

    addFormatToken('M', ['MM', 2], 'Mo', function () {
        return this.month() + 1;
    });

    addFormatToken('MMM', 0, 0, function (format) {
        return this.localeData().monthsShort(this, format);
    });

    addFormatToken('MMMM', 0, 0, function (format) {
        return this.localeData().months(this, format);
    });

    // ALIASES

    addUnitAlias('month', 'M');

    // PRIORITY

    addUnitPriority('month', 8);

    // PARSING

    addRegexToken('M', match1to2);
    addRegexToken('MM', match1to2, match2);
    addRegexToken('MMM', function (isStrict, locale) {
        return locale.monthsShortRegex(isStrict);
    });
    addRegexToken('MMMM', function (isStrict, locale) {
        return locale.monthsRegex(isStrict);
    });

    addParseToken(['M', 'MM'], function (input, array) {
        array[MONTH] = toInt(input) - 1;
    });

    addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
        var month = config._locale.monthsParse(input, token, config._strict);
        // if we didn't find a month name, mark the date as invalid.
        if (month != null) {
            array[MONTH] = month;
        } else {
            getParsingFlags(config).invalidMonth = input;
        }
    });

    // LOCALES

    var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split(
            '_'
        ),
        defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split(
            '_'
        ),
        MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,
        defaultMonthsShortRegex = matchWord,
        defaultMonthsRegex = matchWord;

    function localeMonths(m, format) {
        if (!m) {
            return isArray(this._months)
                ? this._months
                : this._months['standalone'];
        }
        return isArray(this._months)
            ? this._months[m.month()]
            : this._months[
                  (this._months.isFormat || MONTHS_IN_FORMAT).test(format)
                      ? 'format'
                      : 'standalone'
              ][m.month()];
    }

    function localeMonthsShort(m, format) {
        if (!m) {
            return isArray(this._monthsShort)
                ? this._monthsShort
                : this._monthsShort['standalone'];
        }
        return isArray(this._monthsShort)
            ? this._monthsShort[m.month()]
            : this._monthsShort[
                  MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'
              ][m.month()];
    }

    function handleStrictParse(monthName, format, strict) {
        var i,
            ii,
            mom,
            llc = monthName.toLocaleLowerCase();
        if (!this._monthsParse) {
            // this is not used
            this._monthsParse = [];
            this._longMonthsParse = [];
            this._shortMonthsParse = [];
            for (i = 0; i < 12; ++i) {
                mom = createUTC([2000, i]);
                this._shortMonthsParse[i] = this.monthsShort(
                    mom,
                    ''
                ).toLocaleLowerCase();
                this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
            }
        }

        if (strict) {
            if (format === 'MMM') {
                ii = indexOf.call(this._shortMonthsParse, llc);
                return ii !== -1 ? ii : null;
            } else {
                ii = indexOf.call(this._longMonthsParse, llc);
                return ii !== -1 ? ii : null;
            }
        } else {
            if (format === 'MMM') {
                ii = indexOf.call(this._shortMonthsParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._longMonthsParse, llc);
                return ii !== -1 ? ii : null;
            } else {
                ii = indexOf.call(this._longMonthsParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._shortMonthsParse, llc);
                return ii !== -1 ? ii : null;
            }
        }
    }

    function localeMonthsParse(monthName, format, strict) {
        var i, mom, regex;

        if (this._monthsParseExact) {
            return handleStrictParse.call(this, monthName, format, strict);
        }

        if (!this._monthsParse) {
            this._monthsParse = [];
            this._longMonthsParse = [];
            this._shortMonthsParse = [];
        }

        // TODO: add sorting
        // Sorting makes sure if one month (or abbr) is a prefix of another
        // see sorting in computeMonthsParse
        for (i = 0; i < 12; i++) {
            // make the regex if we don't have it already
            mom = createUTC([2000, i]);
            if (strict && !this._longMonthsParse[i]) {
                this._longMonthsParse[i] = new RegExp(
                    '^' + this.months(mom, '').replace('.', '') + '$',
                    'i'
                );
                this._shortMonthsParse[i] = new RegExp(
                    '^' + this.monthsShort(mom, '').replace('.', '') + '$',
                    'i'
                );
            }
            if (!strict && !this._monthsParse[i]) {
                regex =
                    '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
                this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
            }
            // test the regex
            if (
                strict &&
                format === 'MMMM' &&
                this._longMonthsParse[i].test(monthName)
            ) {
                return i;
            } else if (
                strict &&
                format === 'MMM' &&
                this._shortMonthsParse[i].test(monthName)
            ) {
                return i;
            } else if (!strict && this._monthsParse[i].test(monthName)) {
                return i;
            }
        }
    }

    // MOMENTS

    function setMonth(mom, value) {
        var dayOfMonth;

        if (!mom.isValid()) {
            // No op
            return mom;
        }

        if (typeof value === 'string') {
            if (/^\d+$/.test(value)) {
                value = toInt(value);
            } else {
                value = mom.localeData().monthsParse(value);
                // TODO: Another silent failure?
                if (!isNumber(value)) {
                    return mom;
                }
            }
        }

        dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
        mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
        return mom;
    }

    function getSetMonth(value) {
        if (value != null) {
            setMonth(this, value);
            hooks.updateOffset(this, true);
            return this;
        } else {
            return get(this, 'Month');
        }
    }

    function getDaysInMonth() {
        return daysInMonth(this.year(), this.month());
    }

    function monthsShortRegex(isStrict) {
        if (this._monthsParseExact) {
            if (!hasOwnProp(this, '_monthsRegex')) {
                computeMonthsParse.call(this);
            }
            if (isStrict) {
                return this._monthsShortStrictRegex;
            } else {
                return this._monthsShortRegex;
            }
        } else {
            if (!hasOwnProp(this, '_monthsShortRegex')) {
                this._monthsShortRegex = defaultMonthsShortRegex;
            }
            return this._monthsShortStrictRegex && isStrict
                ? this._monthsShortStrictRegex
                : this._monthsShortRegex;
        }
    }

    function monthsRegex(isStrict) {
        if (this._monthsParseExact) {
            if (!hasOwnProp(this, '_monthsRegex')) {
                computeMonthsParse.call(this);
            }
            if (isStrict) {
                return this._monthsStrictRegex;
            } else {
                return this._monthsRegex;
            }
        } else {
            if (!hasOwnProp(this, '_monthsRegex')) {
                this._monthsRegex = defaultMonthsRegex;
            }
            return this._monthsStrictRegex && isStrict
                ? this._monthsStrictRegex
                : this._monthsRegex;
        }
    }

    function computeMonthsParse() {
        function cmpLenRev(a, b) {
            return b.length - a.length;
        }

        var shortPieces = [],
            longPieces = [],
            mixedPieces = [],
            i,
            mom;
        for (i = 0; i < 12; i++) {
            // make the regex if we don't have it already
            mom = createUTC([2000, i]);
            shortPieces.push(this.monthsShort(mom, ''));
            longPieces.push(this.months(mom, ''));
            mixedPieces.push(this.months(mom, ''));
            mixedPieces.push(this.monthsShort(mom, ''));
        }
        // Sorting makes sure if one month (or abbr) is a prefix of another it
        // will match the longer piece.
        shortPieces.sort(cmpLenRev);
        longPieces.sort(cmpLenRev);
        mixedPieces.sort(cmpLenRev);
        for (i = 0; i < 12; i++) {
            shortPieces[i] = regexEscape(shortPieces[i]);
            longPieces[i] = regexEscape(longPieces[i]);
        }
        for (i = 0; i < 24; i++) {
            mixedPieces[i] = regexEscape(mixedPieces[i]);
        }

        this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
        this._monthsShortRegex = this._monthsRegex;
        this._monthsStrictRegex = new RegExp(
            '^(' + longPieces.join('|') + ')',
            'i'
        );
        this._monthsShortStrictRegex = new RegExp(
            '^(' + shortPieces.join('|') + ')',
            'i'
        );
    }

    // FORMATTING

    addFormatToken('Y', 0, 0, function () {
        var y = this.year();
        return y <= 9999 ? zeroFill(y, 4) : '+' + y;
    });

    addFormatToken(0, ['YY', 2], 0, function () {
        return this.year() % 100;
    });

    addFormatToken(0, ['YYYY', 4], 0, 'year');
    addFormatToken(0, ['YYYYY', 5], 0, 'year');
    addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');

    // ALIASES

    addUnitAlias('year', 'y');

    // PRIORITIES

    addUnitPriority('year', 1);

    // PARSING

    addRegexToken('Y', matchSigned);
    addRegexToken('YY', match1to2, match2);
    addRegexToken('YYYY', match1to4, match4);
    addRegexToken('YYYYY', match1to6, match6);
    addRegexToken('YYYYYY', match1to6, match6);

    addParseToken(['YYYYY', 'YYYYYY'], YEAR);
    addParseToken('YYYY', function (input, array) {
        array[YEAR] =
            input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
    });
    addParseToken('YY', function (input, array) {
        array[YEAR] = hooks.parseTwoDigitYear(input);
    });
    addParseToken('Y', function (input, array) {
        array[YEAR] = parseInt(input, 10);
    });

    // HELPERS

    function daysInYear(year) {
        return isLeapYear(year) ? 366 : 365;
    }

    // HOOKS

    hooks.parseTwoDigitYear = function (input) {
        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
    };

    // MOMENTS

    var getSetYear = makeGetSet('FullYear', true);

    function getIsLeapYear() {
        return isLeapYear(this.year());
    }

    function createDate(y, m, d, h, M, s, ms) {
        // can't just apply() to create a date:
        // https://stackoverflow.com/q/181348
        var date;
        // the date constructor remaps years 0-99 to 1900-1999
        if (y < 100 && y >= 0) {
            // preserve leap years using a full 400 year cycle, then reset
            date = new Date(y + 400, m, d, h, M, s, ms);
            if (isFinite(date.getFullYear())) {
                date.setFullYear(y);
            }
        } else {
            date = new Date(y, m, d, h, M, s, ms);
        }

        return date;
    }

    function createUTCDate(y) {
        var date, args;
        // the Date.UTC function remaps years 0-99 to 1900-1999
        if (y < 100 && y >= 0) {
            args = Array.prototype.slice.call(arguments);
            // preserve leap years using a full 400 year cycle, then reset
            args[0] = y + 400;
            date = new Date(Date.UTC.apply(null, args));
            if (isFinite(date.getUTCFullYear())) {
                date.setUTCFullYear(y);
            }
        } else {
            date = new Date(Date.UTC.apply(null, arguments));
        }

        return date;
    }

    // start-of-first-week - start-of-year
    function firstWeekOffset(year, dow, doy) {
        var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
            fwd = 7 + dow - doy,
            // first-week day local weekday -- which local weekday is fwd
            fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;

        return -fwdlw + fwd - 1;
    }

    // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
    function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
        var localWeekday = (7 + weekday - dow) % 7,
            weekOffset = firstWeekOffset(year, dow, doy),
            dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
            resYear,
            resDayOfYear;

        if (dayOfYear <= 0) {
            resYear = year - 1;
            resDayOfYear = daysInYear(resYear) + dayOfYear;
        } else if (dayOfYear > daysInYear(year)) {
            resYear = year + 1;
            resDayOfYear = dayOfYear - daysInYear(year);
        } else {
            resYear = year;
            resDayOfYear = dayOfYear;
        }

        return {
            year: resYear,
            dayOfYear: resDayOfYear,
        };
    }

    function weekOfYear(mom, dow, doy) {
        var weekOffset = firstWeekOffset(mom.year(), dow, doy),
            week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
            resWeek,
            resYear;

        if (week < 1) {
            resYear = mom.year() - 1;
            resWeek = week + weeksInYear(resYear, dow, doy);
        } else if (week > weeksInYear(mom.year(), dow, doy)) {
            resWeek = week - weeksInYear(mom.year(), dow, doy);
            resYear = mom.year() + 1;
        } else {
            resYear = mom.year();
            resWeek = week;
        }

        return {
            week: resWeek,
            year: resYear,
        };
    }

    function weeksInYear(year, dow, doy) {
        var weekOffset = firstWeekOffset(year, dow, doy),
            weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
        return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
    }

    // FORMATTING

    addFormatToken('w', ['ww', 2], 'wo', 'week');
    addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');

    // ALIASES

    addUnitAlias('week', 'w');
    addUnitAlias('isoWeek', 'W');

    // PRIORITIES

    addUnitPriority('week', 5);
    addUnitPriority('isoWeek', 5);

    // PARSING

    addRegexToken('w', match1to2);
    addRegexToken('ww', match1to2, match2);
    addRegexToken('W', match1to2);
    addRegexToken('WW', match1to2, match2);

    addWeekParseToken(['w', 'ww', 'W', 'WW'], function (
        input,
        week,
        config,
        token
    ) {
        week[token.substr(0, 1)] = toInt(input);
    });

    // HELPERS

    // LOCALES

    function localeWeek(mom) {
        return weekOfYear(mom, this._week.dow, this._week.doy).week;
    }

    var defaultLocaleWeek = {
        dow: 0, // Sunday is the first day of the week.
        doy: 6, // The week that contains Jan 6th is the first week of the year.
    };

    function localeFirstDayOfWeek() {
        return this._week.dow;
    }

    function localeFirstDayOfYear() {
        return this._week.doy;
    }

    // MOMENTS

    function getSetWeek(input) {
        var week = this.localeData().week(this);
        return input == null ? week : this.add((input - week) * 7, 'd');
    }

    function getSetISOWeek(input) {
        var week = weekOfYear(this, 1, 4).week;
        return input == null ? week : this.add((input - week) * 7, 'd');
    }

    // FORMATTING

    addFormatToken('d', 0, 'do', 'day');

    addFormatToken('dd', 0, 0, function (format) {
        return this.localeData().weekdaysMin(this, format);
    });

    addFormatToken('ddd', 0, 0, function (format) {
        return this.localeData().weekdaysShort(this, format);
    });

    addFormatToken('dddd', 0, 0, function (format) {
        return this.localeData().weekdays(this, format);
    });

    addFormatToken('e', 0, 0, 'weekday');
    addFormatToken('E', 0, 0, 'isoWeekday');

    // ALIASES

    addUnitAlias('day', 'd');
    addUnitAlias('weekday', 'e');
    addUnitAlias('isoWeekday', 'E');

    // PRIORITY
    addUnitPriority('day', 11);
    addUnitPriority('weekday', 11);
    addUnitPriority('isoWeekday', 11);

    // PARSING

    addRegexToken('d', match1to2);
    addRegexToken('e', match1to2);
    addRegexToken('E', match1to2);
    addRegexToken('dd', function (isStrict, locale) {
        return locale.weekdaysMinRegex(isStrict);
    });
    addRegexToken('ddd', function (isStrict, locale) {
        return locale.weekdaysShortRegex(isStrict);
    });
    addRegexToken('dddd', function (isStrict, locale) {
        return locale.weekdaysRegex(isStrict);
    });

    addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
        var weekday = config._locale.weekdaysParse(input, token, config._strict);
        // if we didn't get a weekday name, mark the date as invalid
        if (weekday != null) {
            week.d = weekday;
        } else {
            getParsingFlags(config).invalidWeekday = input;
        }
    });

    addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
        week[token] = toInt(input);
    });

    // HELPERS

    function parseWeekday(input, locale) {
        if (typeof input !== 'string') {
            return input;
        }

        if (!isNaN(input)) {
            return parseInt(input, 10);
        }

        input = locale.weekdaysParse(input);
        if (typeof input === 'number') {
            return input;
        }

        return null;
    }

    function parseIsoWeekday(input, locale) {
        if (typeof input === 'string') {
            return locale.weekdaysParse(input) % 7 || 7;
        }
        return isNaN(input) ? null : input;
    }

    // LOCALES
    function shiftWeekdays(ws, n) {
        return ws.slice(n, 7).concat(ws.slice(0, n));
    }

    var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split(
            '_'
        ),
        defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
        defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
        defaultWeekdaysRegex = matchWord,
        defaultWeekdaysShortRegex = matchWord,
        defaultWeekdaysMinRegex = matchWord;

    function localeWeekdays(m, format) {
        var weekdays = isArray(this._weekdays)
            ? this._weekdays
            : this._weekdays[
                  m && m !== true && this._weekdays.isFormat.test(format)
                      ? 'format'
                      : 'standalone'
              ];
        return m === true
            ? shiftWeekdays(weekdays, this._week.dow)
            : m
            ? weekdays[m.day()]
            : weekdays;
    }

    function localeWeekdaysShort(m) {
        return m === true
            ? shiftWeekdays(this._weekdaysShort, this._week.dow)
            : m
            ? this._weekdaysShort[m.day()]
            : this._weekdaysShort;
    }

    function localeWeekdaysMin(m) {
        return m === true
            ? shiftWeekdays(this._weekdaysMin, this._week.dow)
            : m
            ? this._weekdaysMin[m.day()]
            : this._weekdaysMin;
    }

    function handleStrictParse$1(weekdayName, format, strict) {
        var i,
            ii,
            mom,
            llc = weekdayName.toLocaleLowerCase();
        if (!this._weekdaysParse) {
            this._weekdaysParse = [];
            this._shortWeekdaysParse = [];
            this._minWeekdaysParse = [];

            for (i = 0; i < 7; ++i) {
                mom = createUTC([2000, 1]).day(i);
                this._minWeekdaysParse[i] = this.weekdaysMin(
                    mom,
                    ''
                ).toLocaleLowerCase();
                this._shortWeekdaysParse[i] = this.weekdaysShort(
                    mom,
                    ''
                ).toLocaleLowerCase();
                this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
            }
        }

        if (strict) {
            if (format === 'dddd') {
                ii = indexOf.call(this._weekdaysParse, llc);
                return ii !== -1 ? ii : null;
            } else if (format === 'ddd') {
                ii = indexOf.call(this._shortWeekdaysParse, llc);
                return ii !== -1 ? ii : null;
            } else {
                ii = indexOf.call(this._minWeekdaysParse, llc);
                return ii !== -1 ? ii : null;
            }
        } else {
            if (format === 'dddd') {
                ii = indexOf.call(this._weekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._shortWeekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._minWeekdaysParse, llc);
                return ii !== -1 ? ii : null;
            } else if (format === 'ddd') {
                ii = indexOf.call(this._shortWeekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._weekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._minWeekdaysParse, llc);
                return ii !== -1 ? ii : null;
            } else {
                ii = indexOf.call(this._minWeekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._weekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._shortWeekdaysParse, llc);
                return ii !== -1 ? ii : null;
            }
        }
    }

    function localeWeekdaysParse(weekdayName, format, strict) {
        var i, mom, regex;

        if (this._weekdaysParseExact) {
            return handleStrictParse$1.call(this, weekdayName, format, strict);
        }

        if (!this._weekdaysParse) {
            this._weekdaysParse = [];
            this._minWeekdaysParse = [];
            this._shortWeekdaysParse = [];
            this._fullWeekdaysParse = [];
        }

        for (i = 0; i < 7; i++) {
            // make the regex if we don't have it already

            mom = createUTC([2000, 1]).day(i);
            if (strict && !this._fullWeekdaysParse[i]) {
                this._fullWeekdaysParse[i] = new RegExp(
                    '^' + this.weekdays(mom, '').replace('.', '\\.?') + '$',
                    'i'
                );
                this._shortWeekdaysParse[i] = new RegExp(
                    '^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$',
                    'i'
                );
                this._minWeekdaysParse[i] = new RegExp(
                    '^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$',
                    'i'
                );
            }
            if (!this._weekdaysParse[i]) {
                regex =
                    '^' +
                    this.weekdays(mom, '') +
                    '|^' +
                    this.weekdaysShort(mom, '') +
                    '|^' +
                    this.weekdaysMin(mom, '');
                this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
            }
            // test the regex
            if (
                strict &&
                format === 'dddd' &&
                this._fullWeekdaysParse[i].test(weekdayName)
            ) {
                return i;
            } else if (
                strict &&
                format === 'ddd' &&
                this._shortWeekdaysParse[i].test(weekdayName)
            ) {
                return i;
            } else if (
                strict &&
                format === 'dd' &&
                this._minWeekdaysParse[i].test(weekdayName)
            ) {
                return i;
            } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
                return i;
            }
        }
    }

    // MOMENTS

    function getSetDayOfWeek(input) {
        if (!this.isValid()) {
            return input != null ? this : NaN;
        }
        var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
        if (input != null) {
            input = parseWeekday(input, this.localeData());
            return this.add(input - day, 'd');
        } else {
            return day;
        }
    }

    function getSetLocaleDayOfWeek(input) {
        if (!this.isValid()) {
            return input != null ? this : NaN;
        }
        var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
        return input == null ? weekday : this.add(input - weekday, 'd');
    }

    function getSetISODayOfWeek(input) {
        if (!this.isValid()) {
            return input != null ? this : NaN;
        }

        // behaves the same as moment#day except
        // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
        // as a setter, sunday should belong to the previous week.

        if (input != null) {
            var weekday = parseIsoWeekday(input, this.localeData());
            return this.day(this.day() % 7 ? weekday : weekday - 7);
        } else {
            return this.day() || 7;
        }
    }

    function weekdaysRegex(isStrict) {
        if (this._weekdaysParseExact) {
            if (!hasOwnProp(this, '_weekdaysRegex')) {
                computeWeekdaysParse.call(this);
            }
            if (isStrict) {
                return this._weekdaysStrictRegex;
            } else {
                return this._weekdaysRegex;
            }
        } else {
            if (!hasOwnProp(this, '_weekdaysRegex')) {
                this._weekdaysRegex = defaultWeekdaysRegex;
            }
            return this._weekdaysStrictRegex && isStrict
                ? this._weekdaysStrictRegex
                : this._weekdaysRegex;
        }
    }

    function weekdaysShortRegex(isStrict) {
        if (this._weekdaysParseExact) {
            if (!hasOwnProp(this, '_weekdaysRegex')) {
                computeWeekdaysParse.call(this);
            }
            if (isStrict) {
                return this._weekdaysShortStrictRegex;
            } else {
                return this._weekdaysShortRegex;
            }
        } else {
            if (!hasOwnProp(this, '_weekdaysShortRegex')) {
                this._weekdaysShortRegex = defaultWeekdaysShortRegex;
            }
            return this._weekdaysShortStrictRegex && isStrict
                ? this._weekdaysShortStrictRegex
                : this._weekdaysShortRegex;
        }
    }

    function weekdaysMinRegex(isStrict) {
        if (this._weekdaysParseExact) {
            if (!hasOwnProp(this, '_weekdaysRegex')) {
                computeWeekdaysParse.call(this);
            }
            if (isStrict) {
                return this._weekdaysMinStrictRegex;
            } else {
                return this._weekdaysMinRegex;
            }
        } else {
            if (!hasOwnProp(this, '_weekdaysMinRegex')) {
                this._weekdaysMinRegex = defaultWeekdaysMinRegex;
            }
            return this._weekdaysMinStrictRegex && isStrict
                ? this._weekdaysMinStrictRegex
                : this._weekdaysMinRegex;
        }
    }

    function computeWeekdaysParse() {
        function cmpLenRev(a, b) {
            return b.length - a.length;
        }

        var minPieces = [],
            shortPieces = [],
            longPieces = [],
            mixedPieces = [],
            i,
            mom,
            minp,
            shortp,
            longp;
        for (i = 0; i < 7; i++) {
            // make the regex if we don't have it already
            mom = createUTC([2000, 1]).day(i);
            minp = regexEscape(this.weekdaysMin(mom, ''));
            shortp = regexEscape(this.weekdaysShort(mom, ''));
            longp = regexEscape(this.weekdays(mom, ''));
            minPieces.push(minp);
            shortPieces.push(shortp);
            longPieces.push(longp);
            mixedPieces.push(minp);
            mixedPieces.push(shortp);
            mixedPieces.push(longp);
        }
        // Sorting makes sure if one weekday (or abbr) is a prefix of another it
        // will match the longer piece.
        minPieces.sort(cmpLenRev);
        shortPieces.sort(cmpLenRev);
        longPieces.sort(cmpLenRev);
        mixedPieces.sort(cmpLenRev);

        this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
        this._weekdaysShortRegex = this._weekdaysRegex;
        this._weekdaysMinRegex = this._weekdaysRegex;

        this._weekdaysStrictRegex = new RegExp(
            '^(' + longPieces.join('|') + ')',
            'i'
        );
        this._weekdaysShortStrictRegex = new RegExp(
            '^(' + shortPieces.join('|') + ')',
            'i'
        );
        this._weekdaysMinStrictRegex = new RegExp(
            '^(' + minPieces.join('|') + ')',
            'i'
        );
    }

    // FORMATTING

    function hFormat() {
        return this.hours() % 12 || 12;
    }

    function kFormat() {
        return this.hours() || 24;
    }

    addFormatToken('H', ['HH', 2], 0, 'hour');
    addFormatToken('h', ['hh', 2], 0, hFormat);
    addFormatToken('k', ['kk', 2], 0, kFormat);

    addFormatToken('hmm', 0, 0, function () {
        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
    });

    addFormatToken('hmmss', 0, 0, function () {
        return (
            '' +
            hFormat.apply(this) +
            zeroFill(this.minutes(), 2) +
            zeroFill(this.seconds(), 2)
        );
    });

    addFormatToken('Hmm', 0, 0, function () {
        return '' + this.hours() + zeroFill(this.minutes(), 2);
    });

    addFormatToken('Hmmss', 0, 0, function () {
        return (
            '' +
            this.hours() +
            zeroFill(this.minutes(), 2) +
            zeroFill(this.seconds(), 2)
        );
    });

    function meridiem(token, lowercase) {
        addFormatToken(token, 0, 0, function () {
            return this.localeData().meridiem(
                this.hours(),
                this.minutes(),
                lowercase
            );
        });
    }

    meridiem('a', true);
    meridiem('A', false);

    // ALIASES

    addUnitAlias('hour', 'h');

    // PRIORITY
    addUnitPriority('hour', 13);

    // PARSING

    function matchMeridiem(isStrict, locale) {
        return locale._meridiemParse;
    }

    addRegexToken('a', matchMeridiem);
    addRegexToken('A', matchMeridiem);
    addRegexToken('H', match1to2);
    addRegexToken('h', match1to2);
    addRegexToken('k', match1to2);
    addRegexToken('HH', match1to2, match2);
    addRegexToken('hh', match1to2, match2);
    addRegexToken('kk', match1to2, match2);

    addRegexToken('hmm', match3to4);
    addRegexToken('hmmss', match5to6);
    addRegexToken('Hmm', match3to4);
    addRegexToken('Hmmss', match5to6);

    addParseToken(['H', 'HH'], HOUR);
    addParseToken(['k', 'kk'], function (input, array, config) {
        var kInput = toInt(input);
        array[HOUR] = kInput === 24 ? 0 : kInput;
    });
    addParseToken(['a', 'A'], function (input, array, config) {
        config._isPm = config._locale.isPM(input);
        config._meridiem = input;
    });
    addParseToken(['h', 'hh'], function (input, array, config) {
        array[HOUR] = toInt(input);
        getParsingFlags(config).bigHour = true;
    });
    addParseToken('hmm', function (input, array, config) {
        var pos = input.length - 2;
        array[HOUR] = toInt(input.substr(0, pos));
        array[MINUTE] = toInt(input.substr(pos));
        getParsingFlags(config).bigHour = true;
    });
    addParseToken('hmmss', function (input, array, config) {
        var pos1 = input.length - 4,
            pos2 = input.length - 2;
        array[HOUR] = toInt(input.substr(0, pos1));
        array[MINUTE] = toInt(input.substr(pos1, 2));
        array[SECOND] = toInt(input.substr(pos2));
        getParsingFlags(config).bigHour = true;
    });
    addParseToken('Hmm', function (input, array, config) {
        var pos = input.length - 2;
        array[HOUR] = toInt(input.substr(0, pos));
        array[MINUTE] = toInt(input.substr(pos));
    });
    addParseToken('Hmmss', function (input, array, config) {
        var pos1 = input.length - 4,
            pos2 = input.length - 2;
        array[HOUR] = toInt(input.substr(0, pos1));
        array[MINUTE] = toInt(input.substr(pos1, 2));
        array[SECOND] = toInt(input.substr(pos2));
    });

    // LOCALES

    function localeIsPM(input) {
        // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
        // Using charAt should be more compatible.
        return (input + '').toLowerCase().charAt(0) === 'p';
    }

    var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i,
        // Setting the hour should keep the time, because the user explicitly
        // specified which hour they want. So trying to maintain the same hour (in
        // a new timezone) makes sense. Adding/subtracting hours does not follow
        // this rule.
        getSetHour = makeGetSet('Hours', true);

    function localeMeridiem(hours, minutes, isLower) {
        if (hours > 11) {
            return isLower ? 'pm' : 'PM';
        } else {
            return isLower ? 'am' : 'AM';
        }
    }

    var baseConfig = {
        calendar: defaultCalendar,
        longDateFormat: defaultLongDateFormat,
        invalidDate: defaultInvalidDate,
        ordinal: defaultOrdinal,
        dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
        relativeTime: defaultRelativeTime,

        months: defaultLocaleMonths,
        monthsShort: defaultLocaleMonthsShort,

        week: defaultLocaleWeek,

        weekdays: defaultLocaleWeekdays,
        weekdaysMin: defaultLocaleWeekdaysMin,
        weekdaysShort: defaultLocaleWeekdaysShort,

        meridiemParse: defaultLocaleMeridiemParse,
    };

    // internal storage for locale config files
    var locales = {},
        localeFamilies = {},
        globalLocale;

    function commonPrefix(arr1, arr2) {
        var i,
            minl = Math.min(arr1.length, arr2.length);
        for (i = 0; i < minl; i += 1) {
            if (arr1[i] !== arr2[i]) {
                return i;
            }
        }
        return minl;
    }

    function normalizeLocale(key) {
        return key ? key.toLowerCase().replace('_', '-') : key;
    }

    // pick the locale from the array
    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
    function chooseLocale(names) {
        var i = 0,
            j,
            next,
            locale,
            split;

        while (i < names.length) {
            split = normalizeLocale(names[i]).split('-');
            j = split.length;
            next = normalizeLocale(names[i + 1]);
            next = next ? next.split('-') : null;
            while (j > 0) {
                locale = loadLocale(split.slice(0, j).join('-'));
                if (locale) {
                    return locale;
                }
                if (
                    next &&
                    next.length >= j &&
                    commonPrefix(split, next) >= j - 1
                ) {
                    //the next array item is better than a shallower substring of this one
                    break;
                }
                j--;
            }
            i++;
        }
        return globalLocale;
    }

    function loadLocale(name) {
        var oldLocale = null,
            aliasedRequire;
        // TODO: Find a better way to register and load all the locales in Node
        if (
            locales[name] === undefined &&
            typeof module !== 'undefined' &&
            module &&
            module.exports
        ) {
            try {
                oldLocale = globalLocale._abbr;
                aliasedRequire = require;
                aliasedRequire('./locale/' + name);
                getSetGlobalLocale(oldLocale);
            } catch (e) {
                // mark as not found to avoid repeating expensive file require call causing high CPU
                // when trying to find en-US, en_US, en-us for every format call
                locales[name] = null; // null means not found
            }
        }
        return locales[name];
    }

    // This function will load locale and then set the global locale.  If
    // no arguments are passed in, it will simply return the current global
    // locale key.
    function getSetGlobalLocale(key, values) {
        var data;
        if (key) {
            if (isUndefined(values)) {
                data = getLocale(key);
            } else {
                data = defineLocale(key, values);
            }

            if (data) {
                // moment.duration._locale = moment._locale = data;
                globalLocale = data;
            } else {
                if (typeof console !== 'undefined' && console.warn) {
                    //warn user if arguments are passed but the locale could not be set
                    console.warn(
                        'Locale ' + key + ' not found. Did you forget to load it?'
                    );
                }
            }
        }

        return globalLocale._abbr;
    }

    function defineLocale(name, config) {
        if (config !== null) {
            var locale,
                parentConfig = baseConfig;
            config.abbr = name;
            if (locales[name] != null) {
                deprecateSimple(
                    'defineLocaleOverride',
                    'use moment.updateLocale(localeName, config) to change ' +
                        'an existing locale. moment.defineLocale(localeName, ' +
                        'config) should only be used for creating a new locale ' +
                        'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'
                );
                parentConfig = locales[name]._config;
            } else if (config.parentLocale != null) {
                if (locales[config.parentLocale] != null) {
                    parentConfig = locales[config.parentLocale]._config;
                } else {
                    locale = loadLocale(config.parentLocale);
                    if (locale != null) {
                        parentConfig = locale._config;
                    } else {
                        if (!localeFamilies[config.parentLocale]) {
                            localeFamilies[config.parentLocale] = [];
                        }
                        localeFamilies[config.parentLocale].push({
                            name: name,
                            config: config,
                        });
                        return null;
                    }
                }
            }
            locales[name] = new Locale(mergeConfigs(parentConfig, config));

            if (localeFamilies[name]) {
                localeFamilies[name].forEach(function (x) {
                    defineLocale(x.name, x.config);
                });
            }

            // backwards compat for now: also set the locale
            // make sure we set the locale AFTER all child locales have been
            // created, so we won't end up with the child locale set.
            getSetGlobalLocale(name);

            return locales[name];
        } else {
            // useful for testing
            delete locales[name];
            return null;
        }
    }

    function updateLocale(name, config) {
        if (config != null) {
            var locale,
                tmpLocale,
                parentConfig = baseConfig;

            if (locales[name] != null && locales[name].parentLocale != null) {
                // Update existing child locale in-place to avoid memory-leaks
                locales[name].set(mergeConfigs(locales[name]._config, config));
            } else {
                // MERGE
                tmpLocale = loadLocale(name);
                if (tmpLocale != null) {
                    parentConfig = tmpLocale._config;
                }
                config = mergeConfigs(parentConfig, config);
                if (tmpLocale == null) {
                    // updateLocale is called for creating a new locale
                    // Set abbr so it will have a name (getters return
                    // undefined otherwise).
                    config.abbr = name;
                }
                locale = new Locale(config);
                locale.parentLocale = locales[name];
                locales[name] = locale;
            }

            // backwards compat for now: also set the locale
            getSetGlobalLocale(name);
        } else {
            // pass null for config to unupdate, useful for tests
            if (locales[name] != null) {
                if (locales[name].parentLocale != null) {
                    locales[name] = locales[name].parentLocale;
                    if (name === getSetGlobalLocale()) {
                        getSetGlobalLocale(name);
                    }
                } else if (locales[name] != null) {
                    delete locales[name];
                }
            }
        }
        return locales[name];
    }

    // returns locale data
    function getLocale(key) {
        var locale;

        if (key && key._locale && key._locale._abbr) {
            key = key._locale._abbr;
        }

        if (!key) {
            return globalLocale;
        }

        if (!isArray(key)) {
            //short-circuit everything else
            locale = loadLocale(key);
            if (locale) {
                return locale;
            }
            key = [key];
        }

        return chooseLocale(key);
    }

    function listLocales() {
        return keys(locales);
    }

    function checkOverflow(m) {
        var overflow,
            a = m._a;

        if (a && getParsingFlags(m).overflow === -2) {
            overflow =
                a[MONTH] < 0 || a[MONTH] > 11
                    ? MONTH
                    : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH])
                    ? DATE
                    : a[HOUR] < 0 ||
                      a[HOUR] > 24 ||
                      (a[HOUR] === 24 &&
                          (a[MINUTE] !== 0 ||
                              a[SECOND] !== 0 ||
                              a[MILLISECOND] !== 0))
                    ? HOUR
                    : a[MINUTE] < 0 || a[MINUTE] > 59
                    ? MINUTE
                    : a[SECOND] < 0 || a[SECOND] > 59
                    ? SECOND
                    : a[MILLISECOND] < 0 || a[MILLISECOND] > 999
                    ? MILLISECOND
                    : -1;

            if (
                getParsingFlags(m)._overflowDayOfYear &&
                (overflow < YEAR || overflow > DATE)
            ) {
                overflow = DATE;
            }
            if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
                overflow = WEEK;
            }
            if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
                overflow = WEEKDAY;
            }

            getParsingFlags(m).overflow = overflow;
        }

        return m;
    }

    // iso 8601 regex
    // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
    var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
        basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
        tzRegex = /Z|[+-]\d\d(?::?\d\d)?/,
        isoDates = [
            ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
            ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
            ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
            ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
            ['YYYY-DDD', /\d{4}-\d{3}/],
            ['YYYY-MM', /\d{4}-\d\d/, false],
            ['YYYYYYMMDD', /[+-]\d{10}/],
            ['YYYYMMDD', /\d{8}/],
            ['GGGG[W]WWE', /\d{4}W\d{3}/],
            ['GGGG[W]WW', /\d{4}W\d{2}/, false],
            ['YYYYDDD', /\d{7}/],
            ['YYYYMM', /\d{6}/, false],
            ['YYYY', /\d{4}/, false],
        ],
        // iso time formats and regexes
        isoTimes = [
            ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
            ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
            ['HH:mm:ss', /\d\d:\d\d:\d\d/],
            ['HH:mm', /\d\d:\d\d/],
            ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
            ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
            ['HHmmss', /\d\d\d\d\d\d/],
            ['HHmm', /\d\d\d\d/],
            ['HH', /\d\d/],
        ],
        aspNetJsonRegex = /^\/?Date\((-?\d+)/i,
        // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
        rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,
        obsOffsets = {
            UT: 0,
            GMT: 0,
            EDT: -4 * 60,
            EST: -5 * 60,
            CDT: -5 * 60,
            CST: -6 * 60,
            MDT: -6 * 60,
            MST: -7 * 60,
            PDT: -7 * 60,
            PST: -8 * 60,
        };

    // date from iso format
    function configFromISO(config) {
        var i,
            l,
            string = config._i,
            match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
            allowTime,
            dateFormat,
            timeFormat,
            tzFormat;

        if (match) {
            getParsingFlags(config).iso = true;

            for (i = 0, l = isoDates.length; i < l; i++) {
                if (isoDates[i][1].exec(match[1])) {
                    dateFormat = isoDates[i][0];
                    allowTime = isoDates[i][2] !== false;
                    break;
                }
            }
            if (dateFormat == null) {
                config._isValid = false;
                return;
            }
            if (match[3]) {
                for (i = 0, l = isoTimes.length; i < l; i++) {
                    if (isoTimes[i][1].exec(match[3])) {
                        // match[2] should be 'T' or space
                        timeFormat = (match[2] || ' ') + isoTimes[i][0];
                        break;
                    }
                }
                if (timeFormat == null) {
                    config._isValid = false;
                    return;
                }
            }
            if (!allowTime && timeFormat != null) {
                config._isValid = false;
                return;
            }
            if (match[4]) {
                if (tzRegex.exec(match[4])) {
                    tzFormat = 'Z';
                } else {
                    config._isValid = false;
                    return;
                }
            }
            config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
            configFromStringAndFormat(config);
        } else {
            config._isValid = false;
        }
    }

    function extractFromRFC2822Strings(
        yearStr,
        monthStr,
        dayStr,
        hourStr,
        minuteStr,
        secondStr
    ) {
        var result = [
            untruncateYear(yearStr),
            defaultLocaleMonthsShort.indexOf(monthStr),
            parseInt(dayStr, 10),
            parseInt(hourStr, 10),
            parseInt(minuteStr, 10),
        ];

        if (secondStr) {
            result.push(parseInt(secondStr, 10));
        }

        return result;
    }

    function untruncateYear(yearStr) {
        var year = parseInt(yearStr, 10);
        if (year <= 49) {
            return 2000 + year;
        } else if (year <= 999) {
            return 1900 + year;
        }
        return year;
    }

    function preprocessRFC2822(s) {
        // Remove comments and folding whitespace and replace multiple-spaces with a single space
        return s
            .replace(/\([^)]*\)|[\n\t]/g, ' ')
            .replace(/(\s\s+)/g, ' ')
            .replace(/^\s\s*/, '')
            .replace(/\s\s*$/, '');
    }

    function checkWeekday(weekdayStr, parsedInput, config) {
        if (weekdayStr) {
            // TODO: Replace the vanilla JS Date object with an independent day-of-week check.
            var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
                weekdayActual = new Date(
                    parsedInput[0],
                    parsedInput[1],
                    parsedInput[2]
                ).getDay();
            if (weekdayProvided !== weekdayActual) {
                getParsingFlags(config).weekdayMismatch = true;
                config._isValid = false;
                return false;
            }
        }
        return true;
    }

    function calculateOffset(obsOffset, militaryOffset, numOffset) {
        if (obsOffset) {
            return obsOffsets[obsOffset];
        } else if (militaryOffset) {
            // the only allowed military tz is Z
            return 0;
        } else {
            var hm = parseInt(numOffset, 10),
                m = hm % 100,
                h = (hm - m) / 100;
            return h * 60 + m;
        }
    }

    // date and time from ref 2822 format
    function configFromRFC2822(config) {
        var match = rfc2822.exec(preprocessRFC2822(config._i)),
            parsedArray;
        if (match) {
            parsedArray = extractFromRFC2822Strings(
                match[4],
                match[3],
                match[2],
                match[5],
                match[6],
                match[7]
            );
            if (!checkWeekday(match[1], parsedArray, config)) {
                return;
            }

            config._a = parsedArray;
            config._tzm = calculateOffset(match[8], match[9], match[10]);

            config._d = createUTCDate.apply(null, config._a);
            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);

            getParsingFlags(config).rfc2822 = true;
        } else {
            config._isValid = false;
        }
    }

    // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict
    function configFromString(config) {
        var matched = aspNetJsonRegex.exec(config._i);
        if (matched !== null) {
            config._d = new Date(+matched[1]);
            return;
        }

        configFromISO(config);
        if (config._isValid === false) {
            delete config._isValid;
        } else {
            return;
        }

        configFromRFC2822(config);
        if (config._isValid === false) {
            delete config._isValid;
        } else {
            return;
        }

        if (config._strict) {
            config._isValid = false;
        } else {
            // Final attempt, use Input Fallback
            hooks.createFromInputFallback(config);
        }
    }

    hooks.createFromInputFallback = deprecate(
        'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
            'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
            'discouraged and will be removed in an upcoming major release. Please refer to ' +
            'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
        function (config) {
            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
        }
    );

    // Pick the first defined of two or three arguments.
    function defaults(a, b, c) {
        if (a != null) {
            return a;
        }
        if (b != null) {
            return b;
        }
        return c;
    }

    function currentDateArray(config) {
        // hooks is actually the exported moment object
        var nowValue = new Date(hooks.now());
        if (config._useUTC) {
            return [
                nowValue.getUTCFullYear(),
                nowValue.getUTCMonth(),
                nowValue.getUTCDate(),
            ];
        }
        return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
    }

    // convert an array to a date.
    // the array should mirror the parameters below
    // note: all values past the year are optional and will default to the lowest possible value.
    // [year, month, day , hour, minute, second, millisecond]
    function configFromArray(config) {
        var i,
            date,
            input = [],
            currentDate,
            expectedWeekday,
            yearToUse;

        if (config._d) {
            return;
        }

        currentDate = currentDateArray(config);

        //compute day of the year from weeks and weekdays
        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
            dayOfYearFromWeekInfo(config);
        }

        //if the day of the year is set, figure out what it is
        if (config._dayOfYear != null) {
            yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);

            if (
                config._dayOfYear > daysInYear(yearToUse) ||
                config._dayOfYear === 0
            ) {
                getParsingFlags(config)._overflowDayOfYear = true;
            }

            date = createUTCDate(yearToUse, 0, config._dayOfYear);
            config._a[MONTH] = date.getUTCMonth();
            config._a[DATE] = date.getUTCDate();
        }

        // Default to current date.
        // * if no year, month, day of month are given, default to today
        // * if day of month is given, default month and year
        // * if month is given, default only year
        // * if year is given, don't default anything
        for (i = 0; i < 3 && config._a[i] == null; ++i) {
            config._a[i] = input[i] = currentDate[i];
        }

        // Zero out whatever was not defaulted, including time
        for (; i < 7; i++) {
            config._a[i] = input[i] =
                config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i];
        }

        // Check for 24:00:00.000
        if (
            config._a[HOUR] === 24 &&
            config._a[MINUTE] === 0 &&
            config._a[SECOND] === 0 &&
            config._a[MILLISECOND] === 0
        ) {
            config._nextDay = true;
            config._a[HOUR] = 0;
        }

        config._d = (config._useUTC ? createUTCDate : createDate).apply(
            null,
            input
        );
        expectedWeekday = config._useUTC
            ? config._d.getUTCDay()
            : config._d.getDay();

        // Apply timezone offset from input. The actual utcOffset can be changed
        // with parseZone.
        if (config._tzm != null) {
            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
        }

        if (config._nextDay) {
            config._a[HOUR] = 24;
        }

        // check for mismatching day of week
        if (
            config._w &&
            typeof config._w.d !== 'undefined' &&
            config._w.d !== expectedWeekday
        ) {
            getParsingFlags(config).weekdayMismatch = true;
        }
    }

    function dayOfYearFromWeekInfo(config) {
        var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek;

        w = config._w;
        if (w.GG != null || w.W != null || w.E != null) {
            dow = 1;
            doy = 4;

            // TODO: We need to take the current isoWeekYear, but that depends on
            // how we interpret now (local, utc, fixed offset). So create
            // a now version of current config (take local/utc/offset flags, and
            // create now).
            weekYear = defaults(
                w.GG,
                config._a[YEAR],
                weekOfYear(createLocal(), 1, 4).year
            );
            week = defaults(w.W, 1);
            weekday = defaults(w.E, 1);
            if (weekday < 1 || weekday > 7) {
                weekdayOverflow = true;
            }
        } else {
            dow = config._locale._week.dow;
            doy = config._locale._week.doy;

            curWeek = weekOfYear(createLocal(), dow, doy);

            weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);

            // Default to current week.
            week = defaults(w.w, curWeek.week);

            if (w.d != null) {
                // weekday -- low day numbers are considered next week
                weekday = w.d;
                if (weekday < 0 || weekday > 6) {
                    weekdayOverflow = true;
                }
            } else if (w.e != null) {
                // local weekday -- counting starts from beginning of week
                weekday = w.e + dow;
                if (w.e < 0 || w.e > 6) {
                    weekdayOverflow = true;
                }
            } else {
                // default to beginning of week
                weekday = dow;
            }
        }
        if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
            getParsingFlags(config)._overflowWeeks = true;
        } else if (weekdayOverflow != null) {
            getParsingFlags(config)._overflowWeekday = true;
        } else {
            temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
            config._a[YEAR] = temp.year;
            config._dayOfYear = temp.dayOfYear;
        }
    }

    // constant that refers to the ISO standard
    hooks.ISO_8601 = function () {};

    // constant that refers to the RFC 2822 form
    hooks.RFC_2822 = function () {};

    // date from string and format string
    function configFromStringAndFormat(config) {
        // TODO: Move this to another part of the creation flow to prevent circular deps
        if (config._f === hooks.ISO_8601) {
            configFromISO(config);
            return;
        }
        if (config._f === hooks.RFC_2822) {
            configFromRFC2822(config);
            return;
        }
        config._a = [];
        getParsingFlags(config).empty = true;

        // This array is used to make a Date, either with `new Date` or `Date.UTC`
        var string = '' + config._i,
            i,
            parsedInput,
            tokens,
            token,
            skipped,
            stringLength = string.length,
            totalParsedInputLength = 0,
            era;

        tokens =
            expandFormat(config._f, config._locale).match(formattingTokens) || [];

        for (i = 0; i < tokens.length; i++) {
            token = tokens[i];
            parsedInput = (string.match(getParseRegexForToken(token, config)) ||
                [])[0];
            if (parsedInput) {
                skipped = string.substr(0, string.indexOf(parsedInput));
                if (skipped.length > 0) {
                    getParsingFlags(config).unusedInput.push(skipped);
                }
                string = string.slice(
                    string.indexOf(parsedInput) + parsedInput.length
                );
                totalParsedInputLength += parsedInput.length;
            }
            // don't parse if it's not a known token
            if (formatTokenFunctions[token]) {
                if (parsedInput) {
                    getParsingFlags(config).empty = false;
                } else {
                    getParsingFlags(config).unusedTokens.push(token);
                }
                addTimeToArrayFromToken(token, parsedInput, config);
            } else if (config._strict && !parsedInput) {
                getParsingFlags(config).unusedTokens.push(token);
            }
        }

        // add remaining unparsed input length to the string
        getParsingFlags(config).charsLeftOver =
            stringLength - totalParsedInputLength;
        if (string.length > 0) {
            getParsingFlags(config).unusedInput.push(string);
        }

        // clear _12h flag if hour is <= 12
        if (
            config._a[HOUR] <= 12 &&
            getParsingFlags(config).bigHour === true &&
            config._a[HOUR] > 0
        ) {
            getParsingFlags(config).bigHour = undefined;
        }

        getParsingFlags(config).parsedDateParts = config._a.slice(0);
        getParsingFlags(config).meridiem = config._meridiem;
        // handle meridiem
        config._a[HOUR] = meridiemFixWrap(
            config._locale,
            config._a[HOUR],
            config._meridiem
        );

        // handle era
        era = getParsingFlags(config).era;
        if (era !== null) {
            config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]);
        }

        configFromArray(config);
        checkOverflow(config);
    }

    function meridiemFixWrap(locale, hour, meridiem) {
        var isPm;

        if (meridiem == null) {
            // nothing to do
            return hour;
        }
        if (locale.meridiemHour != null) {
            return locale.meridiemHour(hour, meridiem);
        } else if (locale.isPM != null) {
            // Fallback
            isPm = locale.isPM(meridiem);
            if (isPm && hour < 12) {
                hour += 12;
            }
            if (!isPm && hour === 12) {
                hour = 0;
            }
            return hour;
        } else {
            // this is not supposed to happen
            return hour;
        }
    }

    // date from string and array of format strings
    function configFromStringAndArray(config) {
        var tempConfig,
            bestMoment,
            scoreToBeat,
            i,
            currentScore,
            validFormatFound,
            bestFormatIsValid = false;

        if (config._f.length === 0) {
            getParsingFlags(config).invalidFormat = true;
            config._d = new Date(NaN);
            return;
        }

        for (i = 0; i < config._f.length; i++) {
            currentScore = 0;
            validFormatFound = false;
            tempConfig = copyConfig({}, config);
            if (config._useUTC != null) {
                tempConfig._useUTC = config._useUTC;
            }
            tempConfig._f = config._f[i];
            configFromStringAndFormat(tempConfig);

            if (isValid(tempConfig)) {
                validFormatFound = true;
            }

            // if there is any input that was not parsed add a penalty for that format
            currentScore += getParsingFlags(tempConfig).charsLeftOver;

            //or tokens
            currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;

            getParsingFlags(tempConfig).score = currentScore;

            if (!bestFormatIsValid) {
                if (
                    scoreToBeat == null ||
                    currentScore < scoreToBeat ||
                    validFormatFound
                ) {
                    scoreToBeat = currentScore;
                    bestMoment = tempConfig;
                    if (validFormatFound) {
                        bestFormatIsValid = true;
                    }
                }
            } else {
                if (currentScore < scoreToBeat) {
                    scoreToBeat = currentScore;
                    bestMoment = tempConfig;
                }
            }
        }

        extend(config, bestMoment || tempConfig);
    }

    function configFromObject(config) {
        if (config._d) {
            return;
        }

        var i = normalizeObjectUnits(config._i),
            dayOrDate = i.day === undefined ? i.date : i.day;
        config._a = map(
            [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond],
            function (obj) {
                return obj && parseInt(obj, 10);
            }
        );

        configFromArray(config);
    }

    function createFromConfig(config) {
        var res = new Moment(checkOverflow(prepareConfig(config)));
        if (res._nextDay) {
            // Adding is smart enough around DST
            res.add(1, 'd');
            res._nextDay = undefined;
        }

        return res;
    }

    function prepareConfig(config) {
        var input = config._i,
            format = config._f;

        config._locale = config._locale || getLocale(config._l);

        if (input === null || (format === undefined && input === '')) {
            return createInvalid({ nullInput: true });
        }

        if (typeof input === 'string') {
            config._i = input = config._locale.preparse(input);
        }

        if (isMoment(input)) {
            return new Moment(checkOverflow(input));
        } else if (isDate(input)) {
            config._d = input;
        } else if (isArray(format)) {
            configFromStringAndArray(config);
        } else if (format) {
            configFromStringAndFormat(config);
        } else {
            configFromInput(config);
        }

        if (!isValid(config)) {
            config._d = null;
        }

        return config;
    }

    function configFromInput(config) {
        var input = config._i;
        if (isUndefined(input)) {
            config._d = new Date(hooks.now());
        } else if (isDate(input)) {
            config._d = new Date(input.valueOf());
        } else if (typeof input === 'string') {
            configFromString(config);
        } else if (isArray(input)) {
            config._a = map(input.slice(0), function (obj) {
                return parseInt(obj, 10);
            });
            configFromArray(config);
        } else if (isObject(input)) {
            configFromObject(config);
        } else if (isNumber(input)) {
            // from milliseconds
            config._d = new Date(input);
        } else {
            hooks.createFromInputFallback(config);
        }
    }

    function createLocalOrUTC(input, format, locale, strict, isUTC) {
        var c = {};

        if (format === true || format === false) {
            strict = format;
            format = undefined;
        }

        if (locale === true || locale === false) {
            strict = locale;
            locale = undefined;
        }

        if (
            (isObject(input) && isObjectEmpty(input)) ||
            (isArray(input) && input.length === 0)
        ) {
            input = undefined;
        }
        // object construction must be done this way.
        // https://github.com/moment/moment/issues/1423
        c._isAMomentObject = true;
        c._useUTC = c._isUTC = isUTC;
        c._l = locale;
        c._i = input;
        c._f = format;
        c._strict = strict;

        return createFromConfig(c);
    }

    function createLocal(input, format, locale, strict) {
        return createLocalOrUTC(input, format, locale, strict, false);
    }

    var prototypeMin = deprecate(
            'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
            function () {
                var other = createLocal.apply(null, arguments);
                if (this.isValid() && other.isValid()) {
                    return other < this ? this : other;
                } else {
                    return createInvalid();
                }
            }
        ),
        prototypeMax = deprecate(
            'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
            function () {
                var other = createLocal.apply(null, arguments);
                if (this.isValid() && other.isValid()) {
                    return other > this ? this : other;
                } else {
                    return createInvalid();
                }
            }
        );

    // Pick a moment m from moments so that m[fn](other) is true for all
    // other. This relies on the function fn to be transitive.
    //
    // moments should either be an array of moment objects or an array, whose
    // first element is an array of moment objects.
    function pickBy(fn, moments) {
        var res, i;
        if (moments.length === 1 && isArray(moments[0])) {
            moments = moments[0];
        }
        if (!moments.length) {
            return createLocal();
        }
        res = moments[0];
        for (i = 1; i < moments.length; ++i) {
            if (!moments[i].isValid() || moments[i][fn](res)) {
                res = moments[i];
            }
        }
        return res;
    }

    // TODO: Use [].sort instead?
    function min() {
        var args = [].slice.call(arguments, 0);

        return pickBy('isBefore', args);
    }

    function max() {
        var args = [].slice.call(arguments, 0);

        return pickBy('isAfter', args);
    }

    var now = function () {
        return Date.now ? Date.now() : +new Date();
    };

    var ordering = [
        'year',
        'quarter',
        'month',
        'week',
        'day',
        'hour',
        'minute',
        'second',
        'millisecond',
    ];

    function isDurationValid(m) {
        var key,
            unitHasDecimal = false,
            i;
        for (key in m) {
            if (
                hasOwnProp(m, key) &&
                !(
                    indexOf.call(ordering, key) !== -1 &&
                    (m[key] == null || !isNaN(m[key]))
                )
            ) {
                return false;
            }
        }

        for (i = 0; i < ordering.length; ++i) {
            if (m[ordering[i]]) {
                if (unitHasDecimal) {
                    return false; // only allow non-integers for smallest unit
                }
                if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
                    unitHasDecimal = true;
                }
            }
        }

        return true;
    }

    function isValid$1() {
        return this._isValid;
    }

    function createInvalid$1() {
        return createDuration(NaN);
    }

    function Duration(duration) {
        var normalizedInput = normalizeObjectUnits(duration),
            years = normalizedInput.year || 0,
            quarters = normalizedInput.quarter || 0,
            months = normalizedInput.month || 0,
            weeks = normalizedInput.week || normalizedInput.isoWeek || 0,
            days = normalizedInput.day || 0,
            hours = normalizedInput.hour || 0,
            minutes = normalizedInput.minute || 0,
            seconds = normalizedInput.second || 0,
            milliseconds = normalizedInput.millisecond || 0;

        this._isValid = isDurationValid(normalizedInput);

        // representation for dateAddRemove
        this._milliseconds =
            +milliseconds +
            seconds * 1e3 + // 1000
            minutes * 6e4 + // 1000 * 60
            hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
        // Because of dateAddRemove treats 24 hours as different from a
        // day when working around DST, we need to store them separately
        this._days = +days + weeks * 7;
        // It is impossible to translate months into days without knowing
        // which months you are are talking about, so we have to store
        // it separately.
        this._months = +months + quarters * 3 + years * 12;

        this._data = {};

        this._locale = getLocale();

        this._bubble();
    }

    function isDuration(obj) {
        return obj instanceof Duration;
    }

    function absRound(number) {
        if (number < 0) {
            return Math.round(-1 * number) * -1;
        } else {
            return Math.round(number);
        }
    }

    // compare two arrays, return the number of differences
    function compareArrays(array1, array2, dontConvert) {
        var len = Math.min(array1.length, array2.length),
            lengthDiff = Math.abs(array1.length - array2.length),
            diffs = 0,
            i;
        for (i = 0; i < len; i++) {
            if (
                (dontConvert && array1[i] !== array2[i]) ||
                (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))
            ) {
                diffs++;
            }
        }
        return diffs + lengthDiff;
    }

    // FORMATTING

    function offset(token, separator) {
        addFormatToken(token, 0, 0, function () {
            var offset = this.utcOffset(),
                sign = '+';
            if (offset < 0) {
                offset = -offset;
                sign = '-';
            }
            return (
                sign +
                zeroFill(~~(offset / 60), 2) +
                separator +
                zeroFill(~~offset % 60, 2)
            );
        });
    }

    offset('Z', ':');
    offset('ZZ', '');

    // PARSING

    addRegexToken('Z', matchShortOffset);
    addRegexToken('ZZ', matchShortOffset);
    addParseToken(['Z', 'ZZ'], function (input, array, config) {
        config._useUTC = true;
        config._tzm = offsetFromString(matchShortOffset, input);
    });

    // HELPERS

    // timezone chunker
    // '+10:00' > ['10',  '00']
    // '-1530'  > ['-15', '30']
    var chunkOffset = /([\+\-]|\d\d)/gi;

    function offsetFromString(matcher, string) {
        var matches = (string || '').match(matcher),
            chunk,
            parts,
            minutes;

        if (matches === null) {
            return null;
        }

        chunk = matches[matches.length - 1] || [];
        parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
        minutes = +(parts[1] * 60) + toInt(parts[2]);

        return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes;
    }

    // Return a moment from input, that is local/utc/zone equivalent to model.
    function cloneWithOffset(input, model) {
        var res, diff;
        if (model._isUTC) {
            res = model.clone();
            diff =
                (isMoment(input) || isDate(input)
                    ? input.valueOf()
                    : createLocal(input).valueOf()) - res.valueOf();
            // Use low-level api, because this fn is low-level api.
            res._d.setTime(res._d.valueOf() + diff);
            hooks.updateOffset(res, false);
            return res;
        } else {
            return createLocal(input).local();
        }
    }

    function getDateOffset(m) {
        // On Firefox.24 Date#getTimezoneOffset returns a floating point.
        // https://github.com/moment/moment/pull/1871
        return -Math.round(m._d.getTimezoneOffset());
    }

    // HOOKS

    // This function will be called whenever a moment is mutated.
    // It is intended to keep the offset in sync with the timezone.
    hooks.updateOffset = function () {};

    // MOMENTS

    // keepLocalTime = true means only change the timezone, without
    // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
    // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
    // +0200, so we adjust the time as needed, to be valid.
    //
    // Keeping the time actually adds/subtracts (one hour)
    // from the actual represented time. That is why we call updateOffset
    // a second time. In case it wants us to change the offset again
    // _changeInProgress == true case, then we have to adjust, because
    // there is no such time in the given timezone.
    function getSetOffset(input, keepLocalTime, keepMinutes) {
        var offset = this._offset || 0,
            localAdjust;
        if (!this.isValid()) {
            return input != null ? this : NaN;
        }
        if (input != null) {
            if (typeof input === 'string') {
                input = offsetFromString(matchShortOffset, input);
                if (input === null) {
                    return this;
                }
            } else if (Math.abs(input) < 16 && !keepMinutes) {
                input = input * 60;
            }
            if (!this._isUTC && keepLocalTime) {
                localAdjust = getDateOffset(this);
            }
            this._offset = input;
            this._isUTC = true;
            if (localAdjust != null) {
                this.add(localAdjust, 'm');
            }
            if (offset !== input) {
                if (!keepLocalTime || this._changeInProgress) {
                    addSubtract(
                        this,
                        createDuration(input - offset, 'm'),
                        1,
                        false
                    );
                } else if (!this._changeInProgress) {
                    this._changeInProgress = true;
                    hooks.updateOffset(this, true);
                    this._changeInProgress = null;
                }
            }
            return this;
        } else {
            return this._isUTC ? offset : getDateOffset(this);
        }
    }

    function getSetZone(input, keepLocalTime) {
        if (input != null) {
            if (typeof input !== 'string') {
                input = -input;
            }

            this.utcOffset(input, keepLocalTime);

            return this;
        } else {
            return -this.utcOffset();
        }
    }

    function setOffsetToUTC(keepLocalTime) {
        return this.utcOffset(0, keepLocalTime);
    }

    function setOffsetToLocal(keepLocalTime) {
        if (this._isUTC) {
            this.utcOffset(0, keepLocalTime);
            this._isUTC = false;

            if (keepLocalTime) {
                this.subtract(getDateOffset(this), 'm');
            }
        }
        return this;
    }

    function setOffsetToParsedOffset() {
        if (this._tzm != null) {
            this.utcOffset(this._tzm, false, true);
        } else if (typeof this._i === 'string') {
            var tZone = offsetFromString(matchOffset, this._i);
            if (tZone != null) {
                this.utcOffset(tZone);
            } else {
                this.utcOffset(0, true);
            }
        }
        return this;
    }

    function hasAlignedHourOffset(input) {
        if (!this.isValid()) {
            return false;
        }
        input = input ? createLocal(input).utcOffset() : 0;

        return (this.utcOffset() - input) % 60 === 0;
    }

    function isDaylightSavingTime() {
        return (
            this.utcOffset() > this.clone().month(0).utcOffset() ||
            this.utcOffset() > this.clone().month(5).utcOffset()
        );
    }

    function isDaylightSavingTimeShifted() {
        if (!isUndefined(this._isDSTShifted)) {
            return this._isDSTShifted;
        }

        var c = {},
            other;

        copyConfig(c, this);
        c = prepareConfig(c);

        if (c._a) {
            other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
            this._isDSTShifted =
                this.isValid() && compareArrays(c._a, other.toArray()) > 0;
        } else {
            this._isDSTShifted = false;
        }

        return this._isDSTShifted;
    }

    function isLocal() {
        return this.isValid() ? !this._isUTC : false;
    }

    function isUtcOffset() {
        return this.isValid() ? this._isUTC : false;
    }

    function isUtc() {
        return this.isValid() ? this._isUTC && this._offset === 0 : false;
    }

    // ASP.NET json date format regex
    var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,
        // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
        // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
        // and further modified to allow for strings containing both week and day
        isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;

    function createDuration(input, key) {
        var duration = input,
            // matching against regexp is expensive, do it on demand
            match = null,
            sign,
            ret,
            diffRes;

        if (isDuration(input)) {
            duration = {
                ms: input._milliseconds,
                d: input._days,
                M: input._months,
            };
        } else if (isNumber(input) || !isNaN(+input)) {
            duration = {};
            if (key) {
                duration[key] = +input;
            } else {
                duration.milliseconds = +input;
            }
        } else if ((match = aspNetRegex.exec(input))) {
            sign = match[1] === '-' ? -1 : 1;
            duration = {
                y: 0,
                d: toInt(match[DATE]) * sign,
                h: toInt(match[HOUR]) * sign,
                m: toInt(match[MINUTE]) * sign,
                s: toInt(match[SECOND]) * sign,
                ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match
            };
        } else if ((match = isoRegex.exec(input))) {
            sign = match[1] === '-' ? -1 : 1;
            duration = {
                y: parseIso(match[2], sign),
                M: parseIso(match[3], sign),
                w: parseIso(match[4], sign),
                d: parseIso(match[5], sign),
                h: parseIso(match[6], sign),
                m: parseIso(match[7], sign),
                s: parseIso(match[8], sign),
            };
        } else if (duration == null) {
            // checks for null or undefined
            duration = {};
        } else if (
            typeof duration === 'object' &&
            ('from' in duration || 'to' in duration)
        ) {
            diffRes = momentsDifference(
                createLocal(duration.from),
                createLocal(duration.to)
            );

            duration = {};
            duration.ms = diffRes.milliseconds;
            duration.M = diffRes.months;
        }

        ret = new Duration(duration);

        if (isDuration(input) && hasOwnProp(input, '_locale')) {
            ret._locale = input._locale;
        }

        if (isDuration(input) && hasOwnProp(input, '_isValid')) {
            ret._isValid = input._isValid;
        }

        return ret;
    }

    createDuration.fn = Duration.prototype;
    createDuration.invalid = createInvalid$1;

    function parseIso(inp, sign) {
        // We'd normally use ~~inp for this, but unfortunately it also
        // converts floats to ints.
        // inp may be undefined, so careful calling replace on it.
        var res = inp && parseFloat(inp.replace(',', '.'));
        // apply sign while we're at it
        return (isNaN(res) ? 0 : res) * sign;
    }

    function positiveMomentsDifference(base, other) {
        var res = {};

        res.months =
            other.month() - base.month() + (other.year() - base.year()) * 12;
        if (base.clone().add(res.months, 'M').isAfter(other)) {
            --res.months;
        }

        res.milliseconds = +other - +base.clone().add(res.months, 'M');

        return res;
    }

    function momentsDifference(base, other) {
        var res;
        if (!(base.isValid() && other.isValid())) {
            return { milliseconds: 0, months: 0 };
        }

        other = cloneWithOffset(other, base);
        if (base.isBefore(other)) {
            res = positiveMomentsDifference(base, other);
        } else {
            res = positiveMomentsDifference(other, base);
            res.milliseconds = -res.milliseconds;
            res.months = -res.months;
        }

        return res;
    }

    // TODO: remove 'name' arg after deprecation is removed
    function createAdder(direction, name) {
        return function (val, period) {
            var dur, tmp;
            //invert the arguments, but complain about it
            if (period !== null && !isNaN(+period)) {
                deprecateSimple(
                    name,
                    'moment().' +
                        name +
                        '(period, number) is deprecated. Please use moment().' +
                        name +
                        '(number, period). ' +
                        'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'
                );
                tmp = val;
                val = period;
                period = tmp;
            }

            dur = createDuration(val, period);
            addSubtract(this, dur, direction);
            return this;
        };
    }

    function addSubtract(mom, duration, isAdding, updateOffset) {
        var milliseconds = duration._milliseconds,
            days = absRound(duration._days),
            months = absRound(duration._months);

        if (!mom.isValid()) {
            // No op
            return;
        }

        updateOffset = updateOffset == null ? true : updateOffset;

        if (months) {
            setMonth(mom, get(mom, 'Month') + months * isAdding);
        }
        if (days) {
            set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
        }
        if (milliseconds) {
            mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
        }
        if (updateOffset) {
            hooks.updateOffset(mom, days || months);
        }
    }

    var add = createAdder(1, 'add'),
        subtract = createAdder(-1, 'subtract');

    function isString(input) {
        return typeof input === 'string' || input instanceof String;
    }

    // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined
    function isMomentInput(input) {
        return (
            isMoment(input) ||
            isDate(input) ||
            isString(input) ||
            isNumber(input) ||
            isNumberOrStringArray(input) ||
            isMomentInputObject(input) ||
            input === null ||
            input === undefined
        );
    }

    function isMomentInputObject(input) {
        var objectTest = isObject(input) && !isObjectEmpty(input),
            propertyTest = false,
            properties = [
                'years',
                'year',
                'y',
                'months',
                'month',
                'M',
                'days',
                'day',
                'd',
                'dates',
                'date',
                'D',
                'hours',
                'hour',
                'h',
                'minutes',
                'minute',
                'm',
                'seconds',
                'second',
                's',
                'milliseconds',
                'millisecond',
                'ms',
            ],
            i,
            property;

        for (i = 0; i < properties.length; i += 1) {
            property = properties[i];
            propertyTest = propertyTest || hasOwnProp(input, property);
        }

        return objectTest && propertyTest;
    }

    function isNumberOrStringArray(input) {
        var arrayTest = isArray(input),
            dataTypeTest = false;
        if (arrayTest) {
            dataTypeTest =
                input.filter(function (item) {
                    return !isNumber(item) && isString(input);
                }).length === 0;
        }
        return arrayTest && dataTypeTest;
    }

    function isCalendarSpec(input) {
        var objectTest = isObject(input) && !isObjectEmpty(input),
            propertyTest = false,
            properties = [
                'sameDay',
                'nextDay',
                'lastDay',
                'nextWeek',
                'lastWeek',
                'sameElse',
            ],
            i,
            property;

        for (i = 0; i < properties.length; i += 1) {
            property = properties[i];
            propertyTest = propertyTest || hasOwnProp(input, property);
        }

        return objectTest && propertyTest;
    }

    function getCalendarFormat(myMoment, now) {
        var diff = myMoment.diff(now, 'days', true);
        return diff < -6
            ? 'sameElse'
            : diff < -1
            ? 'lastWeek'
            : diff < 0
            ? 'lastDay'
            : diff < 1
            ? 'sameDay'
            : diff < 2
            ? 'nextDay'
            : diff < 7
            ? 'nextWeek'
            : 'sameElse';
    }

    function calendar$1(time, formats) {
        // Support for single parameter, formats only overload to the calendar function
        if (arguments.length === 1) {
            if (isMomentInput(arguments[0])) {
                time = arguments[0];
                formats = undefined;
            } else if (isCalendarSpec(arguments[0])) {
                formats = arguments[0];
                time = undefined;
            }
        }
        // We want to compare the start of today, vs this.
        // Getting start-of-today depends on whether we're local/utc/offset or not.
        var now = time || createLocal(),
            sod = cloneWithOffset(now, this).startOf('day'),
            format = hooks.calendarFormat(this, sod) || 'sameElse',
            output =
                formats &&
                (isFunction(formats[format])
                    ? formats[format].call(this, now)
                    : formats[format]);

        return this.format(
            output || this.localeData().calendar(format, this, createLocal(now))
        );
    }

    function clone() {
        return new Moment(this);
    }

    function isAfter(input, units) {
        var localInput = isMoment(input) ? input : createLocal(input);
        if (!(this.isValid() && localInput.isValid())) {
            return false;
        }
        units = normalizeUnits(units) || 'millisecond';
        if (units === 'millisecond') {
            return this.valueOf() > localInput.valueOf();
        } else {
            return localInput.valueOf() < this.clone().startOf(units).valueOf();
        }
    }

    function isBefore(input, units) {
        var localInput = isMoment(input) ? input : createLocal(input);
        if (!(this.isValid() && localInput.isValid())) {
            return false;
        }
        units = normalizeUnits(units) || 'millisecond';
        if (units === 'millisecond') {
            return this.valueOf() < localInput.valueOf();
        } else {
            return this.clone().endOf(units).valueOf() < localInput.valueOf();
        }
    }

    function isBetween(from, to, units, inclusivity) {
        var localFrom = isMoment(from) ? from : createLocal(from),
            localTo = isMoment(to) ? to : createLocal(to);
        if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {
            return false;
        }
        inclusivity = inclusivity || '()';
        return (
            (inclusivity[0] === '('
                ? this.isAfter(localFrom, units)
                : !this.isBefore(localFrom, units)) &&
            (inclusivity[1] === ')'
                ? this.isBefore(localTo, units)
                : !this.isAfter(localTo, units))
        );
    }

    function isSame(input, units) {
        var localInput = isMoment(input) ? input : createLocal(input),
            inputMs;
        if (!(this.isValid() && localInput.isValid())) {
            return false;
        }
        units = normalizeUnits(units) || 'millisecond';
        if (units === 'millisecond') {
            return this.valueOf() === localInput.valueOf();
        } else {
            inputMs = localInput.valueOf();
            return (
                this.clone().startOf(units).valueOf() <= inputMs &&
                inputMs <= this.clone().endOf(units).valueOf()
            );
        }
    }

    function isSameOrAfter(input, units) {
        return this.isSame(input, units) || this.isAfter(input, units);
    }

    function isSameOrBefore(input, units) {
        return this.isSame(input, units) || this.isBefore(input, units);
    }

    function diff(input, units, asFloat) {
        var that, zoneDelta, output;

        if (!this.isValid()) {
            return NaN;
        }

        that = cloneWithOffset(input, this);

        if (!that.isValid()) {
            return NaN;
        }

        zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;

        units = normalizeUnits(units);

        switch (units) {
            case 'year':
                output = monthDiff(this, that) / 12;
                break;
            case 'month':
                output = monthDiff(this, that);
                break;
            case 'quarter':
                output = monthDiff(this, that) / 3;
                break;
            case 'second':
                output = (this - that) / 1e3;
                break; // 1000
            case 'minute':
                output = (this - that) / 6e4;
                break; // 1000 * 60
            case 'hour':
                output = (this - that) / 36e5;
                break; // 1000 * 60 * 60
            case 'day':
                output = (this - that - zoneDelta) / 864e5;
                break; // 1000 * 60 * 60 * 24, negate dst
            case 'week':
                output = (this - that - zoneDelta) / 6048e5;
                break; // 1000 * 60 * 60 * 24 * 7, negate dst
            default:
                output = this - that;
        }

        return asFloat ? output : absFloor(output);
    }

    function monthDiff(a, b) {
        if (a.date() < b.date()) {
            // end-of-month calculations work correct when the start month has more
            // days than the end month.
            return -monthDiff(b, a);
        }
        // difference in months
        var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()),
            // b is in (anchor - 1 month, anchor + 1 month)
            anchor = a.clone().add(wholeMonthDiff, 'months'),
            anchor2,
            adjust;

        if (b - anchor < 0) {
            anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
            // linear across the month
            adjust = (b - anchor) / (anchor - anchor2);
        } else {
            anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
            // linear across the month
            adjust = (b - anchor) / (anchor2 - anchor);
        }

        //check for negative zero, return zero if negative zero
        return -(wholeMonthDiff + adjust) || 0;
    }

    hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
    hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';

    function toString() {
        return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
    }

    function toISOString(keepOffset) {
        if (!this.isValid()) {
            return null;
        }
        var utc = keepOffset !== true,
            m = utc ? this.clone().utc() : this;
        if (m.year() < 0 || m.year() > 9999) {
            return formatMoment(
                m,
                utc
                    ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'
                    : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'
            );
        }
        if (isFunction(Date.prototype.toISOString)) {
            // native implementation is ~50x faster, use it when we can
            if (utc) {
                return this.toDate().toISOString();
            } else {
                return new Date(this.valueOf() + this.utcOffset() * 60 * 1000)
                    .toISOString()
                    .replace('Z', formatMoment(m, 'Z'));
            }
        }
        return formatMoment(
            m,
            utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'
        );
    }

    /**
     * Return a human readable representation of a moment that can
     * also be evaluated to get a new moment which is the same
     *
     * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
     */
    function inspect() {
        if (!this.isValid()) {
            return 'moment.invalid(/* ' + this._i + ' */)';
        }
        var func = 'moment',
            zone = '',
            prefix,
            year,
            datetime,
            suffix;
        if (!this.isLocal()) {
            func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
            zone = 'Z';
        }
        prefix = '[' + func + '("]';
        year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY';
        datetime = '-MM-DD[T]HH:mm:ss.SSS';
        suffix = zone + '[")]';

        return this.format(prefix + year + datetime + suffix);
    }

    function format(inputString) {
        if (!inputString) {
            inputString = this.isUtc()
                ? hooks.defaultFormatUtc
                : hooks.defaultFormat;
        }
        var output = formatMoment(this, inputString);
        return this.localeData().postformat(output);
    }

    function from(time, withoutSuffix) {
        if (
            this.isValid() &&
            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())
        ) {
            return createDuration({ to: this, from: time })
                .locale(this.locale())
                .humanize(!withoutSuffix);
        } else {
            return this.localeData().invalidDate();
        }
    }

    function fromNow(withoutSuffix) {
        return this.from(createLocal(), withoutSuffix);
    }

    function to(time, withoutSuffix) {
        if (
            this.isValid() &&
            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())
        ) {
            return createDuration({ from: this, to: time })
                .locale(this.locale())
                .humanize(!withoutSuffix);
        } else {
            return this.localeData().invalidDate();
        }
    }

    function toNow(withoutSuffix) {
        return this.to(createLocal(), withoutSuffix);
    }

    // If passed a locale key, it will set the locale for this
    // instance.  Otherwise, it will return the locale configuration
    // variables for this instance.
    function locale(key) {
        var newLocaleData;

        if (key === undefined) {
            return this._locale._abbr;
        } else {
            newLocaleData = getLocale(key);
            if (newLocaleData != null) {
                this._locale = newLocaleData;
            }
            return this;
        }
    }

    var lang = deprecate(
        'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
        function (key) {
            if (key === undefined) {
                return this.localeData();
            } else {
                return this.locale(key);
            }
        }
    );

    function localeData() {
        return this._locale;
    }

    var MS_PER_SECOND = 1000,
        MS_PER_MINUTE = 60 * MS_PER_SECOND,
        MS_PER_HOUR = 60 * MS_PER_MINUTE,
        MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR;

    // actual modulo - handles negative numbers (for dates before 1970):
    function mod$1(dividend, divisor) {
        return ((dividend % divisor) + divisor) % divisor;
    }

    function localStartOfDate(y, m, d) {
        // the date constructor remaps years 0-99 to 1900-1999
        if (y < 100 && y >= 0) {
            // preserve leap years using a full 400 year cycle, then reset
            return new Date(y + 400, m, d) - MS_PER_400_YEARS;
        } else {
            return new Date(y, m, d).valueOf();
        }
    }

    function utcStartOfDate(y, m, d) {
        // Date.UTC remaps years 0-99 to 1900-1999
        if (y < 100 && y >= 0) {
            // preserve leap years using a full 400 year cycle, then reset
            return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS;
        } else {
            return Date.UTC(y, m, d);
        }
    }

    function startOf(units) {
        var time, startOfDate;
        units = normalizeUnits(units);
        if (units === undefined || units === 'millisecond' || !this.isValid()) {
            return this;
        }

        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;

        switch (units) {
            case 'year':
                time = startOfDate(this.year(), 0, 1);
                break;
            case 'quarter':
                time = startOfDate(
                    this.year(),
                    this.month() - (this.month() % 3),
                    1
                );
                break;
            case 'month':
                time = startOfDate(this.year(), this.month(), 1);
                break;
            case 'week':
                time = startOfDate(
                    this.year(),
                    this.month(),
                    this.date() - this.weekday()
                );
                break;
            case 'isoWeek':
                time = startOfDate(
                    this.year(),
                    this.month(),
                    this.date() - (this.isoWeekday() - 1)
                );
                break;
            case 'day':
            case 'date':
                time = startOfDate(this.year(), this.month(), this.date());
                break;
            case 'hour':
                time = this._d.valueOf();
                time -= mod$1(
                    time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),
                    MS_PER_HOUR
                );
                break;
            case 'minute':
                time = this._d.valueOf();
                time -= mod$1(time, MS_PER_MINUTE);
                break;
            case 'second':
                time = this._d.valueOf();
                time -= mod$1(time, MS_PER_SECOND);
                break;
        }

        this._d.setTime(time);
        hooks.updateOffset(this, true);
        return this;
    }

    function endOf(units) {
        var time, startOfDate;
        units = normalizeUnits(units);
        if (units === undefined || units === 'millisecond' || !this.isValid()) {
            return this;
        }

        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;

        switch (units) {
            case 'year':
                time = startOfDate(this.year() + 1, 0, 1) - 1;
                break;
            case 'quarter':
                time =
                    startOfDate(
                        this.year(),
                        this.month() - (this.month() % 3) + 3,
                        1
                    ) - 1;
                break;
            case 'month':
                time = startOfDate(this.year(), this.month() + 1, 1) - 1;
                break;
            case 'week':
                time =
                    startOfDate(
                        this.year(),
                        this.month(),
                        this.date() - this.weekday() + 7
                    ) - 1;
                break;
            case 'isoWeek':
                time =
                    startOfDate(
                        this.year(),
                        this.month(),
                        this.date() - (this.isoWeekday() - 1) + 7
                    ) - 1;
                break;
            case 'day':
            case 'date':
                time = startOfDate(this.year(), this.month(), this.date() + 1) - 1;
                break;
            case 'hour':
                time = this._d.valueOf();
                time +=
                    MS_PER_HOUR -
                    mod$1(
                        time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),
                        MS_PER_HOUR
                    ) -
                    1;
                break;
            case 'minute':
                time = this._d.valueOf();
                time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1;
                break;
            case 'second':
                time = this._d.valueOf();
                time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1;
                break;
        }

        this._d.setTime(time);
        hooks.updateOffset(this, true);
        return this;
    }

    function valueOf() {
        return this._d.valueOf() - (this._offset || 0) * 60000;
    }

    function unix() {
        return Math.floor(this.valueOf() / 1000);
    }

    function toDate() {
        return new Date(this.valueOf());
    }

    function toArray() {
        var m = this;
        return [
            m.year(),
            m.month(),
            m.date(),
            m.hour(),
            m.minute(),
            m.second(),
            m.millisecond(),
        ];
    }

    function toObject() {
        var m = this;
        return {
            years: m.year(),
            months: m.month(),
            date: m.date(),
            hours: m.hours(),
            minutes: m.minutes(),
            seconds: m.seconds(),
            milliseconds: m.milliseconds(),
        };
    }

    function toJSON() {
        // new Date(NaN).toJSON() === null
        return this.isValid() ? this.toISOString() : null;
    }

    function isValid$2() {
        return isValid(this);
    }

    function parsingFlags() {
        return extend({}, getParsingFlags(this));
    }

    function invalidAt() {
        return getParsingFlags(this).overflow;
    }

    function creationData() {
        return {
            input: this._i,
            format: this._f,
            locale: this._locale,
            isUTC: this._isUTC,
            strict: this._strict,
        };
    }

    addFormatToken('N', 0, 0, 'eraAbbr');
    addFormatToken('NN', 0, 0, 'eraAbbr');
    addFormatToken('NNN', 0, 0, 'eraAbbr');
    addFormatToken('NNNN', 0, 0, 'eraName');
    addFormatToken('NNNNN', 0, 0, 'eraNarrow');

    addFormatToken('y', ['y', 1], 'yo', 'eraYear');
    addFormatToken('y', ['yy', 2], 0, 'eraYear');
    addFormatToken('y', ['yyy', 3], 0, 'eraYear');
    addFormatToken('y', ['yyyy', 4], 0, 'eraYear');

    addRegexToken('N', matchEraAbbr);
    addRegexToken('NN', matchEraAbbr);
    addRegexToken('NNN', matchEraAbbr);
    addRegexToken('NNNN', matchEraName);
    addRegexToken('NNNNN', matchEraNarrow);

    addParseToken(['N', 'NN', 'NNN', 'NNNN', 'NNNNN'], function (
        input,
        array,
        config,
        token
    ) {
        var era = config._locale.erasParse(input, token, config._strict);
        if (era) {
            getParsingFlags(config).era = era;
        } else {
            getParsingFlags(config).invalidEra = input;
        }
    });

    addRegexToken('y', matchUnsigned);
    addRegexToken('yy', matchUnsigned);
    addRegexToken('yyy', matchUnsigned);
    addRegexToken('yyyy', matchUnsigned);
    addRegexToken('yo', matchEraYearOrdinal);

    addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR);
    addParseToken(['yo'], function (input, array, config, token) {
        var match;
        if (config._locale._eraYearOrdinalRegex) {
            match = input.match(config._locale._eraYearOrdinalRegex);
        }

        if (config._locale.eraYearOrdinalParse) {
            array[YEAR] = config._locale.eraYearOrdinalParse(input, match);
        } else {
            array[YEAR] = parseInt(input, 10);
        }
    });

    function localeEras(m, format) {
        var i,
            l,
            date,
            eras = this._eras || getLocale('en')._eras;
        for (i = 0, l = eras.length; i < l; ++i) {
            switch (typeof eras[i].since) {
                case 'string':
                    // truncate time
                    date = hooks(eras[i].since).startOf('day');
                    eras[i].since = date.valueOf();
                    break;
            }

            switch (typeof eras[i].until) {
                case 'undefined':
                    eras[i].until = +Infinity;
                    break;
                case 'string':
                    // truncate time
                    date = hooks(eras[i].until).startOf('day').valueOf();
                    eras[i].until = date.valueOf();
                    break;
            }
        }
        return eras;
    }

    function localeErasParse(eraName, format, strict) {
        var i,
            l,
            eras = this.eras(),
            name,
            abbr,
            narrow;
        eraName = eraName.toUpperCase();

        for (i = 0, l = eras.length; i < l; ++i) {
            name = eras[i].name.toUpperCase();
            abbr = eras[i].abbr.toUpperCase();
            narrow = eras[i].narrow.toUpperCase();

            if (strict) {
                switch (format) {
                    case 'N':
                    case 'NN':
                    case 'NNN':
                        if (abbr === eraName) {
                            return eras[i];
                        }
                        break;

                    case 'NNNN':
                        if (name === eraName) {
                            return eras[i];
                        }
                        break;

                    case 'NNNNN':
                        if (narrow === eraName) {
                            return eras[i];
                        }
                        break;
                }
            } else if ([name, abbr, narrow].indexOf(eraName) >= 0) {
                return eras[i];
            }
        }
    }

    function localeErasConvertYear(era, year) {
        var dir = era.since <= era.until ? +1 : -1;
        if (year === undefined) {
            return hooks(era.since).year();
        } else {
            return hooks(era.since).year() + (year - era.offset) * dir;
        }
    }

    function getEraName() {
        var i,
            l,
            val,
            eras = this.localeData().eras();
        for (i = 0, l = eras.length; i < l; ++i) {
            // truncate time
            val = this.startOf('day').valueOf();

            if (eras[i].since <= val && val <= eras[i].until) {
                return eras[i].name;
            }
            if (eras[i].until <= val && val <= eras[i].since) {
                return eras[i].name;
            }
        }

        return '';
    }

    function getEraNarrow() {
        var i,
            l,
            val,
            eras = this.localeData().eras();
        for (i = 0, l = eras.length; i < l; ++i) {
            // truncate time
            val = this.startOf('day').valueOf();

            if (eras[i].since <= val && val <= eras[i].until) {
                return eras[i].narrow;
            }
            if (eras[i].until <= val && val <= eras[i].since) {
                return eras[i].narrow;
            }
        }

        return '';
    }

    function getEraAbbr() {
        var i,
            l,
            val,
            eras = this.localeData().eras();
        for (i = 0, l = eras.length; i < l; ++i) {
            // truncate time
            val = this.startOf('day').valueOf();

            if (eras[i].since <= val && val <= eras[i].until) {
                return eras[i].abbr;
            }
            if (eras[i].until <= val && val <= eras[i].since) {
                return eras[i].abbr;
            }
        }

        return '';
    }

    function getEraYear() {
        var i,
            l,
            dir,
            val,
            eras = this.localeData().eras();
        for (i = 0, l = eras.length; i < l; ++i) {
            dir = eras[i].since <= eras[i].until ? +1 : -1;

            // truncate time
            val = this.startOf('day').valueOf();

            if (
                (eras[i].since <= val && val <= eras[i].until) ||
                (eras[i].until <= val && val <= eras[i].since)
            ) {
                return (
                    (this.year() - hooks(eras[i].since).year()) * dir +
                    eras[i].offset
                );
            }
        }

        return this.year();
    }

    function erasNameRegex(isStrict) {
        if (!hasOwnProp(this, '_erasNameRegex')) {
            computeErasParse.call(this);
        }
        return isStrict ? this._erasNameRegex : this._erasRegex;
    }

    function erasAbbrRegex(isStrict) {
        if (!hasOwnProp(this, '_erasAbbrRegex')) {
            computeErasParse.call(this);
        }
        return isStrict ? this._erasAbbrRegex : this._erasRegex;
    }

    function erasNarrowRegex(isStrict) {
        if (!hasOwnProp(this, '_erasNarrowRegex')) {
            computeErasParse.call(this);
        }
        return isStrict ? this._erasNarrowRegex : this._erasRegex;
    }

    function matchEraAbbr(isStrict, locale) {
        return locale.erasAbbrRegex(isStrict);
    }

    function matchEraName(isStrict, locale) {
        return locale.erasNameRegex(isStrict);
    }

    function matchEraNarrow(isStrict, locale) {
        return locale.erasNarrowRegex(isStrict);
    }

    function matchEraYearOrdinal(isStrict, locale) {
        return locale._eraYearOrdinalRegex || matchUnsigned;
    }

    function computeErasParse() {
        var abbrPieces = [],
            namePieces = [],
            narrowPieces = [],
            mixedPieces = [],
            i,
            l,
            eras = this.eras();

        for (i = 0, l = eras.length; i < l; ++i) {
            namePieces.push(regexEscape(eras[i].name));
            abbrPieces.push(regexEscape(eras[i].abbr));
            narrowPieces.push(regexEscape(eras[i].narrow));

            mixedPieces.push(regexEscape(eras[i].name));
            mixedPieces.push(regexEscape(eras[i].abbr));
            mixedPieces.push(regexEscape(eras[i].narrow));
        }

        this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
        this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i');
        this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i');
        this._erasNarrowRegex = new RegExp(
            '^(' + narrowPieces.join('|') + ')',
            'i'
        );
    }

    // FORMATTING

    addFormatToken(0, ['gg', 2], 0, function () {
        return this.weekYear() % 100;
    });

    addFormatToken(0, ['GG', 2], 0, function () {
        return this.isoWeekYear() % 100;
    });

    function addWeekYearFormatToken(token, getter) {
        addFormatToken(0, [token, token.length], 0, getter);
    }

    addWeekYearFormatToken('gggg', 'weekYear');
    addWeekYearFormatToken('ggggg', 'weekYear');
    addWeekYearFormatToken('GGGG', 'isoWeekYear');
    addWeekYearFormatToken('GGGGG', 'isoWeekYear');

    // ALIASES

    addUnitAlias('weekYear', 'gg');
    addUnitAlias('isoWeekYear', 'GG');

    // PRIORITY

    addUnitPriority('weekYear', 1);
    addUnitPriority('isoWeekYear', 1);

    // PARSING

    addRegexToken('G', matchSigned);
    addRegexToken('g', matchSigned);
    addRegexToken('GG', match1to2, match2);
    addRegexToken('gg', match1to2, match2);
    addRegexToken('GGGG', match1to4, match4);
    addRegexToken('gggg', match1to4, match4);
    addRegexToken('GGGGG', match1to6, match6);
    addRegexToken('ggggg', match1to6, match6);

    addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (
        input,
        week,
        config,
        token
    ) {
        week[token.substr(0, 2)] = toInt(input);
    });

    addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
        week[token] = hooks.parseTwoDigitYear(input);
    });

    // MOMENTS

    function getSetWeekYear(input) {
        return getSetWeekYearHelper.call(
            this,
            input,
            this.week(),
            this.weekday(),
            this.localeData()._week.dow,
            this.localeData()._week.doy
        );
    }

    function getSetISOWeekYear(input) {
        return getSetWeekYearHelper.call(
            this,
            input,
            this.isoWeek(),
            this.isoWeekday(),
            1,
            4
        );
    }

    function getISOWeeksInYear() {
        return weeksInYear(this.year(), 1, 4);
    }

    function getISOWeeksInISOWeekYear() {
        return weeksInYear(this.isoWeekYear(), 1, 4);
    }

    function getWeeksInYear() {
        var weekInfo = this.localeData()._week;
        return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
    }

    function getWeeksInWeekYear() {
        var weekInfo = this.localeData()._week;
        return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy);
    }

    function getSetWeekYearHelper(input, week, weekday, dow, doy) {
        var weeksTarget;
        if (input == null) {
            return weekOfYear(this, dow, doy).year;
        } else {
            weeksTarget = weeksInYear(input, dow, doy);
            if (week > weeksTarget) {
                week = weeksTarget;
            }
            return setWeekAll.call(this, input, week, weekday, dow, doy);
        }
    }

    function setWeekAll(weekYear, week, weekday, dow, doy) {
        var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
            date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);

        this.year(date.getUTCFullYear());
        this.month(date.getUTCMonth());
        this.date(date.getUTCDate());
        return this;
    }

    // FORMATTING

    addFormatToken('Q', 0, 'Qo', 'quarter');

    // ALIASES

    addUnitAlias('quarter', 'Q');

    // PRIORITY

    addUnitPriority('quarter', 7);

    // PARSING

    addRegexToken('Q', match1);
    addParseToken('Q', function (input, array) {
        array[MONTH] = (toInt(input) - 1) * 3;
    });

    // MOMENTS

    function getSetQuarter(input) {
        return input == null
            ? Math.ceil((this.month() + 1) / 3)
            : this.month((input - 1) * 3 + (this.month() % 3));
    }

    // FORMATTING

    addFormatToken('D', ['DD', 2], 'Do', 'date');

    // ALIASES

    addUnitAlias('date', 'D');

    // PRIORITY
    addUnitPriority('date', 9);

    // PARSING

    addRegexToken('D', match1to2);
    addRegexToken('DD', match1to2, match2);
    addRegexToken('Do', function (isStrict, locale) {
        // TODO: Remove "ordinalParse" fallback in next major release.
        return isStrict
            ? locale._dayOfMonthOrdinalParse || locale._ordinalParse
            : locale._dayOfMonthOrdinalParseLenient;
    });

    addParseToken(['D', 'DD'], DATE);
    addParseToken('Do', function (input, array) {
        array[DATE] = toInt(input.match(match1to2)[0]);
    });

    // MOMENTS

    var getSetDayOfMonth = makeGetSet('Date', true);

    // FORMATTING

    addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');

    // ALIASES

    addUnitAlias('dayOfYear', 'DDD');

    // PRIORITY
    addUnitPriority('dayOfYear', 4);

    // PARSING

    addRegexToken('DDD', match1to3);
    addRegexToken('DDDD', match3);
    addParseToken(['DDD', 'DDDD'], function (input, array, config) {
        config._dayOfYear = toInt(input);
    });

    // HELPERS

    // MOMENTS

    function getSetDayOfYear(input) {
        var dayOfYear =
            Math.round(
                (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5
            ) + 1;
        return input == null ? dayOfYear : this.add(input - dayOfYear, 'd');
    }

    // FORMATTING

    addFormatToken('m', ['mm', 2], 0, 'minute');

    // ALIASES

    addUnitAlias('minute', 'm');

    // PRIORITY

    addUnitPriority('minute', 14);

    // PARSING

    addRegexToken('m', match1to2);
    addRegexToken('mm', match1to2, match2);
    addParseToken(['m', 'mm'], MINUTE);

    // MOMENTS

    var getSetMinute = makeGetSet('Minutes', false);

    // FORMATTING

    addFormatToken('s', ['ss', 2], 0, 'second');

    // ALIASES

    addUnitAlias('second', 's');

    // PRIORITY

    addUnitPriority('second', 15);

    // PARSING

    addRegexToken('s', match1to2);
    addRegexToken('ss', match1to2, match2);
    addParseToken(['s', 'ss'], SECOND);

    // MOMENTS

    var getSetSecond = makeGetSet('Seconds', false);

    // FORMATTING

    addFormatToken('S', 0, 0, function () {
        return ~~(this.millisecond() / 100);
    });

    addFormatToken(0, ['SS', 2], 0, function () {
        return ~~(this.millisecond() / 10);
    });

    addFormatToken(0, ['SSS', 3], 0, 'millisecond');
    addFormatToken(0, ['SSSS', 4], 0, function () {
        return this.millisecond() * 10;
    });
    addFormatToken(0, ['SSSSS', 5], 0, function () {
        return this.millisecond() * 100;
    });
    addFormatToken(0, ['SSSSSS', 6], 0, function () {
        return this.millisecond() * 1000;
    });
    addFormatToken(0, ['SSSSSSS', 7], 0, function () {
        return this.millisecond() * 10000;
    });
    addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
        return this.millisecond() * 100000;
    });
    addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
        return this.millisecond() * 1000000;
    });

    // ALIASES

    addUnitAlias('millisecond', 'ms');

    // PRIORITY

    addUnitPriority('millisecond', 16);

    // PARSING

    addRegexToken('S', match1to3, match1);
    addRegexToken('SS', match1to3, match2);
    addRegexToken('SSS', match1to3, match3);

    var token, getSetMillisecond;
    for (token = 'SSSS'; token.length <= 9; token += 'S') {
        addRegexToken(token, matchUnsigned);
    }

    function parseMs(input, array) {
        array[MILLISECOND] = toInt(('0.' + input) * 1000);
    }

    for (token = 'S'; token.length <= 9; token += 'S') {
        addParseToken(token, parseMs);
    }

    getSetMillisecond = makeGetSet('Milliseconds', false);

    // FORMATTING

    addFormatToken('z', 0, 0, 'zoneAbbr');
    addFormatToken('zz', 0, 0, 'zoneName');

    // MOMENTS

    function getZoneAbbr() {
        return this._isUTC ? 'UTC' : '';
    }

    function getZoneName() {
        return this._isUTC ? 'Coordinated Universal Time' : '';
    }

    var proto = Moment.prototype;

    proto.add = add;
    proto.calendar = calendar$1;
    proto.clone = clone;
    proto.diff = diff;
    proto.endOf = endOf;
    proto.format = format;
    proto.from = from;
    proto.fromNow = fromNow;
    proto.to = to;
    proto.toNow = toNow;
    proto.get = stringGet;
    proto.invalidAt = invalidAt;
    proto.isAfter = isAfter;
    proto.isBefore = isBefore;
    proto.isBetween = isBetween;
    proto.isSame = isSame;
    proto.isSameOrAfter = isSameOrAfter;
    proto.isSameOrBefore = isSameOrBefore;
    proto.isValid = isValid$2;
    proto.lang = lang;
    proto.locale = locale;
    proto.localeData = localeData;
    proto.max = prototypeMax;
    proto.min = prototypeMin;
    proto.parsingFlags = parsingFlags;
    proto.set = stringSet;
    proto.startOf = startOf;
    proto.subtract = subtract;
    proto.toArray = toArray;
    proto.toObject = toObject;
    proto.toDate = toDate;
    proto.toISOString = toISOString;
    proto.inspect = inspect;
    if (typeof Symbol !== 'undefined' && Symbol.for != null) {
        proto[Symbol.for('nodejs.util.inspect.custom')] = function () {
            return 'Moment<' + this.format() + '>';
        };
    }
    proto.toJSON = toJSON;
    proto.toString = toString;
    proto.unix = unix;
    proto.valueOf = valueOf;
    proto.creationData = creationData;
    proto.eraName = getEraName;
    proto.eraNarrow = getEraNarrow;
    proto.eraAbbr = getEraAbbr;
    proto.eraYear = getEraYear;
    proto.year = getSetYear;
    proto.isLeapYear = getIsLeapYear;
    proto.weekYear = getSetWeekYear;
    proto.isoWeekYear = getSetISOWeekYear;
    proto.quarter = proto.quarters = getSetQuarter;
    proto.month = getSetMonth;
    proto.daysInMonth = getDaysInMonth;
    proto.week = proto.weeks = getSetWeek;
    proto.isoWeek = proto.isoWeeks = getSetISOWeek;
    proto.weeksInYear = getWeeksInYear;
    proto.weeksInWeekYear = getWeeksInWeekYear;
    proto.isoWeeksInYear = getISOWeeksInYear;
    proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear;
    proto.date = getSetDayOfMonth;
    proto.day = proto.days = getSetDayOfWeek;
    proto.weekday = getSetLocaleDayOfWeek;
    proto.isoWeekday = getSetISODayOfWeek;
    proto.dayOfYear = getSetDayOfYear;
    proto.hour = proto.hours = getSetHour;
    proto.minute = proto.minutes = getSetMinute;
    proto.second = proto.seconds = getSetSecond;
    proto.millisecond = proto.milliseconds = getSetMillisecond;
    proto.utcOffset = getSetOffset;
    proto.utc = setOffsetToUTC;
    proto.local = setOffsetToLocal;
    proto.parseZone = setOffsetToParsedOffset;
    proto.hasAlignedHourOffset = hasAlignedHourOffset;
    proto.isDST = isDaylightSavingTime;
    proto.isLocal = isLocal;
    proto.isUtcOffset = isUtcOffset;
    proto.isUtc = isUtc;
    proto.isUTC = isUtc;
    proto.zoneAbbr = getZoneAbbr;
    proto.zoneName = getZoneName;
    proto.dates = deprecate(
        'dates accessor is deprecated. Use date instead.',
        getSetDayOfMonth
    );
    proto.months = deprecate(
        'months accessor is deprecated. Use month instead',
        getSetMonth
    );
    proto.years = deprecate(
        'years accessor is deprecated. Use year instead',
        getSetYear
    );
    proto.zone = deprecate(
        'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/',
        getSetZone
    );
    proto.isDSTShifted = deprecate(
        'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information',
        isDaylightSavingTimeShifted
    );

    function createUnix(input) {
        return createLocal(input * 1000);
    }

    function createInZone() {
        return createLocal.apply(null, arguments).parseZone();
    }

    function preParsePostFormat(string) {
        return string;
    }

    var proto$1 = Locale.prototype;

    proto$1.calendar = calendar;
    proto$1.longDateFormat = longDateFormat;
    proto$1.invalidDate = invalidDate;
    proto$1.ordinal = ordinal;
    proto$1.preparse = preParsePostFormat;
    proto$1.postformat = preParsePostFormat;
    proto$1.relativeTime = relativeTime;
    proto$1.pastFuture = pastFuture;
    proto$1.set = set;
    proto$1.eras = localeEras;
    proto$1.erasParse = localeErasParse;
    proto$1.erasConvertYear = localeErasConvertYear;
    proto$1.erasAbbrRegex = erasAbbrRegex;
    proto$1.erasNameRegex = erasNameRegex;
    proto$1.erasNarrowRegex = erasNarrowRegex;

    proto$1.months = localeMonths;
    proto$1.monthsShort = localeMonthsShort;
    proto$1.monthsParse = localeMonthsParse;
    proto$1.monthsRegex = monthsRegex;
    proto$1.monthsShortRegex = monthsShortRegex;
    proto$1.week = localeWeek;
    proto$1.firstDayOfYear = localeFirstDayOfYear;
    proto$1.firstDayOfWeek = localeFirstDayOfWeek;

    proto$1.weekdays = localeWeekdays;
    proto$1.weekdaysMin = localeWeekdaysMin;
    proto$1.weekdaysShort = localeWeekdaysShort;
    proto$1.weekdaysParse = localeWeekdaysParse;

    proto$1.weekdaysRegex = weekdaysRegex;
    proto$1.weekdaysShortRegex = weekdaysShortRegex;
    proto$1.weekdaysMinRegex = weekdaysMinRegex;

    proto$1.isPM = localeIsPM;
    proto$1.meridiem = localeMeridiem;

    function get$1(format, index, field, setter) {
        var locale = getLocale(),
            utc = createUTC().set(setter, index);
        return locale[field](utc, format);
    }

    function listMonthsImpl(format, index, field) {
        if (isNumber(format)) {
            index = format;
            format = undefined;
        }

        format = format || '';

        if (index != null) {
            return get$1(format, index, field, 'month');
        }

        var i,
            out = [];
        for (i = 0; i < 12; i++) {
            out[i] = get$1(format, i, field, 'month');
        }
        return out;
    }

    // ()
    // (5)
    // (fmt, 5)
    // (fmt)
    // (true)
    // (true, 5)
    // (true, fmt, 5)
    // (true, fmt)
    function listWeekdaysImpl(localeSorted, format, index, field) {
        if (typeof localeSorted === 'boolean') {
            if (isNumber(format)) {
                index = format;
                format = undefined;
            }

            format = format || '';
        } else {
            format = localeSorted;
            index = format;
            localeSorted = false;

            if (isNumber(format)) {
                index = format;
                format = undefined;
            }

            format = format || '';
        }

        var locale = getLocale(),
            shift = localeSorted ? locale._week.dow : 0,
            i,
            out = [];

        if (index != null) {
            return get$1(format, (index + shift) % 7, field, 'day');
        }

        for (i = 0; i < 7; i++) {
            out[i] = get$1(format, (i + shift) % 7, field, 'day');
        }
        return out;
    }

    function listMonths(format, index) {
        return listMonthsImpl(format, index, 'months');
    }

    function listMonthsShort(format, index) {
        return listMonthsImpl(format, index, 'monthsShort');
    }

    function listWeekdays(localeSorted, format, index) {
        return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
    }

    function listWeekdaysShort(localeSorted, format, index) {
        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
    }

    function listWeekdaysMin(localeSorted, format, index) {
        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
    }

    getSetGlobalLocale('en', {
        eras: [
            {
                since: '0001-01-01',
                until: +Infinity,
                offset: 1,
                name: 'Anno Domini',
                narrow: 'AD',
                abbr: 'AD',
            },
            {
                since: '0000-12-31',
                until: -Infinity,
                offset: 1,
                name: 'Before Christ',
                narrow: 'BC',
                abbr: 'BC',
            },
        ],
        dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
        ordinal: function (number) {
            var b = number % 10,
                output =
                    toInt((number % 100) / 10) === 1
                        ? 'th'
                        : b === 1
                        ? 'st'
                        : b === 2
                        ? 'nd'
                        : b === 3
                        ? 'rd'
                        : 'th';
            return number + output;
        },
    });

    // Side effect imports

    hooks.lang = deprecate(
        'moment.lang is deprecated. Use moment.locale instead.',
        getSetGlobalLocale
    );
    hooks.langData = deprecate(
        'moment.langData is deprecated. Use moment.localeData instead.',
        getLocale
    );

    var mathAbs = Math.abs;

    function abs() {
        var data = this._data;

        this._milliseconds = mathAbs(this._milliseconds);
        this._days = mathAbs(this._days);
        this._months = mathAbs(this._months);

        data.milliseconds = mathAbs(data.milliseconds);
        data.seconds = mathAbs(data.seconds);
        data.minutes = mathAbs(data.minutes);
        data.hours = mathAbs(data.hours);
        data.months = mathAbs(data.months);
        data.years = mathAbs(data.years);

        return this;
    }

    function addSubtract$1(duration, input, value, direction) {
        var other = createDuration(input, value);

        duration._milliseconds += direction * other._milliseconds;
        duration._days += direction * other._days;
        duration._months += direction * other._months;

        return duration._bubble();
    }

    // supports only 2.0-style add(1, 's') or add(duration)
    function add$1(input, value) {
        return addSubtract$1(this, input, value, 1);
    }

    // supports only 2.0-style subtract(1, 's') or subtract(duration)
    function subtract$1(input, value) {
        return addSubtract$1(this, input, value, -1);
    }

    function absCeil(number) {
        if (number < 0) {
            return Math.floor(number);
        } else {
            return Math.ceil(number);
        }
    }

    function bubble() {
        var milliseconds = this._milliseconds,
            days = this._days,
            months = this._months,
            data = this._data,
            seconds,
            minutes,
            hours,
            years,
            monthsFromDays;

        // if we have a mix of positive and negative values, bubble down first
        // check: https://github.com/moment/moment/issues/2166
        if (
            !(
                (milliseconds >= 0 && days >= 0 && months >= 0) ||
                (milliseconds <= 0 && days <= 0 && months <= 0)
            )
        ) {
            milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
            days = 0;
            months = 0;
        }

        // The following code bubbles up values, see the tests for
        // examples of what that means.
        data.milliseconds = milliseconds % 1000;

        seconds = absFloor(milliseconds / 1000);
        data.seconds = seconds % 60;

        minutes = absFloor(seconds / 60);
        data.minutes = minutes % 60;

        hours = absFloor(minutes / 60);
        data.hours = hours % 24;

        days += absFloor(hours / 24);

        // convert days to months
        monthsFromDays = absFloor(daysToMonths(days));
        months += monthsFromDays;
        days -= absCeil(monthsToDays(monthsFromDays));

        // 12 months -> 1 year
        years = absFloor(months / 12);
        months %= 12;

        data.days = days;
        data.months = months;
        data.years = years;

        return this;
    }

    function daysToMonths(days) {
        // 400 years have 146097 days (taking into account leap year rules)
        // 400 years have 12 months === 4800
        return (days * 4800) / 146097;
    }

    function monthsToDays(months) {
        // the reverse of daysToMonths
        return (months * 146097) / 4800;
    }

    function as(units) {
        if (!this.isValid()) {
            return NaN;
        }
        var days,
            months,
            milliseconds = this._milliseconds;

        units = normalizeUnits(units);

        if (units === 'month' || units === 'quarter' || units === 'year') {
            days = this._days + milliseconds / 864e5;
            months = this._months + daysToMonths(days);
            switch (units) {
                case 'month':
                    return months;
                case 'quarter':
                    return months / 3;
                case 'year':
                    return months / 12;
            }
        } else {
            // handle milliseconds separately because of floating point math errors (issue #1867)
            days = this._days + Math.round(monthsToDays(this._months));
            switch (units) {
                case 'week':
                    return days / 7 + milliseconds / 6048e5;
                case 'day':
                    return days + milliseconds / 864e5;
                case 'hour':
                    return days * 24 + milliseconds / 36e5;
                case 'minute':
                    return days * 1440 + milliseconds / 6e4;
                case 'second':
                    return days * 86400 + milliseconds / 1000;
                // Math.floor prevents floating point math errors here
                case 'millisecond':
                    return Math.floor(days * 864e5) + milliseconds;
                default:
                    throw new Error('Unknown unit ' + units);
            }
        }
    }

    // TODO: Use this.as('ms')?
    function valueOf$1() {
        if (!this.isValid()) {
            return NaN;
        }
        return (
            this._milliseconds +
            this._days * 864e5 +
            (this._months % 12) * 2592e6 +
            toInt(this._months / 12) * 31536e6
        );
    }

    function makeAs(alias) {
        return function () {
            return this.as(alias);
        };
    }

    var asMilliseconds = makeAs('ms'),
        asSeconds = makeAs('s'),
        asMinutes = makeAs('m'),
        asHours = makeAs('h'),
        asDays = makeAs('d'),
        asWeeks = makeAs('w'),
        asMonths = makeAs('M'),
        asQuarters = makeAs('Q'),
        asYears = makeAs('y');

    function clone$1() {
        return createDuration(this);
    }

    function get$2(units) {
        units = normalizeUnits(units);
        return this.isValid() ? this[units + 's']() : NaN;
    }

    function makeGetter(name) {
        return function () {
            return this.isValid() ? this._data[name] : NaN;
        };
    }

    var milliseconds = makeGetter('milliseconds'),
        seconds = makeGetter('seconds'),
        minutes = makeGetter('minutes'),
        hours = makeGetter('hours'),
        days = makeGetter('days'),
        months = makeGetter('months'),
        years = makeGetter('years');

    function weeks() {
        return absFloor(this.days() / 7);
    }

    var round = Math.round,
        thresholds = {
            ss: 44, // a few seconds to seconds
            s: 45, // seconds to minute
            m: 45, // minutes to hour
            h: 22, // hours to day
            d: 26, // days to month/week
            w: null, // weeks to month
            M: 11, // months to year
        };

    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
    function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
        return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
    }

    function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) {
        var duration = createDuration(posNegDuration).abs(),
            seconds = round(duration.as('s')),
            minutes = round(duration.as('m')),
            hours = round(duration.as('h')),
            days = round(duration.as('d')),
            months = round(duration.as('M')),
            weeks = round(duration.as('w')),
            years = round(duration.as('y')),
            a =
                (seconds <= thresholds.ss && ['s', seconds]) ||
                (seconds < thresholds.s && ['ss', seconds]) ||
                (minutes <= 1 && ['m']) ||
                (minutes < thresholds.m && ['mm', minutes]) ||
                (hours <= 1 && ['h']) ||
                (hours < thresholds.h && ['hh', hours]) ||
                (days <= 1 && ['d']) ||
                (days < thresholds.d && ['dd', days]);

        if (thresholds.w != null) {
            a =
                a ||
                (weeks <= 1 && ['w']) ||
                (weeks < thresholds.w && ['ww', weeks]);
        }
        a = a ||
            (months <= 1 && ['M']) ||
            (months < thresholds.M && ['MM', months]) ||
            (years <= 1 && ['y']) || ['yy', years];

        a[2] = withoutSuffix;
        a[3] = +posNegDuration > 0;
        a[4] = locale;
        return substituteTimeAgo.apply(null, a);
    }

    // This function allows you to set the rounding function for relative time strings
    function getSetRelativeTimeRounding(roundingFunction) {
        if (roundingFunction === undefined) {
            return round;
        }
        if (typeof roundingFunction === 'function') {
            round = roundingFunction;
            return true;
        }
        return false;
    }

    // This function allows you to set a threshold for relative time strings
    function getSetRelativeTimeThreshold(threshold, limit) {
        if (thresholds[threshold] === undefined) {
            return false;
        }
        if (limit === undefined) {
            return thresholds[threshold];
        }
        thresholds[threshold] = limit;
        if (threshold === 's') {
            thresholds.ss = limit - 1;
        }
        return true;
    }

    function humanize(argWithSuffix, argThresholds) {
        if (!this.isValid()) {
            return this.localeData().invalidDate();
        }

        var withSuffix = false,
            th = thresholds,
            locale,
            output;

        if (typeof argWithSuffix === 'object') {
            argThresholds = argWithSuffix;
            argWithSuffix = false;
        }
        if (typeof argWithSuffix === 'boolean') {
            withSuffix = argWithSuffix;
        }
        if (typeof argThresholds === 'object') {
            th = Object.assign({}, thresholds, argThresholds);
            if (argThresholds.s != null && argThresholds.ss == null) {
                th.ss = argThresholds.s - 1;
            }
        }

        locale = this.localeData();
        output = relativeTime$1(this, !withSuffix, th, locale);

        if (withSuffix) {
            output = locale.pastFuture(+this, output);
        }

        return locale.postformat(output);
    }

    var abs$1 = Math.abs;

    function sign(x) {
        return (x > 0) - (x < 0) || +x;
    }

    function toISOString$1() {
        // for ISO strings we do not use the normal bubbling rules:
        //  * milliseconds bubble up until they become hours
        //  * days do not bubble at all
        //  * months bubble up until they become years
        // This is because there is no context-free conversion between hours and days
        // (think of clock changes)
        // and also not between days and months (28-31 days per month)
        if (!this.isValid()) {
            return this.localeData().invalidDate();
        }

        var seconds = abs$1(this._milliseconds) / 1000,
            days = abs$1(this._days),
            months = abs$1(this._months),
            minutes,
            hours,
            years,
            s,
            total = this.asSeconds(),
            totalSign,
            ymSign,
            daysSign,
            hmsSign;

        if (!total) {
            // this is the same as C#'s (Noda) and python (isodate)...
            // but not other JS (goog.date)
            return 'P0D';
        }

        // 3600 seconds -> 60 minutes -> 1 hour
        minutes = absFloor(seconds / 60);
        hours = absFloor(minutes / 60);
        seconds %= 60;
        minutes %= 60;

        // 12 months -> 1 year
        years = absFloor(months / 12);
        months %= 12;

        // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
        s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';

        totalSign = total < 0 ? '-' : '';
        ymSign = sign(this._months) !== sign(total) ? '-' : '';
        daysSign = sign(this._days) !== sign(total) ? '-' : '';
        hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';

        return (
            totalSign +
            'P' +
            (years ? ymSign + years + 'Y' : '') +
            (months ? ymSign + months + 'M' : '') +
            (days ? daysSign + days + 'D' : '') +
            (hours || minutes || seconds ? 'T' : '') +
            (hours ? hmsSign + hours + 'H' : '') +
            (minutes ? hmsSign + minutes + 'M' : '') +
            (seconds ? hmsSign + s + 'S' : '')
        );
    }

    var proto$2 = Duration.prototype;

    proto$2.isValid = isValid$1;
    proto$2.abs = abs;
    proto$2.add = add$1;
    proto$2.subtract = subtract$1;
    proto$2.as = as;
    proto$2.asMilliseconds = asMilliseconds;
    proto$2.asSeconds = asSeconds;
    proto$2.asMinutes = asMinutes;
    proto$2.asHours = asHours;
    proto$2.asDays = asDays;
    proto$2.asWeeks = asWeeks;
    proto$2.asMonths = asMonths;
    proto$2.asQuarters = asQuarters;
    proto$2.asYears = asYears;
    proto$2.valueOf = valueOf$1;
    proto$2._bubble = bubble;
    proto$2.clone = clone$1;
    proto$2.get = get$2;
    proto$2.milliseconds = milliseconds;
    proto$2.seconds = seconds;
    proto$2.minutes = minutes;
    proto$2.hours = hours;
    proto$2.days = days;
    proto$2.weeks = weeks;
    proto$2.months = months;
    proto$2.years = years;
    proto$2.humanize = humanize;
    proto$2.toISOString = toISOString$1;
    proto$2.toString = toISOString$1;
    proto$2.toJSON = toISOString$1;
    proto$2.locale = locale;
    proto$2.localeData = localeData;

    proto$2.toIsoString = deprecate(
        'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)',
        toISOString$1
    );
    proto$2.lang = lang;

    // FORMATTING

    addFormatToken('X', 0, 0, 'unix');
    addFormatToken('x', 0, 0, 'valueOf');

    // PARSING

    addRegexToken('x', matchSigned);
    addRegexToken('X', matchTimestamp);
    addParseToken('X', function (input, array, config) {
        config._d = new Date(parseFloat(input) * 1000);
    });
    addParseToken('x', function (input, array, config) {
        config._d = new Date(toInt(input));
    });

    //! moment.js

    hooks.version = '2.27.0';

    setHookCallback(createLocal);

    hooks.fn = proto;
    hooks.min = min;
    hooks.max = max;
    hooks.now = now;
    hooks.utc = createUTC;
    hooks.unix = createUnix;
    hooks.months = listMonths;
    hooks.isDate = isDate;
    hooks.locale = getSetGlobalLocale;
    hooks.invalid = createInvalid;
    hooks.duration = createDuration;
    hooks.isMoment = isMoment;
    hooks.weekdays = listWeekdays;
    hooks.parseZone = createInZone;
    hooks.localeData = getLocale;
    hooks.isDuration = isDuration;
    hooks.monthsShort = listMonthsShort;
    hooks.weekdaysMin = listWeekdaysMin;
    hooks.defineLocale = defineLocale;
    hooks.updateLocale = updateLocale;
    hooks.locales = listLocales;
    hooks.weekdaysShort = listWeekdaysShort;
    hooks.normalizeUnits = normalizeUnits;
    hooks.relativeTimeRounding = getSetRelativeTimeRounding;
    hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
    hooks.calendarFormat = getCalendarFormat;
    hooks.prototype = proto;

    // currently HTML5 input type only supports 24-hour formats
    hooks.HTML5_FMT = {
        DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
        DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
        DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
        DATE: 'YYYY-MM-DD', // <input type="date" />
        TIME: 'HH:mm', // <input type="time" />
        TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
        TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
        WEEK: 'GGGG-[W]WW', // <input type="week" />
        MONTH: 'YYYY-MM', // <input type="month" />
    };

    return hooks;

})));

var AjaxHelper = (function () {
    function AjaxHelper(pageHelper, navigationHelper, formHelper, dialogHelper) {
        var _this = this;
        this.pageHelper = pageHelper;
        this.navigationHelper = navigationHelper;
        this.formHelper = formHelper;
        this.dialogHelper = dialogHelper;
        this.keepTheSessionAlive = false;
        this.ajaxPost = function (url, callbackOnSuccess, data, returnsJson, callbackOnError) {
            if (returnsJson === void 0) { returnsJson = false; }
            _this.setupSessionTimers();
            var options = {
                type: "POST",
                cache: false,
                url: url,
                data: data,
                success: callbackOnSuccess,
                timeout: 4 * 60 * 1000
            };
            if (returnsJson) {
                options.dataType = "json";
            }
            if (callbackOnError) {
                options.error = callbackOnError;
            }
            else {
                options.error = _this.handleAjaxError;
            }
            $.ajax(options);
        };
        this.ajaxPostJson = function (url, callbackOnSuccess, data, callbackOnError) {
            _this.setupSessionTimers();
            var options = {
                type: "POST",
                contentType: "application/json",
                cache: false,
                url: url,
                data: data,
                success: callbackOnSuccess,
                timeout: 4 * 60 * 1000
            };
            if (callbackOnError) {
                options.error = callbackOnError;
            }
            else {
                options.error = _this.handleAjaxError;
            }
            $.ajax(options);
        };
        this.ajaxGet = function (url, callbackOnSuccess, returnsJson) {
            if (returnsJson === void 0) { returnsJson = false; }
            _this.setupSessionTimers();
            var options = {
                type: "GET",
                cache: false,
                error: _this.handleAjaxError,
                url: url,
                success: callbackOnSuccess,
                timeout: 4 * 60 * 1000
            };
            if (returnsJson) {
                options.dataType = "json";
            }
            $.ajax(options);
        };
        this.ajaxSubmit = function (formSelector, url, callbackOnSuccess, returnsJson, isFileUpload, callbackOnError) {
            if (returnsJson === void 0) { returnsJson = false; }
            if (isFileUpload === void 0) { isFileUpload = false; }
            _this.setupSessionTimers();
            var options = {
                type: "POST",
                cache: false,
                error: _this.handleAjaxError,
                url: url,
                success: callbackOnSuccess,
                timeout: 4 * 60 * 1000
            };
            if (isFileUpload) {
                options.isFileUpload = true;
            }
            if (callbackOnError) {
                options.error = callbackOnError;
            }
            else {
                options.error = _this.handleAjaxError;
            }
            if (options.isFileUpload && options.success) {
                options.iframe = true;
                if (options.success && !callbackOnError) {
                    var onSuccess = options.success;
                    options.success = function (errorDetails) {
                        var isThisAnError = !_this.formHelper.isNullOrUndefined(errorDetails) && errorDetails.length > 40 && errorDetails.substr(0, 4) !== "<pre";
                        if (isThisAnError) {
                            _this.dialogHelper.hideWaitIndicator();
                            _this.dialogHelper.showMessageDialog(errorDetails);
                        }
                        else {
                            onSuccess();
                        }
                    };
                }
            }
            $(formSelector).ajaxSubmit(options);
        };
        this.handleAjaxError = function (request, status, error) {
            _this.dialogHelper.hideWaitIndicator();
            if (!_this.formHelper.isNullOrUndefined(error) && !_this.formHelper.isNullOrUndefined(error.message) && error.message.indexOf("Access is denied") === 0) {
                var fileUploadErrorMessage = "An error has occurred when trying to upload the file.<br/><br/>" +
                    "The most common reason for this is the file being too large - the system imposes a " +
                    "size limit on uploads.";
                _this.dialogHelper.showMessageDialog(fileUploadErrorMessage, "Error during file upload");
            }
            else if (request.responseText) {
                _this.dialogHelper.showMessageDialog(request.responseText);
            }
            else if (status === "timeout") {
                var timeoutMessage = "This is taking a long time! Our apologies. It might be because of a system problem " +
                    "or it might just be that the site is busy.<br/><br/>" +
                    "You might want to check back in a few minutes and try again if necessary.";
                _this.dialogHelper.showMessageDialog(timeoutMessage);
            }
        };
        this.setupSessionTimers = function () {
            var pollIntervalInMinutes = 1;
            if (_this.sessionWarningTimer) {
                clearTimeout(_this.sessionWarningTimer);
            }
            if (!_this.formHelper.getBoolean("#uxDoNotPollSessionStatus")) {
                var handleSessionStatus = function (status) {
                    switch (status) {
                        case "LoggedIn":
                            break;
                        case "LoggedOut":
                            _this.navigationHelper.reload();
                            break;
                        case "SessionAlmostExpired":
                            if (!_this.pageHelper.useNativeJavascriptDialogs()) {
                                _this.dialogHelper.showMessageDialog("Due to a period of inactivity you will shortly be logged out. If you don't want this to happen, click the 'OK' button.", "Automatic Logout Warning", function () { _this.ajaxGet("/Home/KeepSessionAlive"); });
                            }
                            break;
                    }
                };
                _this.sessionWarningTimer = setTimeout(function () {
                    if (_this.keepTheSessionAlive) {
                        _this.ajaxGet("/Home/KeepSessionAlive");
                    }
                    else {
                        _this.ajaxGet("/Home/CheckSessionStatus", handleSessionStatus);
                    }
                }, pollIntervalInMinutes * 60 * 1000);
            }
        };
        this.keepSessionAlive = function () {
            _this.keepTheSessionAlive = true;
            _this.setupSessionTimers();
        };
    }
    return AjaxHelper;
}());

var DialogHelper = (function () {
    function DialogHelper(pageHelper, formHelper, reinitialisationHelper) {
        var _this = this;
        this.pageHelper = pageHelper;
        this.formHelper = formHelper;
        this.reinitialisationHelper = reinitialisationHelper;
        this.dialogCount = 0;
        this.isWaitIndicatorVisible = false;
        this.createDialog = function (id, html, widthCssClass, dontDisplayImmediately) {
            if (dontDisplayImmediately === void 0) { dontDisplayImmediately = false; }
            var dlg = $("#" + id);
            if (dlg.length === 0) {
                dlg = _this.createModalPlaceholder(id, widthCssClass);
            }
            dlg.html(html);
            if (!dontDisplayImmediately) {
                _this.displayDialog(dlg);
            }
            return dlg;
        };
        this.displayDialog = function (dialogContentSelector, widthCssClass) {
            _this.hideWaitIndicator();
            var dlg = $(dialogContentSelector);
            if (widthCssClass) {
                dlg.addClass(widthCssClass);
            }
            $(".tps-close-button", dlg).off("click").click(function () { _this.hideDialog(dlg); });
            _this.reinitialisationHelper.run();
            _this.maskAnyVisibleDialogs(dlg);
            dlg.modal({ show: true, keyboard: false, backdrop: "static" });
            dlg.one("shown", function () {
                var element = $(dlg).find(":input:visible:not(button)");
                if ($(element).length === 0) {
                    element = $("button:visible,a:not(.help-button):visible", dlg);
                }
                element.first().focus();
            });
            window.setTimeout(function () {
                if (!$(".modal-backdrop.in").length) {
                    dlg.modal("backdrop");
                }
            }, 600);
        };
        this.hideDialog = function (dialogContentSelector, callback) {
            var dlg = $(dialogContentSelector);
            if (dlg.is(".in")) {
                if (callback) {
                    dlg.one("hidden", callback);
                }
                dlg.modal("hide");
                _this.unmaskNextDialog(dlg);
            }
        };
        this.destroyDialog = function (dialogContentSelector) {
            _this.hideDialog(dialogContentSelector);
            $(dialogContentSelector).html("");
        };
        this.showWaitIndicator = function () {
            if (!_this.isWaitIndicatorVisible) {
                _this.displayDialog("#uxWaitIndicatorDialog");
                _this.isWaitIndicatorVisible = true;
            }
        };
        this.hideWaitIndicator = function () {
            if (_this.isWaitIndicatorVisible) {
                _this.hideDialog("#uxWaitIndicatorDialog");
                _this.isWaitIndicatorVisible = false;
            }
        };
        this.showMessageDialog = function (message, title, callback) {
            if (_this.pageHelper.useNativeJavascriptDialogs()) {
                alert(message.replace(/^\s+/g, "").replace(/\s+$/g, "").replace(/\u0009+/g, ""));
                if (callback) {
                    callback();
                }
            }
            else {
                var dlg = $("#uxShowMessageDialog");
                var contents = $("#uxShowMessageDialogMessage", dlg);
                contents.html(message);
                if (!title) {
                    title = _this.getHeadingForDialog(contents);
                }
                $(".modal-header>h3", dlg).html(title);
                var close = $("#btnClose", dlg);
                close.unbind("click");
                if (callback) {
                    close.click(function () {
                        _this.hideDialog(dlg);
                        callback();
                    });
                }
                else {
                    close.click(function () {
                        _this.hideDialog(dlg);
                    });
                }
                _this.displayDialog(dlg);
            }
        };
        this.showConfirmationDialog = function (confirmationPrompt, callbackIfConfirmed, callbackIfDenied, title) {
            if (_this.pageHelper.useNativeJavascriptDialogs()) {
                if (confirm(confirmationPrompt)) {
                    if (callbackIfConfirmed) {
                        callbackIfConfirmed();
                    }
                }
                else {
                    if (callbackIfDenied) {
                        callbackIfDenied();
                    }
                }
            }
            else {
                var dlg = $("#uxConfirmationDialog");
                var contents = $("#uxConfirmationDialogMessage", dlg);
                contents.html(confirmationPrompt + "<br/><br/>");
                var deniedCallback = callbackIfDenied ? callbackIfDenied : _this.closeConfirmationDialog;
                if (!title) {
                    title = _this.getHeadingForDialog(contents);
                }
                $(".modal-header>h3", dlg).html(title);
                var yep = $("#btnYes2", dlg);
                yep.unbind("click");
                yep.click(function () {
                    _this.closeConfirmationDialog();
                    callbackIfConfirmed();
                });
                var nope = $("#btnNo2", dlg);
                nope.unbind("click");
                nope.click(function () {
                    _this.closeConfirmationDialog();
                    deniedCallback();
                });
                _this.displayDialog(dlg, "modal-narrow");
            }
        };
        this.showCommentsDialog = function (prompt, callbackOnSave, callbackOnCancel, introductoryText, initialText) {
            var dlg = $("#uxCommentsDialog");
            $("#uxCommentsDialogPrompt", dlg).html(prompt);
            var introduction = $("#uxCommentsDialogIntroduction", dlg);
            if (introductoryText) {
                introduction.html(introductoryText);
            }
            else {
                introduction.html("");
            }
            var dialogTextArea = $("#uxCommentsDialogText", dlg);
            if (initialText) {
                dialogTextArea.val(initialText);
            }
            else {
                dialogTextArea.val("");
            }
            _this.formHelper.setupValidation($("#uxCommentsDialogForm", dlg));
            $("#btnCommentsSaveButton", dlg).unbind("click").click(function () {
                if (_this.formHelper.isFormValid($("#uxCommentsDialogForm", dlg))) {
                    _this.hideDialog(dlg);
                    callbackOnSave(dialogTextArea.val());
                }
            });
            $("#btnCommentsCancelButton", dlg).unbind("click").click(function () {
                _this.hideDialog(dlg);
                if (callbackOnCancel) {
                    callbackOnCancel();
                }
            });
            _this.displayDialog(dlg);
        };
        this.showYesNoCancelDialog = function (dialogPrompt, callbackIfYes, callbackIfNo, dialogTitle) {
            var dlg = $("#uxYesNoCancelConfirmationDialog");
            if (dialogTitle) {
                $(".modal-header>h3", dlg).text(dialogTitle);
            }
            $("#uxYesNoCancelConfirmationDialogMessage", dlg).html(dialogPrompt);
            $("#btnYes", dlg).off("click").click(function () {
                _this.hideDialog(dlg);
                callbackIfYes();
            });
            $("#btnNo", dlg).off("click").click(function () {
                _this.hideDialog(dlg);
                callbackIfNo();
            });
            $("#btnCancel", dlg).off("click").click(function () {
                _this.hideDialog(dlg);
            });
            _this.displayDialog(dlg);
        };
        this.createModalPlaceholder = function (id, width) {
            if (!width) {
                width = "";
            }
            var dlg = $("<div id='" + id + "' class='modal hide fade " + width + "'/>");
            $("#uxWaitIndicatorDialog").before(dlg);
            return dlg;
        };
        this.maskAnyVisibleDialogs = function (dialogAboutToBeShown) {
            $(".modal.in").not(dialogAboutToBeShown).addClass("modal-masked");
            _this.dialogCount++;
            dialogAboutToBeShown.attr("data-modal-level", _this.dialogCount);
        };
        this.unmaskNextDialog = function (dialogJustHidden) {
            dialogJustHidden.removeClass("modal-masked").removeAttr("data-modal-level");
            _this.dialogCount--;
            if (_this.dialogCount > 0) {
                var that = _this;
                $(".modal.in").each(function () {
                    if (parseInt($(this).attr("data-modal-level"), 10) === that.dialogCount) {
                        $(this).removeClass("modal-masked");
                    }
                });
            }
        };
        this.getHeadingForDialog = function (dialogContents) {
            var title;
            var embeddedTitle = $("h1,h2,h3,h4", dialogContents);
            if (embeddedTitle.length === 1) {
                title = embeddedTitle.html();
                embeddedTitle.remove();
            }
            else {
                title = "Tuition Protection Service";
            }
            return title;
        };
        this.closeConfirmationDialog = function () {
            _this.hideDialog("#uxConfirmationDialog");
        };
    }
    return DialogHelper;
}());

var FormHelper = (function () {
    function FormHelper() {
        var _this = this;
        this.convertFormToJson = function (formSelector) {
            var json = {};
            var nameValueJson = $(formSelector).serializeArray();
            $.each(nameValueJson, function (i, field) {
                if (json[field.name]) {
                    if (json[field.name].constructor === Array) {
                        var array = json[field.name];
                        array[array.length] = field.value;
                    }
                    else {
                        var originalValue = json[field.name];
                        json[field.name] = [];
                        json[field.name][0] = originalValue;
                        json[field.name][1] = field.value;
                    }
                }
                else {
                    json[field.name] = field.value;
                }
            });
            return json;
        };
        this.clearForm = function (formSelector) {
            var form = $(formSelector);
            $("select.tps-multi-select", form).select2("val", "");
            $(":input:not(:checkbox):not(:radio):not([type=hidden])", form).val("");
            $(":input[id$='_Operator']", form).trigger("reset");
            $(".datepicker", form).attr("typedValue", "");
            $(".tpsdatetime", form).attr("typedValue", "");
            $(":checkbox", form).removeAttr("checked");
            $("span.autoContextMenuTrigger", form).each(function () {
                var trigger = $(this);
                var inputField = "#" + $(this).attr("inputField");
                var firstLi = $("li:first", this);
                $("span:first", trigger).text($(firstLi).html());
                $(inputField).val($(firstLi).attr("key"));
            });
        };
        this.enableControl = function (selector, enable) {
            var ctl = $(selector);
            _this.enableControls(ctl, enable);
        };
        this.enableControls = function (controls, enable) {
            if (enable) {
                controls.removeAttr("disabled").removeClass("disabled");
            }
            else {
                controls.attr("disabled", "disabled").addClass("disabled");
            }
        };
        this.showControl = function (selector, show) {
            var ctl = $(selector);
            if (show) {
                ctl.removeClass("hide");
                ctl.show();
            }
            else {
                ctl.addClass("hide");
                ctl.hide();
            }
        };
        this.getBoolean = function (selector, context) {
            var stringValue = context ? $(selector, context).val() : $(selector).val();
            return _this.convertToBoolean(stringValue);
        };
        this.convertToBoolean = function (stringValue) {
            if (stringValue) {
                switch (stringValue.toLowerCase()) {
                    case "false":
                    case "f":
                    case "no":
                    case "n":
                    case "0":
                        return false;
                    default:
                        return true;
                }
            }
            return false;
        };
        this.isFormValid = function (formSelector) {
            var form = $(formSelector);
            var isValid = form.valid();
            if (!isValid) {
                var container = form.closest("div.modal-body");
                if (container.length) {
                    _this.scrollToElement($("#uxErrorContainer", form), container.length ? container : null);
                }
                $("#uxErrorContainer").focus();
            }
            return isValid;
        };
        this.scrollToElement = function (element, containerSelector, additionalOffset, callback) {
            var top = $(element).offset().top;
            if (additionalOffset) {
                top = top + additionalOffset;
            }
            var container = containerSelector ? $(containerSelector) : $("html, body");
            container.animate({ scrollTop: top }, 1000, "swing", callback);
        };
        this.setupValidation = function (form) {
            var myForm = $(form);
            myForm.append("<div id='uxErrorContainer' class='error' style='display:none' tabIndex='-1'></div>");
            var messages = {};
            $("*[data-title-for-validation]", myForm).each(function () {
                var title = $(this).attr("data-title-for-validation");
                var fieldName = $(this).prop("name");
                var validationMessages = {
                    required: title + " is a required field.",
                    maxlength: title + " can contain no more than " + $(this).attr("maxlength") + " characters.",
                    tpsdate: title + " is not recognisable as a date. Try using the \"dd/mm/yyyy\" style (e.g. \"25/12/2013\").",
                    number: title + " must be a number.",
                    digits: title + " can only contain digits.",
                    min: title + " cannot be smaller than " + $(this).attr("min") + ".",
                    max: title + " cannot be greater than " + $(this).attr("max") + "."
                };
                messages[fieldName] = validationMessages;
            });
            myForm.validate({
                errorLabelContainer: $("#uxErrorContainer", myForm),
                messages: messages
            });
            $("input.hidden-form-submit-button", myForm).click(function () {
                var b = $(this);
                var parent = b.parents(".modal");
                if (!parent.length) {
                    parent = b.parents("div.content");
                }
                $(b.attr("data-default-button-selector"), parent).click();
                return false;
            });
        };
        this.bindEnterKey = function (form, callback) {
            var myForm = $(form);
            myForm.bind("keypress", function (e) {
                if ((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) {
                    callback();
                }
            });
        };
        this.clearValidationWarnings = function (context) {
            var errorContainer = $("#uxErrorContainer", context);
            errorContainer.text("").css("display", "none");
        };
        this.addValidationWarning = function (context, fieldId, message) {
            var errorContainer = $("#uxErrorContainer", context);
            errorContainer.append("<label class='error' for='" + fieldId + "'>" + message + "</label>");
            errorContainer.css("display", "");
            var c = $(context);
            var container = c.is(".modal") ? c.find("div.modal-body") : c.closest("div.modal-body");
            if (container.length) {
                _this.scrollToElement(errorContainer, container);
            }
        };
        this.onTabSelected = function (tabs, callback) {
            var tabStrip = $(tabs);
            $("a[data-toggle='tab']", tabStrip).on("shown", function (e) {
                var id = $(e.target).prop("href").split("#")[1];
                callback(id);
            });
        };
        this.selectTab = function (tabs, tabId) {
            var anchor = $("li>a[href='#" + tabId + "']", $(tabs));
            anchor.tab("show");
        };
        this.selectTabFromUrl = function (tabs) {
            var tabId = window.location.hash.substr(1);
            if (tabId) {
                _this.selectTab(tabs, tabId);
            }
        };
        this.getIdOfSelectedTab = function (tabs) {
            var t = $(tabs);
            var sel = $("li.active>a", t);
            return sel.prop("href").split("#")[1];
        };
        this.hideTab = function (tabs, tabId, show) {
            if (show === void 0) { show = false; }
            var tab = $(tabs).find("li:has(a[href='#" + tabId + "'])");
            _this.showControl(tab, show);
        };
        this.showTab = function (tabs, tabId) {
            _this.hideTab(tabs, tabId, true);
            _this.selectTab(tabs, tabId);
        };
        this.setTabText = function (tabs, tabId, text) {
            _this.getTab(tabs, tabId).text(text);
        };
        this.getTab = function (tabs, tabId) {
            return $("a[href='#" + tabId + "']", tabs);
        };
        this.addRequiredFieldIndicator = function (elements) {
            var container = $(elements).is("div.control-group") ? $(elements) : $(elements).closest("div.control-group");
            var label = $("label.control-label", container);
            if (!label.has("span.label-info").length) {
                label.append("<span class='label label-info'>required</span>");
            }
        };
        this.removeRequiredFieldIndicator = function (elements) {
            var container = $(elements).is("div.control-group") ? $(elements) : $(elements).closest("div.control-group");
            var label = $("label.control-label", container);
            var indicator = $("span.label-info", label);
            indicator.remove();
        };
        this.addRequiredFieldIndicatorForStudents = function (elements) {
            $(elements)
                .not(".alreadyApplied")
                .addClass("alreadyApplied")
                .children("label")
                .append("<span class=\"req\"> (required)</span>");
        };
        this.removeRequiredFieldIndicatorForStudents = function (elements) {
            $(elements)
                .removeClass("alreadyApplied")
                .find("span[class='req']")
                .remove();
        };
        this.addRequiredFieldIndicatorForStudentInputField = function (inputField) {
            var container = $(inputField).parent();
            _this.addRequiredFieldIndicatorForStudents(container);
        };
        this.removeRequiredFieldIndicatorForStudentInputField = function (inputField) {
            var container = $(inputField).parent();
            _this.removeRequiredFieldIndicatorForStudents(container);
        };
        this.showRow = function (selector, show) {
            var row = $(selector);
            if (show) {
                _this.switchRequired($(".wasrequired", row), true);
            }
            else {
                _this.switchRequired($(".required", row), false);
            }
            _this.showControl(row, show);
        };
        this.switchRequired = function (selector, on) {
            if (on) {
                $(selector).filter(".wasrequired").removeClass("wasrequired").addClass("required");
            }
            else {
                $(selector).filter(".required").removeClass("required").addClass("wasrequired");
            }
        };
        this.setupAddressValidation = function (container, isAddressOptional) {
            if (isAddressOptional === void 0) { isAddressOptional = false; }
            var that = _this;
            var handleAddressCountryChange = function () {
                var addressCombo = $(this);
                var isAddressOptional = addressCombo.attr("is-address-optional") === "true";
                var isDisabled = addressCombo.is(":disabled");
                var container = addressCombo.closest("form");
                var allAddressElements;
                var message;
                if (addressCombo.is(".residential")) {
                    message = " residential address ";
                    allAddressElements = $(".residential", container);
                }
                else if (addressCombo.is(".postal")) {
                    message = " postal address ";
                    allAddressElements = $(".postal", container);
                }
                else {
                    message = " address ";
                    allAddressElements = $(".address-country,.address-postcode,.address-state,.address-locality,.address-line1", container);
                }
                var country = allAddressElements.filter(".address-country");
                var postcode = allAddressElements.filter(".address-postcode");
                var state = allAddressElements.filter(".address-state");
                var locality = allAddressElements.filter(".address-locality");
                var line1 = allAddressElements.filter(".address-line1");
                if (isDisabled || isAddressOptional) {
                    line1.rules("add", { required: false, messages: { required: "" } });
                    locality.rules("add", { required: false, messages: { required: "" } });
                    country.rules("add", { required: false, messages: { required: "" } });
                    that.removeRequiredFieldIndicator(line1);
                    that.removeRequiredFieldIndicator(locality);
                    that.removeRequiredFieldIndicator(country);
                }
                else {
                    line1.rules("add", { required: true, messages: { required: "The" + message + "line 1 field is required" } });
                    locality.rules("add", { required: true, messages: { required: "The" + message + "suburb/city field is required" } });
                    country.rules("add", { required: true, messages: { required: "The" + message + "country field is required" } });
                    that.addRequiredFieldIndicator(line1);
                    that.addRequiredFieldIndicator(locality);
                    that.addRequiredFieldIndicator(country);
                }
                var isAustralia = country.val() === "AUS";
                if (isAustralia && !isDisabled) {
                    if (isAddressOptional) {
                        postcode.rules("add", { required: false, messages: { required: "" } });
                        state.rules("add", { required: false, messages: { required: "" } });
                        that.removeRequiredFieldIndicator(postcode);
                        that.removeRequiredFieldIndicator(state);
                    }
                    else {
                        postcode.rules("add", { required: true, messages: { required: "The" + message + "postcode field is required" } });
                        state.rules("add", { required: true, messages: { required: "The" + message + "state field is required" } });
                        that.addRequiredFieldIndicator(postcode);
                        that.addRequiredFieldIndicator(state);
                    }
                    postcode.addClass("digits");
                    postcode.attr("maxlength", 4);
                    postcode.attr("minlength", 4);
                    postcode.attr("Title", "The" + message + "postcode must be exactly 4 digits");
                    state.prop("disabled", false);
                }
                else {
                    postcode.removeAttrs("maxlength minlength Title");
                    postcode.removeClass("digits");
                    postcode.rules("add", { required: false, messages: { required: "" } });
                    state.rules("add", { required: false, messages: { required: "" } });
                    state.prop("disabled", true);
                    state.val("");
                    that.removeRequiredFieldIndicator(postcode);
                    that.removeRequiredFieldIndicator(state);
                }
            };
            var countryDropdowns = $(".address-country", $(container));
            if (isAddressOptional) {
                countryDropdowns.attr("is-address-optional", "true");
            }
            countryDropdowns
                .unbind("change", handleAddressCountryChange)
                .bind("change", handleAddressCountryChange);
            countryDropdowns.each(handleAddressCountryChange);
        };
        this.setupAddressValidationForStudents = function (container) {
            var that = _this;
            var handleAddressCountryChangeForStudents = function () {
                var addressCombo = $(this);
                var isDisabled = addressCombo.is(":disabled");
                var container = addressCombo.closest("fieldset");
                var allAddressElements;
                var message;
                if (addressCombo.is(".residential")) {
                    message = " residential address ";
                    allAddressElements = $(".residential", container);
                }
                else if (addressCombo.is(".postal")) {
                    message = " postal address ";
                    allAddressElements = $(".postal", container);
                }
                else {
                    message = " address ";
                    allAddressElements = $(".address-country,.address-postcode,.address-state,.address-locality,.address-line1", container);
                }
                var country = allAddressElements.filter(".address-country");
                var postcode = allAddressElements.filter(".address-postcode");
                var state = allAddressElements.filter(".address-state");
                var locality = allAddressElements.filter(".address-locality");
                var line1 = allAddressElements.filter(".address-line1");
                if (!isDisabled) {
                    line1.rules("add", { required: true, messages: { required: "The" + message + "line 1 field is required" } });
                    locality.rules("add", { required: true, messages: { required: "The" + message + "suburb/city field is required" } });
                    country.rules("add", { required: true, messages: { required: "The" + message + "country field is required" } });
                    that.addRequiredFieldIndicatorForStudentInputField(line1);
                    that.addRequiredFieldIndicatorForStudentInputField(locality);
                    that.addRequiredFieldIndicatorForStudentInputField(country);
                }
                else {
                    line1.rules("add", { required: false, messages: { required: "" } });
                    locality.rules("add", { required: false, messages: { required: "" } });
                    country.rules("add", { required: false, messages: { required: "" } });
                    that.removeRequiredFieldIndicatorForStudentInputField(line1);
                    that.removeRequiredFieldIndicatorForStudentInputField(locality);
                    that.removeRequiredFieldIndicatorForStudentInputField(country);
                }
                var isAustralia = country.val() === "AUS";
                if (isAustralia && !isDisabled) {
                    postcode.rules("add", { required: true, messages: { required: "The" + message + "postcode field is required" } });
                    postcode.addClass("digits");
                    postcode.attr("maxlength", 4);
                    postcode.attr("minlength", 4);
                    postcode.attr("Title", "The" + message + "postcode must be exactly 4 digits");
                    state.rules("add", { required: true, messages: { required: "The" + message + "state field is required" } });
                    state.prop("disabled", false);
                    that.addRequiredFieldIndicatorForStudentInputField(postcode);
                    that.addRequiredFieldIndicatorForStudentInputField(state);
                }
                else {
                    postcode.removeAttrs("maxlength minlength Title");
                    postcode.removeClass("digits");
                    postcode.rules("add", { required: false, messages: { required: "" } });
                    state.rules("add", { required: false, messages: { required: "" } });
                    state.prop("disabled", true);
                    state.val("");
                    that.removeRequiredFieldIndicatorForStudentInputField(postcode);
                    that.removeRequiredFieldIndicatorForStudentInputField(state);
                }
            };
            var countryDropdowns = $(".address-country", $(container));
            var that = _this;
            countryDropdowns
                .unbind("change", handleAddressCountryChangeForStudents)
                .bind("change", handleAddressCountryChangeForStudents);
            countryDropdowns.each(handleAddressCountryChangeForStudents);
        };
        this.toggleRequiredFieldValidation = function (inputFieldSelector, isRequired) {
            var field = $(inputFieldSelector);
            if (isRequired) {
                field.addClass("required");
            }
            else {
                field.removeClass("required");
            }
        };
        this.isNullOrUndefined = function (v) {
            return v === null || v === undefined;
        };
        this.isEmptyNullOrUndefined = function (v) {
            return v === null || v === undefined || v === "";
        };
    }
    return FormHelper;
}());

var InitialisationHelper = (function () {
    function InitialisationHelper(formHelper, navigationHelper, parsingHelper, ajaxHelper, reinitialisationHelper) {
        var _this = this;
        this.formHelper = formHelper;
        this.navigationHelper = navigationHelper;
        this.parsingHelper = parsingHelper;
        this.ajaxHelper = ajaxHelper;
        this.reinitialisationHelper = reinitialisationHelper;
        this.pageHasBeenInitialised = false;
        this.run = function () {
            if (!_this.pageHasBeenInitialised) {
                _this.initialiseOnceOnly();
                _this.pageHasBeenInitialised = true;
            }
            _this.reinitialisationHelper.run();
        };
        this.initialiseOnceOnly = function () {
            jQuery().modal.Constructor.prototype.enforceFocus = function () {
            };
            $(".modal[data-color]").on("show hidden", function () {
                $("body")
                    .toggleClass("modal-color-" + $(this).data("color"));
            });
            var nav = $("#tpsMainNavigationBar");
            nav.affix({ offset: nav.position() });
            $("#tpsMainNavigationBarWrapper").height(nav.height());
            $("#mainNav")
                .find("a")
                .focus(function () { $(this).closest("ul").css("left", "-10px"); })
                .blur(function () { $(this).closest("ul").css("left", ""); });
            _this.ajaxHelper.setupSessionTimers();
            $("#uxSelectCurrentProviderMenuItem").click(_this.showSelectProviderDialog);
            $("#uxChangePasswordMenuButton").click(_this.showChangePasswordDialog);
            $('#coe-search-form').off('submit').on('submit', function (e) {
                e.preventDefault();
                e.stopPropagation();
                var coeCode = $('#uxCoeCode').val();
                if (coeCode != '') {
                    helper.dialog.showWaitIndicator();
                    helper.ajax.ajaxPost("/Coe/GetListingOfMatchingCoesnapshotsByCoeCode", function (data) {
                        if (data['count'] == 0) {
                            helper.dialog.showMessageDialog("Found no matching CoE Snapshot.", "CoE quick search");
                        }
                        if (data['count'] == 1) {
                            var coeId = data['matchedCoeId'];
                            helper.navigation.redirect("/Coe/View/" + coeId);
                        }
                        if (data['count'] > 1) {
                            this.tpsDialogForCoeQuickSearch = new DialogForSelectingAQuickSearchCoe();
                            helper.dialog.hideWaitIndicator();
                            this.tpsDialogForCoeQuickSearch.show(data);
                        }
                    }, { coeCode: coeCode });
                }
            });
            var that = _this;
            jQuery.validator.addMethod("tpsdate", function (value, element) {
                return this.optional(element) || !that.formHelper.isNullOrUndefined(that.parsingHelper.parseDate(value));
            }, "Please enter a valid date.");
            jQuery.validator.addMethod("tpsdatetime", function (value, element) {
                return this.optional(element) || !that.formHelper.isNullOrUndefined(that.parsingHelper.parseDateTime(value));
            }, "Please enter a valid date/time.");
            jQuery.validator.addMethod("tpstime", function (value, element) {
                return this.optional(element) || !that.formHelper.isNullOrUndefined(that.parsingHelper.parseTime(value));
            }, "Please enter a valid time.");
            jQuery.validator.addMethod("tpscurrency", function (value, element) {
                var currency = that.parsingHelper.parseCurrency(value);
                return this.optional(element) || (!that.formHelper.isNullOrUndefined(currency) && currency >= 0 && currency < 922337203685477);
            }, "Please enter a valid currency value.");
            jQuery.validator.addMethod("phone", function (value, element) {
                return this.optional(element) || that.parsingHelper.isValidPhoneNumber(value);
            }, "Please enter a valid phone number. Phone numbers may only include digits, spaces and the + symbol.");
            jQuery.validator.addMethod("tpsname", function (value, element) {
                return this.optional(element) || that.parsingHelper.isValidTpsName(value);
            }, "Please enter a valid name. Names can only include letters, spaces, hyphens, apostrophes and full stops.");
            jQuery.validator.addMethod("alphanumeric", function (value, element) {
                return this.optional(element) || that.parsingHelper.isAlphaNumeric(value);
            }, "Please enter a valid alpha-numeric value. Values may only include letters or digits.");
            jQuery.validator.addMethod("decimal", function (value, element) {
                return this.optional(element) || that.parsingHelper.isDecimal(value);
            }, "Please enter a valid decimal number.");
            jQuery.validator.addMethod("ValidABN", function (value, element) {
                return this.optional(element) || (that.parsingHelper.isAbn(value));
            }, "Please enter a valid Australian Business Number (ABN).");
            jQuery.validator.addMethod("ValidACN", function (value, element) {
                return this.optional(element) || (that.parsingHelper.isAcn(value));
            }, "Please enter a valid Australian Company Number (ACN).");
            jQuery.validator.addMethod("validextensions", function (value, element) {
                var validExtensions = $(element).attr("validExtensions");
                var valid = (value.match(new RegExp("\\.(" + validExtensions + ")$", "gi")) !== null);
                var optional = this.optional(element);
                return optional || valid;
            }, "Please select a valid file type.");
        };
    }
    InitialisationHelper.prototype.showSelectProviderDialog = function () {
        var tpsDialogForSelectingAProvider = new DialogForSelectingAProvider();
        tpsDialogForSelectingAProvider.show();
    };
    InitialisationHelper.prototype.showChangePasswordDialog = function () {
        var dlg = new DialogForChangingPassword();
        dlg.show();
    };
    return InitialisationHelper;
}());

var NavigationHelper = (function () {
    function NavigationHelper() {
        var _this = this;
        this.redirect = function (url) {
            window.location.href = url;
        };
        this.redirectToHome = function () {
            _this.redirect("/");
        };
        this.reload = function (tabSelector) {
            if (tabSelector) {
                _this.redirect(tabSelector);
            }
            window.location.reload();
        };
        this.openInNewWindow = function (url) {
            window.open(url, "_blank");
        };
        this.getQueryStringParameter = function (name) {
            var regex = new RegExp("[?&]" + name + "=([^&]*)");
            var match = regex.exec(window.location.search);
            return match && decodeURIComponent(match[1].replace(/\+/g, " "));
        };
    }
    return NavigationHelper;
}());

var PageHelper = (function () {
    function PageHelper(navigationHelper, formHelper) {
        var _this = this;
        this.navigationHelper = navigationHelper;
        this.formHelper = formHelper;
        this.useNativeJavascriptDialogs = function () {
            return _this.formHelper.getBoolean("#uxUseJavascriptDialogs");
        };
        this.showConfirmationPage = function (title, message) {
            _this.navigationHelper.redirect("/Home/Confirmation?title=" + encodeURIComponent(title) + "&message=" + encodeURIComponent(message));
        };
        this.affixTabs = function (tabs) {
            var menuBar = $("#tpsMainNavigationBarWrapper");
            var offset = tabs.position().top + menuBar.position().top - 10;
            tabs.affix({ offset: offset });
            tabs.parent().height(tabs.height());
        };
        this.affixButtonBar = function (bar) {
            var buttonBar = $(bar);
            if (buttonBar.length) {
                buttonBar.wrap("<div class='tps-affixed-button-bar-wrapper'/>");
                buttonBar.addClass("tps-affixed-button-bar");
                var menuBar = $("#tpsMainNavigationBarWrapper");
                var offset = menuBar.position().top + $("h1").height();
                buttonBar.affix({ offset: offset });
                buttonBar.parent().height(buttonBar.height());
            }
        };
        this.setCookie = function (name, value, days) {
            var expires;
            if (days) {
                var date = new Date();
                date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
                expires = "; expires=" + date.toUTCString();
            }
            else {
                expires = "";
            }
            document.cookie = name + "=" + encodeURI(value) + expires + "; path=/";
        };
        this.getCookie = function (name) {
            var nameEq = name + "=";
            var ca = document.cookie.split(";");
            for (var i = 0; i < ca.length; i++) {
                var c = ca[i];
                while (c.charAt(0) === " ") {
                    c = c.substring(1, c.length);
                }
                if (c.indexOf(nameEq) === 0) {
                    return decodeURI(c.substring(nameEq.length, c.length));
                }
            }
            return null;
        };
    }
    return PageHelper;
}());

var ParsingHelper = (function () {
    function ParsingHelper(formHelper) {
        var _this = this;
        this.formHelper = formHelper;
        this.parseTime = function (timeString) {
            var m = moment(timeString, ["h:mma", "HH:mm"]);
            return m.isValid() ? m.toDate() : null;
        };
        this.parseDate = function (d) {
            var shortFormats = ["DD MM YY", "DD/MM/YY", "DD-MM-YY", "DD MMM YY", "DD-MMM-YY"];
            var longFormats = ["DD MM YYYY", "DD/MM/YYYY", "DD-MM-YYYY", "DD MMM YYYY", "DD-MMM-YYYY"];
            var m = moment(d, shortFormats, null, true);
            if (!m.isValid()) {
                m = moment(d, longFormats, null, true);
            }
            return m.isValid() ? m.toDate() : null;
        };
        this.parseDateTime = function (dt) {
            var formats = _this.dateTimeFormats();
            var m = moment(dt, formats, null, true);
            return m.isValid() ? m.toDate() : null;
        };
        this.dateTimeFormats = function () {
            return [
                "DD MM YY h:mma",
                "DD/MM/YY h:mma",
                "DD-MM-YY h:mma",
                "DD MMM YY h:mma",
                "DD-MMM-YY h:mma",
                "DD MM YYYY h:mma",
                "DD/MM/YYYY h:mma",
                "DD-MM-YYYY h:mma",
                "DD MMM YYYY h:mma",
                "DD-MMM-YYYY h:mma",
                "DD MM YY HH:mm",
                "DD/MM/YY HH:mm",
                "DD-MM-YY HH:mm",
                "DD MMM YY HH:mm",
                "DD-MMM-YY HH:mm",
                "DD MM YYYY HH:mm",
                "DD/MM/YYYY HH:mm",
                "DD-MM-YYYY HH:mm",
                "DD MMM YYYY HH:mm",
                "DD-MMM-YYYY HH:mm",
                "DD MM YY",
                "DD/MM/YY",
                "DD-MM-YY",
                "DD MMM YY",
                "DD-MMM-YY",
                "DD MM YYYY",
                "DD/MM/YYYY",
                "DD-MM-YYYY",
                "DD MMM YYYY",
                "DD-MMM-YYYY",
                "h:mma",
                "HH:mm"
            ];
        };
        this.isValidPhoneNumber = function (phoneNumber) {
            if (phoneNumber.match(/([^\)\(\+\s0-9\-]+)/)) {
                return false;
            }
            else {
                return true;
            }
        };
        this.isValidTpsName = function (name) {
            return name.match(/^[a-zA-Z0-9-\s'",\.&]*$/);
        };
        this.isAlphaNumeric = function (stringToValidate) {
            if (stringToValidate.match(/^[a-zA-Z0-9]*$/)) {
                return true;
            }
            else {
                return false;
            }
        };
        this.isDecimal = function (stringToValidate) {
            if (stringToValidate.match(/^[0-9]*\.?[0-9]*$/)) {
                return true;
            }
            else {
                return false;
            }
        };
        this.isAbn = function (s) {
            var m, as, i, sum = 0;
            m = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19];
            s = String(s).replace(/\s/g, "");
            if (s.length !== 11) {
                return false;
            }
            as = s.split("");
            as[0] = String(parseInt(as[0], 10) - 1);
            for (i = 0; i < as.length; i++) {
                sum += parseInt(as[i], 10) * m[i];
            }
            return (sum % 89 === 0);
        };
        this.isAcn = function (s) {
            var m, as, i, sum = 0, reminder, complement;
            m = [8, 7, 6, 5, 4, 3, 2, 1, 0];
            s = String(s).replace(/\s/g, "");
            if (s.length !== 9) {
                return false;
            }
            as = s.split("");
            for (i = 0; i < as.length; i++) {
                sum += parseInt(as[i], 10) * m[i];
            }
            reminder = sum % 10;
            complement = 10 - reminder;
            if (complement === 10) {
                complement = 0;
            }
            return as[8] === complement;
        };
        this.parseCurrency = function (num) {
            if (_this.formHelper.isEmptyNullOrUndefined(num)) {
                return null;
            }
            var value = parseFloat(num.toString().replace(/\$|\,/g, ""));
            return isNaN(value) ? null : value;
        };
        this.formatCurrency = function (num) {
            if (isNaN(num)) {
                num = 0;
            }
            var negative = num < 0;
            num = Math.abs(num);
            num = Math.floor(num * 100 + 0.50000000001);
            var cents = num % 100;
            var formattedCents = cents < 10 ? "0" + cents : cents.toString();
            var result = Math.floor(num / 100).toString();
            for (var i = 0; i < Math.floor((result.length - (1 + i)) / 3); i++) {
                result = result.substring(0, result.length - (4 * i + 3)) + "," + result.substring(result.length - (4 * i + 3));
            }
            return (negative ? "-" : "") + "$" + result + "." + formattedCents;
        };
        this.formatDecimal = function (num) {
            if (isNaN(num)) {
                return null;
            }
            return parseFloat(num.toString()).toFixed(2);
        };
        this.formatDate = function (date) {
            return moment(date).format("D MMM YYYY");
        };
        this.copyToClipboard = function (str) {
            var e = document.createElement('textarea');
            e.value = str;
            e.setAttribute('readonly', '');
            e.style.position = 'absolute';
            e.style.left = '-9999px';
            document.body.appendChild(e);
            var selected = document.getSelection().rangeCount > 0
                ? document.getSelection().getRangeAt(0)
                : false;
            e.select();
            document.execCommand('copy');
            document.body.removeChild(e);
            if (!selected)
                return;
            document.getSelection().removeAllRanges();
            document.getSelection().addRange(selected);
        };
    }
    return ParsingHelper;
}());

var ReinitialisationHelper = (function () {
    function ReinitialisationHelper(formHelper, navigationHelper, parsingHelper) {
        var _this = this;
        this.formHelper = formHelper;
        this.navigationHelper = navigationHelper;
        this.parsingHelper = parsingHelper;
        this.run = function () {
            var that = _this;
            $(".datepicker:not(.alreadyApplied)").datepicker().addClass("alreadyApplied");
            $(".dialog-help-button").each(function () {
                var modal = $(this).closest(".modal");
                var header = $(".modal-header>h3", modal);
                header.append($(this));
                $(this).removeClass("hide dialog-help-button");
            });
            $(".panel-help-button").each(function () {
                var panel = $(this).closest(".panel");
                var header = $(">h2", panel);
                header.append($(this));
                $(this).removeClass("hide panel-help-button");
            });
            $(".page-help-button").each(function () {
                var header = $(this).parents("div.content").find("h1, h2, h3, h4").first();
                header.append($(this));
                $(this).removeClass("hide page-help-button");
            });
            $(".tab-help-button").each(function () {
                var tabPane = $(this).closest(".tab-pane");
                var modal = tabPane.closest(".modal");
                var id = tabPane.prop("id");
                var tabSelector = ".nav-tabs>li>a[href='#" + id + "']";
                var tab = modal.length ? $(tabSelector, modal) : $(tabSelector);
                if (tab.length === 0) {
                    tab = $("h1,h2,h3,h4", tabPane).first();
                }
                tab.append($(this));
                $(this).removeClass("hide tab-help-button");
            });
            $(".inline-help:not(.alreadyApplied)")
                .popover({ template: "<div class=\"popover\"><div class=\"arrow\"></div><div class=\"popover-inner\"><div class=\"popover-content\"><p></p></div></div></div>" })
                .addClass("alreadyApplied");
            $(".help-button:not(.alreadyApplied)")
                .popover({ html: true, trigger: "manual" })
                .addClass("alreadyApplied")
                .click(function () {
                var context = $(this);
                $(".help-button.in").not(context).removeClass("in").popover("hide");
                if (context.is(".in")) {
                    context.removeClass("in");
                    context.popover("hide");
                }
                else {
                    context.addClass("in");
                    context.popover("show");
                }
                $(".tps-help-close-button").click(function () {
                    context.removeClass("in");
                    context.popover("hide");
                });
                $(".tps-help-open-in-window-button").click(function () {
                    context.removeClass("in");
                    context.popover("hide");
                    var url = "/Help/View/" + context.attr("data-help-context");
                    that.navigationHelper.openInNewWindow(url);
                });
                $(".tps-help-edit-in-place-button").click(function () {
                    context.removeClass("in");
                    context.popover("hide");
                    var dlg = new DialogForEditingHelpText();
                    dlg.show(context.attr("data-help-context"), that.navigationHelper.reload);
                });
            });
            $(".expander:not(.alreadyApplied)").each(function () {
                $(this).addClass("alreadyApplied").bind("click", function () {
                    $("#" + $(this).attr("expandSection")).slideToggle();
                    $(this).toggleClass("collapsed");
                });
            });
            $(".MoreLess:not(.alreadyApplied)")
                .addClass("alreadyApplied")
                .click(function () { that.toggleCollapsableBlock($(this)); });
            $(".tps-multi-select:not(.alreadyApplied),.tps-searchable-select:not(.alreadyApplied)")
                .addClass("alreadyApplied")
                .select2();
            $(".deewrFileInputArea:not(.alreadyApplied)").each(function () {
                $(this).addClass("alreadyApplied");
                var uName = "#ux" + $(".deewrFileInput", this).attr("name") + "Uploader";
                var iName = uName + "Input";
                $(uName).bind("change", function () {
                    $(iName).val($(this).val());
                }).change();
            });
            var selector = ".fieldLabel.labelRequired:not(.alreadyApplied), " +
                ".fieldLabelBold.labelRequired:not(.alreadyApplied), " +
                ".fieldLabelLeft.labelRequired:not(.alreadyApplied), " +
                "div.item.labelRequired:not(.alreadyApplied)";
            _this.formHelper.addRequiredFieldIndicatorForStudents($(selector));
            $(".dropdown-menu-item:not(.alreadyApplied)").bind("click", function () {
                $(this).addClass("alreadyApplied");
                var parent = $(this).closest(".tps-context-menu");
                var input = $("input", parent);
                input.val($(this).attr("data-dropdown-value"));
                $("a.dropdown-toggle>span", parent).text($(this).text());
            });
            $(".tps-date-range:not(.alreadyApplied)").each(function () {
                $(this).addClass("alreadyApplied");
                var name = $(this).attr("data-range-name");
                var pickers = $(".dropdown-menu-item", this);
                var inputField = $("label input", this);
                var fromDateCell = $("#" + name + "_FromDateCell", this);
                var toDateCell = $("#" + name + "_ToDateCell", this);
                var andCell = $("#" + name + "_AndCell", this);
                pickers.bind("click", function () {
                    if (inputField.val() === "IsBetween") {
                        fromDateCell.removeClass("hide");
                        toDateCell.removeClass("hide");
                        andCell.removeClass("hide");
                    }
                    else if (inputField.val() === "Equal" || inputField.val() === "OnOrAfter") {
                        fromDateCell.removeClass("hide");
                        toDateCell.addClass("hide");
                        andCell.addClass("hide");
                    }
                    else if (inputField.val() === "OnOrBefore") {
                        fromDateCell.addClass("hide");
                        toDateCell.removeClass("hide");
                        andCell.addClass("hide");
                    }
                });
                $(pickers[0]).trigger("click");
            });
            $(".tps-numeric-range:not(.alreadyApplied)").each(function () {
                $(this).addClass("alreadyApplied");
                var name = $(this).attr("data-range-name");
                var pickers = $(".dropdown-menu-item", this);
                var inputField = $("label input", this);
                var lowerValueCell = $("#" + name + "_LowerValue", this);
                var upperValueCell = $("#" + name + "_UpperValue", this);
                var andCell = $("#" + name + "_AndCell", this);
                pickers.bind("click", function () {
                    if (inputField.val() === "IsBetween") {
                        lowerValueCell.removeClass("hide");
                        upperValueCell.removeClass("hide");
                        andCell.removeClass("hide");
                    }
                    else if (inputField.val() === "Equal" || inputField.val() === "GreaterThan" || inputField.val() === "GreaterThanOrEqualTo") {
                        lowerValueCell.removeClass("hide");
                        upperValueCell.addClass("hide");
                        andCell.addClass("hide");
                    }
                    else if (inputField.val() === "LessThan" || inputField.val() === "LessThanOrEqualTo") {
                        lowerValueCell.addClass("hide");
                        upperValueCell.removeClass("hide");
                        andCell.addClass("hide");
                    }
                });
                $(pickers[0]).trigger("click");
            });
            $(".dateRangeHolder:not(.alreadyApplied)").each(function () {
                $(this).addClass("alreadyApplied");
                var name = $(this).attr("name");
                var inputField = $("#" + name + "_Operator", this);
                var fromDateCell = $("#" + name + "_FromDateCell", this);
                var toDateCell = $("#" + name + "_ToDateCell", this);
                var andCell = $("#" + name + "_AndCell", this);
                $(this).bind("reset", function () {
                    inputField.val("IsBetween");
                    inputField.trigger("valueChanged");
                });
                inputField.bind("valueChanged", function () {
                    if (inputField.val() === "IsBetween") {
                        fromDateCell.removeClass("hide");
                        toDateCell.removeClass("hide");
                        andCell.removeClass("hide");
                    }
                    else if (inputField.val() === "Equal" || inputField.val() === "OnOrAfter") {
                        fromDateCell.removeClass("hide");
                        toDateCell.addClass("hide");
                        andCell.addClass("hide");
                    }
                    else if (inputField.val() === "OnOrBefore") {
                        fromDateCell.addClass("hide");
                        toDateCell.removeClass("hide");
                        andCell.addClass("hide");
                    }
                });
                inputField.trigger("valueChanged");
            });
            $(".integerRangeHolder:not(.alreadyApplied), .decimalRangeHolder:not(.alreadyApplied)").each(function () {
                $(this).addClass("alreadyApplied");
                var name = $(this).attr("name");
                var inputField = $("#" + name + "_Operator", this);
                var lowerValueCell = $("#" + name + "_LowerValue", this);
                var upperValueCell = $("#" + name + "_UpperValue", this);
                var andCell = $("#" + name + "_AndCell", this);
                $(this).bind("reset", function () {
                    inputField.val("IsBetween");
                    inputField.trigger("valueChanged");
                });
                inputField.bind("valueChanged", function () {
                    if (inputField.val() === "IsBetween") {
                        lowerValueCell.removeClass("hide");
                        upperValueCell.removeClass("hide");
                        andCell.removeClass("hide");
                    }
                    else if (inputField.val() === "Equal" || inputField.val() === "GreaterThan" || inputField.val() === "GreaterThanOrEqualTo") {
                        lowerValueCell.removeClass("hide");
                        upperValueCell.addClass("hide");
                        andCell.addClass("hide");
                    }
                    else if (inputField.val() === "LessThan" || inputField.val() === "LessThanOrEqualTo") {
                        lowerValueCell.addClass("hide");
                        upperValueCell.removeClass("hide");
                        andCell.addClass("hide");
                    }
                });
            });
            $(".tpsdatetime")
                .focus(function () {
                var field = $(this);
                field.removeClass("datepickerInvalidTextInput");
                field.removeClass("datepickerValidTextInput");
                if (!that.formHelper.isNullOrUndefined(field.attr("typedValue"))) {
                    field.val(field.attr("typedValue"));
                }
            })
                .blur(function () {
                var field = $(this);
                field.attr("typedValue", field.val());
                field.removeClass("datepickerInvalidTextInput");
                field.removeClass("datepickerValidTextInput");
                var date = that.parsingHelper.parseDateTime(field.val());
                if (that.formHelper.isNullOrUndefined(date)) {
                    field.addClass("datepickerInvalidTextInput");
                }
                else {
                    var ampm = date.getHours() < 12 ? "AM" : "PM";
                    var hours = date.getHours() < 12 ? date.getHours() : date.getHours() - 12;
                    if (hours === 0) {
                        hours += 12;
                    }
                    var h = hours.toString();
                    h = h.lpad("0", 2);
                    var m = date.getMinutes().toString();
                    m = m.lpad("0", 2);
                    var s = date.getSeconds().toString();
                    s = s.lpad("0", 2);
                    field.val($.datepicker.formatDate("dd M yy", date) + " " + h + ":" + m + ":" + s + ampm);
                    field.addClass("datepickerValidTextInput");
                }
            });
            $(".tpstime")
                .focus(function () {
                var field = $(this);
                field.removeClass("datepickerInvalidTextInput");
                field.removeClass("datepickerValidTextInput");
                if (!that.formHelper.isNullOrUndefined(field.attr("typedValue"))) {
                    field.val(field.attr("typedValue"));
                }
                field.select();
            })
                .blur(function () {
                var field = $(this);
                field.attr("typedValue", field.val());
                field.removeClass("datepickerInvalidTextInput");
                field.removeClass("datepickerValidTextInput");
                var date = that.parsingHelper.parseTime(field.val());
                if (that.formHelper.isNullOrUndefined(date)) {
                    field.addClass("datepickerInvalidTextInput");
                }
                else {
                    var ampm = date.getHours() < 12 ? "AM" : "PM";
                    var hours = date.getHours() < 12 ? date.getHours() : date.getHours() - 12;
                    if (hours === 0) {
                        hours += 12;
                    }
                    var h = hours.toString();
                    h = h.lpad("0", 2);
                    var m = date.getMinutes().toString();
                    m = m.lpad("0", 2);
                    field.val(h + ":" + m + " " + ampm);
                    field.addClass("datepickerValidTextInput");
                }
            });
            $(".tpscurrency")
                .focus(function () {
                var field = $(this);
                if (!that.formHelper.isNullOrUndefined(field.attr("typedValue"))) {
                    field.val(field.attr("typedValue"));
                }
                field.select();
            })
                .blur(function () {
                var field = $(this);
                field.attr("typedValue", field.val());
                var num = that.parsingHelper.parseCurrency(field.val());
                if (!that.formHelper.isNullOrUndefined(num)) {
                    field.val(that.parsingHelper.formatCurrency(num));
                }
            });
            $(".tpsdecimal")
                .blur(function () {
                var field = $(this);
                var num = parseFloat(field.val());
                if (!isNaN(num)) {
                    field.val(that.parsingHelper.formatDecimal(num));
                }
            });
            $(".trimmed")
                .blur(function () {
                var field = $(this);
                field.val(field.val().trim());
            });
            $(".report-button").unbind("click").click(function () {
                var reportId = $(this).attr("reportid");
                var reportContext = $(this).attr("report-context");
                var reportDefaultValue = $(this).attr("report-default-value");
                var tpsDialogForEnteringReportParameters = new DialogForEnteringReportParameters();
                tpsDialogForEnteringReportParameters.show(reportId, reportContext, reportDefaultValue);
            });
            $("#uxPageLoadingIndicator").addClass("hide");
            $("#uxMainContent").removeClass("hide");
        };
        this.toggleCollapsableBlock = function (anchor) {
            var expanding = anchor.hasClass("toggle-more");
            var collapserId = anchor.attr("collapser-id");
            _this.formHelper.showControl("#togglemore" + collapserId + ",#short" + collapserId, !expanding);
            _this.formHelper.showControl("#toggleless" + collapserId + ",#long" + collapserId, expanding);
        };
    }
    return ReinitialisationHelper;
}());

var TableHelper = (function () {
    function TableHelper(pageHelper, formHelper, ajaxHelper) {
        var _this = this;
        this.pageHelper = pageHelper;
        this.formHelper = formHelper;
        this.ajaxHelper = ajaxHelper;
        this.pageableTableUrls = {};
        this.pageableTableCallbacks = {};
        this.pageableTableData = {};
        this.pageableTableFullRowSelectFlags = {};
        this.pageableTableCellSelectFlags = {};
        this.pageableTableOnRowClickCallbacks = {};
        this.pageableTableOnCellClickCallbacks = {};
        this.loadSortableTable = function (url, containerSelector, successCallback, sortColumn, isAscending, fullRowSelect, onRowClickedCallback, data) {
            _this.loadPageableTable(url, containerSelector, successCallback, 1, 20, sortColumn, isAscending, fullRowSelect, onRowClickedCallback, data);
        };
        this.loadPageableTable = function (url, containerSelector, successCallback, pageNumber, pageSize, sortColumn, isAscending, fullRowSelect, onRowClickedCallback, data, overrideSavedSettings, cellSelect, onCellSelectedCallback) {
            _this.pageableTableCallbacks[containerSelector] = successCallback;
            _this.pageableTableUrls[containerSelector] = url;
            _this.pageableTableData[containerSelector] = data;
            _this.pageableTableFullRowSelectFlags[containerSelector] = fullRowSelect;
            _this.pageableTableOnRowClickCallbacks[containerSelector] = onRowClickedCallback;
            _this.pageableTableCellSelectFlags[containerSelector] = cellSelect;
            _this.pageableTableOnCellClickCallbacks[containerSelector] = onCellSelectedCallback;
            if (_this.formHelper.isNullOrUndefined(sortColumn)) {
                sortColumn = "";
            }
            var pageSizeFromCookie = _this.pageHelper.getCookie(containerSelector);
            if (pageSizeFromCookie && !isNaN(parseInt(pageSizeFromCookie, 10))) {
                pageSize = parseInt(pageSizeFromCookie, 10);
            }
            var pagingDetails = {
                pageNumber: pageNumber,
                pageSize: pageSize,
                sortColumn: sortColumn,
                order: isAscending ? "Ascending" : "Descending",
                overrideSavedSettings: overrideSavedSettings ? true : false,
                containerSelector: containerSelector
            };
            jQuery.extend(pagingDetails, data);
            var gridOptions = {
                alternatingRows: true,
                fullRowSelect: fullRowSelect,
                onRowSelect: onRowClickedCallback,
                containerSelector: containerSelector,
                cellSelect: cellSelect,
                onCellSelect: onCellSelectedCallback
            };
            var container = $(containerSelector);
            container.find(".loading").removeClass("hide");
            container.find(".tps-record-count").addClass("hide");
            _this.ajaxHelper.ajaxPost(url, function (html) {
                container.html(html);
                container.deewrGrid(gridOptions);
                var externalWell = $(".table-buttons-criteria-description[data-corresponding-listing='" + container.prop("id") + "']");
                var automaticWell = $(".tps-hidden-caption", container);
                var embeddedWell = $(".table-buttons-criteria-description", container);
                if (externalWell.length > 0) {
                    externalWell.text(automaticWell.text());
                }
                else if (embeddedWell.length > 0) {
                    embeddedWell.text(automaticWell.text());
                }
                else {
                    automaticWell.removeClass("hide");
                }
                if (successCallback) {
                    successCallback();
                }
            }, pagingDetails);
        };
        this.refreshSortableTable = function (containerSelector, data) {
            _this.refreshPageableTable(containerSelector, data);
        };
        this.refreshPageableTable = function (containerSelector, data, newPageNumber) {
            var pageNumber = newPageNumber ? newPageNumber : $(containerSelector + " .pagenumberstorage").val();
            var pageSize = $(containerSelector + " .pagesizestorage").val();
            var sortColumn = $(containerSelector + " .sortcolumnstorage").val();
            var isAscending = $(containerSelector + " .sortorderstorage").val() === "Ascending";
            var url = _this.pageableTableUrls[containerSelector];
            var successCallback = _this.pageableTableCallbacks[containerSelector];
            var fullRowSelect = _this.pageableTableFullRowSelectFlags[containerSelector];
            var onRowClickCallback = _this.pageableTableOnRowClickCallbacks[containerSelector];
            var cellSelect = _this.pageableTableCellSelectFlags[containerSelector];
            var onCellClickCallback = _this.pageableTableOnCellClickCallbacks[containerSelector];
            if (!data) {
                data = _this.pageableTableData[containerSelector];
            }
            _this.loadPageableTable(url, containerSelector, successCallback, pageNumber, pageSize, sortColumn, isAscending, fullRowSelect, onRowClickCallback, data, true, cellSelect, onCellClickCallback);
        };
        this.getPageableTablePageNumber = function (containerSelector) {
            return parseInt($(".pagenumberstorage", $(containerSelector)).val(), 10);
        };
        this.getPageableTablePageCount = function (containerSelector) {
            return parseInt($(".totalpagecountstorage", $(containerSelector)).val(), 10);
        };
        this.getPageableTableRecordCount = function (containerSelector) {
            return parseInt($(".totalrecordcountstorage", $(containerSelector)).val(), 10);
        };
        this.setupTable = function (table, callback) {
            var selectable = callback ? true : false;
            var gridOptions = {
                fullRowSelect: selectable,
                onRowSelect: callback
            };
            $(table).deewrGrid(gridOptions);
        };
    }
    return TableHelper;
}());

var Helper = (function () {
    function Helper() {
        this.navigation = new NavigationHelper();
        this.form = new FormHelper();
        this.parsing = new ParsingHelper(this.form);
        this.page = new PageHelper(this.navigation, this.form);
        this.reinitialisation = new ReinitialisationHelper(this.form, this.navigation, this.parsing);
        this.dialog = new DialogHelper(this.page, this.form, this.reinitialisation);
        this.ajax = new AjaxHelper(this.page, this.navigation, this.form, this.dialog);
        this.table = new TableHelper(this.page, this.form, this.ajax);
        this.initialisation = new InitialisationHelper(this.form, this.navigation, this.parsing, this.ajax, this.reinitialisation);
    }
    return Helper;
}());
var helper = new Helper();
$(document).ready(helper.initialisation.run);
$.ajaxSettings.traditional = true;
if (!window.console) {
    console = {};
}
console.log = console.log || function () {
};
console.warn = console.warn || function () {
};
console.error = console.error || function () {
};
console.info = console.info || function () {
};
if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (elt) {
        var len = this.length + 0;
        var from = Number(arguments[1]) || 0;
        from = (from < 0) ? Math.ceil(from) : Math.floor(from);
        if (from < 0) {
            from += len;
        }
        for (; from < len; from++) {
            if (from in this && this[from] === elt) {
                return from;
            }
        }
        return -1;
    };
}
String.prototype.trim = function () {
    return this.replace(/^\s+|\s+$/g, "");
};
String.prototype.startsWith = function (str) {
    return (this.match("^" + str) === str);
};
String.prototype.lpad = function (padString, length) {
    var str = this;
    while (str.length < length) {
        str = padString + str;
    }
    return str;
};

jQuery.fn.deewrGrid = function (options) {
    var settings = jQuery.extend({
        cellSelect: false,
        fullRowSelect: false,
        onRowSelect: function () {
        },
        onCellSelect: function () {
        }
    }, options);
    var container = $(this);
    var table = container.is("table") ? container : $("table", container);
    table.addClass("deewrGrid table data-table table-condensed");
    if (settings.fullRowSelect) {
        if (!settings.cellSelect) {
            table.addClass("table-hover");
        }
        $("thead tr th:last-child", table).addClass("rightmost");
        $("tbody tr td:last-child", table).addClass("rightmost");
        $("thead tr", table).append("<th class='navCol'></th>");
        $("tbody tr", table).each(function () {
            var td = $(this).find("td:first");
            var linkText = td.text();
            var columnNo = 1;
            while (linkText === "" && columnNo < $(this).find("td").length) {
                td = $(this).find("td").eq(columnNo);
                linkText = td.text();
                columnNo++;
            }
            var header = td.closest("table").find("th").eq(td.index()).text().trim();
            $(this).append("<td class='navCol'><a href='#' class='readers'>Press enter to select " + header + " " + linkText + "</a></td>");
        });
        $(".navCol a", this)
            .focus(function () { $(this).closest("tr").find("td:not(.notselectable)").addClass("selecting"); })
            .blur(function () { $(this).closest("tr").find("td:not(.notselectable)").removeClass("selecting"); });
    }
    if (settings.cellSelect) {
        $("tbody td:not(.notselectable)", table)
            .mouseenter(function () { $(this).addClass("selecting"); })
            .mouseleave(function () { $(this).removeClass("selecting"); });
    }
    $("tbody tr:not(.notselectable) td:not(.notselectable)", table).click(function () {
        if (settings.fullRowSelect) {
            if (settings.onRowSelect) {
                settings.onRowSelect($(this).closest("tr"));
            }
        }
        else if (settings.cellSelect) {
            $(this).removeClass("selecting");
            if (settings.onCellSelect) {
                settings.onCellSelect($(this));
            }
        }
    });
    $(".sortable", table).click(function (event) {
        event.preventDefault();
        var previousSortColumn = $(".sortcolumnstorage", container).val();
        var previousSortOrder = $(".sortorderstorage", container).val();
        var sortColumn = $(this).attr("sortField");
        var sortOrder = "Ascending";
        if (previousSortColumn === sortColumn) {
            sortOrder = previousSortOrder === "Ascending" ? "Descending" : "Ascending";
        }
        $(".sortcolumnstorage", container).val(sortColumn);
        $(".sortorderstorage", container).val(sortOrder);
        $(".pagenumberstorage", container).val(1);
        helper.table.refreshPageableTable(container.selector);
    });
    $(".tps-pager:not(.disabled)", this).click(function (event) {
        event.preventDefault();
        var newPageNumber = $(this).attr("data-page-number");
        $(".pagenumberstorage", container).val(newPageNumber);
        helper.table.refreshPageableTable(container.selector);
    });
    var previousPageBankButton = $(".tps-page-bank-previous", this);
    var nextPageBankButton = $(".tps-page-bank-next", this);
    var pagerButtons = $(".tps-numbered-page", this);
    var pagingBankInformation = $(".pagination", this);
    nextPageBankButton.click(function (event) {
        event.preventDefault();
        var lastPageShown = parseInt(pagingBankInformation.attr("data-last-page-shown"), 10);
        var numberOfPages = parseInt(pagingBankInformation.attr("data-number-of-pages"), 10);
        var end = Math.min(lastPageShown + 5, numberOfPages);
        var start = Math.max(end - 4, 1);
        pagerButtons.each(function () {
            var pageNumber = parseInt($(this).attr("data-page-number"), 10);
            if (pageNumber < start || pageNumber > end) {
                $(this).parent().addClass("hide");
            }
            else {
                $(this).parent().removeClass("hide");
            }
        });
        previousPageBankButton.parent().removeClass("hide");
        if (end === numberOfPages) {
            nextPageBankButton.parent().addClass("hide");
        }
        pagingBankInformation.attr("data-first-page-shown", start);
        pagingBankInformation.attr("data-last-page-shown", end);
    });
    previousPageBankButton.click(function (event) {
        event.preventDefault();
        var firstPageShown = parseInt(pagingBankInformation.attr("data-first-page-shown"), 10);
        var numberOfPages = parseInt(pagingBankInformation.attr("data-number-of-pages"), 10);
        var start = Math.max(firstPageShown - 5, 1);
        var end = Math.min(start + 4, numberOfPages);
        pagerButtons.each(function () {
            var pageNumber = parseInt($(this).attr("data-page-number"), 10);
            if (pageNumber < start || pageNumber > end) {
                $(this).parent().addClass("hide");
            }
            else {
                $(this).parent().removeClass("hide");
            }
        });
        nextPageBankButton.parent().removeClass("hide");
        if (start === 1) {
            previousPageBankButton.parent().addClass("hide");
        }
        pagingBankInformation.attr("data-first-page-shown", start);
        pagingBankInformation.attr("data-last-page-shown", end);
    });
    $(".pageSize", this).focus(function () { $(this).addClass("hasfocus"); });
    $(".pageSize", this).blur(function () { $(this).removeClass("hasfocus"); });
    $(".pageSize", this).keypress(function (event) {
        if ((event.which && event.which === 13) || (event.keyCode && event.keyCode === 13)) {
            event.preventDefault();
            var newPageSize = parseInt($(this).val(), 10);
            if (isNaN(newPageSize) || newPageSize <= 0 || newPageSize > 500) {
                helper.dialog.showMessageDialog("Please enter a valid page size between 1 and 500.");
            }
            else {
                $(".pagesizestorage", container).val(newPageSize);
                var cookieKey = $(".pagingcontainerselector", container).val();
                helper.page.setCookie(cookieKey, newPageSize.toString(), 30);
                helper.table.refreshPageableTable(container.selector);
            }
        }
    });
};

//$(document).ready();

var googleMaps = new GoogleMaps();
var loadedGoogleMapsScript = false;

function GoogleMaps()
{
	var me = this;
	var geocoder;
	var mapSelector;
	var initCallback;
	var infoWindow;
	var infoWindowId = -1;

	this.initialise = function()
	{
		if (initCallback)
			initCallback();
		// Insert jQuery trigger event?
	};

	this.loadScript = function()
	{
		var script = document.createElement("script");
		script.type = "text/javascript";
		script.src = "https://maps.googleapis.com/maps/api/js?v=weekly&callback=googleMaps.initialise&client=gme-deewr&channel=tps";
		document.body.appendChild(script);
	};

	this.load = function(callback)
	{
		/// <summary>
		/// This method loads the google maps script asyncronously
		/// </summary>
		initCallback = callback;
		$(me.loadScript);
	};

	this.createMap = function(mapId)
	{
		if (typeof google === undefined)
			return;

		var myOptions = {
			zoom: 10,
			center: new google.maps.LatLng(-28, 135),
			mapTypeId: google.maps.MapTypeId.ROADMAP
		};

		var map = new google.maps.Map(document.getElementById(mapId), myOptions);
		return map;
	};


	this.getLatLongFromAddress = function(address, callbackOnSuccess, callbackOnFail)
	{
		if (!geocoder)
		{
			geocoder = new google.maps.Geocoder();
		}

		geocoder.geocode({ 'address': address }, function(results, status)
		{
			if (status == google.maps.GeocoderStatus.OK)
			{
				callbackOnSuccess(results[0].geometry.location);
			}
			else
			{
				callbackOnFail(status);
			}
		});
	};

	this.openInfoWindow = function(map, marker, id, content)
	{
		if (!infoWindow) infoWindow = new google.maps.InfoWindow({ content: content });

		infoWindowId = id;
		infoWindow.setContent(content);
		infoWindow.open(map, marker);
	};

	this.getInfoWindow = function()
	{
		if (!infoWindow) infoWindow = new google.maps.InfoWindow();
		return infoWindow;
	};

	this.getInfoWindowId = function()
	{
		return infoWindowId;
	};

	this.onInfoWindowClose = function(callback)
	{
		google.maps.event.addListener(googleMaps.getInfoWindow(), 'closeclick', callback);
	};
}

function Marker(id, marker, data)
{
	this.id = id;
	this.marker = marker;
	this.data = data;
}

function MarkerCollection()
{
	var me = this;
	var markers = [];
	var latLngBounds = new google.maps.LatLngBounds();

	this.add = function(id, marker, data)
	{
		/// <summary>
		/// Adds a marker to the collection
		/// </summary>  
		/// <param name="marker">
		/// the google map marker to add
		/// </param>
		/// <param name="data">
		/// addition data to associate with the marker
		///</param>
		markers.push(new Marker(id, marker, data));
		latLngBounds.extend(marker.position);
	};

	this.getMarker = function(id)
	{
		var marker;
		
		$.each(markers, function(index, obj)
		{
			if (obj.id == id) marker = obj.marker;
		});

		return marker;
	};

	this.markers = markers;

	this.bounds = latLngBounds;
}

// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS

/**
* @license Highcharts JS v2.3.3 (2012-10-04)
*
* (c) 2009-2011 Torstein Hønsi
*
* License: www.highcharts.com/license
*/

// JSLint options:
/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */

(function () {
	// encapsulated variables
	var UNDEFINED,
	doc = document,
	win = window,
	math = Math,
	mathRound = math.round,
	mathFloor = math.floor,
	mathCeil = math.ceil,
	mathMax = math.max,
	mathMin = math.min,
	mathAbs = math.abs,
	mathCos = math.cos,
	mathSin = math.sin,
	mathPI = math.PI,
	deg2rad = mathPI * 2 / 360,


	// some variables
	userAgent = navigator.userAgent,
	isOpera = win.opera,
	isIE = /msie/i.test(userAgent) && !isOpera,
	docMode8 = doc.documentMode === 8,
	isWebKit = /AppleWebKit/.test(userAgent),
	isFirefox = /Firefox/.test(userAgent),
	SVG_NS = 'http://www.w3.org/2000/svg',
	hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
	hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
	useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext,
	Renderer,
	hasTouch = doc.documentElement.ontouchstart !== UNDEFINED,
	symbolSizes = {},
	idCounter = 0,
	garbageBin,
	defaultOptions,
	dateFormat, // function
	globalAnimation,
	pathAnim,
	timeUnits,
	noop = function () { },

	// some constants for frequently used strings
	DIV = 'div',
	ABSOLUTE = 'absolute',
	RELATIVE = 'relative',
	HIDDEN = 'hidden',
	PREFIX = 'highcharts-',
	VISIBLE = 'visible',
	PX = 'px',
	NONE = 'none',
	M = 'M',
	L = 'L',
	/*
	* Empirical lowest possible opacities for TRACKER_FILL
	* IE6: 0.002
	* IE7: 0.002
	* IE8: 0.002
	* IE9: 0.00000000001 (unlimited)
	* FF: 0.00000000001 (unlimited)
	* Chrome: 0.000001
	* Safari: 0.000001
	* Opera: 0.00000000001 (unlimited)
	*/
	TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.000001 : 0.002) + ')', // invisible but clickable
	//TRACKER_FILL = 'rgba(192,192,192,0.5)',
	NORMAL_STATE = '',
	HOVER_STATE = 'hover',
	SELECT_STATE = 'select',
	MILLISECOND = 'millisecond',
	SECOND = 'second',
	MINUTE = 'minute',
	HOUR = 'hour',
	DAY = 'day',
	WEEK = 'week',
	MONTH = 'month',
	YEAR = 'year',

	// constants for attributes
	FILL = 'fill',
	LINEAR_GRADIENT = 'linearGradient',
	STOPS = 'stops',
	STROKE = 'stroke',
	STROKE_WIDTH = 'stroke-width',

	// time methods, changed based on whether or not UTC is used
	makeTime,
	getMinutes,
	getHours,
	getDay,
	getDate,
	getMonth,
	getFullYear,
	setMinutes,
	setHours,
	setDate,
	setMonth,
	setFullYear,


	// lookup over the types and the associated classes
	seriesTypes = {};

	// The Highcharts namespace
	win.Highcharts = {};

	/**
	* Extend an object with the members of another
	* @param {Object} a The object to be extended
	* @param {Object} b The object to add to the first one
	*/
	function extend(a, b) {
		var n;
		if (!a) {
			a = {};
		}
		for (n in b) {
			a[n] = b[n];
		}
		return a;
	}

	/**
	* Take an array and turn into a hash with even number arguments as keys and odd numbers as
	* values. Allows creating constants for commonly used style properties, attributes etc.
	* Avoid it in performance critical situations like looping
	*/
	function hash() {
		var i = 0,
		args = arguments,
		length = args.length,
		obj = {};
		for (; i < length; i++) {
			obj[args[i++]] = args[i];
		}
		return obj;
	}

	/**
	* Shortcut for parseInt
	* @param {Object} s
	* @param {Number} mag Magnitude
	*/
	function pInt(s, mag) {
		return parseInt(s, mag || 10);
	}

	/**
	* Check for string
	* @param {Object} s
	*/
	function isString(s) {
		return typeof s === 'string';
	}

	/**
	* Check for object
	* @param {Object} obj
	*/
	function isObject(obj) {
		return typeof obj === 'object';
	}

	/**
	* Check for array
	* @param {Object} obj
	*/
	function isArray(obj) {
		return Object.prototype.toString.call(obj) === '[object Array]';
	}

	/**
	* Check for number
	* @param {Object} n
	*/
	function isNumber(n) {
		return typeof n === 'number';
	}

	function log2lin(num) {
		return math.log(num) / math.LN10;
	}
	function lin2log(num) {
		return math.pow(10, num);
	}

	/**
	* Remove last occurence of an item from an array
	* @param {Array} arr
	* @param {Mixed} item
	*/
	function erase(arr, item) {
		var i = arr.length;
		while (i--) {
			if (arr[i] === item) {
				arr.splice(i, 1);
				break;
			}
		}
		//return arr;
	}

	/**
	* Returns true if the object is not null or undefined. Like MooTools' $.defined.
	* @param {Object} obj
	*/
	function defined(obj) {
		return obj !== UNDEFINED && obj !== null;
	}

	/**
	* Set or get an attribute or an object of attributes. Can't use jQuery attr because
	* it attempts to set expando properties on the SVG element, which is not allowed.
	*
	* @param {Object} elem The DOM element to receive the attribute(s)
	* @param {String|Object} prop The property or an abject of key-value pairs
	* @param {String} value The value if a single property is set
	*/
	function attr(elem, prop, value) {
		var key,
		setAttribute = 'setAttribute',
		ret;

		// if the prop is a string
		if (isString(prop)) {
			// set the value
			if (defined(value)) {

				elem[setAttribute](prop, value);

				// get the value
			} else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
				ret = elem.getAttribute(prop);
			}

			// else if prop is defined, it is a hash of key/value pairs
		} else if (defined(prop) && isObject(prop)) {
			for (key in prop) {
				elem[setAttribute](key, prop[key]);
			}
		}
		return ret;
	}
	/**
	* Check if an element is an array, and if not, make it into an array. Like
	* MooTools' $.splat.
	*/
	function splat(obj) {
		return isArray(obj) ? obj : [obj];
	}


	/**
	* Return the first value that is defined. Like MooTools' $.pick.
	*/
	function pick() {
		var args = arguments,
		i,
		arg,
		length = args.length;
		for (i = 0; i < length; i++) {
			arg = args[i];
			if (typeof arg !== 'undefined' && arg !== null) {
				return arg;
			}
		}
	}

	/**
	* Set CSS on a given element
	* @param {Object} el
	* @param {Object} styles Style object with camel case property names
	*/
	function css(el, styles) {
		if (isIE) {
			if (styles && styles.opacity !== UNDEFINED) {
				styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
			}
		}
		extend(el.style, styles);
	}

	/**
	* Utility function to create element with attributes and styles
	* @param {Object} tag
	* @param {Object} attribs
	* @param {Object} styles
	* @param {Object} parent
	* @param {Object} nopad
	*/
	function createElement(tag, attribs, styles, parent, nopad) {
		var el = doc.createElement(tag);
		if (attribs) {
			extend(el, attribs);
		}
		if (nopad) {
			css(el, { padding: 0, border: NONE, margin: 0 });
		}
		if (styles) {
			css(el, styles);
		}
		if (parent) {
			parent.appendChild(el);
		}
		return el;
	}

	/**
	* Extend a prototyped class by new members
	* @param {Object} parent
	* @param {Object} members
	*/
	function extendClass(parent, members) {
		var object = function () { };
		object.prototype = new parent();
		extend(object.prototype, members);
		return object;
	}

	/**
	* How many decimals are there in a number
	*/
	function getDecimals(number) {

		number = (number || 0).toString();

		return number.indexOf('.') > -1 ?
		number.split('.')[1].length :
		0;
	}

	/**
	* Format a number and return a string based on input settings
	* @param {Number} number The input number to format
	* @param {Number} decimals The amount of decimals
	* @param {String} decPoint The decimal point, defaults to the one given in the lang options
	* @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
	*/
	function numberFormat(number, decimals, decPoint, thousandsSep) {
		var lang = defaultOptions.lang,
		// http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
		n = number,
		c = decimals === -1 ?
			getDecimals(number) :
			(isNaN(decimals = mathAbs(decimals)) ? 2 : decimals),
		d = decPoint === undefined ? lang.decimalPoint : decPoint,
		t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
		s = n < 0 ? "-" : "",
		i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
		j = i.length > 3 ? i.length % 3 : 0;

		return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
		(c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
	}

	/**
	* Pad a string to a given length by adding 0 to the beginning
	* @param {Number} number
	* @param {Number} length
	*/
	function pad(number, length) {
		// Create an array of the remaining length +1 and join it with 0's
		return new Array((length || 2) + 1 - String(number).length).join(0) + number;
	}

	/**
	* Wrap a method with extended functionality, preserving the original function
	* @param {Object} obj The context object that the method belongs to 
	* @param {String} method The name of the method to extend
	* @param {Function} func A wrapper function callback. This function is called with the same arguments
	* as the original function, except that the original function is unshifted and passed as the first 
	* argument. 
	*/
	function wrap(obj, method, func) {
		var proceed = obj[method];
		obj[method] = function () {
			var args = Array.prototype.slice.call(arguments);
			args.unshift(proceed);
			return func.apply(this, args);
		};
	}

	/**
	* Based on http://www.php.net/manual/en/function.strftime.php
	* @param {String} format
	* @param {Number} timestamp
	* @param {Boolean} capitalize
	*/
	dateFormat = function (format, timestamp, capitalize) {
		if (!defined(timestamp) || isNaN(timestamp)) {
			return 'Invalid date';
		}
		format = pick(format, '%Y-%m-%d %H:%M:%S');

		var date = new Date(timestamp),
		key, // used in for constuct below
		// get the basic time values
		hours = date[getHours](),
		day = date[getDay](),
		dayOfMonth = date[getDate](),
		month = date[getMonth](),
		fullYear = date[getFullYear](),
		lang = defaultOptions.lang,
		langWeekdays = lang.weekdays,
		/* // uncomment this and the 'W' format key below to enable week numbers
		weekNumber = function () {
		var clone = new Date(date.valueOf()),
		day = clone[getDay]() == 0 ? 7 : clone[getDay](),
		dayNumber;
		clone.setDate(clone[getDate]() + 4 - day);
		dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000);
		return 1 + mathFloor(dayNumber / 7);
		},
		*/

		// list all format keys
		replacements = {

			// Day
			'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
			'A': langWeekdays[day], // Long weekday, like 'Monday'
			'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
			'e': dayOfMonth, // Day of the month, 1 through 31

			// Week (none implemented)
			//'W': weekNumber(),

			// Month
			'b': lang.shortMonths[month], // Short month, like 'Jan'
			'B': lang.months[month], // Long month, like 'January'
			'm': pad(month + 1), // Two digit month number, 01 through 12

			// Year
			'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
			'Y': fullYear, // Four digits year, like 2009

			// Time
			'H': pad(hours), // Two digits hours in 24h format, 00 through 23
			'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
			'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
			'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
			'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
			'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
			'S': pad(date.getSeconds()), // Two digits seconds, 00 through  59
			'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)
		};


		// do the replaces
		for (key in replacements) {
			format = format.replace('%' + key, replacements[key]);
		}

		// Optionally capitalize the string and return
		return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
	};

	/**
	* Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
	* @param {Number} interval
	* @param {Array} multiples
	* @param {Number} magnitude
	* @param {Object} options
	*/
	function normalizeTickInterval(interval, multiples, magnitude, options) {
		var normalized, i;

		// round to a tenfold of 1, 2, 2.5 or 5
		magnitude = pick(magnitude, 1);
		normalized = interval / magnitude;

		// multiples for a linear scale
		if (!multiples) {
			multiples = [1, 2, 2.5, 5, 10];

			// the allowDecimals option
			if (options && options.allowDecimals === false) {
				if (magnitude === 1) {
					multiples = [1, 2, 5, 10];
				} else if (magnitude <= 0.1) {
					multiples = [1 / magnitude];
				}
			}
		}

		// normalize the interval to the nearest multiple
		for (i = 0; i < multiples.length; i++) {
			interval = multiples[i];
			if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {
				break;
			}
		}

		// multiply back to the correct magnitude
		interval *= magnitude;

		return interval;
	}

	/**
	* Get a normalized tick interval for dates. Returns a configuration object with
	* unit range (interval), count and name. Used to prepare data for getTimeTicks. 
	* Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
	* of segments in stock charts, the normalizing logic was extracted in order to 
	* prevent it for running over again for each segment having the same interval. 
	* #662, #697.
	*/
	function normalizeTimeTickInterval(tickInterval, unitsOption) {
		var units = unitsOption || [[
				MILLISECOND, // unit name
				[1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
			], [
				SECOND,
				[1, 2, 5, 10, 15, 30]
			], [
				MINUTE,
				[1, 2, 5, 10, 15, 30]
			], [
				HOUR,
				[1, 2, 3, 4, 6, 8, 12]
			], [
				DAY,
				[1, 2]
			], [
				WEEK,
				[1, 2]
			], [
				MONTH,
				[1, 2, 3, 4, 6]
			], [
				YEAR,
				null
			]],
		unit = units[units.length - 1], // default unit is years
		interval = timeUnits[unit[0]],
		multiples = unit[1],
		count,
		i;

		// loop through the units to find the one that best fits the tickInterval
		for (i = 0; i < units.length; i++) {
			unit = units[i];
			interval = timeUnits[unit[0]];
			multiples = unit[1];


			if (units[i + 1]) {
				// lessThan is in the middle between the highest multiple and the next unit.
				var lessThan = (interval * multiples[multiples.length - 1] +
						timeUnits[units[i + 1][0]]) / 2;

				// break and keep the current unit
				if (tickInterval <= lessThan) {
					break;
				}
			}
		}

		// prevent 2.5 years intervals, though 25, 250 etc. are allowed
		if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
			multiples = [1, 2, 5];
		}

		// prevent 2.5 years intervals, though 25, 250 etc. are allowed
		if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
			multiples = [1, 2, 5];
		}

		// get the count
		count = normalizeTickInterval(tickInterval / interval, multiples);

		return {
			unitRange: interval,
			count: count,
			unitName: unit[0]
		};
	}

	/**
	* Set the tick positions to a time unit that makes sense, for example
	* on the first of each month or on every Monday. Return an array
	* with the time positions. Used in datetime axes as well as for grouping
	* data on a datetime axis.
	*
	* @param {Object} normalizedInterval The interval in axis values (ms) and the count
	* @param {Number} min The minimum in axis values
	* @param {Number} max The maximum in axis values
	* @param {Number} startOfWeek
	*/
	function getTimeTicks(normalizedInterval, min, max, startOfWeek) {
		var tickPositions = [],
		i,
		higherRanks = {},
		useUTC = defaultOptions.global.useUTC,
		minYear, // used in months and years as a basis for Date.UTC()
		minDate = new Date(min),
		interval = normalizedInterval.unitRange,
		count = normalizedInterval.count;



		if (interval >= timeUnits[SECOND]) { // second
			minDate.setMilliseconds(0);
			minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
			count * mathFloor(minDate.getSeconds() / count));
		}

		if (interval >= timeUnits[MINUTE]) { // minute
			minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
			count * mathFloor(minDate[getMinutes]() / count));
		}

		if (interval >= timeUnits[HOUR]) { // hour
			minDate[setHours](interval >= timeUnits[DAY] ? 0 :
			count * mathFloor(minDate[getHours]() / count));
		}

		if (interval >= timeUnits[DAY]) { // day
			minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
			count * mathFloor(minDate[getDate]() / count));
		}

		if (interval >= timeUnits[MONTH]) { // month
			minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
			count * mathFloor(minDate[getMonth]() / count));
			minYear = minDate[getFullYear]();
		}

		if (interval >= timeUnits[YEAR]) { // year
			minYear -= minYear % count;
			minDate[setFullYear](minYear);
		}

		// week is a special case that runs outside the hierarchy
		if (interval === timeUnits[WEEK]) {
			// get start of current week, independent of count
			minDate[setDate](minDate[getDate]() - minDate[getDay]() +
			pick(startOfWeek, 1));
		}


		// get tick positions
		i = 1;
		minYear = minDate[getFullYear]();
		var time = minDate.getTime(),
		minMonth = minDate[getMonth](),
		minDateDate = minDate[getDate](),
		timezoneOffset = useUTC ?
			0 :
			(24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950

		// iterate and add tick positions at appropriate values
		while (time < max) {
			tickPositions.push(time);

			// if the interval is years, use Date.UTC to increase years
			if (interval === timeUnits[YEAR]) {
				time = makeTime(minYear + i * count, 0);

				// if the interval is months, use Date.UTC to increase months
			} else if (interval === timeUnits[MONTH]) {
				time = makeTime(minYear, minMonth + i * count);

				// if we're using global time, the interval is not fixed as it jumps
				// one hour at the DST crossover
			} else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
				time = makeTime(minYear, minMonth, minDateDate +
				i * count * (interval === timeUnits[DAY] ? 1 : 7));

				// else, the interval is fixed and we use simple addition
			} else {
				time += interval * count;

				// mark new days if the time is dividable by day
				if (interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset) {
					higherRanks[time] = DAY;
				}
			}

			i++;
		}

		// push the last time
		tickPositions.push(time);

		// record information on the chosen unit - for dynamic label formatter
		tickPositions.info = extend(normalizedInterval, {
			higherRanks: higherRanks,
			totalRange: interval * count
		});

		return tickPositions;
	}

	/**
	* Helper class that contains variuos counters that are local to the chart.
	*/
	function ChartCounters() {
		this.color = 0;
		this.symbol = 0;
	}

	ChartCounters.prototype = {
		/**
		* Wraps the color counter if it reaches the specified length.
		*/
		wrapColor: function (length) {
			if (this.color >= length) {
				this.color = 0;
			}
		},

		/**
		* Wraps the symbol counter if it reaches the specified length.
		*/
		wrapSymbol: function (length) {
			if (this.symbol >= length) {
				this.symbol = 0;
			}
		}
	};


	/**
	* Utility method that sorts an object array and keeping the order of equal items.
	* ECMA script standard does not specify the behaviour when items are equal.
	*/
	function stableSort(arr, sortFunction) {
		var length = arr.length,
		sortValue,
		i;

		// Add index to each item
		for (i = 0; i < length; i++) {
			arr[i].ss_i = i; // stable sort index
		}

		arr.sort(function (a, b) {
			sortValue = sortFunction(a, b);
			return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
		});

		// Remove index from items
		for (i = 0; i < length; i++) {
			delete arr[i].ss_i; // stable sort index
		}
	}

	/**
	* Non-recursive method to find the lowest member of an array. Math.min raises a maximum
	* call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
	* method is slightly slower, but safe.
	*/
	function arrayMin(data) {
		var i = data.length,
		min = data[0];

		while (i--) {
			if (data[i] < min) {
				min = data[i];
			}
		}
		return min;
	}

	/**
	* Non-recursive method to find the lowest member of an array. Math.min raises a maximum
	* call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
	* method is slightly slower, but safe.
	*/
	function arrayMax(data) {
		var i = data.length,
		max = data[0];

		while (i--) {
			if (data[i] > max) {
				max = data[i];
			}
		}
		return max;
	}

	/**
	* Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
	* It loops all properties and invokes destroy if there is a destroy method. The property is
	* then delete'ed.
	* @param {Object} The object to destroy properties on
	* @param {Object} Exception, do not destroy this property, only delete it.
	*/
	function destroyObjectProperties(obj, except) {
		var n;
		for (n in obj) {
			// If the object is non-null and destroy is defined
			if (obj[n] && obj[n] !== except && obj[n].destroy) {
				// Invoke the destroy
				obj[n].destroy();
			}

			// Delete the property from the object.
			delete obj[n];
		}
	}


	/**
	* Discard an element by moving it to the bin and delete
	* @param {Object} The HTML node to discard
	*/
	function discardElement(element) {
		// create a garbage bin element, not part of the DOM
		if (!garbageBin) {
			garbageBin = createElement(DIV);
		}

		// move the node and empty bin
		if (element) {
			garbageBin.appendChild(element);
		}
		garbageBin.innerHTML = '';
	}

	/**
	* Provide error messages for debugging, with links to online explanation 
	*/
	function error(code, stop) {
		var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;
		if (stop) {
			throw msg;
		} else if (win.console) {
			console.log(msg);
		}
	}

	/**
	* Fix JS round off float errors
	* @param {Number} num
	*/
	function correctFloat(num) {
		return parseFloat(
		num.toPrecision(14)
	);
	}

	/**
	* Set the global animation to either a given value, or fall back to the
	* given chart's animation option
	* @param {Object} animation
	* @param {Object} chart
	*/
	function setAnimation(animation, chart) {
		globalAnimation = pick(animation, chart.animation);
	}

	/**
	* The time unit lookup
	*/
	/*jslint white: true*/
	timeUnits = hash(
	MILLISECOND, 1,
	SECOND, 1000,
	MINUTE, 60000,
	HOUR, 3600000,
	DAY, 24 * 3600000,
	WEEK, 7 * 24 * 3600000,
	MONTH, 30 * 24 * 3600000,
	YEAR, 31556952000
);
	/*jslint white: false*/
	/**
	* Path interpolation algorithm used across adapters
	*/
	pathAnim = {
		/**
		* Prepare start and end values so that the path can be animated one to one
		*/
		init: function (elem, fromD, toD) {
			fromD = fromD || '';
			var shift = elem.shift,
			bezier = fromD.indexOf('C') > -1,
			numParams = bezier ? 7 : 3,
			endLength,
			slice,
			i,
			start = fromD.split(' '),
			end = [].concat(toD), // copy
			startBaseLine,
			endBaseLine,
			sixify = function (arr) { // in splines make move points have six parameters like bezier curves
				i = arr.length;
				while (i--) {
					if (arr[i] === M) {
						arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
					}
				}
			};

			if (bezier) {
				sixify(start);
				sixify(end);
			}

			// pull out the base lines before padding
			if (elem.isArea) {
				startBaseLine = start.splice(start.length - 6, 6);
				endBaseLine = end.splice(end.length - 6, 6);
			}

			// if shifting points, prepend a dummy point to the end path
			if (shift <= end.length / numParams) {
				while (shift--) {
					end = [].concat(end).splice(0, numParams).concat(end);
				}
			}
			elem.shift = 0; // reset for following animations

			// copy and append last point until the length matches the end length
			if (start.length) {
				endLength = end.length;
				while (start.length < endLength) {

					//bezier && sixify(start);
					slice = [].concat(start).splice(start.length - numParams, numParams);
					if (bezier) { // disable first control point
						slice[numParams - 6] = slice[numParams - 2];
						slice[numParams - 5] = slice[numParams - 1];
					}
					start = start.concat(slice);
				}
			}

			if (startBaseLine) { // append the base lines for areas
				start = start.concat(startBaseLine);
				end = end.concat(endBaseLine);
			}
			return [start, end];
		},

		/**
		* Interpolate each value of the path and return the array
		*/
		step: function (start, end, pos, complete) {
			var ret = [],
			i = start.length,
			startVal;

			if (pos === 1) { // land on the final path without adjustment points appended in the ends
				ret = complete;

			} else if (i === end.length && pos < 1) {
				while (i--) {
					startVal = parseFloat(start[i]);
					ret[i] =
					isNaN(startVal) ? // a letter instruction like M or L
						start[i] :
						pos * (parseFloat(end[i] - startVal)) + startVal;

				}
			} else { // if animation is finished or length not matching, land on right value
				ret = end;
			}
			return ret;
		}
	};

	(function ($) {
		/**
		* The default HighchartsAdapter for jQuery
		*/
		win.HighchartsAdapter = win.HighchartsAdapter || ($ && {

			/**
			* Initialize the adapter by applying some extensions to jQuery
			*/
			init: function (pathAnim) {

				// extend the animate function to allow SVG animations
				var Fx = $.fx,
				Step = Fx.step,
				dSetter,
				Tween = $.Tween,
				propHooks = Tween && Tween.propHooks;

				/*jslint unparam: true*//* allow unused param x in this function */
				$.extend($.easing, {
					easeOutQuad: function (x, t, b, c, d) {
						return -c * (t /= d) * (t - 2) + b;
					}
				});
				/*jslint unparam: false*/


				// extend some methods to check for elem.attr, which means it is a Highcharts SVG object
				$.each(['cur', '_default', 'width', 'height'], function (i, fn) {
					var obj = Step,
					base,
					elem;

					// Handle different parent objects
					if (fn === 'cur') {
						obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype

					} else if (fn === '_default' && Tween) { // jQuery 1.8 model
						obj = propHooks[fn];
						fn = 'set';
					}

					// Overwrite the method
					base = obj[fn];
					if (base) { // step.width and step.height don't exist in jQuery < 1.7

						// create the extended function replacement
						obj[fn] = function (fx) {

							// Fx.prototype.cur does not use fx argument
							fx = i ? fx : this;

							// shortcut
							elem = fx.elem;

							// Fx.prototype.cur returns the current value. The other ones are setters
							// and returning a value has no effect.
							return elem.attr ? // is SVG element wrapper
							elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method
							base.apply(this, arguments); // use jQuery's built-in method
						};
					}
				});


				// Define the setter function for d (path definitions)
				dSetter = function (fx) {
					var elem = fx.elem,
					ends;

					// Normally start and end should be set in state == 0, but sometimes,
					// for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
					// in these cases
					if (!fx.started) {
						ends = pathAnim.init(elem, elem.d, elem.toD);
						fx.start = ends[0];
						fx.end = ends[1];
						fx.started = true;
					}


					// interpolate each value of the path
					elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
				};

				// jQuery 1.8 style
				if (Tween) {
					propHooks.d = {
						set: dSetter
					};
					// pre 1.8
				} else {
					// animate paths
					Step.d = dSetter;
				}

				/**
				* Utility for iterating over an array. Parameters are reversed compared to jQuery.
				* @param {Array} arr
				* @param {Function} fn
				*/
				this.each = Array.prototype.forEach ?
				function (arr, fn) { // modern browsers
					return Array.prototype.forEach.call(arr, fn);

				} :
				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;
						}
					}
				};

				// Register Highcharts as a jQuery plugin
				// TODO: MooTools and prototype as well?
				// TODO: StockChart
				/*$.fn.highcharts = function(options, callback) {
				options.chart = merge(options.chart, { renderTo: this[0] });
				this.chart = new Chart(options, callback);
				return this;
				};*/
			},

			/**
			* Downloads a script and executes a callback when done.
			* @param {String} scriptLocation
			* @param {Function} callback
			*/
			getScript: $.getScript,

			/**
			* Return the index of an item in an array, or -1 if not found
			*/
			inArray: $.inArray,

			/**
			* A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method.
			* @param {Object} elem The HTML element
			* @param {String} method Which method to run on the wrapped element
			*/
			adapterRun: function (elem, method) {
				return $(elem)[method]();
			},

			/**
			* Filter an array
			*/
			grep: $.grep,

			/**
			* Map an array
			* @param {Array} arr
			* @param {Function} fn
			*/
			map: function (arr, fn) {
				//return jQuery.map(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;

			},

			/**
			* Deep merge two objects and return a third object
			*/
			merge: function () {
				var args = arguments;
				return $.extend(true, null, args[0], args[1], args[2], args[3]);
			},

			/**
			* Get the position of an element relative to the top left of the page
			*/
			offset: function (el) {
				return $(el).offset();
			},

			/**
			* Add an event listener
			* @param {Object} el A HTML element or custom object
			* @param {String} event The event type
			* @param {Function} fn The event handler
			*/
			addEvent: function (el, event, fn) {
				$(el).bind(event, fn);
			},

			/**
			* Remove event added with addEvent
			* @param {Object} el The object
			* @param {String} eventType The event type. Leave blank to remove all events.
			* @param {Function} handler The function to remove
			*/
			removeEvent: function (el, eventType, handler) {
				// workaround for jQuery issue with unbinding custom events:
				// http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2
				var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
				if (doc[func] && !el[func]) {
					el[func] = function () { };
				}

				$(el).unbind(eventType, handler);
			},

			/**
			* Fire an event on a custom object
			* @param {Object} el
			* @param {String} type
			* @param {Object} eventArguments
			* @param {Function} defaultFunction
			*/
			fireEvent: function (el, type, eventArguments, defaultFunction) {
				var event = $.Event(type),
				detachedType = 'detached' + type,
				defaultPrevented;

				// Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts
				// never uses these properties, Chrome includes them in the default click event and
				// raises the warning when they are copied over in the extend statement below.
				//
				// To avoid problems in IE (see #1010) where we cannot delete the properties and avoid
				// testing if they are there (warning in chrome) the only option is to test if running IE.
				if (!isIE && eventArguments) {
					delete eventArguments.layerX;
					delete eventArguments.layerY;
				}

				extend(event, eventArguments);

				// Prevent jQuery from triggering the object method that is named the
				// same as the event. For example, if the event is 'select', jQuery
				// attempts calling el.select and it goes into a loop.
				if (el[type]) {
					el[detachedType] = el[type];
					el[type] = null;
				}

				// Wrap preventDefault and stopPropagation in try/catch blocks in
				// order to prevent JS errors when cancelling events on non-DOM
				// objects. #615.
				/*jslint unparam: true*/
				$.each(['preventDefault', 'stopPropagation'], function (i, fn) {
					var base = event[fn];
					event[fn] = function () {
						try {
							base.call(event);
						} catch (e) {
							if (fn === 'preventDefault') {
								defaultPrevented = true;
							}
						}
					};
				});
				/*jslint unparam: false*/

				// trigger it
				$(el).trigger(event);

				// attach the method
				if (el[detachedType]) {
					el[type] = el[detachedType];
					el[detachedType] = null;
				}

				if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {
					defaultFunction(event);
				}
			},

			/**
			* Extension method needed for MooTools
			*/
			washMouseEvent: function (e) {
				var ret = e.originalEvent || e;

				// computed by jQuery, needed by IE8
				if (ret.pageX === UNDEFINED) { // #1236
					ret.pageX = e.pageX;
					ret.pageY = e.pageY;
				}

				return ret;
			},

			/**
			* Animate a HTML element or SVG element wrapper
			* @param {Object} el
			* @param {Object} params
			* @param {Object} options jQuery-like animation options: duration, easing, callback
			*/
			animate: function (el, params, options) {
				var $el = $(el);
				if (params.d) {
					el.toD = params.d; // keep the array form for paths, used in $.fx.step.d
					params.d = 1; // because in jQuery, animating to an array has a different meaning
				}

				$el.stop();
				$el.animate(params, options);

			},
			/**
			* Stop running animation
			*/
			stop: function (el) {
				$(el).stop();
			}
		});
	} (win.jQuery));


	// check for a custom HighchartsAdapter defined prior to this file
	var globalAdapter = win.HighchartsAdapter,
	adapter = globalAdapter || {};

	// Initialize the adapter
	if (globalAdapter) {
		globalAdapter.init.call(globalAdapter, pathAnim);
	}


	// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
	// and all the utility functions will be null. In that case they are populated by the
	// default adapters below.
	var adapterRun = adapter.adapterRun,
	getScript = adapter.getScript,
	inArray = adapter.inArray,
	each = adapter.each,
	grep = adapter.grep,
	offset = adapter.offset,
	map = adapter.map,
	merge = adapter.merge,
	addEvent = adapter.addEvent,
	removeEvent = adapter.removeEvent,
	fireEvent = adapter.fireEvent,
	washMouseEvent = adapter.washMouseEvent,
	animate = adapter.animate,
	stop = adapter.stop;



	/* ****************************************************************************
	* Handle the options                                                         *
	*****************************************************************************/
	var 

defaultLabelOptions = {
	enabled: true,
	// rotation: 0,
	align: 'center',
	x: 0,
	y: 15,
	/*formatter: function () {
	return this.value;
	},*/
	style: {
		color: '#666',
		fontSize: '11px',
		lineHeight: '14px'
	}
};

	defaultOptions = {
		colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
		'#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
		symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
		lang: {
			loading: 'Loading...',
			months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
				'August', 'September', 'October', 'November', 'December'],
			shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
			weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
			decimalPoint: '.',
			numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels
			resetZoom: 'Reset zoom',
			resetZoomTitle: 'Reset zoom level 1:1',
			thousandsSep: ','
		},
		global: {
			useUTC: true,
			canvasToolsURL: 'http://code.highcharts.com/2.3.3/modules/canvas-tools.js',
			VMLRadialGradientURL: 'http://code.highcharts.com/2.3.3/gfx/vml-radial-gradient.png'
		},
		chart: {
			//animation: true,
			//alignTicks: false,
			//reflow: true,
			//className: null,
			//events: { load, selection },
			//margin: [null],
			//marginTop: null,
			//marginRight: null,
			//marginBottom: null,
			//marginLeft: null,
			borderColor: '#4572A7',
			//borderWidth: 0,
			borderRadius: 5,
			defaultSeriesType: 'line',
			ignoreHiddenSeries: true,
			//inverted: false,
			//shadow: false,
			spacingTop: 10,
			spacingRight: 10,
			spacingBottom: 15,
			spacingLeft: 10,
			style: {
				fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
				fontSize: '12px'
			},
			backgroundColor: '#FFFFFF',
			//plotBackgroundColor: null,
			plotBorderColor: '#C0C0C0',
			//plotBorderWidth: 0,
			//plotShadow: false,
			//zoomType: ''
			resetZoomButton: {
				theme: {
					zIndex: 20
				},
				position: {
					align: 'right',
					x: -10,
					//verticalAlign: 'top',
					y: 10
				}
				// relativeTo: 'plot'
			}
		},
		title: {
			text: 'Chart title',
			align: 'center',
			// floating: false,
			// margin: 15,
			// x: 0,
			// verticalAlign: 'top',
			y: 15,
			style: {
				color: '#3E576F',
				fontSize: '16px'
			}

		},
		subtitle: {
			text: '',
			align: 'center',
			// floating: false
			// x: 0,
			// verticalAlign: 'top',
			y: 30,
			style: {
				color: '#6D869F'
			}
		},

		plotOptions: {
			line: { // base series options
				allowPointSelect: false,
				showCheckbox: false,
				animation: {
					duration: 1000
				},
				//connectNulls: false,
				//cursor: 'default',
				//clip: true,
				//dashStyle: null,
				//enableMouseTracking: true,
				events: {},
				//legendIndex: 0,
				lineWidth: 2,
				shadow: true,
				// stacking: null,
				marker: {
					enabled: true,
					//symbol: null,
					lineWidth: 0,
					radius: 4,
					lineColor: '#FFFFFF',
					//fillColor: null,
					states: { // states for a single point
						hover: {
							enabled: true
							//radius: base + 2
						},
						select: {
							fillColor: '#FFFFFF',
							lineColor: '#000000',
							lineWidth: 2
						}
					}
				},
				point: {
					events: {}
				},
				dataLabels: merge(defaultLabelOptions, {
					enabled: false,
					formatter: function () {
						return this.y;
					},
					verticalAlign: 'bottom', // above singular point
					y: 0
					// backgroundColor: undefined,
					// borderColor: undefined,
					// borderRadius: undefined,
					// borderWidth: undefined,
					// padding: 3,
					// shadow: false
				}),
				cropThreshold: 300, // draw points outside the plot area when the number of points is less than this
				pointRange: 0,
				//pointStart: 0,
				//pointInterval: 1,
				showInLegend: true,
				states: { // states for the entire series
					hover: {
						//enabled: false,
						//lineWidth: base + 1,
						marker: {
							// lineWidth: base + 1,
							// radius: base + 1
						}
					},
					select: {
						marker: {}
					}
				},
				stickyTracking: true
				//tooltip: {
				//pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b>'
				//valueDecimals: null,
				//xDateFormat: '%A, %b %e, %Y',
				//valuePrefix: '',
				//ySuffix: ''				
				//}
				// turboThreshold: 1000
				// zIndex: null
			}
		},
		labels: {
			//items: [],
			style: {
				//font: defaultFont,
				position: ABSOLUTE,
				color: '#3E576F'
			}
		},
		legend: {
			enabled: true,
			align: 'center',
			//floating: false,
			layout: 'horizontal',
			labelFormatter: function () {
				return this.name;
			},
			borderWidth: 1,
			borderColor: '#909090',
			borderRadius: 5,
			navigation: {
				// animation: true,
				activeColor: '#3E576F',
				// arrowSize: 12
				inactiveColor: '#CCC'
				// style: {} // text styles
			},
			// margin: 10,
			// reversed: false,
			shadow: false,
			// backgroundColor: null,
			/*style: {
			padding: '5px'
			},*/
			itemStyle: {
				cursor: 'pointer',
				color: '#3E576F',
				fontSize: '12px'
			},
			itemHoverStyle: {
				//cursor: 'pointer', removed as of #601
				color: '#000'
			},
			itemHiddenStyle: {
				color: '#CCC'
			},
			itemCheckboxStyle: {
				position: ABSOLUTE,
				width: '13px', // for IE precision
				height: '13px'
			},
			// itemWidth: undefined,
			symbolWidth: 16,
			symbolPadding: 5,
			verticalAlign: 'bottom',
			// width: undefined,
			x: 0,
			y: 0
		},

		loading: {
			// hideDuration: 100,
			labelStyle: {
				fontWeight: 'bold',
				position: RELATIVE,
				top: '1em'
			},
			// showDuration: 0,
			style: {
				position: ABSOLUTE,
				backgroundColor: 'white',
				opacity: 0.5,
				textAlign: 'center'
			}
		},

		tooltip: {
			enabled: true,
			//crosshairs: null,
			backgroundColor: 'rgba(255, 255, 255, .85)',
			borderWidth: 2,
			borderRadius: 5,
			dateTimeLabelFormats: {
				millisecond: '%A, %b %e, %H:%M:%S.%L',
				second: '%A, %b %e, %H:%M:%S',
				minute: '%A, %b %e, %H:%M',
				hour: '%A, %b %e, %H:%M',
				day: '%A, %b %e, %Y',
				week: 'Week from %A, %b %e, %Y',
				month: '%B %Y',
				year: '%Y'
			},
			//formatter: defaultFormatter,
			headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
			pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
			shadow: true,
			shared: useCanVG,
			snap: hasTouch ? 25 : 10,
			style: {
				color: '#333333',
				fontSize: '12px',
				padding: '5px',
				whiteSpace: 'nowrap'
			}
			//xDateFormat: '%A, %b %e, %Y',
			//valueDecimals: null,
			//valuePrefix: '',
			//valueSuffix: ''
		},

		credits: {
			enabled: true,
			text: 'Highcharts.com',
			href: 'http://www.highcharts.com',
			position: {
				align: 'right',
				x: -10,
				verticalAlign: 'bottom',
				y: -5
			},
			style: {
				cursor: 'pointer',
				color: '#909090',
				fontSize: '10px'
			}
		}
	};




	// Series defaults
	var defaultPlotOptions = defaultOptions.plotOptions,
	defaultSeriesOptions = defaultPlotOptions.line;

	// set the default time methods
	setTimeMethods();



	/**
	* Set the time methods globally based on the useUTC option. Time method can be either
	* local time or UTC (default).
	*/
	function setTimeMethods() {
		var useUTC = defaultOptions.global.useUTC,
		GET = useUTC ? 'getUTC' : 'get',
		SET = useUTC ? 'setUTC' : 'set';

		makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {
			return new Date(
			year,
			month,
			pick(date, 1),
			pick(hours, 0),
			pick(minutes, 0),
			pick(seconds, 0)
		).getTime();
		};
		getMinutes = GET + 'Minutes';
		getHours = GET + 'Hours';
		getDay = GET + 'Day';
		getDate = GET + 'Date';
		getMonth = GET + 'Month';
		getFullYear = GET + 'FullYear';
		setMinutes = SET + 'Minutes';
		setHours = SET + 'Hours';
		setDate = SET + 'Date';
		setMonth = SET + 'Month';
		setFullYear = SET + 'FullYear';

	}

	/**
	* Merge the default options with custom options and return the new options structure
	* @param {Object} options The new custom options
	*/
	function setOptions(options) {

		// Pull out axis options and apply them to the respective default axis options 
		/*defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis);
		defaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis);
		options.xAxis = options.yAxis = UNDEFINED;*/

		// Merge in the default options
		defaultOptions = merge(defaultOptions, options);

		// Apply UTC
		setTimeMethods();

		return defaultOptions;
	}

	/**
	* Get the updated default options. Merely exposing defaultOptions for outside modules
	* isn't enough because the setOptions method creates a new object.
	*/
	function getOptions() {
		return defaultOptions;
	}



	/**
	* Handle color operations. The object methods are chainable.
	* @param {String} input The input color in either rbga or hex format
	*/
	var Color = function (input) {
		// declare variables
		var rgba = [], result;

		/**
		* Parse the input color to rgba array
		* @param {String} input
		*/
		function init(input) {

			// rgba
			result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input);
			if (result) {
				rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
			} else { // hex
				result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
				if (result) {
					rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
				}
			}

		}
		/**
		* Return the color a specified format
		* @param {String} format
		*/
		function get(format) {
			var ret;

			// it's NaN if gradient colors on a column chart
			if (rgba && !isNaN(rgba[0])) {
				if (format === 'rgb') {
					ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
				} else if (format === 'a') {
					ret = rgba[3];
				} else {
					ret = 'rgba(' + rgba.join(',') + ')';
				}
			} else {
				ret = input;
			}
			return ret;
		}

		/**
		* Brighten the color
		* @param {Number} alpha
		*/
		function brighten(alpha) {
			if (isNumber(alpha) && alpha !== 0) {
				var i;
				for (i = 0; i < 3; i++) {
					rgba[i] += pInt(alpha * 255);

					if (rgba[i] < 0) {
						rgba[i] = 0;
					}
					if (rgba[i] > 255) {
						rgba[i] = 255;
					}
				}
			}
			return this;
		}
		/**
		* Set the color's opacity to a given alpha value
		* @param {Number} alpha
		*/
		function setOpacity(alpha) {
			rgba[3] = alpha;
			return this;
		}

		// initialize: parse the input
		init(input);

		// public methods
		return {
			get: get,
			brighten: brighten,
			setOpacity: setOpacity
		};
	};


	/**
	* A wrapper object for SVG elements
	*/
	function SVGElement() { }

	SVGElement.prototype = {
		/**
		* Initialize the SVG renderer
		* @param {Object} renderer
		* @param {String} nodeName
		*/
		init: function (renderer, nodeName) {
			var wrapper = this;
			wrapper.element = nodeName === 'span' ?
			createElement(nodeName) :
			doc.createElementNS(SVG_NS, nodeName);
			wrapper.renderer = renderer;
			/**
			* A collection of attribute setters. These methods, if defined, are called right before a certain
			* attribute is set on an element wrapper. Returning false prevents the default attribute
			* setter to run. Returning a value causes the default setter to set that value. Used in
			* Renderer.label.
			*/
			wrapper.attrSetters = {};
		},
		/**
		* Animate a given attribute
		* @param {Object} params
		* @param {Number} options The same options as in jQuery animation
		* @param {Function} complete Function to perform at the end of animation
		*/
		animate: function (params, options, complete) {
			var animOptions = pick(options, globalAnimation, true);
			stop(this); // stop regardless of animation actually running, or reverting to .attr (#607)
			if (animOptions) {
				animOptions = merge(animOptions);
				if (complete) { // allows using a callback with the global animation without overwriting it
					animOptions.complete = complete;
				}
				animate(this, params, animOptions);
			} else {
				this.attr(params);
				if (complete) {
					complete();
				}
			}
		},
		/**
		* Set or get a given attribute
		* @param {Object|String} hash
		* @param {Mixed|Undefined} val
		*/
		attr: function (hash, val) {
			var wrapper = this,
			key,
			value,
			result,
			i,
			child,
			element = wrapper.element,
			nodeName = element.nodeName.toLowerCase(), // Android2 requires lower for "text"
			renderer = wrapper.renderer,
			skipAttr,
			titleNode,
			attrSetters = wrapper.attrSetters,
			shadows = wrapper.shadows,
			hasSetSymbolSize,
			doTransform,
			ret = wrapper;

			// single key-value pair
			if (isString(hash) && defined(val)) {
				key = hash;
				hash = {};
				hash[key] = val;
			}

			// used as a getter: first argument is a string, second is undefined
			if (isString(hash)) {
				key = hash;
				if (nodeName === 'circle') {
					key = { x: 'cx', y: 'cy'}[key] || key;
				} else if (key === 'strokeWidth') {
					key = 'stroke-width';
				}
				ret = attr(element, key) || wrapper[key] || 0;

				if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
					ret = parseFloat(ret);
				}

				// setter
			} else {

				for (key in hash) {
					skipAttr = false; // reset
					value = hash[key];

					// check for a specific attribute setter
					result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);

					if (result !== false) {
						if (result !== UNDEFINED) {
							value = result; // the attribute setter has returned a new value to set
						}

						// paths
						if (key === 'd') {
							if (value && value.join) { // join path
								value = value.join(' ');
							}
							if (/(NaN| {2}|^$)/.test(value)) {
								value = 'M 0 0';
							}
							//wrapper.d = value; // shortcut for animations

							// update child tspans x values
						} else if (key === 'x' && nodeName === 'text') {
							for (i = 0; i < element.childNodes.length; i++) {
								child = element.childNodes[i];
								// if the x values are equal, the tspan represents a linebreak
								if (attr(child, 'x') === attr(element, 'x')) {
									//child.setAttribute('x', value);
									attr(child, 'x', value);
								}
							}

							if (wrapper.rotation) {
								attr(element, 'transform', 'rotate(' + wrapper.rotation + ' ' + value + ' ' +
								pInt(hash.y || attr(element, 'y')) + ')');
							}

							// apply gradients
						} else if (key === 'fill') {
							value = renderer.color(value, element, key);

							// circle x and y
						} else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
							key = { x: 'cx', y: 'cy'}[key] || key;

							// rectangle border radius
						} else if (nodeName === 'rect' && key === 'r') {
							attr(element, {
								rx: value,
								ry: value
							});
							skipAttr = true;

							// translation and text rotation
						} else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
							doTransform = true;
							skipAttr = true;

							// apply opacity as subnode (required by legacy WebKit and Batik)
						} else if (key === 'stroke') {
							value = renderer.color(value, element, key);

							// emulate VML's dashstyle implementation
						} else if (key === 'dashstyle') {
							key = 'stroke-dasharray';
							value = value && value.toLowerCase();
							if (value === 'solid') {
								value = NONE;
							} else if (value) {
								value = value
								.replace('shortdashdotdot', '3,1,1,1,1,1,')
								.replace('shortdashdot', '3,1,1,1')
								.replace('shortdot', '1,1,')
								.replace('shortdash', '3,1,')
								.replace('longdash', '8,3,')
								.replace(/dot/g, '1,3,')
								.replace('dash', '4,3,')
								.replace(/,$/, '')
								.split(','); // ending comma

								i = value.length;
								while (i--) {
									value[i] = pInt(value[i]) * hash['stroke-width'];
								}
								value = value.join(',');
							}

							// special
						} else if (key === 'isTracker') {
							wrapper[key] = value;

							// IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
							// is unable to cast them. Test again with final IE9.
						} else if (key === 'width') {
							value = pInt(value);

							// Text alignment
						} else if (key === 'align') {
							key = 'text-anchor';
							value = { left: 'start', center: 'middle', right: 'end'}[value];

							// Title requires a subnode, #431
						} else if (key === 'title') {
							titleNode = element.getElementsByTagName('title')[0];
							if (!titleNode) {
								titleNode = doc.createElementNS(SVG_NS, 'title');
								element.appendChild(titleNode);
							}
							titleNode.textContent = value;
						}

						// jQuery animate changes case
						if (key === 'strokeWidth') {
							key = 'stroke-width';
						}

						// Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
						if (isWebKit && key === 'stroke-width' && value === 0) {
							value = 0.000001;
						}

						// symbols
						if (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {


							if (!hasSetSymbolSize) {
								wrapper.symbolAttr(hash);
								hasSetSymbolSize = true;
							}
							skipAttr = true;
						}

						// let the shadow follow the main element
						if (shadows && /^(width|height|visibility|x|y|d|transform)$/.test(key)) {
							i = shadows.length;
							while (i--) {
								attr(
								shadows[i],
								key,
								key === 'height' ?
									mathMax(value - (shadows[i].cutHeight || 0), 0) :
									value
							);
							}
						}

						// validate heights
						if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
							value = 0;
						}

						// Record for animation and quick access without polling the DOM
						wrapper[key] = value;

						// Update transform
						if (doTransform) {
							wrapper.updateTransform();
						}


						if (key === 'text') {
							// Delete bBox memo when the text changes
							if (value !== wrapper.textStr) {
								delete wrapper.bBox;
							}
							wrapper.textStr = value;
							if (wrapper.added) {
								renderer.buildText(wrapper);
							}
						} else if (!skipAttr) {
							attr(element, key, value);
						}

					}

				}

			}

			return ret;
		},

		/**
		* If one of the symbol size affecting parameters are changed,
		* check all the others only once for each call to an element's
		* .attr() method
		* @param {Object} hash
		*/
		symbolAttr: function (hash) {
			var wrapper = this;

			each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {
				wrapper[key] = pick(hash[key], wrapper[key]);
			});

			wrapper.attr({
				d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper)
			});
		},

		/**
		* Apply a clipping path to this object
		* @param {String} id
		*/
		clip: function (clipRect) {
			return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE);
		},

		/**
		* Calculate the coordinates needed for drawing a rectangle crisply and return the
		* calculated attributes
		* @param {Number} strokeWidth
		* @param {Number} x
		* @param {Number} y
		* @param {Number} width
		* @param {Number} height
		*/
		crisp: function (strokeWidth, x, y, width, height) {

			var wrapper = this,
			key,
			attribs = {},
			values = {},
			normalizer;

			strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;
			normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors

			// normalize for crisp edges
			values.x = mathFloor(x || wrapper.x || 0) + normalizer;
			values.y = mathFloor(y || wrapper.y || 0) + normalizer;
			values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
			values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
			values.strokeWidth = strokeWidth;

			for (key in values) {
				if (wrapper[key] !== values[key]) { // only set attribute if changed
					wrapper[key] = attribs[key] = values[key];
				}
			}

			return attribs;
		},

		/**
		* Set styles for the element
		* @param {Object} styles
		*/
		css: function (styles) {
			/*jslint unparam: true*//* allow unused param a in the regexp function below */
			var elemWrapper = this,
			elem = elemWrapper.element,
			textWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text',
			n,
			serializedCss = '',
			hyphenate = function (a, b) { return '-' + b.toLowerCase(); };
			/*jslint unparam: false*/

			// convert legacy
			if (styles && styles.color) {
				styles.fill = styles.color;
			}

			// Merge the new styles with the old ones
			styles = extend(
			elemWrapper.styles,
			styles
		);

			// store object
			elemWrapper.styles = styles;


			// Don't handle line wrap on canvas
			if (useCanVG && textWidth) {
				delete styles.width;
			}

			// serialize and set style attribute
			if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
				if (textWidth) {
					delete styles.width;
				}
				css(elemWrapper.element, styles);
			} else {
				for (n in styles) {
					serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
				}
				elemWrapper.attr({
					style: serializedCss
				});
			}


			// re-build text
			if (textWidth && elemWrapper.added) {
				elemWrapper.renderer.buildText(elemWrapper);
			}

			return elemWrapper;
		},

		/**
		* Add an event listener
		* @param {String} eventType
		* @param {Function} handler
		*/
		on: function (eventType, handler) {
			var fn = handler;
			// touch
			if (hasTouch && eventType === 'click') {
				eventType = 'touchstart';
				fn = function (e) {
					e.preventDefault();
					handler();
				};
			}
			// simplest possible event model for internal use
			this.element['on' + eventType] = fn;
			return this;
		},

		/**
		* Set the coordinates needed to draw a consistent radial gradient across
		* pie slices regardless of positioning inside the chart. The format is
		* [centerX, centerY, diameter] in pixels.
		*/
		setRadialReference: function (coordinates) {
			this.element.radialReference = coordinates;
			return this;
		},

		/**
		* Move an object and its children by x and y values
		* @param {Number} x
		* @param {Number} y
		*/
		translate: function (x, y) {
			return this.attr({
				translateX: x,
				translateY: y
			});
		},

		/**
		* Invert a group, rotate and flip
		*/
		invert: function () {
			var wrapper = this;
			wrapper.inverted = true;
			wrapper.updateTransform();
			return wrapper;
		},

		/**
		* Apply CSS to HTML elements. This is used in text within SVG rendering and
		* by the VML renderer
		*/
		htmlCss: function (styles) {
			var wrapper = this,
			element = wrapper.element,
			textWidth = styles && element.tagName === 'SPAN' && styles.width;

			if (textWidth) {
				delete styles.width;
				wrapper.textWidth = textWidth;
				wrapper.updateTransform();
			}

			wrapper.styles = extend(wrapper.styles, styles);
			css(wrapper.element, styles);

			return wrapper;
		},



		/**
		* VML and useHTML method for calculating the bounding box based on offsets
		* @param {Boolean} refresh Whether to force a fresh value from the DOM or to
		* use the cached value
		*
		* @return {Object} A hash containing values for x, y, width and height
		*/

		htmlGetBBox: function () {
			var wrapper = this,
			element = wrapper.element,
			bBox = wrapper.bBox;

			// faking getBBox in exported SVG in legacy IE
			if (!bBox) {
				// faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?)
				if (element.nodeName === 'text') {
					element.style.position = ABSOLUTE;
				}

				bBox = wrapper.bBox = {
					x: element.offsetLeft,
					y: element.offsetTop,
					width: element.offsetWidth,
					height: element.offsetHeight
				};
			}

			return bBox;
		},

		/**
		* VML override private method to update elements based on internal
		* properties based on SVG transform
		*/
		htmlUpdateTransform: function () {
			// aligning non added elements is expensive
			if (!this.added) {
				this.alignOnAdd = true;
				return;
			}

			var wrapper = this,
			renderer = wrapper.renderer,
			elem = wrapper.element,
			translateX = wrapper.translateX || 0,
			translateY = wrapper.translateY || 0,
			x = wrapper.x || 0,
			y = wrapper.y || 0,
			align = wrapper.textAlign || 'left',
			alignCorrection = { left: 0, center: 0.5, right: 1}[align],
			nonLeft = align && align !== 'left',
			shadows = wrapper.shadows;

			// apply translate
			if (translateX || translateY) {
				css(elem, {
					marginLeft: translateX,
					marginTop: translateY
				});
				if (shadows) { // used in labels/tooltip
					each(shadows, function (shadow) {
						css(shadow, {
							marginLeft: translateX + 1,
							marginTop: translateY + 1
						});
					});
				}
			}

			// apply inversion
			if (wrapper.inverted) { // wrapper is a group
				each(elem.childNodes, function (child) {
					renderer.invertChild(child, elem);
				});
			}

			if (elem.tagName === 'SPAN') {

				var width, height,
				rotation = wrapper.rotation,
				baseline,
				radians = 0,
				costheta = 1,
				sintheta = 0,
				quad,
				textWidth = pInt(wrapper.textWidth),
				xCorr = wrapper.xCorr || 0,
				yCorr = wrapper.yCorr || 0,
				currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','),
				rotationStyle = {},
				cssTransformKey;

				if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed

					if (defined(rotation)) {

						if (renderer.isSVG) { // #916
							cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : '';
							rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';

						} else {
							radians = rotation * deg2rad; // deg to rad
							costheta = mathCos(radians);
							sintheta = mathSin(radians);

							// Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
							// but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
							// has support for CSS3 transform. The getBBox method also needs to be updated
							// to compensate for the rotation, like it currently does for SVG.
							// Test case: http://highcharts.com/tests/?file=text-rotation
							rotationStyle.filter = rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
								', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
								', sizingMethod=\'auto expand\')'].join('') : NONE;
						}
						css(elem, rotationStyle);
					}

					width = pick(wrapper.elemWidth, elem.offsetWidth);
					height = pick(wrapper.elemHeight, elem.offsetHeight);

					// update textWidth
					if (width > textWidth && /[ \-]/.test(elem.innerText)) { // #983
						css(elem, {
							width: textWidth + PX,
							display: 'block',
							whiteSpace: 'normal'
						});
						width = textWidth;
					}

					// correct x and y
					baseline = renderer.fontMetrics(elem.style.fontSize).b;
					xCorr = costheta < 0 && -width;
					yCorr = sintheta < 0 && -height;

					// correct for baseline and corners spilling out after rotation
					quad = costheta * sintheta < 0;
					xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection);
					yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);

					// correct for the length/height of the text
					if (nonLeft) {
						xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
						if (rotation) {
							yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
						}
						css(elem, {
							textAlign: align
						});
					}

					// record correction
					wrapper.xCorr = xCorr;
					wrapper.yCorr = yCorr;
				}

				// apply position with correction
				css(elem, {
					left: (x + xCorr) + PX,
					top: (y + yCorr) + PX
				});

				// record current text transform
				wrapper.cTT = currentTextTransform;
			}
		},

		/**
		* Private method to update the transform attribute based on internal
		* properties
		*/
		updateTransform: function () {
			var wrapper = this,
			translateX = wrapper.translateX || 0,
			translateY = wrapper.translateY || 0,
			inverted = wrapper.inverted,
			rotation = wrapper.rotation,
			transform = [];

			// flipping affects translate as adjustment for flipping around the group's axis
			if (inverted) {
				translateX += wrapper.attr('width');
				translateY += wrapper.attr('height');
			}

			// apply translate
			if (translateX || translateY) {
				transform.push('translate(' + translateX + ',' + translateY + ')');
			}

			// apply rotation
			if (inverted) {
				transform.push('rotate(90) scale(-1,1)');
			} else if (rotation) { // text rotation
				transform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')');
			}

			if (transform.length) {
				attr(wrapper.element, 'transform', transform.join(' '));
			}
		},
		/**
		* Bring the element to the front
		*/
		toFront: function () {
			var element = this.element;
			element.parentNode.appendChild(element);
			return this;
		},


		/**
		* Break down alignment options like align, verticalAlign, x and y
		* to x and y relative to the chart.
		*
		* @param {Object} alignOptions
		* @param {Boolean} alignByTranslate
		* @param {Object} box The box to align to, needs a width and height
		*
		*/
		align: function (alignOptions, alignByTranslate, box) {
			var elemWrapper = this;

			if (!alignOptions) { // called on resize
				alignOptions = elemWrapper.alignOptions;
				alignByTranslate = elemWrapper.alignByTranslate;
			} else { // first call on instanciate
				elemWrapper.alignOptions = alignOptions;
				elemWrapper.alignByTranslate = alignByTranslate;
				if (!box) { // boxes other than renderer handle this internally
					elemWrapper.renderer.alignedObjects.push(elemWrapper);
				}
			}

			box = pick(box, elemWrapper.renderer);

			var align = alignOptions.align,
			vAlign = alignOptions.verticalAlign,
			x = (box.x || 0) + (alignOptions.x || 0), // default: left align
			y = (box.y || 0) + (alignOptions.y || 0), // default: top align
			attribs = {};


			// align
			if (align === 'right' || align === 'center') {
				x += (box.width - (alignOptions.width || 0)) /
					{ right: 1, center: 2}[align];
			}
			attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);


			// vertical align
			if (vAlign === 'bottom' || vAlign === 'middle') {
				y += (box.height - (alignOptions.height || 0)) /
					({ bottom: 1, middle: 2}[vAlign] || 1);

			}
			attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);

			// animate only if already placed
			elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs);
			elemWrapper.placed = true;
			elemWrapper.alignAttr = attribs;

			return elemWrapper;
		},

		/**
		* Get the bounding box (width, height, x and y) for the element
		*/
		getBBox: function () {
			var wrapper = this,
			bBox = wrapper.bBox,
			renderer = wrapper.renderer,
			width,
			height,
			rotation = wrapper.rotation,
			element = wrapper.element,
			styles = wrapper.styles,
			rad = rotation * deg2rad;

			if (!bBox) {
				// SVG elements
				if (element.namespaceURI === SVG_NS || renderer.forExport) {
					try { // Fails in Firefox if the container has display: none.

						bBox = element.getBBox ?
						// SVG: use extend because IE9 is not allowed to change width and height in case
						// of rotation (below)
						extend({}, element.getBBox()) :
						// Canvas renderer and legacy IE in export mode
						{
						width: element.offsetWidth,
						height: element.offsetHeight
					};
				} catch (e) { }

				// If the bBox is not set, the try-catch block above failed. The other condition
				// is for Opera that returns a width of -Infinity on hidden elements.
				if (!bBox || bBox.width < 0) {
					bBox = { width: 0, height: 0 };
				}


				// VML Renderer or useHTML within SVG
			} else {

				bBox = wrapper.htmlGetBBox();

			}

			// True SVG elements as well as HTML elements in modern browsers using the .useHTML option
			// need to compensated for rotation
			if (renderer.isSVG) {
				width = bBox.width;
				height = bBox.height;

				// Adjust for rotated text
				if (rotation) {
					bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
					bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
				}
			}

			// Workaround for wrong bounding box in IE9 and IE10 (#1101)
			if (isIE && styles && styles.fontSize === '11px' && height === 22.700000762939453) {
				bBox.height = 14;
			}

			wrapper.bBox = bBox;
		}
		return bBox;
	},

	/**
	* Show the element
	*/
	show: function () {
		return this.attr({ visibility: VISIBLE });
	},

	/**
	* Hide the element
	*/
	hide: function () {
		return this.attr({ visibility: HIDDEN });
	},

	/**
	* Add the element
	* @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
	*    to append the element to the renderer.box.
	*/
	add: function (parent) {

		var renderer = this.renderer,
			parentWrapper = parent || renderer,
			parentNode = parentWrapper.element || renderer.box,
			childNodes = parentNode.childNodes,
			element = this.element,
			zIndex = attr(element, 'zIndex'),
			otherElement,
			otherZIndex,
			i,
			inserted;

		if (parent) {
			this.parentGroup = parent;
		}

		// mark as inverted
		this.parentInverted = parent && parent.inverted;

		// build formatted text
		if (this.textStr !== undefined) {
			renderer.buildText(this);
		}

		// mark the container as having z indexed children
		if (zIndex) {
			parentWrapper.handleZ = true;
			zIndex = pInt(zIndex);
		}

		// insert according to this and other elements' zIndex
		if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
			for (i = 0; i < childNodes.length; i++) {
				otherElement = childNodes[i];
				otherZIndex = attr(otherElement, 'zIndex');
				if (otherElement !== element && (
				// insert before the first element with a higher zIndex
						pInt(otherZIndex) > zIndex ||
				// if no zIndex given, insert before the first element with a zIndex
						(!defined(zIndex) && defined(otherZIndex))

						)) {
					parentNode.insertBefore(element, otherElement);
					inserted = true;
					break;
				}
			}
		}

		// default: append at the end
		if (!inserted) {
			parentNode.appendChild(element);
		}

		// mark as added
		this.added = true;

		// fire an event for internal hooks
		fireEvent(this, 'add');

		return this;
	},

	/**
	* Removes a child either by removeChild or move to garbageBin.
	* Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
	*/
	safeRemoveChild: function (element) {
		var parentNode = element.parentNode;
		if (parentNode) {
			parentNode.removeChild(element);
		}
	},

	/**
	* Destroy the element and element wrapper
	*/
	destroy: function () {
		var wrapper = this,
			element = wrapper.element || {},
			shadows = wrapper.shadows,
			key,
			i;

		// remove events
		element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
		stop(wrapper); // stop running animations

		if (wrapper.clipPath) {
			wrapper.clipPath = wrapper.clipPath.destroy();
		}

		// Destroy stops in case this is a gradient object
		if (wrapper.stops) {
			for (i = 0; i < wrapper.stops.length; i++) {
				wrapper.stops[i] = wrapper.stops[i].destroy();
			}
			wrapper.stops = null;
		}

		// remove element
		wrapper.safeRemoveChild(element);

		// destroy shadows
		if (shadows) {
			each(shadows, function (shadow) {
				wrapper.safeRemoveChild(shadow);
			});
		}

		// remove from alignObjects
		erase(wrapper.renderer.alignedObjects, wrapper);

		for (key in wrapper) {
			delete wrapper[key];
		}

		return null;
	},

	/**
	* Empty a group element
	*/
	empty: function () {
		var element = this.element,
			childNodes = element.childNodes,
			i = childNodes.length;

		while (i--) {
			element.removeChild(childNodes[i]);
		}
	},

	/**
	* Add a shadow to the element. Must be done after the element is added to the DOM
	* @param {Boolean|Object} shadowOptions
	*/
	shadow: function (shadowOptions, group, cutOff) {
		var shadows = [],
			i,
			shadow,
			element = this.element,
			strokeWidth,
			shadowWidth,
			shadowElementOpacity,

		// compensate for inverted plot area
			transform;


		if (shadowOptions) {
			shadowWidth = pick(shadowOptions.width, 3);
			shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
			transform = this.parentInverted ?
				'(-1,-1)' :
				'(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')';
			for (i = 1; i <= shadowWidth; i++) {
				shadow = element.cloneNode(0);
				strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
				attr(shadow, {
					'isShadow': 'true',
					'stroke': shadowOptions.color || 'black',
					'stroke-opacity': shadowElementOpacity * i,
					'stroke-width': strokeWidth,
					'transform': 'translate' + transform,
					'fill': NONE
				});
				if (cutOff) {
					attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0));
					shadow.cutHeight = strokeWidth;
				}

				if (group) {
					group.element.appendChild(shadow);
				} else {
					element.parentNode.insertBefore(shadow, element);
				}

				shadows.push(shadow);
			}

			this.shadows = shadows;
		}
		return this;

	}
};


/**
* The default SVG renderer
*/
var SVGRenderer = function () {
	this.init.apply(this, arguments);
};
SVGRenderer.prototype = {
	Element: SVGElement,

	/**
	* Initialize the SVGRenderer
	* @param {Object} container
	* @param {Number} width
	* @param {Number} height
	* @param {Boolean} forExport
	*/
	init: function (container, width, height, forExport) {
		var renderer = this,
			loc = location,
			boxWrapper;

		boxWrapper = renderer.createElement('svg')
			.attr({
				xmlns: SVG_NS,
				version: '1.1'
			});
		container.appendChild(boxWrapper.element);

		// object properties
		renderer.isSVG = true;
		renderer.box = boxWrapper.element;
		renderer.boxWrapper = boxWrapper;
		renderer.alignedObjects = [];

		// Page url used for internal references. #24, #672, #1070
		renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ?
			loc.href
				.replace(/#.*?$/, '') // remove the hash
				.replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes
				.replace(/ /g, '%20') : // replace spaces (needed for Safari only)
			'';

		renderer.defs = this.createElement('defs').add();
		renderer.forExport = forExport;
		renderer.gradients = {}; // Object where gradient SvgElements are stored

		renderer.setSize(width, height, false);



		// Issue 110 workaround:
		// In Firefox, if a div is positioned by percentage, its pixel position may land
		// between pixels. The container itself doesn't display this, but an SVG element
		// inside this container will be drawn at subpixel precision. In order to draw
		// sharp lines, this must be compensated for. This doesn't seem to work inside
		// iframes though (like in jsFiddle).
		var subPixelFix, rect;
		if (isFirefox && container.getBoundingClientRect) {
			renderer.subPixelFix = subPixelFix = function () {
				css(container, { left: 0, top: 0 });
				rect = container.getBoundingClientRect();
				css(container, {
					left: (mathCeil(rect.left) - rect.left) + PX,
					top: (mathCeil(rect.top) - rect.top) + PX
				});
			};

			// run the fix now
			subPixelFix();

			// run it on resize
			addEvent(win, 'resize', subPixelFix);
		}
	},

	/**
	* Detect whether the renderer is hidden. This happens when one of the parent elements
	* has display: none. #608.
	*/
	isHidden: function () {
		return !this.boxWrapper.getBBox().width;
	},

	/**
	* Destroys the renderer and its allocated members.
	*/
	destroy: function () {
		var renderer = this,
			rendererDefs = renderer.defs;
		renderer.box = null;
		renderer.boxWrapper = renderer.boxWrapper.destroy();

		// Call destroy on all gradient elements
		destroyObjectProperties(renderer.gradients || {});
		renderer.gradients = null;

		// Defs are null in VMLRenderer
		// Otherwise, destroy them here.
		if (rendererDefs) {
			renderer.defs = rendererDefs.destroy();
		}

		// Remove sub pixel fix handler
		// We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed
		// See issue #982
		if (renderer.subPixelFix) {
			removeEvent(win, 'resize', renderer.subPixelFix);
		}

		renderer.alignedObjects = null;

		return null;
	},

	/**
	* Create a wrapper for an SVG element
	* @param {Object} nodeName
	*/
	createElement: function (nodeName) {
		var wrapper = new this.Element();
		wrapper.init(this, nodeName);
		return wrapper;
	},

	/**
	* Dummy function for use in canvas renderer
	*/
	draw: function () { },

	/**
	* Parse a simple HTML string into SVG tspans
	*
	* @param {Object} textNode The parent text SVG node
	*/
	buildText: function (wrapper) {
		var textNode = wrapper.element,
			lines = pick(wrapper.textStr, '').toString()
				.replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
				.replace(/<(i|em)>/g, '<span style="font-style:italic">')
				.replace(/<a/g, '<span')
				.replace(/<\/(b|strong|i|em|a)>/g, '</span>')
				.split(/<br.*?>/g),
			childNodes = textNode.childNodes,
			styleRegex = /style="([^"]+)"/,
			hrefRegex = /href="([^"]+)"/,
			parentX = attr(textNode, 'x'),
			textStyles = wrapper.styles,
			width = textStyles && textStyles.width && pInt(textStyles.width),
			textLineHeight = textStyles && textStyles.lineHeight,
			lastLine,
			GET_COMPUTED_STYLE = 'getComputedStyle',
			i = childNodes.length,
			linePositions = [];

		// Needed in IE9 because it doesn't report tspan's offsetHeight (#893)
		function getLineHeightByBBox(lineNo) {
			linePositions[lineNo] = textNode.getBBox ?
				textNode.getBBox().height :
				wrapper.renderer.fontMetrics(textNode.style.fontSize).h; // #990
			return mathRound(linePositions[lineNo] - (linePositions[lineNo - 1] || 0));
		}

		// remove old text
		while (i--) {
			textNode.removeChild(childNodes[i]);
		}

		if (width && !wrapper.added) {
			this.box.appendChild(textNode); // attach it to the DOM to read offset width
		}

		// remove empty line at end
		if (lines[lines.length - 1] === '') {
			lines.pop();
		}

		// build the lines
		each(lines, function (line, lineNo) {
			var spans, spanNo = 0, lineHeight;

			line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
			spans = line.split('|||');

			each(spans, function (span) {
				if (span !== '' || spans.length === 1) {
					var attributes = {},
						tspan = doc.createElementNS(SVG_NS, 'tspan'),
						spanStyle; // #390
					if (styleRegex.test(span)) {
						spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');
						attr(tspan, 'style', spanStyle);
					}
					if (hrefRegex.test(span)) {
						attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
						css(tspan, { cursor: 'pointer' });
					}

					span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
						.replace(/&lt;/g, '<')
						.replace(/&gt;/g, '>');

					// issue #38 workaround.
					/*if (reverse) {
					arr = [];
					i = span.length;
					while (i--) {
					arr.push(span.charAt(i));
					}
					span = arr.join('');
					}*/

					// add the text node
					tspan.appendChild(doc.createTextNode(span));

					if (!spanNo) { // first span in a line, align it to the left
						attributes.x = parentX;
					} else {
						// Firefox ignores spaces at the front or end of the tspan
						attributes.dx = 3; // space
					}

					// first span on subsequent line, add the line height
					if (!spanNo) {
						if (lineNo) {

							// allow getting the right offset height in exporting in IE
							if (!hasSVG && wrapper.renderer.forExport) {
								css(tspan, { display: 'block' });
							}

							// Webkit and opera sometimes return 'normal' as the line height. In that
							// case, webkit uses offsetHeight, while Opera falls back to 18
							lineHeight = win[GET_COMPUTED_STYLE] &&
								pInt(win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height'));

							if (!lineHeight || isNaN(lineHeight)) {
								lineHeight = textLineHeight || lastLine.offsetHeight || getLineHeightByBBox(lineNo) || 18;
							}
							attr(tspan, 'dy', lineHeight);
						}
						lastLine = tspan; // record for use in next line
					}

					// add attributes
					attr(tspan, attributes);

					// append it
					textNode.appendChild(tspan);

					spanNo++;

					// check width and apply soft breaks
					if (width) {
						var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
							tooLong,
							actualWidth,
							rest = [];

						while (words.length || rest.length) {
							delete wrapper.bBox; // delete cache
							actualWidth = wrapper.getBBox().width;
							tooLong = actualWidth > width;
							if (!tooLong || words.length === 1) { // new line needed
								words = rest;
								rest = [];
								if (words.length) {
									tspan = doc.createElementNS(SVG_NS, 'tspan');
									attr(tspan, {
										dy: textLineHeight || 16,
										x: parentX
									});
									if (spanStyle) { // #390
										attr(tspan, 'style', spanStyle);
									}
									textNode.appendChild(tspan);

									if (actualWidth > width) { // a single word is pressing it out
										width = actualWidth;
									}
								}
							} else { // append to existing line tspan
								tspan.removeChild(tspan.firstChild);
								rest.unshift(words.pop());
							}
							if (words.length) {
								tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
							}
						}
					}
				}
			});
		});
	},

	/**
	* Create a button with preset states
	* @param {String} text
	* @param {Number} x
	* @param {Number} y
	* @param {Function} callback
	* @param {Object} normalState
	* @param {Object} hoverState
	* @param {Object} pressedState
	*/
	button: function (text, x, y, callback, normalState, hoverState, pressedState) {
		var label = this.label(text, x, y),
			curState = 0,
			stateOptions,
			stateStyle,
			normalStyle,
			hoverStyle,
			pressedStyle,
			STYLE = 'style',
			verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };

		// prepare the attributes
		/*jslint white: true*/
		normalState = merge(hash(
			STROKE_WIDTH, 1,
			STROKE, '#999',
			FILL, hash(
				LINEAR_GRADIENT, verticalGradient,
				STOPS, [
					[0, '#FFF'],
					[1, '#DDD']
				]
			),
			'r', 3,
			'padding', 3,
			STYLE, hash(
				'color', 'black'
			)
		), normalState);
		/*jslint white: false*/
		normalStyle = normalState[STYLE];
		delete normalState[STYLE];

		/*jslint white: true*/
		hoverState = merge(normalState, hash(
			STROKE, '#68A',
			FILL, hash(
				LINEAR_GRADIENT, verticalGradient,
				STOPS, [
					[0, '#FFF'],
					[1, '#ACF']
				]
			)
		), hoverState);
		/*jslint white: false*/
		hoverStyle = hoverState[STYLE];
		delete hoverState[STYLE];

		/*jslint white: true*/
		pressedState = merge(normalState, hash(
			STROKE, '#68A',
			FILL, hash(
				LINEAR_GRADIENT, verticalGradient,
				STOPS, [
					[0, '#9BD'],
					[1, '#CDF']
				]
			)
		), pressedState);
		/*jslint white: false*/
		pressedStyle = pressedState[STYLE];
		delete pressedState[STYLE];

		// add the events
		addEvent(label.element, 'mouseenter', function () {
			label.attr(hoverState)
				.css(hoverStyle);
		});
		addEvent(label.element, 'mouseleave', function () {
			stateOptions = [normalState, hoverState, pressedState][curState];
			stateStyle = [normalStyle, hoverStyle, pressedStyle][curState];
			label.attr(stateOptions)
				.css(stateStyle);
		});

		label.setState = function (state) {
			curState = state;
			if (!state) {
				label.attr(normalState)
					.css(normalStyle);
			} else if (state === 2) {
				label.attr(pressedState)
					.css(pressedStyle);
			}
		};

		return label
			.on('click', function () {
				callback.call(label);
			})
			.attr(normalState)
			.css(extend({ cursor: 'default' }, normalStyle));
	},

	/**
	* Make a straight line crisper by not spilling out to neighbour pixels
	* @param {Array} points
	* @param {Number} width
	*/
	crispLine: function (points, width) {
		// points format: [M, 0, 0, L, 100, 0]
		// normalize to a crisp line
		if (points[1] === points[4]) {
			// Substract due to #1129. Now bottom and left axis gridlines behave the same.
			points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2);
		}
		if (points[2] === points[5]) {
			points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
		}
		return points;
	},


	/**
	* Draw a path
	* @param {Array} path An SVG path in array form
	*/
	path: function (path) {
		var attr = {
			fill: NONE
		};
		if (isArray(path)) {
			attr.d = path;
		} else if (isObject(path)) { // attributes
			extend(attr, path);
		}
		return this.createElement('path').attr(attr);
	},

	/**
	* Draw and return an SVG circle
	* @param {Number} x The x position
	* @param {Number} y The y position
	* @param {Number} r The radius
	*/
	circle: function (x, y, r) {
		var attr = isObject(x) ?
			x :
			{
				x: x,
				y: y,
				r: r
			};

		return this.createElement('circle').attr(attr);
	},

	/**
	* Draw and return an arc
	* @param {Number} x X position
	* @param {Number} y Y position
	* @param {Number} r Radius
	* @param {Number} innerR Inner radius like used in donut charts
	* @param {Number} start Starting angle
	* @param {Number} end Ending angle
	*/
	arc: function (x, y, r, innerR, start, end) {
		// arcs are defined as symbols for the ability to set
		// attributes in attr and animate

		if (isObject(x)) {
			y = x.y;
			r = x.r;
			innerR = x.innerR;
			start = x.start;
			end = x.end;
			x = x.x;
		}
		return this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
			innerR: innerR || 0,
			start: start || 0,
			end: end || 0
		});
	},

	/**
	* Draw and return a rectangle
	* @param {Number} x Left position
	* @param {Number} y Top position
	* @param {Number} width
	* @param {Number} height
	* @param {Number} r Border corner radius
	* @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
	*/
	rect: function (x, y, width, height, r, strokeWidth) {

		r = isObject(x) ? x.r : r;

		var wrapper = this.createElement('rect').attr({
			rx: r,
			ry: r,
			fill: NONE
		});
		return wrapper.attr(
				isObject(x) ?
					x :
		// do not crispify when an object is passed in (as in column charts)
					wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))
			);
	},

	/**
	* Resize the box and re-align all aligned elements
	* @param {Object} width
	* @param {Object} height
	* @param {Boolean} animate
	*
	*/
	setSize: function (width, height, animate) {
		var renderer = this,
			alignedObjects = renderer.alignedObjects,
			i = alignedObjects.length;

		renderer.width = width;
		renderer.height = height;

		renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
			width: width,
			height: height
		});

		while (i--) {
			alignedObjects[i].align();
		}
	},

	/**
	* Create a group
	* @param {String} name The group will be given a class name of 'highcharts-{name}'.
	*     This can be used for styling and scripting.
	*/
	g: function (name) {
		var elem = this.createElement('g');
		return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;
	},

	/**
	* Display an image
	* @param {String} src
	* @param {Number} x
	* @param {Number} y
	* @param {Number} width
	* @param {Number} height
	*/
	image: function (src, x, y, width, height) {
		var attribs = {
			preserveAspectRatio: NONE
		},
			elemWrapper;

		// optional properties
		if (arguments.length > 1) {
			extend(attribs, {
				x: x,
				y: y,
				width: width,
				height: height
			});
		}

		elemWrapper = this.createElement('image').attr(attribs);

		// set the href in the xlink namespace
		if (elemWrapper.element.setAttributeNS) {
			elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
				'href', src);
		} else {
			// could be exporting in IE
			// using href throws "not supported" in ie7 and under, requries regex shim to fix later
			elemWrapper.element.setAttribute('hc-svg-href', src);
		}

		return elemWrapper;
	},

	/**
	* Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
	*
	* @param {Object} symbol
	* @param {Object} x
	* @param {Object} y
	* @param {Object} radius
	* @param {Object} options
	*/
	symbol: function (symbol, x, y, width, height, options) {

		var obj,

		// get the symbol definition function
			symbolFn = this.symbols[symbol],

		// check if there's a path defined for this symbol
			path = symbolFn && symbolFn(
				mathRound(x),
				mathRound(y),
				width,
				height,
				options
			),

			imageRegex = /^url\((.*?)\)$/,
			imageSrc,
			imageSize,
			centerImage;

		if (path) {

			obj = this.path(path);
			// expando properties for use in animate and attr
			extend(obj, {
				symbolName: symbol,
				x: x,
				y: y,
				width: width,
				height: height
			});
			if (options) {
				extend(obj, options);
			}


			// image symbols
		} else if (imageRegex.test(symbol)) {

			// On image load, set the size and position
			centerImage = function (img, size) {
				img.attr({
					width: size[0],
					height: size[1]
				});

				if (!img.alignByTranslate) { // #185
					img.translate(
						-mathRound(size[0] / 2),
						-mathRound(size[1] / 2)
					);
				}
			};

			imageSrc = symbol.match(imageRegex)[1];
			imageSize = symbolSizes[imageSrc];

			// create the image synchronously, add attribs async
			obj = this.image(imageSrc)
				.attr({
					x: x,
					y: y
				});

			if (imageSize) {
				centerImage(obj, imageSize);
			} else {
				// initialize image to be 0 size so export will still function if there's no cached sizes
				obj.attr({ width: 0, height: 0 });

				// create a dummy JavaScript image to get the width and height
				createElement('img', {
					onload: function () {
						var img = this;

						centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]);
					},
					src: imageSrc
				});
			}
		}

		return obj;
	},

	/**
	* An extendable collection of functions for defining symbol paths.
	*/
	symbols: {
		'circle': function (x, y, w, h) {
			var cpw = 0.166 * w;
			return [
				M, x + w / 2, y,
				'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h,
				'C', x - cpw, y + h, x - cpw, y, x + w / 2, y,
				'Z'
			];
		},

		'square': function (x, y, w, h) {
			return [
				M, x, y,
				L, x + w, y,
				x + w, y + h,
				x, y + h,
				'Z'
			];
		},

		'triangle': function (x, y, w, h) {
			return [
				M, x + w / 2, y,
				L, x + w, y + h,
				x, y + h,
				'Z'
			];
		},

		'triangle-down': function (x, y, w, h) {
			return [
				M, x, y,
				L, x + w, y,
				x + w / 2, y + h,
				'Z'
			];
		},
		'diamond': function (x, y, w, h) {
			return [
				M, x + w / 2, y,
				L, x + w, y + h / 2,
				x + w / 2, y + h,
				x, y + h / 2,
				'Z'
			];
		},
		'arc': function (x, y, w, h, options) {
			var start = options.start,
				radius = options.r || w || h,
				end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
				innerRadius = options.innerR,
				open = options.open,
				cosStart = mathCos(start),
				sinStart = mathSin(start),
				cosEnd = mathCos(end),
				sinEnd = mathSin(end),
				longArc = options.end - start < mathPI ? 0 : 1;

			return [
				M,
				x + radius * cosStart,
				y + radius * sinStart,
				'A', // arcTo
				radius, // x radius
				radius, // y radius
				0, // slanting
				longArc, // long or short arc
				1, // clockwise
				x + radius * cosEnd,
				y + radius * sinEnd,
				open ? M : L,
				x + innerRadius * cosEnd,
				y + innerRadius * sinEnd,
				'A', // arcTo
				innerRadius, // x radius
				innerRadius, // y radius
				0, // slanting
				longArc, // long or short arc
				0, // clockwise
				x + innerRadius * cosStart,
				y + innerRadius * sinStart,

				open ? '' : 'Z' // close
			];
		}
	},

	/**
	* Define a clipping rectangle
	* @param {String} id
	* @param {Number} x
	* @param {Number} y
	* @param {Number} width
	* @param {Number} height
	*/
	clipRect: function (x, y, width, height) {
		var wrapper,
			id = PREFIX + idCounter++,

			clipPath = this.createElement('clipPath').attr({
				id: id
			}).add(this.defs);

		wrapper = this.rect(x, y, width, height, 0).add(clipPath);
		wrapper.id = id;
		wrapper.clipPath = clipPath;

		return wrapper;
	},


	/**
	* Take a color and return it if it's a string, make it a gradient if it's a
	* gradient configuration object. Prior to Highstock, an array was used to define
	* a linear gradient with pixel positions relative to the SVG. In newer versions
	* we change the coordinates to apply relative to the shape, using coordinates
	* 0-1 within the shape. To preserve backwards compatibility, linearGradient
	* in this definition is an object of x1, y1, x2 and y2.
	*
	* @param {Object} color The color or config object
	*/
	color: function (color, elem, prop) {
		var renderer = this,
			colorObject,
			regexRgba = /^rgba/,
			gradName;

		// Apply linear or radial gradients
		if (color && color.linearGradient) {
			gradName = 'linearGradient';
		} else if (color && color.radialGradient) {
			gradName = 'radialGradient';
		}

		if (gradName) {
			var gradAttr = color[gradName],
				gradients = renderer.gradients,
				gradientObject,
				stopColor,
				stopOpacity,
				radialReference = elem.radialReference;

			// Check if a gradient object with the same config object is created within this renderer
			if (!gradAttr.id || !gradients[gradAttr.id]) {

				// Keep < 2.2 kompatibility
				if (isArray(gradAttr)) {
					color[gradName] = gradAttr = {
						x1: gradAttr[0],
						y1: gradAttr[1],
						x2: gradAttr[2],
						y2: gradAttr[3],
						gradientUnits: 'userSpaceOnUse'
					};
				}

				// Correct the radial gradient for the radial reference system
				if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
					extend(gradAttr, {
						cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
						cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
						r: gradAttr.r * radialReference[2],
						gradientUnits: 'userSpaceOnUse'
					});
				}

				// Set the id and create the element
				gradAttr.id = PREFIX + idCounter++;
				gradients[gradAttr.id] = gradientObject = renderer.createElement(gradName)
					.attr(gradAttr)
					.add(renderer.defs);


				// The gradient needs to keep a list of stops to be able to destroy them
				gradientObject.stops = [];
				each(color.stops, function (stop) {
					var stopObject;
					if (regexRgba.test(stop[1])) {
						colorObject = Color(stop[1]);
						stopColor = colorObject.get('rgb');
						stopOpacity = colorObject.get('a');
					} else {
						stopColor = stop[1];
						stopOpacity = 1;
					}
					stopObject = renderer.createElement('stop').attr({
						offset: stop[0],
						'stop-color': stopColor,
						'stop-opacity': stopOpacity
					}).add(gradientObject);

					// Add the stop element to the gradient
					gradientObject.stops.push(stopObject);
				});
			}

			// Return the reference to the gradient object
			return 'url(' + renderer.url + '#' + gradAttr.id + ')';

			// Webkit and Batik can't show rgba.
		} else if (regexRgba.test(color)) {
			colorObject = Color(color);
			attr(elem, prop + '-opacity', colorObject.get('a'));

			return colorObject.get('rgb');


		} else {
			// Remove the opacity attribute added above. Does not throw if the attribute is not there.
			elem.removeAttribute(prop + '-opacity');

			return color;
		}

	},


	/**
	* Add text to the SVG object
	* @param {String} str
	* @param {Number} x Left position
	* @param {Number} y Top position
	* @param {Boolean} useHTML Use HTML to render the text
	*/
	text: function (str, x, y, useHTML) {

		// declare variables
		var renderer = this,
			defaultChartStyle = defaultOptions.chart.style,
			fakeSVG = useCanVG || (!hasSVG && renderer.forExport),
			wrapper;

		if (useHTML && !renderer.forExport) {
			return renderer.html(str, x, y);
		}

		x = mathRound(pick(x, 0));
		y = mathRound(pick(y, 0));

		wrapper = renderer.createElement('text')
			.attr({
				x: x,
				y: y,
				text: str
			})
			.css({
				fontFamily: defaultChartStyle.fontFamily,
				fontSize: defaultChartStyle.fontSize
			});

		// Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063)	
		if (fakeSVG) {
			wrapper.css({
				position: ABSOLUTE
			});
		}

		wrapper.x = x;
		wrapper.y = y;
		return wrapper;
	},


	/**
	* Create HTML text node. This is used by the VML renderer as well as the SVG
	* renderer through the useHTML option.
	*
	* @param {String} str
	* @param {Number} x
	* @param {Number} y
	*/
	html: function (str, x, y) {
		var defaultChartStyle = defaultOptions.chart.style,
			wrapper = this.createElement('span'),
			attrSetters = wrapper.attrSetters,
			element = wrapper.element,
			renderer = wrapper.renderer;

		// Text setter
		attrSetters.text = function (value) {
			if (value !== element.innerHTML) {
				delete this.bBox;
			}
			element.innerHTML = value;
			return false;
		};

		// Various setters which rely on update transform
		attrSetters.x = attrSetters.y = attrSetters.align = function (value, key) {
			if (key === 'align') {
				key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML.
			}
			wrapper[key] = value;
			wrapper.htmlUpdateTransform();
			return false;
		};

		// Set the default attributes
		wrapper.attr({
			text: str,
			x: mathRound(x),
			y: mathRound(y)
		})
			.css({
				position: ABSOLUTE,
				whiteSpace: 'nowrap',
				fontFamily: defaultChartStyle.fontFamily,
				fontSize: defaultChartStyle.fontSize
			});

		// Use the HTML specific .css method
		wrapper.css = wrapper.htmlCss;

		// This is specific for HTML within SVG
		if (renderer.isSVG) {
			wrapper.add = function (svgGroupWrapper) {

				var htmlGroup,
					container = renderer.box.parentNode,
					parentGroup,
					parents = [];

				// Create a mock group to hold the HTML elements
				if (svgGroupWrapper) {
					htmlGroup = svgGroupWrapper.div;
					if (!htmlGroup) {

						// Read the parent chain into an array and read from top down
						parentGroup = svgGroupWrapper;
						while (parentGroup) {

							parents.push(parentGroup);

							// Move up to the next parent group
							parentGroup = parentGroup.parentGroup;
						}

						// Ensure dynamically updating position when any parent is translated
						each(parents.reverse(), function (parentGroup) {
							var htmlGroupStyle;

							// Create a HTML div and append it to the parent div to emulate 
							// the SVG group structure
							htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, {
								className: attr(parentGroup.element, 'class')
							}, {
								position: ABSOLUTE,
								left: (parentGroup.translateX || 0) + PX,
								top: (parentGroup.translateY || 0) + PX
							}, htmlGroup || container); // the top group is appended to container

							// Shortcut
							htmlGroupStyle = htmlGroup.style;

							// Set listeners to update the HTML div's position whenever the SVG group
							// position is changed
							extend(parentGroup.attrSetters, {
								translateX: function (value) {
									htmlGroupStyle.left = value + PX;
								},
								translateY: function (value) {
									htmlGroupStyle.top = value + PX;
								},
								visibility: function (value, key) {
									htmlGroupStyle[key] = value;
								}
							});
						});

					}
				} else {
					htmlGroup = container;
				}

				htmlGroup.appendChild(element);

				// Shared with VML:
				wrapper.added = true;
				if (wrapper.alignOnAdd) {
					wrapper.htmlUpdateTransform();
				}

				return wrapper;
			};
		}
		return wrapper;
	},

	/**
	* Utility to return the baseline offset and total line height from the font size
	*/
	fontMetrics: function (fontSize) {
		fontSize = pInt(fontSize || 11);

		// Empirical values found by comparing font size and bounding box height.
		// Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/
		var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2),
			baseline = mathRound(lineHeight * 0.8);

		return {
			h: lineHeight,
			b: baseline
		};
	},

	/**
	* Add a label, a text item that can hold a colored or gradient background
	* as well as a border and shadow.
	* @param {string} str
	* @param {Number} x
	* @param {Number} y
	* @param {String} shape
	* @param {Number} anchorX In case the shape has a pointer, like a flag, this is the
	*    coordinates it should be pinned to
	* @param {Number} anchorY
	* @param {Boolean} baseline Whether to position the label relative to the text baseline,
	*    like renderer.text, or to the upper border of the rectangle. 
	* @param {String} className Class name for the group 
	*/
	label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {

		var renderer = this,
			wrapper = renderer.g(className),
			text = renderer.text('', 0, 0, useHTML)
				.attr({
					zIndex: 1
				}),
		//.add(wrapper),
			box,
			bBox,
			alignFactor = 0,
			padding = 3,
			width,
			height,
			wrapperX,
			wrapperY,
			crispAdjust = 0,
			deferredAttr = {},
			baselineOffset,
			attrSetters = wrapper.attrSetters;

		/**
		* This function runs after the label is added to the DOM (when the bounding box is
		* available), and after the text of the label is updated to detect the new bounding
		* box and reflect it in the border box.
		*/
		function updateBoxSize() {
			var boxY,
				style = text.element.style;

			bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&
				text.getBBox();
			wrapper.width = (width || bBox.width || 0) + 2 * padding;
			wrapper.height = (height || bBox.height || 0) + 2 * padding;

			// update the label-scoped y offset
			baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;


			// create the border box if it is not already present
			if (!box) {
				boxY = baseline ? -baselineOffset : 0;

				wrapper.box = box = shape ?
					renderer.symbol(shape, -alignFactor * padding, boxY, wrapper.width, wrapper.height) :
					renderer.rect(-alignFactor * padding, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
				box.add(wrapper);
			}

			// apply the box attributes
			box.attr(merge({
				width: wrapper.width,
				height: wrapper.height
			}, deferredAttr));
			deferredAttr = null;
		}

		/**
		* This function runs after setting text or padding, but only if padding is changed
		*/
		function updateTextPadding() {
			var styles = wrapper.styles,
				textAlign = styles && styles.textAlign,
				x = padding * (1 - alignFactor),
				y;

			// determin y based on the baseline
			y = baseline ? 0 : baselineOffset;

			// compensate for alignment
			if (defined(width) && (textAlign === 'center' || textAlign === 'right')) {
				x += { center: 0.5, right: 1}[textAlign] * (width - bBox.width);
			}

			// update if anything changed
			if (x !== text.x || y !== text.y) {
				text.attr({
					x: x,
					y: y
				});
			}

			// record current values
			text.x = x;
			text.y = y;
		}

		/**
		* Set a box attribute, or defer it if the box is not yet created
		* @param {Object} key
		* @param {Object} value
		*/
		function boxAttr(key, value) {
			if (box) {
				box.attr(key, value);
			} else {
				deferredAttr[key] = value;
			}
		}

		function getSizeAfterAdd() {
			text.add(wrapper);
			wrapper.attr({
				text: str, // alignment is available now
				x: x,
				y: y
			});

			if (defined(anchorX)) {
				wrapper.attr({
					anchorX: anchorX,
					anchorY: anchorY
				});
			}
		}

		/**
		* After the text element is added, get the desired size of the border box
		* and add it before the text in the DOM.
		*/
		addEvent(wrapper, 'add', getSizeAfterAdd);

		/*
		* Add specific attribute setters.
		*/

		// only change local variables
		attrSetters.width = function (value) {
			width = value;
			return false;
		};
		attrSetters.height = function (value) {
			height = value;
			return false;
		};
		attrSetters.padding = function (value) {
			if (defined(value) && value !== padding) {
				padding = value;
				updateTextPadding();
			}

			return false;
		};

		// change local variable and set attribue as well
		attrSetters.align = function (value) {
			alignFactor = { left: 0, center: 0.5, right: 1}[value];
			return false; // prevent setting text-anchor on the group
		};

		// apply these to the box and the text alike
		attrSetters.text = function (value, key) {
			text.attr(key, value);
			updateBoxSize();
			updateTextPadding();
			return false;
		};

		// apply these to the box but not to the text
		attrSetters[STROKE_WIDTH] = function (value, key) {
			crispAdjust = value % 2 / 2;
			boxAttr(key, value);
			return false;
		};
		attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {
			boxAttr(key, value);
			return false;
		};
		attrSetters.anchorX = function (value, key) {
			anchorX = value;
			boxAttr(key, value + crispAdjust - wrapperX);
			return false;
		};
		attrSetters.anchorY = function (value, key) {
			anchorY = value;
			boxAttr(key, value - wrapperY);
			return false;
		};

		// rename attributes
		attrSetters.x = function (value) {
			wrapper.x = value; // for animation getter
			value -= alignFactor * ((width || bBox.width) + padding);
			wrapperX = mathRound(value);

			wrapper.attr('translateX', wrapperX);
			return false;
		};
		attrSetters.y = function (value) {
			wrapperY = wrapper.y = mathRound(value);
			wrapper.attr('translateY', value);
			return false;
		};

		// Redirect certain methods to either the box or the text
		var baseCss = wrapper.css;
		return extend(wrapper, {
			/**
			* Pick up some properties and apply them to the text instead of the wrapper
			*/
			css: function (styles) {
				if (styles) {
					var textStyles = {};
					styles = merge({}, styles); // create a copy to avoid altering the original object (#537)
					each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width'], function (prop) {
						if (styles[prop] !== UNDEFINED) {
							textStyles[prop] = styles[prop];
							delete styles[prop];
						}
					});
					text.css(textStyles);
				}
				return baseCss.call(wrapper, styles);
			},
			/**
			* Return the bounding box of the box, not the group
			*/
			getBBox: function () {
				return box.getBBox();
			},
			/**
			* Apply the shadow to the box
			*/
			shadow: function (b) {
				box.shadow(b);
				return wrapper;
			},
			/**
			* Destroy and release memory.
			*/
			destroy: function () {
				removeEvent(wrapper, 'add', getSizeAfterAdd);

				// Added by button implementation
				removeEvent(wrapper.element, 'mouseenter');
				removeEvent(wrapper.element, 'mouseleave');

				if (text) {
					text = text.destroy();
				}
				if (box) {
					box = box.destroy();
				}
				// Call base implementation to destroy the rest
				SVGElement.prototype.destroy.call(wrapper);
			}
		});
	}
}; // end SVGRenderer


// general renderer
Renderer = SVGRenderer;


/* ****************************************************************************
*                                                                            *
* START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              *
*                                                                            *
* For applications and websites that don't need IE support, like platform    *
* targeted mobile apps and web apps, this code can be removed.               *
*                                                                            *
*****************************************************************************/

/**
* @constructor
*/
var VMLRenderer;
if (!hasSVG && !useCanVG) {

	/**
	* The VML element wrapper.
	*/
	var VMLElement = {

		/**
		* Initialize a new VML element wrapper. It builds the markup as a string
		* to minimize DOM traffic.
		* @param {Object} renderer
		* @param {Object} nodeName
		*/
		init: function (renderer, nodeName) {
			var wrapper = this,
			markup = ['<', nodeName, ' filled="f" stroked="f"'],
			style = ['position: ', ABSOLUTE, ';'];

			// divs and shapes need size
			if (nodeName === 'shape' || nodeName === DIV) {
				style.push('left:0;top:0;width:1px;height:1px;');
			}
			if (docMode8) {
				style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE);
			}

			markup.push(' style="', style.join(''), '"/>');

			// create element with default attributes and style
			if (nodeName) {
				markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ?
				markup.join('')
				: renderer.prepVML(markup);
				wrapper.element = createElement(markup);
			}

			wrapper.renderer = renderer;
			wrapper.attrSetters = {};
		},

		/**
		* Add the node to the given parent
		* @param {Object} parent
		*/
		add: function (parent) {
			var wrapper = this,
			renderer = wrapper.renderer,
			element = wrapper.element,
			box = renderer.box,
			inverted = parent && parent.inverted,

			// get the parent node
			parentNode = parent ?
				parent.element || parent :
				box;


			// if the parent group is inverted, apply inversion on all children
			if (inverted) { // only on groups
				renderer.invertChild(element, parentNode);
			}

			// append it
			parentNode.appendChild(element);

			// align text after adding to be able to read offset
			wrapper.added = true;
			if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {
				wrapper.updateTransform();
			}

			// fire an event for internal hooks
			fireEvent(wrapper, 'add');

			return wrapper;
		},

		/**
		* VML always uses htmlUpdateTransform
		*/
		updateTransform: SVGElement.prototype.htmlUpdateTransform,

		/**
		* Get or set attributes
		*/
		attr: function (hash, val) {
			var wrapper = this,
			key,
			value,
			i,
			result,
			element = wrapper.element || {},
			elemStyle = element.style,
			nodeName = element.nodeName,
			renderer = wrapper.renderer,
			symbolName = wrapper.symbolName,
			hasSetSymbolSize,
			shadows = wrapper.shadows,
			skipAttr,
			attrSetters = wrapper.attrSetters,
			ret = wrapper;

			// single key-value pair
			if (isString(hash) && defined(val)) {
				key = hash;
				hash = {};
				hash[key] = val;
			}

			// used as a getter, val is undefined
			if (isString(hash)) {
				key = hash;
				if (key === 'strokeWidth' || key === 'stroke-width') {
					ret = wrapper.strokeweight;
				} else {
					ret = wrapper[key];
				}

				// setter
			} else {
				for (key in hash) {
					value = hash[key];
					skipAttr = false;

					// check for a specific attribute setter
					result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);

					if (result !== false && value !== null) { // #620

						if (result !== UNDEFINED) {
							value = result; // the attribute setter has returned a new value to set
						}


						// prepare paths
						// symbols
						if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) {
							// if one of the symbol size affecting parameters are changed,
							// check all the others only once for each call to an element's
							// .attr() method
							if (!hasSetSymbolSize) {
								wrapper.symbolAttr(hash);

								hasSetSymbolSize = true;
							}
							skipAttr = true;

						} else if (key === 'd') {
							value = value || [];
							wrapper.d = value.join(' '); // used in getter for animation

							// convert paths
							i = value.length;
							var convertedPath = [];
							while (i--) {

								// Multiply by 10 to allow subpixel precision.
								// Substracting half a pixel seems to make the coordinates
								// align with SVG, but this hasn't been tested thoroughly
								if (isNumber(value[i])) {
									convertedPath[i] = mathRound(value[i] * 10) - 5;
								} else if (value[i] === 'Z') { // close the path
									convertedPath[i] = 'x';
								} else {
									convertedPath[i] = value[i];
								}

							}
							value = convertedPath.join(' ') || 'x';
							element.path = value;

							// update shadows
							if (shadows) {
								i = shadows.length;
								while (i--) {
									shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value;
								}
							}
							skipAttr = true;

							// handle visibility
						} else if (key === 'visibility') {

							// let the shadow follow the main element
							if (shadows) {
								i = shadows.length;
								while (i--) {
									shadows[i].style[key] = value;
								}
							}

							// Instead of toggling the visibility CSS property, move the div out of the viewport. 
							// This works around #61 and #586							
							if (nodeName === 'DIV') {
								value = value === HIDDEN ? '-999em' : 0;
								key = 'top';
							}

							elemStyle[key] = value;
							skipAttr = true;

							// directly mapped to css
						} else if (key === 'zIndex') {

							if (value) {
								elemStyle[key] = value;
							}
							skipAttr = true;

							// width and height
						} else if (key === 'width' || key === 'height') {

							value = mathMax(0, value); // don't set width or height below zero (#311)

							this[key] = value; // used in getter

							// clipping rectangle special
							if (wrapper.updateClipping) {
								wrapper[key] = value;
								wrapper.updateClipping();
							} else {
								// normal
								elemStyle[key] = value;
							}

							skipAttr = true;

							// x and y
						} else if (key === 'x' || key === 'y') {
							wrapper[key] = value; // used in getter
							elemStyle[{ x: 'left', y: 'top'}[key]] = value;

							// class name
						} else if (key === 'class') {
							// IE8 Standards mode has problems retrieving the className
							element.className = value;

							// stroke
						} else if (key === 'stroke') {

							value = renderer.color(value, element, key);

							key = 'strokecolor';

							// stroke width
						} else if (key === 'stroke-width' || key === 'strokeWidth') {
							element.stroked = value ? true : false;
							key = 'strokeweight';
							wrapper[key] = value; // used in getter, issue #113
							if (isNumber(value)) {
								value += PX;
							}

							// dashStyle
						} else if (key === 'dashstyle') {
							var strokeElem = element.getElementsByTagName('stroke')[0] ||
							createElement(renderer.prepVML(['<stroke/>']), null, null, element);
							strokeElem[key] = value || 'solid';
							wrapper.dashstyle = value; /* because changing stroke-width will change the dash length
							and cause an epileptic effect */
							skipAttr = true;

							// fill
						} else if (key === 'fill') {

							if (nodeName === 'SPAN') { // text color
								elemStyle.color = value;
							} else {
								element.filled = value !== NONE ? true : false;

								value = renderer.color(value, element, key, wrapper);

								key = 'fillcolor';
							}

							// rotation on VML elements
						} else if (nodeName === 'shape' && key === 'rotation') {
							wrapper[key] = value;
							// Correction for the 1x1 size of the shape container. Used in gauge needles.
							element.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;
							element.style.top = mathRound(mathCos(value * deg2rad)) + PX;

							// translation for animation
						} else if (key === 'translateX' || key === 'translateY' || key === 'rotation') {
							wrapper[key] = value;
							wrapper.updateTransform();

							skipAttr = true;

							// text for rotated and non-rotated elements
						} else if (key === 'text') {
							this.bBox = null;
							element.innerHTML = value;
							skipAttr = true;
						}

						if (!skipAttr) {
							if (docMode8) { // IE8 setAttribute bug
								element[key] = value;
							} else {
								attr(element, key, value);
							}
						}

					}
				}
			}
			return ret;
		},

		/**
		* Set the element's clipping to a predefined rectangle
		*
		* @param {String} id The id of the clip rectangle
		*/
		clip: function (clipRect) {
			var wrapper = this,
			clipMembers,
			element = wrapper.element,
			parentNode = element.parentNode,
			cssRet;

			if (clipRect) {
				clipMembers = clipRect.members;
				clipMembers.push(wrapper);
				wrapper.destroyClip = function () {
					erase(clipMembers, wrapper);
				};
				// Issue #863 workaround - related to #140, #61, #74
				if (parentNode && parentNode.className === 'highcharts-tracker' && !docMode8) {
					css(element, { visibility: HIDDEN });
				}
				cssRet = clipRect.getCSS(wrapper);

			} else {
				if (wrapper.destroyClip) {
					wrapper.destroyClip();
				}
				cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214
			}

			return wrapper.css(cssRet);

		},

		/**
		* Set styles for the element
		* @param {Object} styles
		*/
		css: SVGElement.prototype.htmlCss,

		/**
		* Removes a child either by removeChild or move to garbageBin.
		* Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
		*/
		safeRemoveChild: function (element) {
			// discardElement will detach the node from its parent before attaching it
			// to the garbage bin. Therefore it is important that the node is attached and have parent.
			if (element.parentNode) {
				discardElement(element);
			}
		},

		/**
		* Extend element.destroy by removing it from the clip members array
		*/
		destroy: function () {
			if (this.destroyClip) {
				this.destroyClip();
			}

			return SVGElement.prototype.destroy.apply(this);
		},

		/**
		* Remove all child nodes of a group, except the v:group element
		*/
		empty: function () {
			var element = this.element,
			childNodes = element.childNodes,
			i = childNodes.length,
			node;

			while (i--) {
				node = childNodes[i];
				node.parentNode.removeChild(node);
			}
		},

		/**
		* Add an event listener. VML override for normalizing event parameters.
		* @param {String} eventType
		* @param {Function} handler
		*/
		on: function (eventType, handler) {
			// simplest possible event model for internal use
			this.element['on' + eventType] = function () {
				var evt = win.event;
				evt.target = evt.srcElement;
				handler(evt);
			};
			return this;
		},

		/**
		* In stacked columns, cut off the shadows so that they don't overlap
		*/
		cutOffPath: function (path, length) {

			var len;

			path = path.split(/[ ,]/);
			len = path.length;

			if (len === 9 || len === 11) {
				path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length;
			}
			return path.join(' ');
		},

		/**
		* Apply a drop shadow by copying elements and giving them different strokes
		* @param {Boolean|Object} shadowOptions
		*/
		shadow: function (shadowOptions, group, cutOff) {
			var shadows = [],
			i,
			element = this.element,
			renderer = this.renderer,
			shadow,
			elemStyle = element.style,
			markup,
			path = element.path,
			strokeWidth,
			modifiedPath,
			shadowWidth,
			shadowElementOpacity;

			// some times empty paths are not strings
			if (path && typeof path.value !== 'string') {
				path = 'x';
			}
			modifiedPath = path;

			if (shadowOptions) {
				shadowWidth = pick(shadowOptions.width, 3);
				shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
				for (i = 1; i <= 3; i++) {

					strokeWidth = (shadowWidth * 2) + 1 - (2 * i);

					// Cut off shadows for stacked column items
					if (cutOff) {
						modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5);
					}

					markup = ['<shape isShadow="true" strokeweight="', strokeWidth,
					'" filled="false" path="', modifiedPath,
					'" coordsize="10 10" style="', element.style.cssText, '" />'];

					shadow = createElement(renderer.prepVML(markup),
					null, {
						left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1),
						top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1)
					}
				);
					if (cutOff) {
						shadow.cutOff = strokeWidth + 1;
					}

					// apply the opacity
					markup = ['<stroke color="', shadowOptions.color || 'black', '" opacity="', shadowElementOpacity * i, '"/>'];
					createElement(renderer.prepVML(markup), null, null, shadow);


					// insert it
					if (group) {
						group.element.appendChild(shadow);
					} else {
						element.parentNode.insertBefore(shadow, element);
					}

					// record it
					shadows.push(shadow);

				}

				this.shadows = shadows;
			}
			return this;

		}
	};
	VMLElement = extendClass(SVGElement, VMLElement);

	/**
	* The VML renderer
	*/
	var VMLRendererExtension = { // inherit SVGRenderer

		Element: VMLElement,
		isIE8: userAgent.indexOf('MSIE 8.0') > -1,


		/**
		* Initialize the VMLRenderer
		* @param {Object} container
		* @param {Number} width
		* @param {Number} height
		*/
		init: function (container, width, height) {
			var renderer = this,
			boxWrapper,
			box;

			renderer.alignedObjects = [];

			boxWrapper = renderer.createElement(DIV);
			box = boxWrapper.element;
			box.style.position = RELATIVE; // for freeform drawing using renderer directly
			container.appendChild(boxWrapper.element);


			// generate the containing box
			renderer.box = box;
			renderer.boxWrapper = boxWrapper;


			renderer.setSize(width, height, false);

			// The only way to make IE6 and IE7 print is to use a global namespace. However,
			// with IE8 the only way to make the dynamic shapes visible in screen and print mode
			// seems to be to add the xmlns attribute and the behaviour style inline.
			if (!doc.namespaces.hcv) {

				doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');

				// setup default css
				doc.createStyleSheet().cssText =
				'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
				'{ behavior:url(#default#VML); display: inline-block; } ';

			}
		},


		/**
		* Detect whether the renderer is hidden. This happens when one of the parent elements
		* has display: none
		*/
		isHidden: function () {
			return !this.box.offsetWidth;
		},

		/**
		* Define a clipping rectangle. In VML it is accomplished by storing the values
		* for setting the CSS style to all associated members.
		*
		* @param {Number} x
		* @param {Number} y
		* @param {Number} width
		* @param {Number} height
		*/
		clipRect: function (x, y, width, height) {

			// create a dummy element
			var clipRect = this.createElement(),
			isObj = isObject(x);

			// mimic a rectangle with its style object for automatic updating in attr
			return extend(clipRect, {
				members: [],
				left: isObj ? x.x : x,
				top: isObj ? x.y : y,
				width: isObj ? x.width : width,
				height: isObj ? x.height : height,
				getCSS: function (wrapper) {
					var inverted = wrapper.inverted,
					rect = this,
					top = rect.top,
					left = rect.left,
					right = left + rect.width,
					bottom = top + rect.height,
					ret = {
						clip: 'rect(' +
							mathRound(inverted ? left : top) + 'px,' +
							mathRound(inverted ? bottom : right) + 'px,' +
							mathRound(inverted ? right : bottom) + 'px,' +
							mathRound(inverted ? top : left) + 'px)'
					};

					// issue 74 workaround
					if (!inverted && docMode8 && wrapper.element.nodeName !== 'IMG') {
						extend(ret, {
							width: right + PX,
							height: bottom + PX
						});
					}

					return ret;
				},

				// used in attr and animation to update the clipping of all members
				updateClipping: function () {
					each(clipRect.members, function (member) {
						member.css(clipRect.getCSS(member));
					});
				}
			});

		},


		/**
		* Take a color and return it if it's a string, make it a gradient if it's a
		* gradient configuration object, and apply opacity.
		*
		* @param {Object} color The color or config object
		*/
		color: function (color, elem, prop, wrapper) {
			var renderer = this,
			colorObject,
			regexRgba = /^rgba/,
			markup,
			fillType,
			ret = NONE;

			// Check for linear or radial gradient
			if (color && color.linearGradient) {
				fillType = 'gradient';
			} else if (color && color.radialGradient) {
				fillType = 'pattern';
			}


			if (fillType) {

				var stopColor,
				stopOpacity,
				gradient = color.linearGradient || color.radialGradient,
				x1,
				y1,
				x2,
				y2,
				opacity1,
				opacity2,
				color1,
				color2,
				fillAttr = '',
				stops = color.stops,
				firstStop,
				lastStop,
				colors = [],
				addFillNode = function () {
					// Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2
					// are reversed.
					markup = ['<fill colors="' + colors.join(',') + '" opacity="', opacity2, '" o:opacity2="', opacity1,
						'" type="', fillType, '" ', fillAttr, 'focus="100%" method="any" />'];
					createElement(renderer.prepVML(markup), null, null, elem);
				};

				// Extend from 0 to 1
				firstStop = stops[0];
				lastStop = stops[stops.length - 1];
				if (firstStop[0] > 0) {
					stops.unshift([
					0,
					firstStop[1]
				]);
				}
				if (lastStop[0] < 1) {
					stops.push([
					1,
					lastStop[1]
				]);
				}

				// Compute the stops
				each(stops, function (stop, i) {
					if (regexRgba.test(stop[1])) {
						colorObject = Color(stop[1]);
						stopColor = colorObject.get('rgb');
						stopOpacity = colorObject.get('a');
					} else {
						stopColor = stop[1];
						stopOpacity = 1;
					}

					// Build the color attribute
					colors.push((stop[0] * 100) + '% ' + stopColor);

					// Only start and end opacities are allowed, so we use the first and the last
					if (!i) {
						opacity1 = stopOpacity;
						color2 = stopColor;
					} else {
						opacity2 = stopOpacity;
						color1 = stopColor;
					}
				});

				// Apply the gradient to fills only.
				if (prop === 'fill') {

					// Handle linear gradient angle
					if (fillType === 'gradient') {
						x1 = gradient.x1 || gradient[0] || 0;
						y1 = gradient.y1 || gradient[1] || 0;
						x2 = gradient.x2 || gradient[2] || 0;
						y2 = gradient.y2 || gradient[3] || 0;
						fillAttr = 'angle="' + (90 - math.atan(
						(y2 - y1) / // y vector
						(x2 - x1) // x vector
						) * 180 / mathPI) + '"';

						addFillNode();

						// Radial (circular) gradient
					} else {

						var r = gradient.r,
						sizex = r * 2,
						sizey = r * 2,
						cx = gradient.cx,
						cy = gradient.cy,
						radialReference = elem.radialReference,
						bBox,
						applyRadialGradient = function () {
							if (radialReference) {
								bBox = wrapper.getBBox();
								cx += (radialReference[0] - bBox.x) / bBox.width - 0.5;
								cy += (radialReference[1] - bBox.y) / bBox.height - 0.5;
								sizex *= radialReference[2] / bBox.width;
								sizey *= radialReference[2] / bBox.height;
							}
							fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' +
								'size="' + sizex + ',' + sizey + '" ' +
								'origin="0.5,0.5" ' +
								'position="' + cx + ',' + cy + '" ' +
								'color2="' + color2 + '" ';

							addFillNode();
						};

						// Apply radial gradient
						if (wrapper.added) {
							applyRadialGradient();
						} else {
							// We need to know the bounding box to get the size and position right
							addEvent(wrapper, 'add', applyRadialGradient);
						}

						// The fill element's color attribute is broken in IE8 standards mode, so we
						// need to set the parent shape's fillcolor attribute instead.
						ret = color1;
					}

					// Gradients are not supported for VML stroke, return the first color. #722.
				} else {
					ret = stopColor;
				}

				// if the color is an rgba color, split it and add a fill node
				// to hold the opacity component
			} else if (regexRgba.test(color) && elem.tagName !== 'IMG') {

				colorObject = Color(color);

				markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
				createElement(this.prepVML(markup), null, null, elem);

				ret = colorObject.get('rgb');


			} else {
				var strokeNodes = elem.getElementsByTagName(prop);
				if (strokeNodes.length) {
					strokeNodes[0].opacity = 1;
				}
				ret = color;
			}

			return ret;
		},

		/**
		* Take a VML string and prepare it for either IE8 or IE6/IE7.
		* @param {Array} markup A string array of the VML markup to prepare
		*/
		prepVML: function (markup) {
			var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
			isIE8 = this.isIE8;

			markup = markup.join('');

			if (isIE8) { // add xmlns and style inline
				markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
				if (markup.indexOf('style="') === -1) {
					markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
				} else {
					markup = markup.replace('style="', 'style="' + vmlStyle);
				}

			} else { // add namespace
				markup = markup.replace('<', '<hcv:');
			}

			return markup;
		},

		/**
		* Create rotated and aligned text
		* @param {String} str
		* @param {Number} x
		* @param {Number} y
		*/
		text: SVGRenderer.prototype.html,

		/**
		* Create and return a path element
		* @param {Array} path
		*/
		path: function (path) {
			var attr = {
				// subpixel precision down to 0.1 (width and height = 1px)
				coordsize: '10 10'
			};
			if (isArray(path)) {
				attr.d = path;
			} else if (isObject(path)) { // attributes
				extend(attr, path);
			}
			// create the shape
			return this.createElement('shape').attr(attr);
		},

		/**
		* Create and return a circle element. In VML circles are implemented as
		* shapes, which is faster than v:oval
		* @param {Number} x
		* @param {Number} y
		* @param {Number} r
		*/
		circle: function (x, y, r) {
			return this.symbol('circle').attr({ x: x - r, y: y - r, width: 2 * r, height: 2 * r });
		},

		/**
		* Create a group using an outer div and an inner v:group to allow rotating
		* and flipping. A simple v:group would have problems with positioning
		* child HTML elements and CSS clip.
		*
		* @param {String} name The name of the group
		*/
		g: function (name) {
			var wrapper,
			attribs;

			// set the class name
			if (name) {
				attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
			}

			// the div to hold HTML and clipping
			wrapper = this.createElement(DIV).attr(attribs);

			return wrapper;
		},

		/**
		* VML override to create a regular HTML image
		* @param {String} src
		* @param {Number} x
		* @param {Number} y
		* @param {Number} width
		* @param {Number} height
		*/
		image: function (src, x, y, width, height) {
			var obj = this.createElement('img')
			.attr({ src: src });

			if (arguments.length > 1) {
				obj.attr({
					x: x,
					y: y,
					width: width,
					height: height
				});
			}
			return obj;
		},

		/**
		* VML uses a shape for rect to overcome bugs and rotation problems
		*/
		rect: function (x, y, width, height, r, strokeWidth) {

			if (isObject(x)) {
				y = x.y;
				width = x.width;
				height = x.height;
				strokeWidth = x.strokeWidth;
				x = x.x;
			}
			var wrapper = this.symbol('rect');
			wrapper.r = r;

			return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
		},

		/**
		* In the VML renderer, each child of an inverted div (group) is inverted
		* @param {Object} element
		* @param {Object} parentNode
		*/
		invertChild: function (element, parentNode) {
			var parentStyle = parentNode.style;
			css(element, {
				flip: 'x',
				left: pInt(parentStyle.width) - 1,
				top: pInt(parentStyle.height) - 1,
				rotation: -90
			});
		},

		/**
		* Symbol definitions that override the parent SVG renderer's symbols
		*
		*/
		symbols: {
			// VML specific arc function
			arc: function (x, y, w, h, options) {
				var start = options.start,
				end = options.end,
				radius = options.r || w || h,
				cosStart = mathCos(start),
				sinStart = mathSin(start),
				cosEnd = mathCos(end),
				sinEnd = mathSin(end),
				innerRadius = options.innerR,
				circleCorrection = 0.08 / radius, // #760
				innerCorrection = (innerRadius && 0.1 / innerRadius) || 0,
				ret;

				if (end - start === 0) { // no angle, don't show it.
					return ['x'];

				} else if (2 * mathPI - end + start < circleCorrection) { // full circle
					// empirical correction found by trying out the limits for different radii
					cosEnd = -circleCorrection;
				} else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem
					cosEnd = mathCos(start + innerCorrection);
				}

				ret = [
				'wa', // clockwise arc to
				x - radius, // left
				y - radius, // top
				x + radius, // right
				y + radius, // bottom
				x + radius * cosStart, // start x
				y + radius * sinStart, // start y
				x + radius * cosEnd, // end x
				y + radius * sinEnd  // end y
			];

				if (options.open && !innerRadius) {
					ret.push(
					'e',
					M,
					x, // - innerRadius, 
					y// - innerRadius
				);
				}

				ret.push(
				'at', // anti clockwise arc to
				x - innerRadius, // left
				y - innerRadius, // top
				x + innerRadius, // right
				y + innerRadius, // bottom
				x + innerRadius * cosEnd, // start x
				y + innerRadius * sinEnd, // start y
				x + innerRadius * cosStart, // end x
				y + innerRadius * sinStart, // end y
				'x', // finish path
				'e' // close
			);

				return ret;

			},
			// Add circle symbol path. This performs significantly faster than v:oval.
			circle: function (x, y, w, h) {

				return [
				'wa', // clockwisearcto
				x, // left
				y, // top
				x + w, // right
				y + h, // bottom
				x + w, // start x
				y + h / 2,     // start y
				x + w, // end x
				y + h / 2,     // end y
				//'x', // finish path
				'e' // close
			];
			},
			/**
			* Add rectangle symbol path which eases rotation and omits arcsize problems
			* compared to the built-in VML roundrect shape
			*
			* @param {Number} left Left position
			* @param {Number} top Top position
			* @param {Number} r Border radius
			* @param {Object} options Width and height
			*/

			rect: function (left, top, width, height, options) {

				var right = left + width,
				bottom = top + height,
				ret,
				r;

				// No radius, return the more lightweight square
				if (!defined(options) || !options.r) {
					ret = SVGRenderer.prototype.symbols.square.apply(0, arguments);

					// Has radius add arcs for the corners
				} else {

					r = mathMin(options.r, width, height);
					ret = [
					M,
					left + r, top,

					L,
					right - r, top,
					'wa',
					right - 2 * r, top,
					right, top + 2 * r,
					right - r, top,
					right, top + r,

					L,
					right, bottom - r,
					'wa',
					right - 2 * r, bottom - 2 * r,
					right, bottom,
					right, bottom - r,
					right - r, bottom,

					L,
					left + r, bottom,
					'wa',
					left, bottom - 2 * r,
					left + 2 * r, bottom,
					left + r, bottom,
					left, bottom - r,

					L,
					left, top + r,
					'wa',
					left, top,
					left + 2 * r, top + 2 * r,
					left, top + r,
					left + r, top,


					'x',
					'e'
				];
				}
				return ret;
			}
		}
	};
	VMLRenderer = function () {
		this.init.apply(this, arguments);
	};
	VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);

	// general renderer
	Renderer = VMLRenderer;
}

/* ****************************************************************************
*                                                                            *
* END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                *
*                                                                            *
*****************************************************************************/
/* ****************************************************************************
*                                                                            *
* START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT      *
* TARGETING THAT SYSTEM.                                                     *
*                                                                            *
*****************************************************************************/
var CanVGRenderer,
	CanVGController;

if (useCanVG) {
	/**
	* The CanVGRenderer is empty from start to keep the source footprint small.
	* When requested, the CanVGController downloads the rest of the source packaged
	* together with the canvg library.
	*/
	CanVGRenderer = function () {
		// Override the global SVG namespace to fake SVG/HTML that accepts CSS
		SVG_NS = 'http://www.w3.org/1999/xhtml';
	};

	/**
	* Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but 
	* the implementation from SvgRenderer will not be merged in until first render.
	*/
	CanVGRenderer.prototype.symbols = {};

	/**
	* Handles on demand download of canvg rendering support.
	*/
	CanVGController = (function () {
		// List of renderering calls
		var deferredRenderCalls = [];

		/**
		* When downloaded, we are ready to draw deferred charts.
		*/
		function drawDeferred() {
			var callLength = deferredRenderCalls.length,
				callIndex;

			// Draw all pending render calls
			for (callIndex = 0; callIndex < callLength; callIndex++) {
				deferredRenderCalls[callIndex]();
			}
			// Clear the list
			deferredRenderCalls = [];
		}

		return {
			push: function (func, scriptLocation) {
				// Only get the script once
				if (deferredRenderCalls.length === 0) {
					getScript(scriptLocation, drawDeferred);
				}
				// Register render call
				deferredRenderCalls.push(func);
			}
		};
	} ());
} // end CanVGRenderer

/* ****************************************************************************
*                                                                            *
* END OF ANDROID < 3 SPECIFIC CODE                                           *
*                                                                            *
*****************************************************************************/

/**
* General renderer
*/
Renderer = VMLRenderer || CanVGRenderer || SVGRenderer;
/**
* The Tick class
*/
function Tick(axis, pos, type) {
	this.axis = axis;
	this.pos = pos;
	this.type = type || '';
	this.isNew = true;

	if (!type) {
		this.addLabel();
	}
}

Tick.prototype = {
	/**
	* Write the tick label
	*/
	addLabel: function () {
		var tick = this,
			axis = tick.axis,
			options = axis.options,
			chart = axis.chart,
			horiz = axis.horiz,
			categories = axis.categories,
			pos = tick.pos,
			labelOptions = options.labels,
			str,
			tickPositions = axis.tickPositions,
			width = (categories && horiz && categories.length &&
				!labelOptions.step && !labelOptions.staggerLines &&
				!labelOptions.rotation &&
				chart.plotWidth / tickPositions.length) ||
				(!horiz && chart.plotWidth / 2),
			isFirst = pos === tickPositions[0],
			isLast = pos === tickPositions[tickPositions.length - 1],
			css,
			attr,
			value = categories && defined(categories[pos]) ? categories[pos] : pos,
			label = tick.label,
			tickPositionInfo = tickPositions.info,
			dateTimeLabelFormat;

		// Set the datetime label format. If a higher rank is set for this position, use that. If not,
		// use the general format.
		if (axis.isDatetimeAxis && tickPositionInfo) {
			dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName];
		}

		// set properties for access in render method
		tick.isFirst = isFirst;
		tick.isLast = isLast;

		// get the string
		str = axis.labelFormatter.call({
			axis: axis,
			chart: chart,
			isFirst: isFirst,
			isLast: isLast,
			dateTimeLabelFormat: dateTimeLabelFormat,
			value: axis.isLog ? correctFloat(lin2log(value)) : value
		});

		// prepare CSS
		css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };
		css = extend(css, labelOptions.style);

		// first call
		if (!defined(label)) {
			attr = {
				align: labelOptions.align
			};
			if (isNumber(labelOptions.rotation)) {
				attr.rotation = labelOptions.rotation;
			}
			tick.label =
				defined(str) && labelOptions.enabled ?
					chart.renderer.text(
							str,
							0,
							0,
							labelOptions.useHTML
						)
						.attr(attr)
			// without position absolute, IE export sometimes is wrong
						.css(css)
						.add(axis.labelGroup) :
					null;

			// update
		} else if (label) {
			label.attr({
				text: str
			})
				.css(css);
		}
	},

	/**
	* Get the offset height or width of the label
	*/
	getLabelSize: function () {
		var label = this.label,
			axis = this.axis;
		return label ?
			((this.labelBBox = label.getBBox()))[axis.horiz ? 'height' : 'width'] :
			0;
	},

	/**
	* Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision
	* detection with overflow logic.
	*/
	getLabelSides: function () {
		var bBox = this.labelBBox, // assume getLabelSize has run at this point
			axis = this.axis,
			options = axis.options,
			labelOptions = options.labels,
			width = bBox.width,
			leftSide = width * { left: 0, center: 0.5, right: 1}[labelOptions.align] - labelOptions.x;

		return [-leftSide, width - leftSide];
	},

	/**
	* Handle the label overflow by adjusting the labels to the left and right edge, or
	* hide them if they collide into the neighbour label.
	*/
	handleOverflow: function (index, xy) {
		var show = true,
			axis = this.axis,
			chart = axis.chart,
			isFirst = this.isFirst,
			isLast = this.isLast,
			x = xy.x,
			reversed = axis.reversed,
			tickPositions = axis.tickPositions;

		if (isFirst || isLast) {

			var sides = this.getLabelSides(),
				leftSide = sides[0],
				rightSide = sides[1],
				plotLeft = chart.plotLeft,
				plotRight = plotLeft + axis.len,
				neighbour = axis.ticks[tickPositions[index + (isFirst ? 1 : -1)]],
				neighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1];

			if ((isFirst && !reversed) || (isLast && reversed)) {
				// Is the label spilling out to the left of the plot area?
				if (x + leftSide < plotLeft) {

					// Align it to plot left
					x = plotLeft - leftSide;

					// Hide it if it now overlaps the neighbour label
					if (neighbour && x + rightSide > neighbourEdge) {
						show = false;
					}
				}

			} else {
				// Is the label spilling out to the right of the plot area?
				if (x + rightSide > plotRight) {

					// Align it to plot right
					x = plotRight - rightSide;

					// Hide it if it now overlaps the neighbour label
					if (neighbour && x + leftSide < neighbourEdge) {
						show = false;
					}

				}
			}

			// Set the modified x position of the label
			xy.x = x;
		}
		return show;
	},

	/**
	* Get the x and y position for ticks and labels
	*/
	getPosition: function (horiz, pos, tickmarkOffset, old) {
		var axis = this.axis,
			chart = axis.chart,
			cHeight = (old && chart.oldChartHeight) || chart.chartHeight;

		return {
			x: horiz ?
				axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB :
				axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0),

			y: horiz ?
				cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) :
				cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB
		};

	},

	/**
	* Get the x, y position of the tick label
	*/
	getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
		var axis = this.axis,
			transA = axis.transA,
			reversed = axis.reversed,
			staggerLines = axis.staggerLines;

		x = x + labelOptions.x - (tickmarkOffset && horiz ?
			tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
		y = y + labelOptions.y - (tickmarkOffset && !horiz ?
			tickmarkOffset * transA * (reversed ? 1 : -1) : 0);

		// Vertically centered
		if (!defined(labelOptions.y)) {
			y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
		}

		// Correct for staggered labels
		if (staggerLines) {
			y += (index / (step || 1) % staggerLines) * 16;
		}

		return {
			x: x,
			y: y
		};
	},

	/**
	* Extendible method to return the path of the marker
	*/
	getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) {
		return renderer.crispLine([
				M,
				x,
				y,
				L,
				x + (horiz ? 0 : -tickLength),
				y + (horiz ? tickLength : 0)
			], tickWidth);
	},

	/**
	* Put everything in place
	*
	* @param index {Number}
	* @param old {Boolean} Use old coordinates to prepare an animation into new position
	*/
	render: function (index, old) {
		var tick = this,
			axis = tick.axis,
			options = axis.options,
			chart = axis.chart,
			renderer = chart.renderer,
			horiz = axis.horiz,
			type = tick.type,
			label = tick.label,
			pos = tick.pos,
			labelOptions = options.labels,
			gridLine = tick.gridLine,
			gridPrefix = type ? type + 'Grid' : 'grid',
			tickPrefix = type ? type + 'Tick' : 'tick',
			gridLineWidth = options[gridPrefix + 'LineWidth'],
			gridLineColor = options[gridPrefix + 'LineColor'],
			dashStyle = options[gridPrefix + 'LineDashStyle'],
			tickLength = options[tickPrefix + 'Length'],
			tickWidth = options[tickPrefix + 'Width'] || 0,
			tickColor = options[tickPrefix + 'Color'],
			tickPosition = options[tickPrefix + 'Position'],
			gridLinePath,
			mark = tick.mark,
			markPath,
			step = labelOptions.step,
			attribs,
			show = true,
			tickmarkOffset = axis.tickmarkOffset,
			xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
			x = xy.x,
			y = xy.y,
			staggerLines = axis.staggerLines;

		// create the grid line
		if (gridLineWidth) {
			gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old);

			if (gridLine === UNDEFINED) {
				attribs = {
					stroke: gridLineColor,
					'stroke-width': gridLineWidth
				};
				if (dashStyle) {
					attribs.dashstyle = dashStyle;
				}
				if (!type) {
					attribs.zIndex = 1;
				}
				tick.gridLine = gridLine =
					gridLineWidth ?
						renderer.path(gridLinePath)
							.attr(attribs).add(axis.gridGroup) :
						null;
			}

			// If the parameter 'old' is set, the current call will be followed
			// by another call, therefore do not do any animations this time
			if (!old && gridLine && gridLinePath) {
				gridLine[tick.isNew ? 'attr' : 'animate']({
					d: gridLinePath
				});
			}
		}

		// create the tick mark
		if (tickWidth && tickLength) {

			// negate the length
			if (tickPosition === 'inside') {
				tickLength = -tickLength;
			}
			if (axis.opposite) {
				tickLength = -tickLength;
			}

			markPath = tick.getMarkPath(x, y, tickLength, tickWidth, horiz, renderer);

			if (mark) { // updating
				mark.animate({
					d: markPath
				});
			} else { // first time
				tick.mark = renderer.path(
					markPath
				).attr({
					stroke: tickColor,
					'stroke-width': tickWidth
				}).add(axis.axisGroup);
			}
		}

		// the label is created on init - now move it into place
		if (label && !isNaN(x)) {
			label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step);

			// apply show first and show last
			if ((tick.isFirst && !pick(options.showFirstLabel, 1)) ||
					(tick.isLast && !pick(options.showLastLabel, 1))) {
				show = false;

				// Handle label overflow and show or hide accordingly
			} else if (!staggerLines && horiz && labelOptions.overflow === 'justify' && !tick.handleOverflow(index, xy)) {
				show = false;
			}

			// apply step
			if (step && index % step) {
				// show those indices dividable by step
				show = false;
			}

			// Set the new position, and show or hide
			if (show) {
				label[tick.isNew ? 'attr' : 'animate'](xy);
				label.show();
				tick.isNew = false;
			} else {
				label.hide();
			}
		}
	},

	/**
	* Destructor for the tick prototype
	*/
	destroy: function () {
		destroyObjectProperties(this, this.axis);
	}
};

/**
* The object wrapper for plot lines and plot bands
* @param {Object} options
*/
function PlotLineOrBand(axis, options) {
	this.axis = axis;

	if (options) {
		this.options = options;
		this.id = options.id;
	}

	//plotLine.render()
	return this;
}

PlotLineOrBand.prototype = {

	/**
	* Render the plot line or plot band. If it is already existing,
	* move it.
	*/
	render: function () {
		var plotLine = this,
			axis = plotLine.axis,
			horiz = axis.horiz,
			halfPointRange = (axis.pointRange || 0) / 2,
			options = plotLine.options,
			optionsLabel = options.label,
			label = plotLine.label,
			width = options.width,
			to = options.to,
			from = options.from,
			isBand = defined(from) && defined(to),
			value = options.value,
			dashStyle = options.dashStyle,
			svgElem = plotLine.svgElem,
			path = [],
			addEvent,
			eventType,
			xs,
			ys,
			x,
			y,
			color = options.color,
			zIndex = options.zIndex,
			events = options.events,
			attribs,
			renderer = axis.chart.renderer;

		// logarithmic conversion
		if (axis.isLog) {
			from = log2lin(from);
			to = log2lin(to);
			value = log2lin(value);
		}

		// plot line
		if (width) {
			path = axis.getPlotLinePath(value, width);
			attribs = {
				stroke: color,
				'stroke-width': width
			};
			if (dashStyle) {
				attribs.dashstyle = dashStyle;
			}
		} else if (isBand) { // plot band

			// keep within plot area
			from = mathMax(from, axis.min - halfPointRange);
			to = mathMin(to, axis.max + halfPointRange);

			path = axis.getPlotBandPath(from, to, options);
			attribs = {
				fill: color
			};
			if (options.borderWidth) {
				attribs.stroke = options.borderColor;
				attribs['stroke-width'] = options.borderWidth;
			}
		} else {
			return;
		}
		// zIndex
		if (defined(zIndex)) {
			attribs.zIndex = zIndex;
		}

		// common for lines and bands
		if (svgElem) {
			if (path) {
				svgElem.animate({
					d: path
				}, null, svgElem.onGetPath);
			} else {
				svgElem.hide();
				svgElem.onGetPath = function () {
					svgElem.show();
				};
			}
		} else if (path && path.length) {
			plotLine.svgElem = svgElem = renderer.path(path)
				.attr(attribs).add();

			// events
			if (events) {
				addEvent = function (eventType) {
					svgElem.on(eventType, function (e) {
						events[eventType].apply(plotLine, [e]);
					});
				};
				for (eventType in events) {
					addEvent(eventType);
				}
			}
		}

		// the plot band/line label
		if (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) {
			// apply defaults
			optionsLabel = merge({
				align: horiz && isBand && 'center',
				x: horiz ? !isBand && 4 : 10,
				verticalAlign: !horiz && isBand && 'middle',
				y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
				rotation: horiz && !isBand && 90
			}, optionsLabel);

			// add the SVG element
			if (!label) {
				plotLine.label = label = renderer.text(
						optionsLabel.text,
						0,
						0
					)
					.attr({
						align: optionsLabel.textAlign || optionsLabel.align,
						rotation: optionsLabel.rotation,
						zIndex: zIndex
					})
					.css(optionsLabel.style)
					.add();
			}

			// get the bounding box and align the label
			xs = [path[1], path[4], pick(path[6], path[1])];
			ys = [path[2], path[5], pick(path[7], path[2])];
			x = arrayMin(xs);
			y = arrayMin(ys);

			label.align(optionsLabel, false, {
				x: x,
				y: y,
				width: arrayMax(xs) - x,
				height: arrayMax(ys) - y
			});
			label.show();

		} else if (label) { // move out of sight
			label.hide();
		}

		// chainable
		return plotLine;
	},

	/**
	* Remove the plot line or band
	*/
	destroy: function () {
		var plotLine = this,
			axis = plotLine.axis;

		// remove it from the lookup
		erase(axis.plotLinesAndBands, plotLine);

		destroyObjectProperties(plotLine, this.axis);
	}
};
/**
* The class for stack items
*/
function StackItem(axis, options, isNegative, x, stackOption, stacking) {

	var inverted = axis.chart.inverted;

	this.axis = axis;

	// Tells if the stack is negative
	this.isNegative = isNegative;

	// Save the options to be able to style the label
	this.options = options;

	// Save the x value to be able to position the label later
	this.x = x;

	// Save the stack option on the series configuration object, and whether to treat it as percent
	this.stack = stackOption;
	this.percent = stacking === 'percent';

	// The align options and text align varies on whether the stack is negative and
	// if the chart is inverted or not.
	// First test the user supplied value, then use the dynamic.
	this.alignOptions = {
		align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
		verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
		y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
		x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
	};

	this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
}

StackItem.prototype = {
	destroy: function () {
		destroyObjectProperties(this, this.axis);
	},

	/**
	* Sets the total of this stack. Should be called when a serie is hidden or shown
	* since that will affect the total of other stacks.
	*/
	setTotal: function (total) {
		this.total = total;
		this.cum = total;
	},

	/**
	* Renders the stack total label and adds it to the stack label group.
	*/
	render: function (group) {
		var str = this.options.formatter.call(this);  // format the text in the label

		// Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
		if (this.label) {
			this.label.attr({ text: str, visibility: HIDDEN });
			// Create new label
		} else {
			this.label =
				this.axis.chart.renderer.text(str, 0, 0)		// dummy positions, actual position updated with setOffset method in columnseries
					.css(this.options.style)				// apply style
					.attr({
						align: this.textAlign, 			// fix the text-anchor
						rotation: this.options.rotation, // rotation
						visibility: HIDDEN					// hidden until setOffset is called
					})
					.add(group); 						// add to the labels-group
		}
	},

	/**
	* Sets the offset that the stack has from the x value and repositions the label.
	*/
	setOffset: function (xOffset, xWidth) {
		var stackItem = this,
			axis = stackItem.axis,
			chart = axis.chart,
			inverted = chart.inverted,
			neg = this.isNegative, 						// special treatment is needed for negative stacks
			y = axis.translate(this.percent ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates
			yZero = axis.translate(0), 					// stack origin
			h = mathAbs(y - yZero), 						// stack height
			x = chart.xAxis[0].translate(this.x) + xOffset, // stack x position
			plotHeight = chart.plotHeight,
			stackBox = {	// this is the box for the complete stack
				x: inverted ? (neg ? y : y - h) : x,
				y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
				width: inverted ? h : xWidth,
				height: inverted ? xWidth : h
			},
			label = this.label,
			alignAttr;

		if (label) {
			label.align(this.alignOptions, null, stackBox); // align the label to the box

			// Set visibility (#678)
			alignAttr = label.alignAttr;
			label.attr({
				visibility: this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ?
					(hasSVG ? 'inherit' : VISIBLE) :
					HIDDEN
			});
		}
	}
};
/**
* Create a new axis object
* @param {Object} chart
* @param {Object} options
*/
function Axis() {
	this.init.apply(this, arguments);
}

Axis.prototype = {

	/**
	* Default options for the X axis - the Y axis has extended defaults 
	*/
	defaultOptions: {
		// allowDecimals: null,
		// alternateGridColor: null,
		// categories: [],
		dateTimeLabelFormats: {
			millisecond: '%H:%M:%S.%L',
			second: '%H:%M:%S',
			minute: '%H:%M',
			hour: '%H:%M',
			day: '%e. %b',
			week: '%e. %b',
			month: '%b \'%y',
			year: '%Y'
		},
		endOnTick: false,
		gridLineColor: '#C0C0C0',
		// gridLineDashStyle: 'solid',
		// gridLineWidth: 0,
		// reversed: false,

		labels: defaultLabelOptions,
		// { step: null },
		lineColor: '#C0D0E0',
		lineWidth: 1,
		//linkedTo: null,
		//max: undefined,
		//min: undefined,
		minPadding: 0.01,
		maxPadding: 0.01,
		//minRange: null,
		minorGridLineColor: '#E0E0E0',
		// minorGridLineDashStyle: null,
		minorGridLineWidth: 1,
		minorTickColor: '#A0A0A0',
		//minorTickInterval: null,
		minorTickLength: 2,
		minorTickPosition: 'outside', // inside or outside
		//minorTickWidth: 0,
		//opposite: false,
		//offset: 0,
		//plotBands: [{
		//	events: {},
		//	zIndex: 1,
		//	labels: { align, x, verticalAlign, y, style, rotation, textAlign }
		//}],
		//plotLines: [{
		//	events: {}
		//  dashStyle: {}
		//	zIndex:
		//	labels: { align, x, verticalAlign, y, style, rotation, textAlign }
		//}],
		//reversed: false,
		// showFirstLabel: true,
		// showLastLabel: true,
		startOfWeek: 1,
		startOnTick: false,
		tickColor: '#C0D0E0',
		//tickInterval: null,
		tickLength: 5,
		tickmarkPlacement: 'between', // on or between
		tickPixelInterval: 100,
		tickPosition: 'outside',
		tickWidth: 1,
		title: {
			//text: null,
			align: 'middle', // low, middle or high
			//margin: 0 for horizontal, 10 for vertical axes,
			//rotation: 0,
			//side: 'outside',
			style: {
				color: '#6D869F',
				//font: defaultFont.replace('normal', 'bold')
				fontWeight: 'bold'
			}
			//x: 0,
			//y: 0
		},
		type: 'linear' // linear, logarithmic or datetime
	},

	/**
	* This options set extends the defaultOptions for Y axes
	*/
	defaultYAxisOptions: {
		endOnTick: true,
		gridLineWidth: 1,
		tickPixelInterval: 72,
		showLastLabel: true,
		labels: {
			align: 'right',
			x: -8,
			y: 3
		},
		lineWidth: 0,
		maxPadding: 0.05,
		minPadding: 0.05,
		startOnTick: true,
		tickWidth: 0,
		title: {
			rotation: 270,
			text: 'Y-values'
		},
		stackLabels: {
			enabled: false,
			//align: dynamic,
			//y: dynamic,
			//x: dynamic,
			//verticalAlign: dynamic,
			//textAlign: dynamic,
			//rotation: 0,
			formatter: function () {
				return this.total;
			},
			style: defaultLabelOptions.style
		}
	},

	/**
	* These options extend the defaultOptions for left axes
	*/
	defaultLeftAxisOptions: {
		labels: {
			align: 'right',
			x: -8,
			y: null
		},
		title: {
			rotation: 270
		}
	},

	/**
	* These options extend the defaultOptions for right axes
	*/
	defaultRightAxisOptions: {
		labels: {
			align: 'left',
			x: 8,
			y: null
		},
		title: {
			rotation: 90
		}
	},

	/**
	* These options extend the defaultOptions for bottom axes
	*/
	defaultBottomAxisOptions: {
		labels: {
			align: 'center',
			x: 0,
			y: 14
			// overflow: undefined,
			// staggerLines: null
		},
		title: {
			rotation: 0
		}
	},
	/**
	* These options extend the defaultOptions for left axes
	*/
	defaultTopAxisOptions: {
		labels: {
			align: 'center',
			x: 0,
			y: -5
			// overflow: undefined
			// staggerLines: null
		},
		title: {
			rotation: 0
		}
	},

	/**
	* Initialize the axis
	*/
	init: function (chart, userOptions) {


		var isXAxis = userOptions.isX,
			axis = this;

		// Flag, is the axis horizontal
		axis.horiz = chart.inverted ? !isXAxis : isXAxis;

		// Flag, isXAxis
		axis.isXAxis = isXAxis;
		axis.xOrY = isXAxis ? 'x' : 'y';


		axis.opposite = userOptions.opposite; // needed in setOptions
		axis.side = axis.horiz ?
				(axis.opposite ? 0 : 2) : // top : bottom
				(axis.opposite ? 1 : 3);  // right : left

		axis.setOptions(userOptions);


		var options = this.options,
			type = options.type,
			isDatetimeAxis = type === 'datetime';

		axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format


		// Flag, stagger lines or not
		axis.staggerLines = axis.horiz && options.labels.staggerLines;
		axis.userOptions = userOptions;

		//axis.axisTitleMargin = UNDEFINED,// = options.title.margin,
		axis.minPixelPadding = 0;
		//axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series
		//axis.ignoreMaxPadding = UNDEFINED;

		axis.chart = chart;
		axis.reversed = options.reversed;

		// Initial categories
		axis.categories = options.categories;

		// Elements
		//axis.axisGroup = UNDEFINED;
		//axis.gridGroup = UNDEFINED;
		//axis.axisTitle = UNDEFINED;
		//axis.axisLine = UNDEFINED;

		// Flag if type === logarithmic
		axis.isLog = type === 'logarithmic';

		// Flag, if axis is linked to another axis
		axis.isLinked = defined(options.linkedTo);
		// Linked axis.
		//axis.linkedParent = UNDEFINED;

		// Flag if type === datetime
		axis.isDatetimeAxis = isDatetimeAxis;

		// Flag if percentage mode
		//axis.usePercentage = UNDEFINED;


		// Tick positions
		//axis.tickPositions = UNDEFINED; // array containing predefined positions
		// Tick intervals
		//axis.tickInterval = UNDEFINED;
		//axis.minorTickInterval = UNDEFINED;

		axis.tickmarkOffset = (options.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;

		// Major ticks
		axis.ticks = {};
		// Minor ticks
		axis.minorTicks = {};
		//axis.tickAmount = UNDEFINED;

		// List of plotLines/Bands
		axis.plotLinesAndBands = [];

		// Alternate bands
		axis.alternateBands = {};

		// Axis metrics
		//axis.left = UNDEFINED;
		//axis.top = UNDEFINED;
		//axis.width = UNDEFINED;
		//axis.height = UNDEFINED;
		//axis.bottom = UNDEFINED;
		//axis.right = UNDEFINED;
		//axis.transA = UNDEFINED;
		//axis.transB = UNDEFINED;
		//axis.oldTransA = UNDEFINED;
		axis.len = 0;
		//axis.oldMin = UNDEFINED;
		//axis.oldMax = UNDEFINED;
		//axis.oldUserMin = UNDEFINED;
		//axis.oldUserMax = UNDEFINED;
		//axis.oldAxisLength = UNDEFINED;
		axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
		axis.range = options.range;
		axis.offset = options.offset || 0;


		// Dictionary for stacks
		axis.stacks = {};

		// Min and max in the data
		//axis.dataMin = UNDEFINED,
		//axis.dataMax = UNDEFINED,

		// The axis range
		axis.max = null;
		axis.min = null;

		// User set min and max
		//axis.userMin = UNDEFINED,
		//axis.userMax = UNDEFINED,

		// Run Axis

		var eventType,
			events = axis.options.events;

		// Register
		chart.axes.push(axis);
		chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);

		axis.series = []; // populated by Series

		// inverted charts have reversed xAxes as default
		if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) {
			axis.reversed = true;
		}

		axis.removePlotBand = axis.removePlotBandOrLine;
		axis.removePlotLine = axis.removePlotBandOrLine;
		axis.addPlotBand = axis.addPlotBandOrLine;
		axis.addPlotLine = axis.addPlotBandOrLine;


		// register event listeners
		for (eventType in events) {
			addEvent(axis, eventType, events[eventType]);
		}

		// extend logarithmic axis
		if (axis.isLog) {
			axis.val2lin = log2lin;
			axis.lin2val = lin2log;
		}
	},

	/**
	* Merge and set options
	*/
	setOptions: function (userOptions) {
		this.options = merge(
			this.defaultOptions,
			this.isXAxis ? {} : this.defaultYAxisOptions,
			[this.defaultTopAxisOptions, this.defaultRightAxisOptions,
				this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side],
			merge(
				defaultOptions[this.isXAxis ? 'xAxis' : 'yAxis'], // if set in setOptions (#1053)
				userOptions
			)
		);
	},


	/** 
	* The default label formatter. The context is a special config object for the label.
	*/
	defaultLabelFormatter: function () {
		var axis = this.axis,
			value = this.value,
			categories = axis.categories,
			dateTimeLabelFormat = this.dateTimeLabelFormat,
			numericSymbols = defaultOptions.lang.numericSymbols,
			i = numericSymbols && numericSymbols.length,
			multi,
			ret,

		// make sure the same symbol is added for all labels on a linear axis
			numericSymbolDetector = axis.isLog ? value : axis.tickInterval;

		if (categories) {
			ret = value;

		} else if (dateTimeLabelFormat) { // datetime axis
			ret = dateFormat(dateTimeLabelFormat, value);

		} else if (i && numericSymbolDetector >= 1000) {
			// Decide whether we should add a numeric symbol like k (thousands) or M (millions).
			// If we are to enable this in tooltip or other places as well, we can move this
			// logic to the numberFormatter and enable it by a parameter.
			while (i-- && ret === UNDEFINED) {
				multi = Math.pow(1000, i + 1);
				if (numericSymbolDetector >= multi && numericSymbols[i] !== null) {
					ret = numberFormat(value / multi, -1) + numericSymbols[i];
				}
			}
		}

		if (ret === UNDEFINED) {
			if (value >= 1000) { // add thousands separators
				ret = numberFormat(value, 0);

			} else { // small numbers
				ret = numberFormat(value, -1);
			}
		}

		return ret;
	},

	/**
	* Get the minimum and maximum for the series of each axis
	*/
	getSeriesExtremes: function () {
		var axis = this,
			chart = axis.chart,
			stacks = axis.stacks,
			posStack = [],
			negStack = [],
			i;

		axis.hasVisibleSeries = false;

		// reset dataMin and dataMax in case we're redrawing
		axis.dataMin = axis.dataMax = null;

		// loop through this axis' series
		each(axis.series, function (series) {

			if (series.visible || !chart.options.chart.ignoreHiddenSeries) {

				var seriesOptions = series.options,
					stacking,
					posPointStack,
					negPointStack,
					stackKey,
					stackOption,
					negKey,
					xData,
					yData,
					x,
					y,
					threshold = seriesOptions.threshold,
					yDataLength,
					activeYData = [],
					activeCounter = 0;

				axis.hasVisibleSeries = true;

				// Validate threshold in logarithmic axes
				if (axis.isLog && threshold <= 0) {
					threshold = seriesOptions.threshold = null;
				}

				// Get dataMin and dataMax for X axes
				if (axis.isXAxis) {
					xData = series.xData;
					if (xData.length) {
						axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData));
						axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData));
					}

					// Get dataMin and dataMax for Y axes, as well as handle stacking and processed data
				} else {
					var isNegative,
						pointStack,
						key,
						cropped = series.cropped,
						xExtremes = series.xAxis.getExtremes(),
					//findPointRange,
					//pointRange,
						j,
						hasModifyValue = !!series.modifyValue;


					// Handle stacking
					stacking = seriesOptions.stacking;
					axis.usePercentage = stacking === 'percent';

					// create a stack for this particular series type
					if (stacking) {
						stackOption = seriesOptions.stack;
						stackKey = series.type + pick(stackOption, '');
						negKey = '-' + stackKey;
						series.stackKey = stackKey; // used in translate

						posPointStack = posStack[stackKey] || []; // contains the total values for each x
						posStack[stackKey] = posPointStack;

						negPointStack = negStack[negKey] || [];
						negStack[negKey] = negPointStack;
					}
					if (axis.usePercentage) {
						axis.dataMin = 0;
						axis.dataMax = 99;
					}

					// processData can alter series.pointRange, so this goes after
					//findPointRange = series.pointRange === null;

					xData = series.processedXData;
					yData = series.processedYData;
					yDataLength = yData.length;

					// loop over the non-null y values and read them into a local array
					for (i = 0; i < yDataLength; i++) {
						x = xData[i];
						y = yData[i];

						// Read stacked values into a stack based on the x value,
						// the sign of y and the stack key. Stacking is also handled for null values (#739)
						if (stacking) {
							isNegative = y < threshold;
							pointStack = isNegative ? negPointStack : posPointStack;
							key = isNegative ? negKey : stackKey;

							y = pointStack[x] =
								defined(pointStack[x]) ?
									correctFloat(pointStack[x] + y) :
									y;


							// add the series
							if (!stacks[key]) {
								stacks[key] = {};
							}

							// If the StackItem is there, just update the values,
							// if not, create one first
							if (!stacks[key][x]) {
								stacks[key][x] = new StackItem(axis, axis.options.stackLabels, isNegative, x, stackOption, stacking);
							}
							stacks[key][x].setTotal(y);
						}

						// Handle non null values
						if (y !== null && y !== UNDEFINED) {

							// general hook, used for Highstock compare values feature
							if (hasModifyValue) {
								y = series.modifyValue(y);
							}

							// for points within the visible range, including the first point outside the
							// visible range, consider y extremes
							if (cropped || ((xData[i + 1] || x) >= xExtremes.min && (xData[i - 1] || x) <= xExtremes.max)) {

								j = y.length;
								if (j) { // array, like ohlc or range data
									while (j--) {
										if (y[j] !== null) {
											activeYData[activeCounter++] = y[j];
										}
									}
								} else {
									activeYData[activeCounter++] = y;
								}
							}
						}
					}

					// record the least unit distance
					/*if (findPointRange) {
					series.pointRange = pointRange || 1;
					}
					series.closestPointRange = pointRange;*/

					// Get the dataMin and dataMax so far. If percentage is used, the min and max are
					// always 0 and 100. If the length of activeYData is 0, continue with null values.
					if (!axis.usePercentage && activeYData.length) {
						axis.dataMin = mathMin(pick(axis.dataMin, activeYData[0]), arrayMin(activeYData));
						axis.dataMax = mathMax(pick(axis.dataMax, activeYData[0]), arrayMax(activeYData));
					}

					// Adjust to threshold
					if (defined(threshold)) {
						if (axis.dataMin >= threshold) {
							axis.dataMin = threshold;
							axis.ignoreMinPadding = true;
						} else if (axis.dataMax < threshold) {
							axis.dataMax = threshold;
							axis.ignoreMaxPadding = true;
						}
					}
				}
			}
		});

	},

	/**
	* Translate from axis value to pixel position on the chart, or back
	*
	*/
	translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacementBetween) {
		var axis = this,
			axisLength = axis.len,
			sign = 1,
			cvsOffset = 0,
			localA = old ? axis.oldTransA : axis.transA,
			localMin = old ? axis.oldMin : axis.min,
			returnValue,
			postTranslate = axis.options.ordinal || (axis.isLog && handleLog);

		if (!localA) {
			localA = axis.transA;
		}

		if (cvsCoord) {
			sign *= -1; // canvas coordinates inverts the value
			cvsOffset = axisLength;
		}
		if (axis.reversed) { // reversed axis
			sign *= -1;
			cvsOffset -= sign * axisLength;
		}

		if (backwards) { // reverse translation
			if (axis.reversed) {
				val = axisLength - val;
			}
			returnValue = val / localA + localMin; // from chart pixel to value
			if (postTranslate) { // log and ordinal axes
				returnValue = axis.lin2val(returnValue);
			}

		} else { // normal translation, from axis value to pixel, relative to plot
			if (postTranslate) { // log and ordinal axes
				val = axis.val2lin(val);
			}

			returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * axis.minPixelPadding) +
				(pointPlacementBetween ? localA * axis.pointRange / 2 : 0);
		}

		return returnValue;
	},

	/**
	* Create the path for a plot line that goes from the given value on
	* this axis, across the plot to the opposite side
	* @param {Number} value
	* @param {Number} lineWidth Used for calculation crisp line
	* @param {Number] old Use old coordinates (for resizing and rescaling)
	*/
	getPlotLinePath: function (value, lineWidth, old) {
		var axis = this,
			chart = axis.chart,
			axisLeft = axis.left,
			axisTop = axis.top,
			x1,
			y1,
			x2,
			y2,
			translatedValue = axis.translate(value, null, null, old),
			cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
			cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
			skip,
			transB = axis.transB;

		x1 = x2 = mathRound(translatedValue + transB);
		y1 = y2 = mathRound(cHeight - translatedValue - transB);

		if (isNaN(translatedValue)) { // no min or max
			skip = true;

		} else if (axis.horiz) {
			y1 = axisTop;
			y2 = cHeight - axis.bottom;
			if (x1 < axisLeft || x1 > axisLeft + axis.width) {
				skip = true;
			}
		} else {
			x1 = axisLeft;
			x2 = cWidth - axis.right;

			if (y1 < axisTop || y1 > axisTop + axis.height) {
				skip = true;
			}
		}
		return skip ?
			null :
			chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
	},

	/**
	* Create the path for a plot band
	*/
	getPlotBandPath: function (from, to) {

		var toPath = this.getPlotLinePath(to),
			path = this.getPlotLinePath(from);

		if (path && toPath) {
			path.push(
				toPath[4],
				toPath[5],
				toPath[1],
				toPath[2]
			);
		} else { // outside the axis area
			path = null;
		}

		return path;
	},

	/**
	* Set the tick positions of a linear axis to round values like whole tens or every five.
	*/
	getLinearTickPositions: function (tickInterval, min, max) {
		var pos,
			lastPos,
			roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
			roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval),
			tickPositions = [];

		// Populate the intermediate values
		pos = roundedMin;
		while (pos <= roundedMax) {

			// Place the tick on the rounded value
			tickPositions.push(pos);

			// Always add the raw tickInterval, not the corrected one.
			pos = correctFloat(pos + tickInterval);

			// If the interval is not big enough in the current min - max range to actually increase
			// the loop variable, we need to break out to prevent endless loop. Issue #619
			if (pos === lastPos) {
				break;
			}

			// Record the last value
			lastPos = pos;
		}
		return tickPositions;
	},

	/**
	* Set the tick positions of a logarithmic axis
	*/
	getLogTickPositions: function (interval, min, max, minor) {
		var axis = this,
			options = axis.options,
			axisLength = axis.len;

		// Since we use this method for both major and minor ticks,
		// use a local variable and return the result
		var positions = [];

		// Reset
		if (!minor) {
			axis._minorAutoInterval = null;
		}

		// First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
		if (interval >= 0.5) {
			interval = mathRound(interval);
			positions = axis.getLinearTickPositions(interval, min, max);

			// Second case: We need intermediary ticks. For example 
			// 1, 2, 4, 6, 8, 10, 20, 40 etc. 
		} else if (interval >= 0.08) {
			var roundedMin = mathFloor(min),
				intermediate,
				i,
				j,
				len,
				pos,
				lastPos,
				break2;

			if (interval > 0.3) {
				intermediate = [1, 2, 4];
			} else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
				intermediate = [1, 2, 4, 6, 8];
			} else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
				intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
			}

			for (i = roundedMin; i < max + 1 && !break2; i++) {
				len = intermediate.length;
				for (j = 0; j < len && !break2; j++) {
					pos = log2lin(lin2log(i) * intermediate[j]);

					if (pos > min) {
						positions.push(lastPos);
					}

					if (lastPos > max) {
						break2 = true;
					}
					lastPos = pos;
				}
			}

			// Third case: We are so deep in between whole logarithmic values that
			// we might as well handle the tick positions like a linear axis. For
			// example 1.01, 1.02, 1.03, 1.04.
		} else {
			var realMin = lin2log(min),
				realMax = lin2log(max),
				tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'],
				filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
				tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
				totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;

			interval = pick(
				filteredTickIntervalOption,
				axis._minorAutoInterval,
				(realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
			);

			interval = normalizeTickInterval(
				interval,
				null,
				math.pow(10, mathFloor(math.log(interval) / math.LN10))
			);

			positions = map(axis.getLinearTickPositions(
				interval,
				realMin,
				realMax
			), log2lin);

			if (!minor) {
				axis._minorAutoInterval = interval / 5;
			}
		}

		// Set the axis-level tickInterval variable 
		if (!minor) {
			axis.tickInterval = interval;
		}
		return positions;
	},

	/**
	* Return the minor tick positions. For logarithmic axes, reuse the same logic
	* as for major ticks.
	*/
	getMinorTickPositions: function () {
		var axis = this,
			tickPositions = axis.tickPositions,
			minorTickInterval = axis.minorTickInterval;

		var minorTickPositions = [],
			pos,
			i,
			len;

		if (axis.isLog) {
			len = tickPositions.length;
			for (i = 1; i < len; i++) {
				minorTickPositions = minorTickPositions.concat(
					axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true)
				);
			}

		} else {
			for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) {
				minorTickPositions.push(pos);
			}
		}

		return minorTickPositions;
	},

	/**
	* Adjust the min and max for the minimum range. Keep in mind that the series data is 
	* not yet processed, so we don't have information on data cropping and grouping, or 
	* updated axis.pointRange or series.pointRange. The data can't be processed until
	* we have finally established min and max.
	*/
	adjustForMinRange: function () {
		var axis = this,
			options = axis.options,
			min = axis.min,
			max = axis.max,
			zoomOffset,
			spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange,
			closestDataRange,
			i,
			distance,
			xData,
			loopLength,
			minArgs,
			maxArgs;

		// Set the automatic minimum range based on the closest point distance
		if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) {

			if (defined(options.min) || defined(options.max)) {
				axis.minRange = null; // don't do this again

			} else {

				// Find the closest distance between raw data points, as opposed to
				// closestPointRange that applies to processed points (cropped and grouped)
				each(axis.series, function (series) {
					xData = series.xData;
					loopLength = series.xIncrement ? 1 : xData.length - 1;
					for (i = loopLength; i > 0; i--) {
						distance = xData[i] - xData[i - 1];
						if (closestDataRange === UNDEFINED || distance < closestDataRange) {
							closestDataRange = distance;
						}
					}
				});
				axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin);
			}
		}

		// if minRange is exceeded, adjust
		if (max - min < axis.minRange) {
			var minRange = axis.minRange;
			zoomOffset = (minRange - max + min) / 2;

			// if min and max options have been set, don't go beyond it
			minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
			if (spaceAvailable) { // if space is available, stay within the data range
				minArgs[2] = axis.dataMin;
			}
			min = arrayMax(minArgs);

			maxArgs = [min + minRange, pick(options.max, min + minRange)];
			if (spaceAvailable) { // if space is availabe, stay within the data range
				maxArgs[2] = axis.dataMax;
			}

			max = arrayMin(maxArgs);

			// now if the max is adjusted, adjust the min back
			if (max - min < minRange) {
				minArgs[0] = max - minRange;
				minArgs[1] = pick(options.min, max - minRange);
				min = arrayMax(minArgs);
			}
		}

		// Record modified extremes
		axis.min = min;
		axis.max = max;
	},

	/**
	* Update translation information
	*/
	setAxisTranslation: function () {
		var axis = this,
			range = axis.max - axis.min,
			pointRange = 0,
			closestPointRange,
			minPointOffset = 0,
			pointRangePadding = 0,
			linkedParent = axis.linkedParent,
			transA = axis.transA;

		// adjust translation for padding
		if (axis.isXAxis) {
			if (linkedParent) {
				minPointOffset = linkedParent.minPointOffset;
				pointRangePadding = linkedParent.pointRangePadding;

			} else {
				each(axis.series, function (series) {
					var seriesPointRange = series.pointRange,
						pointPlacement = series.options.pointPlacement,
						seriesClosestPointRange = series.closestPointRange;

					pointRange = mathMax(pointRange, seriesPointRange);

					// minPointOffset is the value padding to the left of the axis in order to make
					// room for points with a pointRange, typically columns. When the pointPlacement option
					// is 'between' or 'on', this padding does not apply.
					minPointOffset = mathMax(
						minPointOffset,
						pointPlacement ? 0 : seriesPointRange / 2
					);

					// Determine the total padding needed to the length of the axis to make room for the 
					// pointRange. If the series' pointPlacement is 'on', no padding is added.
					pointRangePadding = mathMax(
						pointRangePadding,
						pointPlacement === 'on' ? 0 : seriesPointRange
					);

					// Set the closestPointRange
					if (!series.noSharedTooltip && defined(seriesClosestPointRange)) {
						closestPointRange = defined(closestPointRange) ?
							mathMin(closestPointRange, seriesClosestPointRange) :
							seriesClosestPointRange;
					}
				});
			}

			// Record minPointOffset and pointRangePadding
			axis.minPointOffset = minPointOffset;
			axis.pointRangePadding = pointRangePadding;

			// pointRange means the width reserved for each point, like in a column chart
			axis.pointRange = pointRange;

			// closestPointRange means the closest distance between points. In columns
			// it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange
			// is some other value
			axis.closestPointRange = closestPointRange;
		}

		// secondary values
		axis.oldTransA = transA;
		//axis.translationSlope = axis.transA = transA = axis.len / ((range + (2 * minPointOffset)) || 1);
		axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1);
		axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend
		axis.minPixelPadding = transA * minPointOffset;
	},

	/**
	* Set the tick positions to round values and optionally extend the extremes
	* to the nearest tick
	*/
	setTickPositions: function (secondPass) {
		var axis = this,
			chart = axis.chart,
			options = axis.options,
			isLog = axis.isLog,
			isDatetimeAxis = axis.isDatetimeAxis,
			isXAxis = axis.isXAxis,
			isLinked = axis.isLinked,
			tickPositioner = axis.options.tickPositioner,
			magnitude,
			maxPadding = options.maxPadding,
			minPadding = options.minPadding,
			length,
			linkedParentExtremes,
			tickIntervalOption = options.tickInterval,
			minTickIntervalOption = options.minTickInterval,
			tickPixelIntervalOption = options.tickPixelInterval,
			tickPositions,
			categories = axis.categories;

		// linked axis gets the extremes from the parent axis
		if (isLinked) {
			axis.linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
			linkedParentExtremes = axis.linkedParent.getExtremes();
			axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
			axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
			if (options.type !== axis.linkedParent.options.type) {
				error(11, 1); // Can't link axes of different type
			}
		} else { // initial min and max from the extreme data values
			axis.min = pick(axis.userMin, options.min, axis.dataMin);
			axis.max = pick(axis.userMax, options.max, axis.dataMax);
		}

		if (isLog) {
			if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978
				error(10, 1); // Can't plot negative values on log axis
			}
			axis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934
			axis.max = correctFloat(log2lin(axis.max));
		}

		// handle zoomed range
		if (axis.range) {
			axis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618
			axis.userMax = axis.max;
			if (secondPass) {
				axis.range = null;  // don't use it when running setExtremes
			}
		}

		// adjust min and max for the minimum range
		axis.adjustForMinRange();

		// pad the values to get clear of the chart's edges
		if (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {
			length = (axis.max - axis.min) || 1;
			if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {
				axis.min -= length * minPadding;
			}
			if (!defined(options.max) && !defined(axis.userMax) && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {
				axis.max += length * maxPadding;
			}
		}

		// get tickInterval
		if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {
			axis.tickInterval = 1;
		} else if (isLinked && !tickIntervalOption &&
				tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) {
			axis.tickInterval = axis.linkedParent.tickInterval;
		} else {
			axis.tickInterval = pick(
				tickIntervalOption,
				categories ? // for categoried axis, 1 is default, for linear axis use tickPix
					1 :
					(axis.max - axis.min) * tickPixelIntervalOption / (axis.len || 1)
			);
		}

		// Now we're finished detecting min and max, crop and group series data. This
		// is in turn needed in order to find tick positions in ordinal axes. 
		if (isXAxis && !secondPass) {
			each(axis.series, function (series) {
				series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax);
			});
		}

		// set the translation factor used in translate function
		axis.setAxisTranslation(secondPass);

		// hook for ordinal axes and radial axes
		if (axis.beforeSetTickPositions) {
			axis.beforeSetTickPositions();
		}

		// hook for extensions, used in Highstock ordinal axes
		if (axis.postProcessTickInterval) {
			axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
		}

		// Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined.
		if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) {
			axis.tickInterval = minTickIntervalOption;
		}

		// for linear axes, get magnitude and normalize the interval
		if (!isDatetimeAxis && !isLog) { // linear
			magnitude = math.pow(10, mathFloor(math.log(axis.tickInterval) / math.LN10));
			if (!tickIntervalOption) {
				axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, magnitude, options);
			}
		}

		// get minorTickInterval
		axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ?
				axis.tickInterval / 5 : options.minorTickInterval;

		// find the tick positions
		axis.tickPositions = tickPositions = options.tickPositions || (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max]));
		if (!tickPositions) {
			if (isDatetimeAxis) {
				tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(
					normalizeTimeTickInterval(axis.tickInterval, options.units),
					axis.min,
					axis.max,
					options.startOfWeek,
					axis.ordinalPositions,
					axis.closestPointRange,
					true
				);
			} else if (isLog) {
				tickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max);
			} else {
				tickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max);
			}
			axis.tickPositions = tickPositions;
		}

		if (!isLinked) {

			// reset min/max or remove extremes based on start/end on tick
			var roundedMin = tickPositions[0],
				roundedMax = tickPositions[tickPositions.length - 1],
				minPointOffset = axis.minPointOffset || 0;

			if (options.startOnTick) {
				axis.min = roundedMin;
			} else if (axis.min - minPointOffset > roundedMin) {
				tickPositions.shift();
			}

			if (options.endOnTick) {
				axis.max = roundedMax;
			} else if (axis.max + minPointOffset < roundedMax) {
				tickPositions.pop();
			}

		}
	},

	/**
	* Set the max ticks of either the x and y axis collection
	*/
	setMaxTicks: function () {

		var chart = this.chart,
			maxTicks = chart.maxTicks,
			tickPositions = this.tickPositions,
			xOrY = this.xOrY;

		if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation
			maxTicks = {
				x: 0,
				y: 0
			};
		}

		if (!this.isLinked && !this.isDatetimeAxis && tickPositions.length > maxTicks[xOrY] && this.options.alignTicks !== false) {
			maxTicks[xOrY] = tickPositions.length;
		}
		chart.maxTicks = maxTicks;
	},

	/**
	* When using multiple axes, adjust the number of ticks to match the highest
	* number of ticks in that group
	*/
	adjustTickAmount: function () {
		var axis = this,
			chart = axis.chart,
			xOrY = axis.xOrY,
			tickPositions = axis.tickPositions,
			maxTicks = chart.maxTicks;

		if (maxTicks && maxTicks[xOrY] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale
			var oldTickAmount = axis.tickAmount,
				calculatedTickAmount = tickPositions.length,
				tickAmount;

			// set the axis-level tickAmount to use below
			axis.tickAmount = tickAmount = maxTicks[xOrY];

			if (calculatedTickAmount < tickAmount) {
				while (tickPositions.length < tickAmount) {
					tickPositions.push(correctFloat(
						tickPositions[tickPositions.length - 1] + axis.tickInterval
					));
				}
				axis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
				axis.max = tickPositions[tickPositions.length - 1];

			}
			if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
				axis.isDirty = true;
			}
		}
	},

	/**
	* Set the scale based on data min and max, user set min and max or options
	*
	*/
	setScale: function () {
		var axis = this,
			stacks = axis.stacks,
			type,
			i,
			isDirtyData,
			isDirtyAxisLength;

		axis.oldMin = axis.min;
		axis.oldMax = axis.max;
		axis.oldAxisLength = axis.len;

		// set the new axisLength
		axis.setAxisSize();
		//axisLength = horiz ? axisWidth : axisHeight;
		isDirtyAxisLength = axis.len !== axis.oldAxisLength;

		// is there new data?
		each(axis.series, function (series) {
			if (series.isDirtyData || series.isDirty ||
					series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well
				isDirtyData = true;
			}
		});

		// do we really need to go through all this?
		if (isDirtyAxisLength || isDirtyData || axis.isLinked ||
			axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) {

			// get data extremes if needed
			axis.getSeriesExtremes();

			// get fixed positions based on tickInterval
			axis.setTickPositions();

			// record old values to decide whether a rescale is necessary later on (#540)
			axis.oldUserMin = axis.userMin;
			axis.oldUserMax = axis.userMax;

			// Mark as dirty if it is not already set to dirty and extremes have changed. #595.
			if (!axis.isDirty) {
				axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax;
			}
		}


		// reset stacks
		if (!axis.isXAxis) {
			for (type in stacks) {
				for (i in stacks[type]) {
					stacks[type][i].cum = stacks[type][i].total;
				}
			}
		}

		// Set the maximum tick amount
		axis.setMaxTicks();
	},

	/**
	* Set the extremes and optionally redraw
	* @param {Number} newMin
	* @param {Number} newMax
	* @param {Boolean} redraw
	* @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	*    configuration
	* @param {Object} eventArguments 
	*
	*/
	setExtremes: function (newMin, newMax, redraw, animation, eventArguments) {
		var axis = this,
			chart = axis.chart;

		redraw = pick(redraw, true); // defaults to true

		// Extend the arguments with min and max
		eventArguments = extend(eventArguments, {
			min: newMin,
			max: newMax
		});

		// Fire the event
		fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler

			axis.userMin = newMin;
			axis.userMax = newMax;

			// Mark for running afterSetExtremes
			axis.isDirtyExtremes = true;

			// redraw
			if (redraw) {
				chart.redraw(animation);
			}
		});
	},

	/**
	* Overridable method for zooming chart. Pulled out in a separate method to allow overriding
	* in stock charts.
	*/
	zoom: function (newMin, newMax) {
		this.setExtremes(newMin, newMax, false, UNDEFINED, { trigger: 'zoom' });
		return true;
	},

	/**
	* Update the axis metrics
	*/
	setAxisSize: function () {
		var axis = this,
			chart = axis.chart,
			options = axis.options;

		var offsetLeft = options.offsetLeft || 0,
			offsetRight = options.offsetRight || 0;

		// basic values
		// expose to use in Series object and navigator
		axis.left = pick(options.left, chart.plotLeft + offsetLeft);
		axis.top = pick(options.top, chart.plotTop);
		axis.width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight);
		axis.height = pick(options.height, chart.plotHeight);
		axis.bottom = chart.chartHeight - axis.height - axis.top;
		axis.right = chart.chartWidth - axis.width - axis.left;
		axis.len = mathMax(axis.horiz ? axis.width : axis.height, 0); // mathMax fixes #905
	},

	/**
	* Get the actual axis extremes
	*/
	getExtremes: function () {
		var axis = this,
			isLog = axis.isLog;

		return {
			min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
			max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
			dataMin: axis.dataMin,
			dataMax: axis.dataMax,
			userMin: axis.userMin,
			userMax: axis.userMax
		};
	},

	/**
	* Get the zero plane either based on zero or on the min or max value.
	* Used in bar and area plots
	*/
	getThreshold: function (threshold) {
		var axis = this,
			isLog = axis.isLog;

		var realMin = isLog ? lin2log(axis.min) : axis.min,
			realMax = isLog ? lin2log(axis.max) : axis.max;

		if (realMin > threshold || threshold === null) {
			threshold = realMin;
		} else if (realMax < threshold) {
			threshold = realMax;
		}

		return axis.translate(threshold, 0, 1, 0, 1);
	},

	/**
	* Add a plot band or plot line after render time
	*
	* @param options {Object} The plotBand or plotLine configuration object
	*/
	addPlotBandOrLine: function (options) {
		var obj = new PlotLineOrBand(this, options).render();
		this.plotLinesAndBands.push(obj);
		return obj;
	},

	/**
	* Render the tick labels to a preliminary position to get their sizes
	*/
	getOffset: function () {
		var axis = this,
			chart = axis.chart,
			renderer = chart.renderer,
			options = axis.options,
			tickPositions = axis.tickPositions,
			ticks = axis.ticks,
			horiz = axis.horiz,
			side = axis.side,
			hasData,
			showAxis,
			titleOffset = 0,
			titleOffsetOption,
			titleMargin = 0,
			axisTitleOptions = options.title,
			labelOptions = options.labels,
			labelOffset = 0, // reset
			axisOffset = chart.axisOffset,
			directionFactor = [-1, 1, 1, -1][side],
			n;


		// For reuse in Axis.render
		axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));
		axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);


		// Create the axisGroup and gridGroup elements on first iteration
		if (!axis.axisGroup) {
			axis.gridGroup = renderer.g('grid')
				.attr({ zIndex: options.gridZIndex || 1 })
				.add();
			axis.axisGroup = renderer.g('axis')
				.attr({ zIndex: options.zIndex || 2 })
				.add();
			axis.labelGroup = renderer.g('axis-labels')
				.attr({ zIndex: labelOptions.zIndex || 7 })
				.add();
		}

		if (hasData || axis.isLinked) {
			each(tickPositions, function (pos) {
				if (!ticks[pos]) {
					ticks[pos] = new Tick(axis, pos);
				} else {
					ticks[pos].addLabel(); // update labels depending on tick interval
				}

			});

			each(tickPositions, function (pos) {
				// left side must be align: right and right side must have align: left for labels
				if (side === 0 || side === 2 || { 1: 'left', 3: 'right'}[side] === labelOptions.align) {

					// get the highest offset
					labelOffset = mathMax(
						ticks[pos].getLabelSize(),
						labelOffset
					);
				}

			});

			if (axis.staggerLines) {
				labelOffset += (axis.staggerLines - 1) * 16;
			}

		} else { // doesn't have data
			for (n in ticks) {
				ticks[n].destroy();
				delete ticks[n];
			}
		}

		if (axisTitleOptions && axisTitleOptions.text) {
			if (!axis.axisTitle) {
				axis.axisTitle = renderer.text(
					axisTitleOptions.text,
					0,
					0,
					axisTitleOptions.useHTML
				)
				.attr({
					zIndex: 7,
					rotation: axisTitleOptions.rotation || 0,
					align:
						axisTitleOptions.textAlign ||
						{ low: 'left', middle: 'center', high: 'right'}[axisTitleOptions.align]
				})
				.css(axisTitleOptions.style)
				.add(axis.axisGroup);
				axis.axisTitle.isNew = true;
			}

			if (showAxis) {
				titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
				titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
				titleOffsetOption = axisTitleOptions.offset;
			}

			// hide or show the title depending on whether showEmpty is set
			axis.axisTitle[showAxis ? 'show' : 'hide']();
		}

		// handle automatic or user set offset
		axis.offset = directionFactor * pick(options.offset, axisOffset[side]);


		axis.axisTitleMargin =
			pick(titleOffsetOption,
				labelOffset + titleMargin +
				(side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])
			);

		axisOffset[side] = mathMax(
			axisOffset[side],
			axis.axisTitleMargin + titleOffset + directionFactor * axis.offset
		);

	},

	/**
	* Get the path for the axis line
	*/
	getLinePath: function (lineWidth) {
		var chart = this.chart,
			opposite = this.opposite,
			offset = this.offset,
			horiz = this.horiz,
			lineLeft = this.left + (opposite ? this.width : 0) + offset,
			lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset;

		this.lineTop = lineTop; // used by flag series

		return chart.renderer.crispLine([
				M,
				horiz ?
					this.left :
					lineLeft,
				horiz ?
					lineTop :
					this.top,
				L,
				horiz ?
					chart.chartWidth - this.right :
					lineLeft,
				horiz ?
					lineTop :
					chart.chartHeight - this.bottom
			], lineWidth);
	},

	/**
	* Position the title
	*/
	getTitlePosition: function () {
		// compute anchor points for each of the title align options
		var horiz = this.horiz,
			axisLeft = this.left,
			axisTop = this.top,
			axisLength = this.len,
			axisTitleOptions = this.options.title,
			margin = horiz ? axisLeft : axisTop,
			opposite = this.opposite,
			offset = this.offset,
			fontSize = pInt(axisTitleOptions.style.fontSize || 12),

		// the position in the length direction of the axis
			alongAxis = {
				low: margin + (horiz ? 0 : axisLength),
				middle: margin + axisLength / 2,
				high: margin + (horiz ? axisLength : 0)
			}[axisTitleOptions.align],

		// the position in the perpendicular direction of the axis
			offAxis = (horiz ? axisTop + this.height : axisLeft) +
				(horiz ? 1 : -1) * // horizontal axis reverses the margin
				(opposite ? -1 : 1) * // so does opposite axes
				this.axisTitleMargin +
				(this.side === 2 ? fontSize : 0);

		return {
			x: horiz ?
				alongAxis :
				offAxis + (opposite ? this.width : 0) + offset +
					(axisTitleOptions.x || 0), // x
			y: horiz ?
				offAxis - (opposite ? this.height : 0) + offset :
				alongAxis + (axisTitleOptions.y || 0) // y
		};
	},

	/**
	* Render the axis
	*/
	render: function () {
		var axis = this,
			chart = axis.chart,
			renderer = chart.renderer,
			options = axis.options,
			isLog = axis.isLog,
			isLinked = axis.isLinked,
			tickPositions = axis.tickPositions,
			axisTitle = axis.axisTitle,
			stacks = axis.stacks,
			ticks = axis.ticks,
			minorTicks = axis.minorTicks,
			alternateBands = axis.alternateBands,
			stackLabelOptions = options.stackLabels,
			alternateGridColor = options.alternateGridColor,
			tickmarkOffset = axis.tickmarkOffset,
			lineWidth = options.lineWidth,
			linePath,
			hasRendered = chart.hasRendered,
			slideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin),
			hasData = axis.hasData,
			showAxis = axis.showAxis,
			from,
			to;

		// If the series has data draw the ticks. Else only the line and title
		if (hasData || isLinked) {

			// minor ticks
			if (axis.minorTickInterval && !axis.categories) {
				each(axis.getMinorTickPositions(), function (pos) {
					if (!minorTicks[pos]) {
						minorTicks[pos] = new Tick(axis, pos, 'minor');
					}

					// render new ticks in old position
					if (slideInTicks && minorTicks[pos].isNew) {
						minorTicks[pos].render(null, true);
					}


					minorTicks[pos].isActive = true;
					minorTicks[pos].render();
				});
			}

			// Major ticks. Pull out the first item and render it last so that
			// we can get the position of the neighbour label. #808.
			each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {

				// Reorganize the indices
				i = (i === tickPositions.length - 1) ? 0 : i + 1;

				// linked axes need an extra check to find out if
				if (!isLinked || (pos >= axis.min && pos <= axis.max)) {

					if (!ticks[pos]) {
						ticks[pos] = new Tick(axis, pos);
					}

					// render new ticks in old position
					if (slideInTicks && ticks[pos].isNew) {
						ticks[pos].render(i, true);
					}

					ticks[pos].isActive = true;
					ticks[pos].render(i);
				}

			});

			// alternate grid color
			if (alternateGridColor) {
				each(tickPositions, function (pos, i) {
					if (i % 2 === 0 && pos < axis.max) {
						if (!alternateBands[pos]) {
							alternateBands[pos] = new PlotLineOrBand(axis);
						}
						from = pos + tickmarkOffset; // #949
						to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max;
						alternateBands[pos].options = {
							from: isLog ? lin2log(from) : from,
							to: isLog ? lin2log(to) : to,
							color: alternateGridColor
						};
						alternateBands[pos].render();
						alternateBands[pos].isActive = true;
					}
				});
			}

			// custom plot lines and bands
			if (!axis._addedPlotLB) { // only first time
				each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {
					//plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render());
					axis.addPlotBandOrLine(plotLineOptions);
				});
				axis._addedPlotLB = true;
			}

		} // end if hasData

		// remove inactive ticks
		each([ticks, minorTicks, alternateBands], function (coll) {
			var pos;
			for (pos in coll) {
				if (!coll[pos].isActive) {
					coll[pos].destroy();
					delete coll[pos];
				} else {
					coll[pos].isActive = false; // reset
				}
			}
		});

		// Static items. As the axis group is cleared on subsequent calls
		// to render, these items are added outside the group.
		// axis line
		if (lineWidth) {
			linePath = axis.getLinePath(lineWidth);
			if (!axis.axisLine) {
				axis.axisLine = renderer.path(linePath)
					.attr({
						stroke: options.lineColor,
						'stroke-width': lineWidth,
						zIndex: 7
					})
					.add(axis.axisGroup);
			} else {
				axis.axisLine.animate({ d: linePath });
			}

			// show or hide the line depending on options.showEmpty
			axis.axisLine[showAxis ? 'show' : 'hide']();
		}

		if (axisTitle && showAxis) {

			axisTitle[axisTitle.isNew ? 'attr' : 'animate'](
				axis.getTitlePosition()
			);
			axisTitle.isNew = false;
		}

		// Stacked totals:
		if (stackLabelOptions && stackLabelOptions.enabled) {
			var stackKey, oneStack, stackCategory,
				stackTotalGroup = axis.stackTotalGroup;

			// Create a separate group for the stack total labels
			if (!stackTotalGroup) {
				axis.stackTotalGroup = stackTotalGroup =
					renderer.g('stack-labels')
						.attr({
							visibility: VISIBLE,
							zIndex: 6
						})
						.add();
			}

			// plotLeft/Top will change when y axis gets wider so we need to translate the
			// stackTotalGroup at every render call. See bug #506 and #516
			stackTotalGroup.translate(chart.plotLeft, chart.plotTop);

			// Render each stack total
			for (stackKey in stacks) {
				oneStack = stacks[stackKey];
				for (stackCategory in oneStack) {
					oneStack[stackCategory].render(stackTotalGroup);
				}
			}
		}
		// End stacked totals

		axis.isDirty = false;
	},

	/**
	* Remove a plot band or plot line from the chart by id
	* @param {Object} id
	*/
	removePlotBandOrLine: function (id) {
		var plotLinesAndBands = this.plotLinesAndBands,
			i = plotLinesAndBands.length;
		while (i--) {
			if (plotLinesAndBands[i].id === id) {
				plotLinesAndBands[i].destroy();
			}
		}
	},

	/**
	* Update the axis title by options
	*/
	setTitle: function (newTitleOptions, redraw) {
		var chart = this.chart,
			options = this.options,
			axisTitle = this.axisTitle;

		options.title = merge(options.title, newTitleOptions);

		this.axisTitle = axisTitle && axisTitle.destroy(); // #922
		this.isDirty = true;

		if (pick(redraw, true)) {
			chart.redraw();
		}
	},

	/**
	* Redraw the axis to reflect changes in the data or axis extremes
	*/
	redraw: function () {
		var axis = this,
			chart = axis.chart;

		// hide tooltip and hover states
		if (chart.tracker.resetTracker) {
			chart.tracker.resetTracker(true);
		}

		// render the axis
		axis.render();

		// move plot lines and bands
		each(axis.plotLinesAndBands, function (plotLine) {
			plotLine.render();
		});

		// mark associated series as dirty and ready for redraw
		each(axis.series, function (series) {
			series.isDirty = true;
		});

	},

	/**
	* Set new axis categories and optionally redraw
	* @param {Array} newCategories
	* @param {Boolean} doRedraw
	*/
	setCategories: function (newCategories, doRedraw) {
		var axis = this,
			chart = axis.chart;

		// set the categories
		axis.categories = axis.userOptions.categories = newCategories;

		// force reindexing tooltips
		each(axis.series, function (series) {
			series.translate();
			series.setTooltipPoints(true);
		});


		// optionally redraw
		axis.isDirty = true;

		if (pick(doRedraw, true)) {
			chart.redraw();
		}
	},

	/**
	* Destroys an Axis instance.
	*/
	destroy: function () {
		var axis = this,
			stacks = axis.stacks,
			stackKey;

		// Remove the events
		removeEvent(axis);

		// Destroy each stack total
		for (stackKey in stacks) {
			destroyObjectProperties(stacks[stackKey]);

			stacks[stackKey] = null;
		}

		// Destroy collections
		each([axis.ticks, axis.minorTicks, axis.alternateBands, axis.plotLinesAndBands], function (coll) {
			destroyObjectProperties(coll);
		});

		// Destroy local variables
		each(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'labelGroup', 'axisTitle'], function (prop) {
			if (axis[prop]) {
				axis[prop] = axis[prop].destroy();
			}
		});
	}


}; // end Axis

/**
* The tooltip object
* @param {Object} chart The chart instance
* @param {Object} options Tooltip options
*/
function Tooltip(chart, options) {
	var borderWidth = options.borderWidth,
		style = options.style,
		padding = pInt(style.padding);

	// Save the chart and options
	this.chart = chart;
	this.options = options;

	// Keep track of the current series
	//this.currentSeries = UNDEFINED;

	// List of crosshairs
	this.crosshairs = [];

	// Current values of x and y when animating
	this.now = { x: 0, y: 0 };

	// The tooltip is initially hidden
	this.isHidden = true;

	// create the label
	this.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip')
		.attr({
			padding: padding,
			fill: options.backgroundColor,
			'stroke-width': borderWidth,
			r: options.borderRadius,
			zIndex: 8
		})
		.css(style)
		.css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117)
		.hide()
		.add();

	// When using canVG the shadow shows up as a gray circle
	// even if the tooltip is hidden.
	if (!useCanVG) {
		this.label.shadow(options.shadow);
	}

	// Public property for getting the shared state.
	this.shared = options.shared;
}

Tooltip.prototype = {
	/**
	* Destroy the tooltip and its elements.
	*/
	destroy: function () {
		each(this.crosshairs, function (crosshair) {
			if (crosshair) {
				crosshair.destroy();
			}
		});

		// Destroy and clear local variables
		if (this.label) {
			this.label = this.label.destroy();
		}
	},

	/**
	* Provide a soft movement for the tooltip
	*
	* @param {Number} x
	* @param {Number} y
	* @private
	*/
	move: function (x, y, anchorX, anchorY) {
		var tooltip = this,
			now = tooltip.now,
			animate = tooltip.options.animation !== false && !tooltip.isHidden;

		// get intermediate values for animation
		extend(now, {
			x: animate ? (2 * now.x + x) / 3 : x,
			y: animate ? (now.y + y) / 2 : y,
			anchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
			anchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY
		});

		// move to the intermediate value
		tooltip.label.attr(now);


		// run on next tick of the mouse tracker
		if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) {

			// never allow two timeouts
			clearTimeout(this.tooltipTimeout);

			// set the fixed interval ticking for the smooth tooltip
			this.tooltipTimeout = setTimeout(function () {
				// The interval function may still be running during destroy, so check that the chart is really there before calling.
				if (tooltip) {
					tooltip.move(x, y, anchorX, anchorY);
				}
			}, 32);

		}
	},

	/**
	* Hide the tooltip
	*/
	hide: function () {
		if (!this.isHidden) {
			var hoverPoints = this.chart.hoverPoints;

			this.label.hide();

			// hide previous hoverPoints and set new
			if (hoverPoints) {
				each(hoverPoints, function (point) {
					point.setState();
				});
			}

			this.chart.hoverPoints = null;
			this.isHidden = true;
		}
	},

	/**
	* Hide the crosshairs
	*/
	hideCrosshairs: function () {
		each(this.crosshairs, function (crosshair) {
			if (crosshair) {
				crosshair.hide();
			}
		});
	},

	/** 
	* Extendable method to get the anchor position of the tooltip
	* from a point or set of points
	*/
	getAnchor: function (points, mouseEvent) {
		var ret,
			chart = this.chart,
			inverted = chart.inverted,
			plotX = 0,
			plotY = 0,
			yAxis;

		points = splat(points);

		// Pie uses a special tooltipPos
		ret = points[0].tooltipPos;

		// When shared, use the average position
		if (!ret) {
			each(points, function (point) {
				yAxis = point.series.yAxis;
				plotX += point.plotX;
				plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +
					(!inverted && yAxis ? yAxis.top - chart.plotTop : 0); // #1151
			});

			plotX /= points.length;
			plotY /= points.length;

			ret = [
				inverted ? chart.plotWidth - plotY : plotX,
				this.shared && !inverted && points.length > 1 && mouseEvent ?
					mouseEvent.chartY - chart.plotTop : // place shared tooltip next to the mouse (#424)
					inverted ? chart.plotHeight - plotX : plotY
			];
		}

		return map(ret, mathRound);
	},

	/**
	* Place the tooltip in a chart without spilling over
	* and not covering the point it self.
	*/
	getPosition: function (boxWidth, boxHeight, point) {

		// Set up the variables
		var chart = this.chart,
			plotLeft = chart.plotLeft,
			plotTop = chart.plotTop,
			plotWidth = chart.plotWidth,
			plotHeight = chart.plotHeight,
			distance = pick(this.options.distance, 12),
			pointX = point.plotX,
			pointY = point.plotY,
			x = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance),
			y = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip
			alignedRight;

		// It is too far to the left, adjust it
		if (x < 7) {
			x = plotLeft + mathMax(pointX, 0) + distance;
		}

		// Test to see if the tooltip is too far to the right,
		// if it is, move it back to be inside and then up to not cover the point.
		if ((x + boxWidth) > (plotLeft + plotWidth)) {
			x -= (x + boxWidth) - (plotLeft + plotWidth);
			y = pointY - boxHeight + plotTop - distance;
			alignedRight = true;
		}

		// If it is now above the plot area, align it to the top of the plot area
		if (y < plotTop + 5) {
			y = plotTop + 5;

			// If the tooltip is still covering the point, move it below instead
			if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
				y = pointY + plotTop + distance; // below
			}
		}

		// Now if the tooltip is below the chart, move it up. It's better to cover the
		// point than to disappear outside the chart. #834.
		if (y + boxHeight > plotTop + plotHeight) {
			y = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below
		}

		return { x: x, y: y };
	},

	/**
	* Refresh the tooltip's text and position.
	* @param {Object} point
	*/
	refresh: function (point, mouseEvent) {
		var tooltip = this,
			chart = tooltip.chart,
			label = tooltip.label,
			options = tooltip.options;

		/**
		* In case no user defined formatter is given, this will be used
		*/
		function defaultFormatter() {
			var pThis = this,
				items = pThis.points || splat(pThis),
				series = items[0].series,
				s;

			// build the header
			s = [series.tooltipHeaderFormatter(items[0].key)];

			// build the values
			each(items, function (item) {
				series = item.series;
				s.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||
					item.point.tooltipFormatter(series.tooltipOptions.pointFormat));
			});

			// footer
			s.push(options.footerFormat || '');

			return s.join('');
		}

		var x,
			y,
			show,
			anchor,
			textConfig = {},
			text,
			pointConfig = [],
			formatter = options.formatter || defaultFormatter,
			hoverPoints = chart.hoverPoints,
			placedTooltipPoint,
			borderColor,
			crosshairsOptions = options.crosshairs,
			shared = tooltip.shared,
			currentSeries;

		// get the reference point coordinates (pie charts use tooltipPos)
		anchor = tooltip.getAnchor(point, mouseEvent);
		x = anchor[0];
		y = anchor[1];

		// shared tooltip, array is sent over
		if (shared && !(point.series && point.series.noSharedTooltip)) {

			// hide previous hoverPoints and set new

			chart.hoverPoints = point;
			if (hoverPoints) {
				each(hoverPoints, function (point) {
					point.setState();
				});
			}

			each(point, function (item) {
				item.setState(HOVER_STATE);

				pointConfig.push(item.getLabelConfig());
			});

			textConfig = {
				x: point[0].category,
				y: point[0].y
			};
			textConfig.points = pointConfig;
			point = point[0];

			// single point tooltip
		} else {
			textConfig = point.getLabelConfig();
		}
		text = formatter.call(textConfig);

		// register the current series
		currentSeries = point.series;


		// For line type series, hide tooltip if the point falls outside the plot
		show = shared || !currentSeries.isCartesian || currentSeries.tooltipOutsidePlot || chart.isInsidePlot(x, y);

		// update the inner HTML
		if (text === false || !show) {
			this.hide();
		} else {

			// show it
			if (tooltip.isHidden) {
				label.show();
			}

			// update text
			label.attr({
				text: text
			});

			// set the stroke color of the box
			borderColor = options.borderColor || point.color || currentSeries.color || '#606060';
			label.attr({
				stroke: borderColor
			});

			placedTooltipPoint = (options.positioner || tooltip.getPosition).call(
				tooltip,
				label.width,
				label.height,
				{ plotX: x, plotY: y }
			);

			// do the move
			tooltip.move(
				mathRound(placedTooltipPoint.x),
				mathRound(placedTooltipPoint.y),
				x + chart.plotLeft,
				y + chart.plotTop
			);


			tooltip.isHidden = false;
		}

		// crosshairs
		if (crosshairsOptions) {
			crosshairsOptions = splat(crosshairsOptions); // [x, y]

			var path,
				i = crosshairsOptions.length,
				attribs,
				axis;

			while (i--) {
				axis = point.series[i ? 'yAxis' : 'xAxis'];
				if (crosshairsOptions[i] && axis) {

					path = axis.getPlotLinePath(
						i ? pick(point.stackY, point.y) : point.x, // #814
						1
					);

					if (tooltip.crosshairs[i]) {
						tooltip.crosshairs[i].attr({ d: path, visibility: VISIBLE });
					} else {
						attribs = {
							'stroke-width': crosshairsOptions[i].width || 1,
							stroke: crosshairsOptions[i].color || '#C0C0C0',
							zIndex: crosshairsOptions[i].zIndex || 2
						};
						if (crosshairsOptions[i].dashStyle) {
							attribs.dashstyle = crosshairsOptions[i].dashStyle;
						}
						tooltip.crosshairs[i] = chart.renderer.path(path)
							.attr(attribs)
							.add();
					}
				}
			}
		}
		fireEvent(chart, 'tooltipRefresh', {
			text: text,
			x: x + chart.plotLeft,
			y: y + chart.plotTop,
			borderColor: borderColor
		});
	}
};
/**
* The mouse tracker object
* @param {Object} chart The Chart instance
* @param {Object} options The root options object
*/
function MouseTracker(chart, options) {
	var zoomType = useCanVG ? '' : options.chart.zoomType;

	// Zoom status
	this.zoomX = /x/.test(zoomType);
	this.zoomY = /y/.test(zoomType);

	// Store reference to options
	this.options = options;

	// Reference to the chart
	this.chart = chart;

	// The interval id
	//this.tooltipTimeout = UNDEFINED;

	// The cached x hover position
	//this.hoverX = UNDEFINED;

	// The chart position
	//this.chartPosition = UNDEFINED;

	// The selection marker element
	//this.selectionMarker = UNDEFINED;

	// False or a value > 0 if a dragging operation
	//this.mouseDownX = UNDEFINED;
	//this.mouseDownY = UNDEFINED;
	this.init(chart, options.tooltip);
}

MouseTracker.prototype = {
	/**
	* Add crossbrowser support for chartX and chartY
	* @param {Object} e The event object in standard browsers
	*/
	normalizeMouseEvent: function (e) {
		var chartPosition,
			chartX,
			chartY,
			ePos;

		// common IE normalizing
		e = e || win.event;
		if (!e.target) {
			e.target = e.srcElement;
		}

		// Framework specific normalizing (#1165)
		e = washMouseEvent(e);

		// iOS
		ePos = e.touches ? e.touches.item(0) : e;

		// get mouse position
		this.chartPosition = chartPosition = offset(this.chart.container);

		// chartX and chartY
		if (ePos.pageX === UNDEFINED) { // IE < 9. #886.
			chartX = e.x;
			chartY = e.y;
		} else {
			chartX = ePos.pageX - chartPosition.left;
			chartY = ePos.pageY - chartPosition.top;
		}

		return extend(e, {
			chartX: mathRound(chartX),
			chartY: mathRound(chartY)
		});
	},

	/**
	* Get the click position in terms of axis values.
	*
	* @param {Object} e A mouse event
	*/
	getMouseCoordinates: function (e) {
		var coordinates = {
			xAxis: [],
			yAxis: []
		},
			chart = this.chart;

		each(chart.axes, function (axis) {
			var isXAxis = axis.isXAxis,
				isHorizontal = chart.inverted ? !isXAxis : isXAxis;

			coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
				axis: axis,
				value: axis.translate(
					(isHorizontal ?
						e.chartX - chart.plotLeft :
						axis.top + axis.len - e.chartY) - axis.minPixelPadding, // #1051
					true
				)
			});
		});
		return coordinates;
	},

	/**
	* Return the index in the tooltipPoints array, corresponding to pixel position in 
	* the plot area.
	*/
	getIndex: function (e) {
		var chart = this.chart;
		return chart.inverted ?
			chart.plotHeight + chart.plotTop - e.chartY :
			e.chartX - chart.plotLeft;
	},

	/**
	* With line type charts with a single tracker, get the point closest to the mouse
	*/
	onmousemove: function (e) {
		var mouseTracker = this,
			chart = mouseTracker.chart,
			series = chart.series,
			tooltip = chart.tooltip,
			point,
			points,
			hoverPoint = chart.hoverPoint,
			hoverSeries = chart.hoverSeries,
			i,
			j,
			distance = chart.chartWidth,
			index = mouseTracker.getIndex(e);

		// shared tooltip
		if (tooltip && mouseTracker.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
			points = [];

			// loop over all series and find the ones with points closest to the mouse
			i = series.length;
			for (j = 0; j < i; j++) {
				if (series[j].visible &&
						series[j].options.enableMouseTracking !== false &&
						!series[j].noSharedTooltip && series[j].tooltipPoints.length) {
					point = series[j].tooltipPoints[index];
					point._dist = mathAbs(index - point[series[j].xAxis.tooltipPosName || 'plotX']);
					distance = mathMin(distance, point._dist);
					points.push(point);
				}
			}
			// remove furthest points
			i = points.length;
			while (i--) {
				if (points[i]._dist > distance) {
					points.splice(i, 1);
				}
			}
			// refresh the tooltip if necessary
			if (points.length && (points[0].plotX !== mouseTracker.hoverX)) {
				tooltip.refresh(points, e);
				mouseTracker.hoverX = points[0].plotX;
			}
		}

		// separate tooltip and general mouse events
		if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker

			// get the point
			point = hoverSeries.tooltipPoints[index];

			// a new point is hovered, refresh the tooltip
			if (point && point !== hoverPoint) {

				// trigger the events
				point.onMouseOver();

			}
		}
	},



	/**
	* Reset the tracking by hiding the tooltip, the hover series state and the hover point
	* 
	* @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible
	*/
	resetTracker: function (allowMove) {
		var mouseTracker = this,
			chart = mouseTracker.chart,
			hoverSeries = chart.hoverSeries,
			hoverPoint = chart.hoverPoint,
			tooltip = chart.tooltip,
			tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint;

		// Narrow in allowMove
		allowMove = allowMove && tooltip && tooltipPoints;

		// Check if the points have moved outside the plot area, #1003
		if (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) {
			allowMove = false;
		}

		// Just move the tooltip, #349
		if (allowMove) {
			tooltip.refresh(tooltipPoints);

			// Full reset
		} else {

			if (hoverPoint) {
				hoverPoint.onMouseOut();
			}

			if (hoverSeries) {
				hoverSeries.onMouseOut();
			}

			if (tooltip) {
				tooltip.hide();
				tooltip.hideCrosshairs();
			}

			mouseTracker.hoverX = null;

		}
	},

	/**
	* Set the JS events on the container element
	*/
	setDOMEvents: function () {
		var lastWasOutsidePlot = true,
			mouseTracker = this,
			chart = mouseTracker.chart,
			container = chart.container,
			hasDragged,
			zoomHor = (mouseTracker.zoomX && !chart.inverted) || (mouseTracker.zoomY && chart.inverted),
			zoomVert = (mouseTracker.zoomY && !chart.inverted) || (mouseTracker.zoomX && chart.inverted);

		/**
		* Mouse up or outside the plot area
		*/
		function drop() {
			if (mouseTracker.selectionMarker) {
				var selectionData = {
					xAxis: [],
					yAxis: []
				},
					selectionBox = mouseTracker.selectionMarker.getBBox(),
					selectionLeft = selectionBox.x - chart.plotLeft,
					selectionTop = selectionBox.y - chart.plotTop,
					runZoom;

				// a selection has been made
				if (hasDragged) {

					// record each axis' min and max
					each(chart.axes, function (axis) {
						if (axis.options.zoomEnabled !== false) {
							var isXAxis = axis.isXAxis,
								isHorizontal = chart.inverted ? !isXAxis : isXAxis,
								selectionMin = axis.translate(
									isHorizontal ?
										selectionLeft :
										chart.plotHeight - selectionTop - selectionBox.height,
									true,
									0,
									0,
									1
								),
								selectionMax = axis.translate(
									(isHorizontal ?
											selectionLeft + selectionBox.width :
											chart.plotHeight - selectionTop) -
										2 * axis.minPixelPadding, // #875
									true,
									0,
									0,
									1
								);

							if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859
								selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
									axis: axis,
									min: mathMin(selectionMin, selectionMax), // for reversed axes,
									max: mathMax(selectionMin, selectionMax)
								});
								runZoom = true;
							}
						}
					});
					if (runZoom) {
						fireEvent(chart, 'selection', selectionData, function (args) { chart.zoom(args); });
					}

				}
				mouseTracker.selectionMarker = mouseTracker.selectionMarker.destroy();
			}

			if (chart) { // it may be destroyed on mouse up - #877
				css(container, { cursor: 'auto' });
				chart.cancelClick = hasDragged; // #370
				chart.mouseIsDown = hasDragged = false;
			}

			removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
		}

		/**
		* Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
		*/
		mouseTracker.hideTooltipOnMouseMove = function (e) {

			// Get e.pageX and e.pageY back in MooTools
			e = washMouseEvent(e);

			// If we're outside, hide the tooltip
			if (mouseTracker.chartPosition && chart.hoverSeries && chart.hoverSeries.isCartesian &&
				!chart.isInsidePlot(e.pageX - mouseTracker.chartPosition.left - chart.plotLeft,
				e.pageY - mouseTracker.chartPosition.top - chart.plotTop)) {
				mouseTracker.resetTracker();
			}
		};

		/**
		* When mouse leaves the container, hide the tooltip.
		*/
		mouseTracker.hideTooltipOnMouseLeave = function () {
			mouseTracker.resetTracker();
			mouseTracker.chartPosition = null; // also reset the chart position, used in #149 fix
		};


		/*
		* Record the starting position of a dragoperation
		*/
		container.onmousedown = function (e) {
			e = mouseTracker.normalizeMouseEvent(e);

			// issue #295, dragging not always working in Firefox
			if (!hasTouch && e.preventDefault) {
				e.preventDefault();
			}

			// record the start position
			chart.mouseIsDown = true;
			chart.cancelClick = false;
			chart.mouseDownX = mouseTracker.mouseDownX = e.chartX;
			mouseTracker.mouseDownY = e.chartY;

			addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
		};

		// The mousemove, touchmove and touchstart event handler
		var mouseMove = function (e) {
			// let the system handle multitouch operations like two finger scroll
			// and pinching
			if (e && e.touches && e.touches.length > 1) {
				return;
			}

			// normalize
			e = mouseTracker.normalizeMouseEvent(e);
			if (!hasTouch) { // not for touch devices
				e.returnValue = false;
			}

			var chartX = e.chartX,
				chartY = e.chartY,
				isOutsidePlot = !chart.isInsidePlot(chartX - chart.plotLeft, chartY - chart.plotTop);

			// on touch devices, only trigger click if a handler is defined
			if (hasTouch && e.type === 'touchstart') {
				if (attr(e.target, 'isTracker')) {
					if (!chart.runTrackerClick) {
						e.preventDefault();
					}
				} else if (!chart.runChartClick && !isOutsidePlot) {
					e.preventDefault();
				}
			}

			// cancel on mouse outside
			if (isOutsidePlot) {

				/*if (!lastWasOutsidePlot) {
				// reset the tracker
				resetTracker();
				}*/

				// drop the selection if any and reset mouseIsDown and hasDragged
				//drop();
				if (chartX < chart.plotLeft) {
					chartX = chart.plotLeft;
				} else if (chartX > chart.plotLeft + chart.plotWidth) {
					chartX = chart.plotLeft + chart.plotWidth;
				}

				if (chartY < chart.plotTop) {
					chartY = chart.plotTop;
				} else if (chartY > chart.plotTop + chart.plotHeight) {
					chartY = chart.plotTop + chart.plotHeight;
				}
			}

			if (chart.mouseIsDown && e.type !== 'touchstart') { // make selection

				// determine if the mouse has moved more than 10px
				hasDragged = Math.sqrt(
					Math.pow(mouseTracker.mouseDownX - chartX, 2) +
					Math.pow(mouseTracker.mouseDownY - chartY, 2)
				);
				if (hasDragged > 10) {
					var clickedInside = chart.isInsidePlot(mouseTracker.mouseDownX - chart.plotLeft, mouseTracker.mouseDownY - chart.plotTop);

					// make a selection
					if (chart.hasCartesianSeries && (mouseTracker.zoomX || mouseTracker.zoomY) && clickedInside) {
						if (!mouseTracker.selectionMarker) {
							mouseTracker.selectionMarker = chart.renderer.rect(
								chart.plotLeft,
								chart.plotTop,
								zoomHor ? 1 : chart.plotWidth,
								zoomVert ? 1 : chart.plotHeight,
								0
							)
							.attr({
								fill: mouseTracker.options.chart.selectionMarkerFill || 'rgba(69,114,167,0.25)',
								zIndex: 7
							})
							.add();
						}
					}

					// adjust the width of the selection marker
					if (mouseTracker.selectionMarker && zoomHor) {
						var xSize = chartX - mouseTracker.mouseDownX;
						mouseTracker.selectionMarker.attr({
							width: mathAbs(xSize),
							x: (xSize > 0 ? 0 : xSize) + mouseTracker.mouseDownX
						});
					}
					// adjust the height of the selection marker
					if (mouseTracker.selectionMarker && zoomVert) {
						var ySize = chartY - mouseTracker.mouseDownY;
						mouseTracker.selectionMarker.attr({
							height: mathAbs(ySize),
							y: (ySize > 0 ? 0 : ySize) + mouseTracker.mouseDownY
						});
					}

					// panning
					if (clickedInside && !mouseTracker.selectionMarker && mouseTracker.options.chart.panning) {
						chart.pan(chartX);
					}
				}

			}

			// Show the tooltip and run mouse over events (#977)			
			if (!isOutsidePlot) {
				mouseTracker.onmousemove(e);
			}

			lastWasOutsidePlot = isOutsidePlot;

			// when outside plot, allow touch-drag by returning true
			return isOutsidePlot || !chart.hasCartesianSeries;
		};

		/*
		* When the mouse enters the container, run mouseMove
		*/
		container.onmousemove = mouseMove;

		/*
		* When the mouse leaves the container, hide the tracking (tooltip).
		*/
		addEvent(container, 'mouseleave', mouseTracker.hideTooltipOnMouseLeave);

		// issue #149 workaround
		// The mouseleave event above does not always fire. Whenever the mouse is moving
		// outside the plotarea, hide the tooltip
		addEvent(doc, 'mousemove', mouseTracker.hideTooltipOnMouseMove);

		container.ontouchstart = function (e) {
			// For touch devices, use touchmove to zoom
			if (mouseTracker.zoomX || mouseTracker.zoomY) {
				container.onmousedown(e);
			}
			// Show tooltip and prevent the lower mouse pseudo event
			mouseMove(e);
		};

		/*
		* Allow dragging the finger over the chart to read the values on touch
		* devices
		*/
		container.ontouchmove = mouseMove;

		/*
		* Allow dragging the finger over the chart to read the values on touch
		* devices
		*/
		container.ontouchend = function () {
			if (hasDragged) {
				mouseTracker.resetTracker();
			}
		};


		// MooTools 1.2.3 doesn't fire this in IE when using addEvent
		container.onclick = function (e) {
			var hoverPoint = chart.hoverPoint,
				plotX,
				plotY;
			e = mouseTracker.normalizeMouseEvent(e);

			e.cancelBubble = true; // IE specific


			if (!chart.cancelClick) {
				// Detect clicks on trackers or tracker groups, #783
				if (hoverPoint && (attr(e.target, 'isTracker') || attr(e.target.parentNode, 'isTracker'))) {
					plotX = hoverPoint.plotX;
					plotY = hoverPoint.plotY;

					// add page position info
					extend(hoverPoint, {
						pageX: mouseTracker.chartPosition.left + chart.plotLeft +
							(chart.inverted ? chart.plotWidth - plotY : plotX),
						pageY: mouseTracker.chartPosition.top + chart.plotTop +
							(chart.inverted ? chart.plotHeight - plotX : plotY)
					});

					// the series click event
					fireEvent(hoverPoint.series, 'click', extend(e, {
						point: hoverPoint
					}));

					// the point click event
					hoverPoint.firePointEvent('click', e);

				} else {
					extend(e, mouseTracker.getMouseCoordinates(e));

					// fire a click event in the chart
					if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
						fireEvent(chart, 'click', e);
					}
				}


			}
		};

	},

	/**
	* Destroys the MouseTracker object and disconnects DOM events.
	*/
	destroy: function () {
		var mouseTracker = this,
			chart = mouseTracker.chart,
			container = chart.container;

		// Destroy the tracker group element
		if (chart.trackerGroup) {
			chart.trackerGroup = chart.trackerGroup.destroy();
		}

		removeEvent(container, 'mouseleave', mouseTracker.hideTooltipOnMouseLeave);
		removeEvent(doc, 'mousemove', mouseTracker.hideTooltipOnMouseMove);
		container.onclick = container.onmousedown = container.onmousemove = container.ontouchstart = container.ontouchend = container.ontouchmove = null;

		// memory and CPU leak
		clearInterval(this.tooltipTimeout);
	},

	// Run MouseTracker
	init: function (chart, options) {
		if (!chart.trackerGroup) {
			chart.trackerGroup = chart.renderer.g('tracker')
				.attr({ zIndex: 9 })
				.add();
		}

		if (options.enabled) {
			chart.tooltip = new Tooltip(chart, options);
		}

		this.setDOMEvents();
	}
};
/**
* The overview of the chart's series
*/
function Legend(chart) {

	this.init(chart);
}

Legend.prototype = {

	/**
	* Initialize the legend
	*/
	init: function (chart) {
		var legend = this,
			options = legend.options = chart.options.legend;

		if (!options.enabled) {
			return;
		}

		var //style = options.style || {}, // deprecated
			itemStyle = options.itemStyle,
			padding = pick(options.padding, 8),
			itemMarginTop = options.itemMarginTop || 0;

		legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype
		legend.itemStyle = itemStyle;
		legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle);
		legend.itemMarginTop = itemMarginTop;
		legend.padding = padding;
		legend.initialItemX = padding;
		legend.initialItemY = padding - 5; // 5 is the number of pixels above the text
		legend.maxItemWidth = 0;
		legend.chart = chart;
		//legend.allItems = UNDEFINED;
		//legend.legendWidth = UNDEFINED;
		//legend.legendHeight = UNDEFINED;
		//legend.offsetWidth = UNDEFINED;
		legend.itemHeight = 0;
		legend.lastLineHeight = 0;
		//legend.itemX = UNDEFINED;
		//legend.itemY = UNDEFINED;
		//legend.lastItemY = UNDEFINED;

		// Elements
		//legend.group = UNDEFINED;
		//legend.box = UNDEFINED;

		// run legend
		legend.render();

		// move checkboxes
		addEvent(legend.chart, 'endResize', function () { legend.positionCheckboxes(); });

		/*		// expose
		return {
		colorizeItem: colorizeItem,
		destroyItem: destroyItem,
		render: render,
		destroy: destroy,
		getLegendWidth: getLegendWidth,
		getLegendHeight: getLegendHeight
		};*/
	},

	/**
	* Set the colors for the legend item
	* @param {Object} item A Series or Point instance
	* @param {Object} visible Dimmed or colored
	*/
	colorizeItem: function (item, visible) {
		var legend = this,
			options = legend.options,
			legendItem = item.legendItem,
			legendLine = item.legendLine,
			legendSymbol = item.legendSymbol,
			hiddenColor = legend.itemHiddenStyle.color,
			textColor = visible ? options.itemStyle.color : hiddenColor,
			symbolColor = visible ? item.color : hiddenColor,
			markerOptions = item.options && item.options.marker,
			symbolAttr = {
				stroke: symbolColor,
				fill: symbolColor
			},
			key,
			val;


		if (legendItem) {
			legendItem.css({ fill: textColor });
		}
		if (legendLine) {
			legendLine.attr({ stroke: symbolColor });
		}

		if (legendSymbol) {

			// Apply marker options
			if (markerOptions) {
				markerOptions = item.convertAttribs(markerOptions);
				for (key in markerOptions) {
					val = markerOptions[key];
					if (val !== UNDEFINED) {
						symbolAttr[key] = val;
					}
				}
			}

			legendSymbol.attr(symbolAttr);
		}
	},

	/**
	* Position the legend item
	* @param {Object} item A Series or Point instance
	*/
	positionItem: function (item) {
		var legend = this,
			options = legend.options,
			symbolPadding = options.symbolPadding,
			ltr = !options.rtl,
			legendItemPos = item._legendItemPos,
			itemX = legendItemPos[0],
			itemY = legendItemPos[1],
			checkbox = item.checkbox;

		if (item.legendGroup) {
			item.legendGroup.translate(
				ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4,
				itemY
			);
		}

		if (checkbox) {
			checkbox.x = itemX;
			checkbox.y = itemY;
		}
	},

	/**
	* Destroy a single legend item
	* @param {Object} item The series or point
	*/
	destroyItem: function (item) {
		var checkbox = item.checkbox;

		// destroy SVG elements
		each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) {
			if (item[key]) {
				item[key].destroy();
			}
		});

		if (checkbox) {
			discardElement(item.checkbox);
		}
	},

	/**
	* Destroys the legend.
	*/
	destroy: function () {
		var legend = this,
			legendGroup = legend.group,
			box = legend.box;

		if (box) {
			legend.box = box.destroy();
		}

		if (legendGroup) {
			legend.group = legendGroup.destroy();
		}
	},

	/**
	* Position the checkboxes after the width is determined
	*/
	positionCheckboxes: function () {
		var legend = this;

		each(legend.allItems, function (item) {
			var checkbox = item.checkbox,
				alignAttr = legend.group.alignAttr;
			if (checkbox) {
				css(checkbox, {
					left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,
					top: (alignAttr.translateY + checkbox.y + 3) + PX
				});
			}
		});
	},

	/**
	* Render a single specific legend item
	* @param {Object} item A series or point
	*/
	renderItem: function (item) {
		var legend = this,
			chart = legend.chart,
			renderer = chart.renderer,
			options = legend.options,
			horizontal = options.layout === 'horizontal',
			symbolWidth = options.symbolWidth,
			symbolPadding = options.symbolPadding,
			itemStyle = legend.itemStyle,
			itemHiddenStyle = legend.itemHiddenStyle,
			padding = legend.padding,
			ltr = !options.rtl,
			itemHeight,
			widthOption = options.width,
			itemMarginBottom = options.itemMarginBottom || 0,
			itemMarginTop = legend.itemMarginTop,
			initialItemX = legend.initialItemX,
			bBox,
			itemWidth,
			li = item.legendItem,
			series = item.series || item,
			itemOptions = series.options,
			showCheckbox = itemOptions.showCheckbox;

		if (!li) { // generate it once, later move it

			// Generate the group box
			// A group to hold the symbol and text. Text is to be appended in Legend class.
			item.legendGroup = renderer.g('legend-item')
				.attr({ zIndex: 1 })
				.add(legend.scrollGroup);

			// Draw the legend symbol inside the group box
			series.drawLegendSymbol(legend, item);

			// Generate the list item text and add it to the group
			item.legendItem = li = renderer.text(
					options.labelFormatter.call(item),
					ltr ? symbolWidth + symbolPadding : -symbolPadding,
					legend.baseline,
					options.useHTML
				)
				.css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021)
				.attr({
					align: ltr ? 'left' : 'right',
					zIndex: 2
				})
				.add(item.legendGroup);

			// Set the events on the item group
			item.legendGroup.on('mouseover', function () {
				item.setState(HOVER_STATE);
				li.css(legend.options.itemHoverStyle);
			})
				.on('mouseout', function () {
					li.css(item.visible ? itemStyle : itemHiddenStyle);
					item.setState();
				})
				.on('click', function (event) {
					var strLegendItemClick = 'legendItemClick',
						fnLegendItemClick = function () {
							item.setVisible();
						};

					// Pass over the click/touch event. #4.
					event = {
						browserEvent: event
					};

					// click the name or symbol
					if (item.firePointEvent) { // point
						item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
					} else {
						fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
					}
				});

			// Colorize the items
			legend.colorizeItem(item, item.visible);

			// add the HTML checkbox on top
			if (itemOptions && showCheckbox) {
				item.checkbox = createElement('input', {
					type: 'checkbox',
					checked: item.selected,
					defaultChecked: item.selected // required by IE7
				}, options.itemCheckboxStyle, chart.container);

				addEvent(item.checkbox, 'click', function (event) {
					var target = event.target;
					fireEvent(item, 'checkboxClick', {
						checked: target.checked
					},
						function () {
							item.select();
						}
					);
				});
			}
		}

		// calculate the positions for the next line
		bBox = li.getBBox();

		itemWidth = item.legendItemWidth =
			options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding +
			(showCheckbox ? 20 : 0);
		legend.itemHeight = itemHeight = bBox.height;

		// if the item exceeds the width, start a new line
		if (horizontal && legend.itemX - initialItemX + itemWidth >
				(widthOption || (chart.chartWidth - 2 * padding - initialItemX))) {
			legend.itemX = initialItemX;
			legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom;
			legend.lastLineHeight = 0; // reset for next line
		}

		// If the item exceeds the height, start a new column
		/*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
		legend.itemY = legend.initialItemY;
		legend.itemX += legend.maxItemWidth;
		legend.maxItemWidth = 0;
		}*/

		// Set the edge positions
		legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth);
		legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
		legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915

		// cache the position of the newly generated or reordered items
		item._legendItemPos = [legend.itemX, legend.itemY];

		// advance
		if (horizontal) {
			legend.itemX += itemWidth;

		} else {
			legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
			legend.lastLineHeight = itemHeight;
		}

		// the width of the widest item
		legend.offsetWidth = widthOption || mathMax(
			horizontal ? legend.itemX - initialItemX : itemWidth,
			legend.offsetWidth
		);
	},

	/**
	* Render the legend. This method can be called both before and after
	* chart.render. If called after, it will only rearrange items instead
	* of creating new ones.
	*/
	render: function () {
		var legend = this,
			chart = legend.chart,
			renderer = chart.renderer,
			legendGroup = legend.group,
			allItems,
			display,
			legendWidth,
			legendHeight,
			box = legend.box,
			options = legend.options,
			padding = legend.padding,
			legendBorderWidth = options.borderWidth,
			legendBackgroundColor = options.backgroundColor;

		legend.itemX = legend.initialItemX;
		legend.itemY = legend.initialItemY;
		legend.offsetWidth = 0;
		legend.lastItemY = 0;

		if (!legendGroup) {
			legend.group = legendGroup = renderer.g('legend')
			// #414, #759. Trackers will be drawn above the legend, but we have 
			// to sacrifice that because tooltips need to be above the legend
			// and trackers above tooltips
				.attr({ zIndex: 7 })
				.add();
			legend.contentGroup = renderer.g()
				.attr({ zIndex: 1 }) // above background
				.add(legendGroup);
			legend.scrollGroup = renderer.g()
				.add(legend.contentGroup);
			legend.clipRect = renderer.clipRect(0, 0, 9999, chart.chartHeight);
			legend.contentGroup.clip(legend.clipRect);
		}

		// add each series or point
		allItems = [];
		each(chart.series, function (serie) {
			var seriesOptions = serie.options;

			if (!seriesOptions.showInLegend) {
				return;
			}

			// use points or series for the legend item depending on legendType
			allItems = allItems.concat(
					serie.legendItems ||
					(seriesOptions.legendType === 'point' ?
							serie.data :
							serie)
			);
		});

		// sort by legendIndex
		stableSort(allItems, function (a, b) {
			return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0);
		});

		// reversed legend
		if (options.reversed) {
			allItems.reverse();
		}

		legend.allItems = allItems;
		legend.display = display = !!allItems.length;

		// render the items
		each(allItems, function (item) {
			legend.renderItem(item);
		});

		// Draw the border
		legendWidth = options.width || legend.offsetWidth;
		legendHeight = legend.lastItemY + legend.lastLineHeight;


		legendHeight = legend.handleOverflow(legendHeight);

		if (legendBorderWidth || legendBackgroundColor) {
			legendWidth += padding;
			legendHeight += padding;

			if (!box) {
				legend.box = box = renderer.rect(
					0,
					0,
					legendWidth,
					legendHeight,
					options.borderRadius,
					legendBorderWidth || 0
				).attr({
					stroke: options.borderColor,
					'stroke-width': legendBorderWidth || 0,
					fill: legendBackgroundColor || NONE
				})
				.add(legendGroup)
				.shadow(options.shadow);
				box.isNew = true;

			} else if (legendWidth > 0 && legendHeight > 0) {
				box[box.isNew ? 'attr' : 'animate'](
					box.crisp(null, null, null, legendWidth, legendHeight)
				);
				box.isNew = false;
			}

			// hide the border if no items
			box[display ? 'show' : 'hide']();
		}

		legend.legendWidth = legendWidth;
		legend.legendHeight = legendHeight;

		// Now that the legend width and height are established, put the items in the 
		// final position
		each(allItems, function (item) {
			legend.positionItem(item);
		});

		// 1.x compatibility: positioning based on style
		/*var props = ['left', 'right', 'top', 'bottom'],
		prop,
		i = 4;
		while (i--) {
		prop = props[i];
		if (options.style[prop] && options.style[prop] !== 'auto') {
		options[i < 2 ? 'align' : 'verticalAlign'] = prop;
		options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1);
		}
		}*/

		if (display) {
			legendGroup.align(extend({
				width: legendWidth,
				height: legendHeight
			}, options), true, chart.spacingBox);
		}

		if (!chart.isResizing) {
			this.positionCheckboxes();
		}
	},

	/**
	* Set up the overflow handling by adding navigation with up and down arrows below the
	* legend.
	*/
	handleOverflow: function (legendHeight) {
		var legend = this,
			chart = this.chart,
			renderer = chart.renderer,
			pageCount,
			options = this.options,
			optionsY = options.y,
			alignTop = options.verticalAlign === 'top',
			spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding,
			maxHeight = options.maxHeight,
			clipHeight,
			clipRect = this.clipRect,
			navOptions = options.navigation,
			animation = pick(navOptions.animation, true),
			arrowSize = navOptions.arrowSize || 12,
			nav = this.nav;

		// Adjust the height
		if (options.layout === 'horizontal') {
			spaceHeight /= 2;
		}
		if (maxHeight) {
			spaceHeight = mathMin(spaceHeight, maxHeight);
		}

		// Reset the legend height and adjust the clipping rectangle
		if (legendHeight > spaceHeight) {

			this.clipHeight = clipHeight = spaceHeight - 20;
			this.pageCount = pageCount = mathCeil(legendHeight / clipHeight);
			this.currentPage = pick(this.currentPage, 1);
			this.fullHeight = legendHeight;

			clipRect.attr({
				height: clipHeight
			});

			// Add navigation elements
			if (!nav) {
				this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group);
				this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize)
					.on('click', function () {
						legend.scroll(-1, animation);
					})
					.add(nav);
				this.pager = renderer.text('', 15, 10)
					.css(navOptions.style)
					.add(nav);
				this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize)
					.on('click', function () {
						legend.scroll(1, animation);
					})
					.add(nav);
			}

			// Set initial position
			legend.scroll(0);

			legendHeight = spaceHeight;

		} else if (nav) {
			clipRect.attr({
				height: chart.chartHeight
			});
			nav.hide();
			this.scrollGroup.attr({
				translateY: 1
			});
		}

		return legendHeight;
	},

	/**
	* Scroll the legend by a number of pages
	* @param {Object} scrollBy
	* @param {Object} animation
	*/
	scroll: function (scrollBy, animation) {
		var pageCount = this.pageCount,
			currentPage = this.currentPage + scrollBy,
			clipHeight = this.clipHeight,
			navOptions = this.options.navigation,
			activeColor = navOptions.activeColor,
			inactiveColor = navOptions.inactiveColor,
			pager = this.pager,
			padding = this.padding;

		// When resizing while looking at the last page
		if (currentPage > pageCount) {
			currentPage = pageCount;
		}

		if (currentPage > 0) {

			if (animation !== UNDEFINED) {
				setAnimation(animation, this.chart);
			}

			this.nav.attr({
				translateX: padding,
				translateY: clipHeight + 7,
				visibility: VISIBLE
			});
			this.up.attr({
				fill: currentPage === 1 ? inactiveColor : activeColor
			})
				.css({
					cursor: currentPage === 1 ? 'default' : 'pointer'
				});
			pager.attr({
				text: currentPage + '/' + this.pageCount
			});
			this.down.attr({
				x: 18 + this.pager.getBBox().width, // adjust to text width
				fill: currentPage === pageCount ? inactiveColor : activeColor
			})
				.css({
					cursor: currentPage === pageCount ? 'default' : 'pointer'
				});

			this.scrollGroup.animate({
				translateY: -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1
			});
			pager.attr({
				text: currentPage + '/' + pageCount
			});


			this.currentPage = currentPage;
		}

	}

};


/**
* The chart class
* @param {Object} options
* @param {Function} callback Function to run when the chart has loaded
*/
function Chart(userOptions, callback) {
	// Handle regular options
	var options,
		seriesOptions = userOptions.series; // skip merging data points to increase performance
	userOptions.series = null;
	options = merge(defaultOptions, userOptions); // do the merge
	options.series = userOptions.series = seriesOptions; // set back the series data

	var optionsChart = options.chart,
		optionsMargin = optionsChart.margin,
		margin = isObject(optionsMargin) ?
			optionsMargin :
			[optionsMargin, optionsMargin, optionsMargin, optionsMargin];

	this.optionsMarginTop = pick(optionsChart.marginTop, margin[0]);
	this.optionsMarginRight = pick(optionsChart.marginRight, margin[1]);
	this.optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]);
	this.optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]);

	var chartEvents = optionsChart.events;

	this.runChartClick = chartEvents && !!chartEvents.click;
	this.callback = callback;
	this.isResizing = 0;
	this.options = options;
	//chartTitleOptions = UNDEFINED;
	//chartSubtitleOptions = UNDEFINED;

	this.axes = [];
	this.series = [];
	this.hasCartesianSeries = optionsChart.showAxes;
	//this.axisOffset = UNDEFINED;
	//this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes
	//this.inverted = UNDEFINED;
	//this.loadingShown = UNDEFINED;
	//this.container = UNDEFINED;
	//this.chartWidth = UNDEFINED;
	//this.chartHeight = UNDEFINED;
	//this.marginRight = UNDEFINED;
	//this.marginBottom = UNDEFINED;
	//this.containerWidth = UNDEFINED;
	//this.containerHeight = UNDEFINED;
	//this.oldChartWidth = UNDEFINED;
	//this.oldChartHeight = UNDEFINED;

	//this.renderTo = UNDEFINED;
	//this.renderToClone = UNDEFINED;
	//this.tracker = UNDEFINED;

	//this.spacingBox = UNDEFINED

	//this.legend = UNDEFINED;

	// Elements
	//this.chartBackground = UNDEFINED;
	//this.plotBackground = UNDEFINED;
	//this.plotBGImage = UNDEFINED;
	//this.plotBorder = UNDEFINED;
	//this.loadingDiv = UNDEFINED;
	//this.loadingSpan = UNDEFINED;

	this.init(chartEvents);
}

Chart.prototype = {

	/**
	* Initialize an individual series, called internally before render time
	*/
	initSeries: function (options) {
		var chart = this,
			optionsChart = chart.options.chart,
			type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
			series = new seriesTypes[type]();

		series.init(this, options);
		return series;
	},

	/**
	* Add a series dynamically after  time
	*
	* @param {Object} options The config options
	* @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
	* @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	*    configuration
	*
	* @return {Object} series The newly created series object
	*/
	addSeries: function (options, redraw, animation) {
		var series,
			chart = this;

		if (options) {
			setAnimation(animation, chart);
			redraw = pick(redraw, true); // defaults to true

			fireEvent(chart, 'addSeries', { options: options }, function () {
				series = chart.initSeries(options);

				chart.isDirtyLegend = true; // the series array is out of sync with the display
				if (redraw) {
					chart.redraw();
				}
			});
		}

		return series;
	},

	/**
	* Check whether a given point is within the plot area
	*
	* @param {Number} plotX Pixel x relative to the plot area
	* @param {Number} plotY Pixel y relative to the plot area
	* @param {Boolean} inverted Whether the chart is inverted
	*/
	isInsidePlot: function (plotX, plotY, inverted) {
		var x = inverted ? plotY : plotX,
			y = inverted ? plotX : plotY;

		return x >= 0 &&
			x <= this.plotWidth &&
			y >= 0 &&
			y <= this.plotHeight;
	},

	/**
	* Adjust all axes tick amounts
	*/
	adjustTickAmounts: function () {
		if (this.options.chart.alignTicks !== false) {
			each(this.axes, function (axis) {
				axis.adjustTickAmount();
			});
		}
		this.maxTicks = null;
	},

	/**
	* Redraw legend, axes or series based on updated data
	*
	* @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	*    configuration
	*/
	redraw: function (animation) {
		var chart = this,
			axes = chart.axes,
			series = chart.series,
			tracker = chart.tracker,
			legend = chart.legend,
			redrawLegend = chart.isDirtyLegend,
			hasStackedSeries,
			isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
			seriesLength = series.length,
			i = seriesLength,
			serie,
			renderer = chart.renderer,
			isHiddenChart = renderer.isHidden(),
			afterRedraw = [];

		setAnimation(animation, chart);

		if (isHiddenChart) {
			chart.cloneRenderTo();
		}

		// link stacked series
		while (i--) {
			serie = series[i];
			if (serie.isDirty && serie.options.stacking) {
				hasStackedSeries = true;
				break;
			}
		}
		if (hasStackedSeries) { // mark others as dirty
			i = seriesLength;
			while (i--) {
				serie = series[i];
				if (serie.options.stacking) {
					serie.isDirty = true;
				}
			}
		}

		// handle updated data in the series
		each(series, function (serie) {
			if (serie.isDirty) { // prepare the data so axis can read it
				if (serie.options.legendType === 'point') {
					redrawLegend = true;
				}
			}
		});

		// handle added or removed series
		if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
			// draw legend graphics
			legend.render();

			chart.isDirtyLegend = false;
		}


		if (chart.hasCartesianSeries) {
			if (!chart.isResizing) {

				// reset maxTicks
				chart.maxTicks = null;

				// set axes scales
				each(axes, function (axis) {
					axis.setScale();
				});
			}
			chart.adjustTickAmounts();
			chart.getMargins();

			// redraw axes
			each(axes, function (axis) {

				// Fire 'afterSetExtremes' only if extremes are set
				if (axis.isDirtyExtremes) { // #821
					axis.isDirtyExtremes = false;
					afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119)
						fireEvent(axis, 'afterSetExtremes', axis.getExtremes()); // #747, #751
					});
				}

				if (axis.isDirty || isDirtyBox || hasStackedSeries) {
					axis.redraw();
					isDirtyBox = true; // #792
				}
			});


		}

		// the plot areas size has changed
		if (isDirtyBox) {
			chart.drawChartBox();
		}


		// redraw affected series
		each(series, function (serie) {
			if (serie.isDirty && serie.visible &&
					(!serie.isCartesian || serie.xAxis)) { // issue #153
				serie.redraw();
			}
		});


		// move tooltip or reset
		if (tracker && tracker.resetTracker) {
			tracker.resetTracker(true);
		}

		// redraw if canvas
		renderer.draw();

		// fire the event
		fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw

		if (isHiddenChart) {
			chart.cloneRenderTo(true);
		}

		// Fire callbacks that are put on hold until after the redraw
		each(afterRedraw, function (callback) {
			callback.call();
		});
	},



	/**
	* Dim the chart and show a loading text or symbol
	* @param {String} str An optional text to show in the loading label instead of the default one
	*/
	showLoading: function (str) {
		var chart = this,
			options = chart.options,
			loadingDiv = chart.loadingDiv;

		var loadingOptions = options.loading;

		// create the layer at the first call
		if (!loadingDiv) {
			chart.loadingDiv = loadingDiv = createElement(DIV, {
				className: PREFIX + 'loading'
			}, extend(loadingOptions.style, {
				left: chart.plotLeft + PX,
				top: chart.plotTop + PX,
				width: chart.plotWidth + PX,
				height: chart.plotHeight + PX,
				zIndex: 10,
				display: NONE
			}), chart.container);

			chart.loadingSpan = createElement(
				'span',
				null,
				loadingOptions.labelStyle,
				loadingDiv
			);

		}

		// update text
		chart.loadingSpan.innerHTML = str || options.lang.loading;

		// show it
		if (!chart.loadingShown) {
			css(loadingDiv, { opacity: 0, display: '' });
			animate(loadingDiv, {
				opacity: loadingOptions.style.opacity
			}, {
				duration: loadingOptions.showDuration || 0
			});
			chart.loadingShown = true;
		}
	},

	/**
	* Hide the loading layer
	*/
	hideLoading: function () {
		var options = this.options,
			loadingDiv = this.loadingDiv;

		if (loadingDiv) {
			animate(loadingDiv, {
				opacity: 0
			}, {
				duration: options.loading.hideDuration || 100,
				complete: function () {
					css(loadingDiv, { display: NONE });
				}
			});
		}
		this.loadingShown = false;
	},

	/**
	* Get an axis, series or point object by id.
	* @param id {String} The id as given in the configuration options
	*/
	get: function (id) {
		var chart = this,
			axes = chart.axes,
			series = chart.series;

		var i,
			j,
			points;

		// search axes
		for (i = 0; i < axes.length; i++) {
			if (axes[i].options.id === id) {
				return axes[i];
			}
		}

		// search series
		for (i = 0; i < series.length; i++) {
			if (series[i].options.id === id) {
				return series[i];
			}
		}

		// search points
		for (i = 0; i < series.length; i++) {
			points = series[i].points || [];
			for (j = 0; j < points.length; j++) {
				if (points[j].id === id) {
					return points[j];
				}
			}
		}
		return null;
	},

	/**
	* Create the Axis instances based on the config options
	*/
	getAxes: function () {
		var chart = this,
			options = this.options;

		var xAxisOptions = options.xAxis || {},
			yAxisOptions = options.yAxis || {},
			optionsArray,
			axis;

		// make sure the options are arrays and add some members
		xAxisOptions = splat(xAxisOptions);
		each(xAxisOptions, function (axis, i) {
			axis.index = i;
			axis.isX = true;
		});

		yAxisOptions = splat(yAxisOptions);
		each(yAxisOptions, function (axis, i) {
			axis.index = i;
		});

		// concatenate all axis options into one array
		optionsArray = xAxisOptions.concat(yAxisOptions);

		each(optionsArray, function (axisOptions) {
			axis = new Axis(chart, axisOptions);
		});

		chart.adjustTickAmounts();
	},


	/**
	* Get the currently selected points from all series
	*/
	getSelectedPoints: function () {
		var points = [];
		each(this.series, function (serie) {
			points = points.concat(grep(serie.points, function (point) {
				return point.selected;
			}));
		});
		return points;
	},

	/**
	* Get the currently selected series
	*/
	getSelectedSeries: function () {
		return grep(this.series, function (serie) {
			return serie.selected;
		});
	},

	/**
	* Display the zoom button
	*/
	showResetZoom: function () {
		var chart = this,
			lang = defaultOptions.lang,
			btnOptions = chart.options.chart.resetZoomButton,
			theme = btnOptions.theme,
			states = theme.states,
			alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';

		this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover)
			.attr({
				align: btnOptions.position.align,
				title: lang.resetZoomTitle
			})
			.add()
			.align(btnOptions.position, false, chart[alignTo]);
		this.resetZoomButton.alignTo = alignTo;

	},

	/**
	* Zoom out to 1:1
	*/
	zoomOut: function () {
		var chart = this,
			resetZoomButton = chart.resetZoomButton;

		fireEvent(chart, 'selection', { resetSelection: true }, function () { chart.zoom(); });
		if (resetZoomButton) {
			chart.resetZoomButton = resetZoomButton.destroy();
		}
	},

	/**
	* Zoom into a given portion of the chart given by axis coordinates
	* @param {Object} event
	*/
	zoom: function (event) {
		var chart = this,
			hasZoomed;

		// if zoom is called with no arguments, reset the axes
		if (!event || event.resetSelection) {
			each(chart.axes, function (axis) {
				hasZoomed = axis.zoom();
			});
		} else { // else, zoom in on all axes
			each(event.xAxis.concat(event.yAxis), function (axisData) {
				var axis = axisData.axis;

				// don't zoom more than minRange
				if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
					hasZoomed = axis.zoom(axisData.min, axisData.max);
				}
			});
		}

		// Show the Reset zoom button
		if (!chart.resetZoomButton) {
			chart.showResetZoom();
		}


		// Redraw
		if (hasZoomed) {
			chart.redraw(
				pick(chart.options.chart.animation, chart.pointCount < 100) // animation
			);
		}
	},

	/**
	* Pan the chart by dragging the mouse across the pane. This function is called
	* on mouse move, and the distance to pan is computed from chartX compared to
	* the first chartX position in the dragging operation.
	*/
	pan: function (chartX) {
		var chart = this;

		var xAxis = chart.xAxis[0],
			mouseDownX = chart.mouseDownX,
			halfPointRange = xAxis.pointRange / 2,
			extremes = xAxis.getExtremes(),
			newMin = xAxis.translate(mouseDownX - chartX, true) + halfPointRange,
			newMax = xAxis.translate(mouseDownX + chart.plotWidth - chartX, true) - halfPointRange,
			hoverPoints = chart.hoverPoints;

		// remove active points for shared tooltip
		if (hoverPoints) {
			each(hoverPoints, function (point) {
				point.setState();
			});
		}

		if (xAxis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {
			xAxis.setExtremes(newMin, newMax, true, false, { trigger: 'pan' });
		}

		chart.mouseDownX = chartX; // set new reference for next run
		css(chart.container, { cursor: 'move' });
	},

	/**
	* Show the title and subtitle of the chart
	*
	* @param titleOptions {Object} New title options
	* @param subtitleOptions {Object} New subtitle options
	*
	*/
	setTitle: function (titleOptions, subtitleOptions) {
		var chart = this,
			options = chart.options,
			chartTitleOptions,
			chartSubtitleOptions;

		chart.chartTitleOptions = chartTitleOptions = merge(options.title, titleOptions);
		chart.chartSubtitleOptions = chartSubtitleOptions = merge(options.subtitle, subtitleOptions);

		// add title and subtitle
		each([
			['title', titleOptions, chartTitleOptions],
			['subtitle', subtitleOptions, chartSubtitleOptions]
		], function (arr) {
			var name = arr[0],
				title = chart[name],
				titleOptions = arr[1],
				chartTitleOptions = arr[2];

			if (title && titleOptions) {
				chart[name] = title = title.destroy(); // remove old
			}

			if (chartTitleOptions && chartTitleOptions.text && !title) {
				chart[name] = chart.renderer.text(
					chartTitleOptions.text,
					0,
					0,
					chartTitleOptions.useHTML
				)
				.attr({
					align: chartTitleOptions.align,
					'class': PREFIX + name,
					zIndex: chartTitleOptions.zIndex || 4
				})
				.css(chartTitleOptions.style)
				.add()
				.align(chartTitleOptions, false, chart.spacingBox);
			}
		});

	},

	/**
	* Get chart width and height according to options and container size
	*/
	getChartSize: function () {
		var chart = this,
			optionsChart = chart.options.chart,
			renderTo = chart.renderToClone || chart.renderTo;

		// get inner width and height from jQuery (#824)
		chart.containerWidth = adapterRun(renderTo, 'width');
		chart.containerHeight = adapterRun(renderTo, 'height');

		chart.chartWidth = optionsChart.width || chart.containerWidth || 600;
		chart.chartHeight = optionsChart.height ||
		// the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
			(chart.containerHeight > 19 ? chart.containerHeight : 400);
	},

	/**
	* Create a clone of the chart's renderTo div and place it outside the viewport to allow
	* size computation on chart.render and chart.redraw
	*/
	cloneRenderTo: function (revert) {
		var clone = this.renderToClone,
			container = this.container;

		// Destroy the clone and bring the container back to the real renderTo div
		if (revert) {
			if (clone) {
				this.renderTo.appendChild(container);
				discardElement(clone);
				delete this.renderToClone;
			}

			// Set up the clone
		} else {
			if (container) {
				this.renderTo.removeChild(container); // do not clone this
			}
			this.renderToClone = clone = this.renderTo.cloneNode(0);
			css(clone, {
				position: ABSOLUTE,
				top: '-9999px',
				display: 'block' // #833
			});
			doc.body.appendChild(clone);
			if (container) {
				clone.appendChild(container);
			}
		}
	},

	/**
	* Get the containing element, determine the size and create the inner container
	* div to hold the chart
	*/
	getContainer: function () {
		var chart = this,
			container,
			optionsChart = chart.options.chart,
			chartWidth,
			chartHeight,
			renderTo,
			containerId;

		chart.renderTo = renderTo = optionsChart.renderTo;
		containerId = PREFIX + idCounter++;

		if (isString(renderTo)) {
			chart.renderTo = renderTo = doc.getElementById(renderTo);
		}

		// Display an error if the renderTo is wrong
		if (!renderTo) {
			error(13, true);
		}

		// remove previous chart
		renderTo.innerHTML = '';

		// If the container doesn't have an offsetWidth, it has or is a child of a node
		// that has display:none. We need to temporarily move it out to a visible
		// state to determine the size, else the legend and tooltips won't render
		// properly
		if (!renderTo.offsetWidth) {
			chart.cloneRenderTo();
		}

		// get the width and height
		chart.getChartSize();
		chartWidth = chart.chartWidth;
		chartHeight = chart.chartHeight;

		// create the inner container
		chart.container = container = createElement(DIV, {
			className: PREFIX + 'container' +
					(optionsChart.className ? ' ' + optionsChart.className : ''),
			id: containerId
		}, extend({
			position: RELATIVE,
			overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
			// content overflow in IE
			width: chartWidth + PX,
			height: chartHeight + PX,
			textAlign: 'left',
			lineHeight: 'normal', // #427
			zIndex: 0 // #1072
		}, optionsChart.style),
			chart.renderToClone || renderTo
		);

		chart.renderer =
			optionsChart.forExport ? // force SVG, used for SVG export
				new SVGRenderer(container, chartWidth, chartHeight, true) :
				new Renderer(container, chartWidth, chartHeight);

		if (useCanVG) {
			// If we need canvg library, extend and configure the renderer
			// to get the tracker for translating mouse events
			chart.renderer.create(chart, container, chartWidth, chartHeight);
		}
	},

	/**
	* Calculate margins by rendering axis labels in a preliminary position. Title,
	* subtitle and legend have already been rendered at this stage, but will be
	* moved into their final positions
	*/
	getMargins: function () {
		var chart = this,
			optionsChart = chart.options.chart,
			spacingTop = optionsChart.spacingTop,
			spacingRight = optionsChart.spacingRight,
			spacingBottom = optionsChart.spacingBottom,
			spacingLeft = optionsChart.spacingLeft,
			axisOffset,
			legend = chart.legend,
			optionsMarginTop = chart.optionsMarginTop,
			optionsMarginLeft = chart.optionsMarginLeft,
			optionsMarginRight = chart.optionsMarginRight,
			optionsMarginBottom = chart.optionsMarginBottom,
			chartTitleOptions = chart.chartTitleOptions,
			chartSubtitleOptions = chart.chartSubtitleOptions,
			legendOptions = chart.options.legend,
			legendMargin = pick(legendOptions.margin, 10),
			legendX = legendOptions.x,
			legendY = legendOptions.y,
			align = legendOptions.align,
			verticalAlign = legendOptions.verticalAlign,
			titleOffset;

		chart.resetMargins();
		axisOffset = chart.axisOffset;

		// adjust for title and subtitle
		if ((chart.title || chart.subtitle) && !defined(chart.optionsMarginTop)) {
			titleOffset = mathMax(
				(chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0,
				(chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0
			);
			if (titleOffset) {
				chart.plotTop = mathMax(chart.plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
			}
		}
		// adjust for legend
		if (legend.display && !legendOptions.floating) {
			if (align === 'right') { // horizontal alignment handled first
				if (!defined(optionsMarginRight)) {
					chart.marginRight = mathMax(
						chart.marginRight,
						legend.legendWidth - legendX + legendMargin + spacingRight
					);
				}
			} else if (align === 'left') {
				if (!defined(optionsMarginLeft)) {
					chart.plotLeft = mathMax(
						chart.plotLeft,
						legend.legendWidth + legendX + legendMargin + spacingLeft
					);
				}

			} else if (verticalAlign === 'top') {
				if (!defined(optionsMarginTop)) {
					chart.plotTop = mathMax(
						chart.plotTop,
						legend.legendHeight + legendY + legendMargin + spacingTop
					);
				}

			} else if (verticalAlign === 'bottom') {
				if (!defined(optionsMarginBottom)) {
					chart.marginBottom = mathMax(
						chart.marginBottom,
						legend.legendHeight - legendY + legendMargin + spacingBottom
					);
				}
			}
		}

		// adjust for scroller
		if (chart.extraBottomMargin) {
			chart.marginBottom += chart.extraBottomMargin;
		}
		if (chart.extraTopMargin) {
			chart.plotTop += chart.extraTopMargin;
		}

		// pre-render axes to get labels offset width
		if (chart.hasCartesianSeries) {
			each(chart.axes, function (axis) {
				axis.getOffset();
			});
		}

		if (!defined(optionsMarginLeft)) {
			chart.plotLeft += axisOffset[3];
		}
		if (!defined(optionsMarginTop)) {
			chart.plotTop += axisOffset[0];
		}
		if (!defined(optionsMarginBottom)) {
			chart.marginBottom += axisOffset[2];
		}
		if (!defined(optionsMarginRight)) {
			chart.marginRight += axisOffset[1];
		}

		chart.setChartSize();

	},

	/**
	* Add the event handlers necessary for auto resizing
	*
	*/
	initReflow: function () {
		var chart = this,
			optionsChart = chart.options.chart,
			renderTo = chart.renderTo,
			reflowTimeout;

		function reflow(e) {
			var width = optionsChart.width || adapterRun(renderTo, 'width'),
				height = optionsChart.height || adapterRun(renderTo, 'height'),
				target = e ? e.target : win; // #805 - MooTools doesn't supply e

			// Width and height checks for display:none. Target is doc in IE8 and Opera,
			// win in Firefox, Chrome and IE9.
			if (width && height && (target === win || target === doc)) {

				if (width !== chart.containerWidth || height !== chart.containerHeight) {
					clearTimeout(reflowTimeout);
					chart.reflowTimeout = reflowTimeout = setTimeout(function () {
						if (chart.container) { // It may have been destroyed in the meantime (#1257)
							chart.resize(width, height, false);
						}
					}, 100);
				}
				chart.containerWidth = width;
				chart.containerHeight = height;
			}
		}
		addEvent(win, 'resize', reflow);
		addEvent(chart, 'destroy', function () {
			removeEvent(win, 'resize', reflow);
		});
	},

	/**
	* Resize the chart to a given width and height
	* @param {Number} width
	* @param {Number} height
	* @param {Object|Boolean} animation
	*/
	// TODO: This method is called setSize in the api
	resize: function (width, height, animation) {
		var chart = this,
			chartWidth,
			chartHeight,
			spacingBox,
			resetZoomButton = chart.resetZoomButton,
			chartTitle = chart.title,
			chartSubtitle = chart.subtitle,
			fireEndResize;

		// Handle the isResizing counter
		chart.isResizing += 1;
		fireEndResize = function () {
			if (chart) {
				fireEvent(chart, 'endResize', null, function () {
					chart.isResizing -= 1;
				});
			}
		};

		// set the animation for the current process
		setAnimation(animation, chart);

		chart.oldChartHeight = chart.chartHeight;
		chart.oldChartWidth = chart.chartWidth;
		if (defined(width)) {
			chart.chartWidth = chartWidth = mathRound(width);
		}
		if (defined(height)) {
			chart.chartHeight = chartHeight = mathRound(height);
		}

		css(chart.container, {
			width: chartWidth + PX,
			height: chartHeight + PX
		});
		chart.renderer.setSize(chartWidth, chartHeight, animation);

		// update axis lengths for more correct tick intervals:
		chart.plotWidth = chartWidth - chart.plotLeft - chart.marginRight;
		chart.plotHeight = chartHeight - chart.plotTop - chart.marginBottom;

		// handle axes
		chart.maxTicks = null;
		each(chart.axes, function (axis) {
			axis.isDirty = true;
			axis.setScale();
		});

		// make sure non-cartesian series are also handled
		each(chart.series, function (serie) {
			serie.isDirty = true;
		});

		chart.isDirtyLegend = true; // force legend redraw
		chart.isDirtyBox = true; // force redraw of plot and chart border

		chart.getMargins();

		// move titles
		spacingBox = chart.spacingBox;
		if (chartTitle) {
			chartTitle.align(null, null, spacingBox);
		}
		if (chartSubtitle) {
			chartSubtitle.align(null, null, spacingBox);
		}

		// Move resize button (#1115)
		if (resetZoomButton && resetZoomButton.align) {
			resetZoomButton.align(null, null, chart[resetZoomButton.alignTo]);
		}

		chart.redraw(animation);


		chart.oldChartHeight = null;
		fireEvent(chart, 'resize');

		// fire endResize and set isResizing back
		// If animation is disabled, fire without delay
		if (globalAnimation === false) {
			fireEndResize();
		} else { // else set a timeout with the animation duration
			setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
		}
	},

	/**
	* Set the public chart properties. This is done before and after the pre-render
	* to determine margin sizes
	*/
	setChartSize: function () {
		var chart = this,
			inverted = chart.inverted,
			chartWidth = chart.chartWidth,
			chartHeight = chart.chartHeight,
			optionsChart = chart.options.chart,
			spacingTop = optionsChart.spacingTop,
			spacingRight = optionsChart.spacingRight,
			spacingBottom = optionsChart.spacingBottom,
			spacingLeft = optionsChart.spacingLeft,
			plotLeft,
			plotTop,
			plotWidth,
			plotHeight,
			plotBorderWidth;

		chart.plotLeft = plotLeft = mathRound(chart.plotLeft);
		chart.plotTop = plotTop = mathRound(chart.plotTop);
		chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - chart.marginRight);
		chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - chart.marginBottom);

		chart.plotSizeX = inverted ? plotHeight : plotWidth;
		chart.plotSizeY = inverted ? plotWidth : plotHeight;

		chart.plotBorderWidth = plotBorderWidth = optionsChart.plotBorderWidth || 0;

		// Set boxes used for alignment
		chart.spacingBox = {
			x: spacingLeft,
			y: spacingTop,
			width: chartWidth - spacingLeft - spacingRight,
			height: chartHeight - spacingTop - spacingBottom
		};
		chart.plotBox = {
			x: plotLeft,
			y: plotTop,
			width: plotWidth,
			height: plotHeight
		};
		chart.clipBox = {
			x: plotBorderWidth / 2,
			y: plotBorderWidth / 2,
			width: chart.plotSizeX - plotBorderWidth,
			height: chart.plotSizeY - plotBorderWidth
		};

		each(chart.axes, function (axis) {
			axis.setAxisSize();
			axis.setAxisTranslation();
		});
	},

	/**
	* Initial margins before auto size margins are applied
	*/
	resetMargins: function () {
		var chart = this,
			optionsChart = chart.options.chart,
			spacingTop = optionsChart.spacingTop,
			spacingRight = optionsChart.spacingRight,
			spacingBottom = optionsChart.spacingBottom,
			spacingLeft = optionsChart.spacingLeft;

		chart.plotTop = pick(chart.optionsMarginTop, spacingTop);
		chart.marginRight = pick(chart.optionsMarginRight, spacingRight);
		chart.marginBottom = pick(chart.optionsMarginBottom, spacingBottom);
		chart.plotLeft = pick(chart.optionsMarginLeft, spacingLeft);
		chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
	},

	/**
	* Draw the borders and backgrounds for chart and plot area
	*/
	drawChartBox: function () {
		var chart = this,
			optionsChart = chart.options.chart,
			renderer = chart.renderer,
			chartWidth = chart.chartWidth,
			chartHeight = chart.chartHeight,
			chartBackground = chart.chartBackground,
			plotBackground = chart.plotBackground,
			plotBorder = chart.plotBorder,
			plotBGImage = chart.plotBGImage,
			chartBorderWidth = optionsChart.borderWidth || 0,
			chartBackgroundColor = optionsChart.backgroundColor,
			plotBackgroundColor = optionsChart.plotBackgroundColor,
			plotBackgroundImage = optionsChart.plotBackgroundImage,
			plotBorderWidth = optionsChart.plotBorderWidth || 0,
			mgn,
			bgAttr,
			plotLeft = chart.plotLeft,
			plotTop = chart.plotTop,
			plotWidth = chart.plotWidth,
			plotHeight = chart.plotHeight,
			plotBox = chart.plotBox,
			clipRect = chart.clipRect,
			clipBox = chart.clipBox;

		// Chart area
		mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);

		if (chartBorderWidth || chartBackgroundColor) {
			if (!chartBackground) {

				bgAttr = {
					fill: chartBackgroundColor || NONE
				};
				if (chartBorderWidth) { // #980
					bgAttr.stroke = optionsChart.borderColor;
					bgAttr['stroke-width'] = chartBorderWidth;
				}
				chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
						optionsChart.borderRadius, chartBorderWidth)
					.attr(bgAttr)
					.add()
					.shadow(optionsChart.shadow);

			} else { // resize
				chartBackground.animate(
					chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
				);
			}
		}


		// Plot background
		if (plotBackgroundColor) {
			if (!plotBackground) {
				chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
					.attr({
						fill: plotBackgroundColor
					})
					.add()
					.shadow(optionsChart.plotShadow);
			} else {
				plotBackground.animate(plotBox);
			}
		}
		if (plotBackgroundImage) {
			if (!plotBGImage) {
				chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
					.add();
			} else {
				plotBGImage.animate(plotBox);
			}
		}

		// Plot clip
		if (!clipRect) {
			chart.clipRect = renderer.clipRect(clipBox);
		} else {
			clipRect.animate({
				width: clipBox.width,
				height: clipBox.height
			});
		}

		// Plot area border
		if (plotBorderWidth) {
			if (!plotBorder) {
				chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, plotBorderWidth)
					.attr({
						stroke: optionsChart.plotBorderColor,
						'stroke-width': plotBorderWidth,
						zIndex: 1
					})
					.add();
			} else {
				plotBorder.animate(
					plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
				);
			}
		}

		// reset
		chart.isDirtyBox = false;
	},

	/**
	* Detect whether a certain chart property is needed based on inspecting its options
	* and series. This mainly applies to the chart.invert property, and in extensions to 
	* the chart.angular and chart.polar properties.
	*/
	propFromSeries: function () {
		var chart = this,
			optionsChart = chart.options.chart,
			klass,
			seriesOptions = chart.options.series,
			i,
			value;


		each(['inverted', 'angular', 'polar'], function (key) {

			// The default series type's class
			klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType];

			// Get the value from available chart-wide properties
			value = (
				chart[key] || // 1. it is set before
				optionsChart[key] || // 2. it is set in the options
				(klass && klass.prototype[key]) // 3. it's default series class requires it
			);

			// 4. Check if any the chart's series require it
			i = seriesOptions && seriesOptions.length;
			while (!value && i--) {
				klass = seriesTypes[seriesOptions[i].type];
				if (klass && klass.prototype[key]) {
					value = true;
				}
			}

			// Set the chart property
			chart[key] = value;
		});

	},

	/**
	* Render all graphics for the chart
	*/
	render: function () {
		var chart = this,
			axes = chart.axes,
			renderer = chart.renderer,
			options = chart.options;

		var labels = options.labels,
			credits = options.credits,
			creditsHref;

		// Title
		chart.setTitle();


		// Legend
		chart.legend = new Legend(chart);

		// Get margins by pre-rendering axes
		// set axes scales
		each(axes, function (axis) {
			axis.setScale();
		});
		chart.getMargins();

		chart.maxTicks = null; // reset for second pass
		each(axes, function (axis) {
			axis.setTickPositions(true); // update to reflect the new margins
			axis.setMaxTicks();
		});
		chart.adjustTickAmounts();
		chart.getMargins(); // second pass to check for new labels


		// Draw the borders and backgrounds
		chart.drawChartBox();


		// Axes
		if (chart.hasCartesianSeries) {
			each(axes, function (axis) {
				axis.render();
			});
		}

		// The series
		if (!chart.seriesGroup) {
			chart.seriesGroup = renderer.g('series-group')
				.attr({ zIndex: 3 })
				.add();
		}
		each(chart.series, function (serie) {
			serie.translate();
			serie.setTooltipPoints();
			serie.render();
		});

		// Labels
		if (labels.items) {
			each(labels.items, function (label) {
				var style = extend(labels.style, label.style),
					x = pInt(style.left) + chart.plotLeft,
					y = pInt(style.top) + chart.plotTop + 12;

				// delete to prevent rewriting in IE
				delete style.left;
				delete style.top;

				renderer.text(
					label.html,
					x,
					y
				)
				.attr({ zIndex: 2 })
				.css(style)
				.add();

			});
		}

		// Credits
		if (credits.enabled && !chart.credits) {
			creditsHref = credits.href;
			chart.credits = renderer.text(
				credits.text,
				0,
				0
			)
			.on('click', function () {
				if (creditsHref) {
					location.href = creditsHref;
				}
			})
			.attr({
				align: credits.position.align,
				zIndex: 8
			})
			.css(credits.style)
			.add()
			.align(credits.position);
		}

		// Set flag
		chart.hasRendered = true;

	},

	/**
	* Clean up memory usage
	*/
	destroy: function () {
		var chart = this,
			axes = chart.axes,
			series = chart.series,
			container = chart.container,
			i,
			parentNode = container && container.parentNode;

		// fire the chart.destoy event
		fireEvent(chart, 'destroy');

		// remove events
		removeEvent(chart);

		// ==== Destroy collections:
		// Destroy axes
		i = axes.length;
		while (i--) {
			axes[i] = axes[i].destroy();
		}

		// Destroy each series
		i = series.length;
		while (i--) {
			series[i] = series[i].destroy();
		}

		// ==== Destroy chart properties:
		each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'tracker', 'scroller', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {
			var prop = chart[name];

			if (prop && prop.destroy) {
				chart[name] = prop.destroy();
			}
		});

		// remove container and all SVG
		if (container) { // can break in IE when destroyed before finished loading
			container.innerHTML = '';
			removeEvent(container);
			if (parentNode) {
				discardElement(container);
			}

		}

		// clean it all up
		for (i in chart) {
			delete chart[i];
		}

	},

	/**
	* Prepare for first rendering after all data are loaded
	*/
	firstRender: function () {
		var chart = this,
			options = chart.options,
			callback = chart.callback;

		// VML namespaces can't be added until after complete. Listening
		// for Perini's doScroll hack is not enough.
		var ONREADYSTATECHANGE = 'onreadystatechange',
		COMPLETE = 'complete';
		// Note: in spite of JSLint's complaints, win == win.top is required
		/*jslint eqeq: true*/
		if ((!hasSVG && (win == win.top && doc.readyState !== COMPLETE)) || (useCanVG && !win.canvg)) {
			/*jslint eqeq: false*/
			if (useCanVG) {
				// Delay rendering until canvg library is downloaded and ready
				CanVGController.push(function () { chart.firstRender(); }, options.global.canvasToolsURL);
			} else {
				doc.attachEvent(ONREADYSTATECHANGE, function () {
					doc.detachEvent(ONREADYSTATECHANGE, chart.firstRender);
					if (doc.readyState === COMPLETE) {
						chart.firstRender();
					}
				});
			}
			return;
		}

		// create the container
		chart.getContainer();

		// Run an early event after the container and renderer are established
		fireEvent(chart, 'init');

		// Initialize range selector for stock charts
		if (Highcharts.RangeSelector && options.rangeSelector.enabled) {
			chart.rangeSelector = new Highcharts.RangeSelector(chart);
		}

		chart.resetMargins();
		chart.setChartSize();

		// Set the common chart properties (mainly invert) from the given series
		chart.propFromSeries();

		// get axes
		chart.getAxes();

		// Initialize the series
		each(options.series || [], function (serieOptions) {
			chart.initSeries(serieOptions);
		});

		// Run an event where series and axes can be added
		//fireEvent(chart, 'beforeRender');

		// Initialize scroller for stock charts
		if (Highcharts.Scroller && (options.navigator.enabled || options.scrollbar.enabled)) {
			chart.scroller = new Highcharts.Scroller(chart);
		}

		// depends on inverted and on margins being set
		chart.tracker = new MouseTracker(chart, options);

		chart.render();

		// add canvas
		chart.renderer.draw();
		// run callbacks
		if (callback) {
			callback.apply(chart, [chart]);
		}
		each(chart.callbacks, function (fn) {
			fn.apply(chart, [chart]);
		});


		// If the chart was rendered outside the top container, put it back in
		chart.cloneRenderTo(true);

		fireEvent(chart, 'load');

	},

	init: function (chartEvents) {
		var chart = this,
			optionsChart = chart.options.chart,
			eventType;

		// Run chart

		// Set up auto resize
		if (optionsChart.reflow !== false) {
			addEvent(chart, 'load', chart.initReflow);
		}

		// Chart event handlers
		if (chartEvents) {
			for (eventType in chartEvents) {
				addEvent(chart, eventType, chartEvents[eventType]);
			}
		}

		chart.xAxis = [];
		chart.yAxis = [];

		// Expose methods and variables
		chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
		chart.setSize = chart.resize;
		chart.pointCount = 0;
		chart.counters = new ChartCounters();
		/*
		if ($) $(function () {
		$container = $('#container');
		var origChartWidth,
		origChartHeight;
		if ($container) {
		$('<button>+</button>')
		.insertBefore($container)
		.click(function () {
		if (origChartWidth === UNDEFINED) {
		origChartWidth = chartWidth;
		origChartHeight = chartHeight;
		}
		chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
		});
		$('<button>-</button>')
		.insertBefore($container)
		.click(function () {
		if (origChartWidth === UNDEFINED) {
		origChartWidth = chartWidth;
		origChartHeight = chartHeight;
		}
		chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
		});
		$('<button>1:1</button>')
		.insertBefore($container)
		.click(function () {
		if (origChartWidth === UNDEFINED) {
		origChartWidth = chartWidth;
		origChartHeight = chartHeight;
		}
		chart.resize(origChartWidth, origChartHeight);
		});
		}
		})
		*/

		chart.firstRender();
	}
}; // end Chart

// Hook for exporting module
Chart.prototype.callbacks = [];
/**
* The Point object and prototype. Inheritable and used as base for PiePoint
*/
var Point = function () { };
Point.prototype = {

	/**
	* Initialize the point
	* @param {Object} series The series object containing this point
	* @param {Object} options The data in either number, array or object format
	*/
	init: function (series, options, x) {
		var point = this,
			counters = series.chart.counters,
			defaultColors;
		point.series = series;
		point.applyOptions(options, x);
		point.pointAttr = {};

		if (series.options.colorByPoint) {
			defaultColors = series.chart.options.colors;
			point.color = point.color || defaultColors[counters.color++];

			// loop back to zero
			counters.wrapColor(defaultColors.length);
		}

		series.chart.pointCount++;
		return point;
	},
	/**
	* Apply the options containing the x and y data and possible some extra properties.
	* This is called on point init or from point.update.
	*
	* @param {Object} options
	*/
	applyOptions: function (options, x) {
		var point = this,
			series = point.series,
			optionsType = typeof options;

		point.config = options;

		// onedimensional array input
		if (optionsType === 'number' || options === null) {
			point.y = options;
		} else if (typeof options[0] === 'number') { // two-dimentional array
			point.x = options[0];
			point.y = options[1];
		} else if (optionsType === 'object' && typeof options.length !== 'number') { // object input
			// copy options directly to point
			extend(point, options);
			point.options = options;

			// This is the fastest way to detect if there are individual point dataLabels that need 
			// to be considered in drawDataLabels. These can only occur in object configs.
			if (options.dataLabels) {
				series._hasPointLabels = true;
			}

			// Same approach as above for markers
			if (options.marker) {
				series._hasPointMarkers = true;
			}
		} else if (typeof options[0] === 'string') { // categorized data with name in first position
			point.name = options[0];
			point.y = options[1];
		}

		/*
		* If no x is set by now, get auto incremented value. All points must have an
		* x value, however the y value can be null to create a gap in the series
		*/
		// todo: skip this? It is only used in applyOptions, in translate it should not be used
		if (point.x === UNDEFINED) {
			point.x = x === UNDEFINED ? series.autoIncrement() : x;
		}

	},

	/**
	* Destroy a point to clear memory. Its reference still stays in series.data.
	*/
	destroy: function () {
		var point = this,
			series = point.series,
			chart = series.chart,
			hoverPoints = chart.hoverPoints,
			prop;

		chart.pointCount--;

		if (hoverPoints) {
			point.setState();
			erase(hoverPoints, point);
			if (!hoverPoints.length) {
				chart.hoverPoints = null;
			}

		}
		if (point === chart.hoverPoint) {
			point.onMouseOut();
		}

		// remove all events
		if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
			removeEvent(point);
			point.destroyElements();
		}

		if (point.legendItem) { // pies have legend items
			chart.legend.destroyItem(point);
		}

		for (prop in point) {
			point[prop] = null;
		}


	},

	/**
	* Destroy SVG elements associated with the point
	*/
	destroyElements: function () {
		var point = this,
			props = ['graphic', 'tracker', 'dataLabel', 'group', 'connector', 'shadowGroup'],
			prop,
			i = 6;
		while (i--) {
			prop = props[i];
			if (point[prop]) {
				point[prop] = point[prop].destroy();
			}
		}
	},

	/**
	* Return the configuration hash needed for the data label and tooltip formatters
	*/
	getLabelConfig: function () {
		var point = this;
		return {
			x: point.category,
			y: point.y,
			key: point.name || point.category,
			series: point.series,
			point: point,
			percentage: point.percentage,
			total: point.total || point.stackTotal
		};
	},

	/**
	* Toggle the selection status of a point
	* @param {Boolean} selected Whether to select or unselect the point.
	* @param {Boolean} accumulate Whether to add to the previous selection. By default,
	*     this happens if the control key (Cmd on Mac) was pressed during clicking.
	*/
	select: function (selected, accumulate) {
		var point = this,
			series = point.series,
			chart = series.chart;

		selected = pick(selected, !point.selected);

		// fire the event with the defalut handler
		point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
			point.selected = selected;
			point.setState(selected && SELECT_STATE);

			// unselect all other points unless Ctrl or Cmd + click
			if (!accumulate) {
				each(chart.getSelectedPoints(), function (loopPoint) {
					if (loopPoint.selected && loopPoint !== point) {
						loopPoint.selected = false;
						loopPoint.setState(NORMAL_STATE);
						loopPoint.firePointEvent('unselect');
					}
				});
			}
		});
	},

	onMouseOver: function () {
		var point = this,
			series = point.series,
			chart = series.chart,
			tooltip = chart.tooltip,
			hoverPoint = chart.hoverPoint;

		// set normal state to previous series
		if (hoverPoint && hoverPoint !== point) {
			hoverPoint.onMouseOut();
		}

		// trigger the event
		point.firePointEvent('mouseOver');

		// update the tooltip
		if (tooltip && (!tooltip.shared || series.noSharedTooltip)) {
			tooltip.refresh(point);
		}

		// hover this
		point.setState(HOVER_STATE);
		chart.hoverPoint = point;
	},

	onMouseOut: function () {
		var chart = this.series.chart,
			hoverPoints = chart.hoverPoints;

		if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887
			this.firePointEvent('mouseOut');

			this.setState();
			chart.hoverPoint = null;
		}
	},

	/**
	* Extendable method for formatting each point's tooltip line
	*
	* @return {String} A string to be concatenated in to the common tooltip text
	*/
	tooltipFormatter: function (pointFormat) {
		var point = this,
			series = point.series,
			seriesTooltipOptions = series.tooltipOptions,
			match = pointFormat.match(/\{(series|point)\.[a-zA-Z]+\}/g),
			splitter = /[{\.}]/,
			obj,
			key,
			replacement,
			repOptionKey,
			parts,
			prop,
			i,
			cfg = {
				y: 0, // 0: use 'value' for repOptionKey
				open: 0,
				high: 0,
				low: 0,
				close: 0,
				percentage: 1, // 1: use the self name for repOptionKey
				total: 1
			};

		// Backwards compatibility to y naming in early Highstock
		seriesTooltipOptions.valuePrefix = seriesTooltipOptions.valuePrefix || seriesTooltipOptions.yPrefix;
		seriesTooltipOptions.valueDecimals = seriesTooltipOptions.valueDecimals || seriesTooltipOptions.yDecimals;
		seriesTooltipOptions.valueSuffix = seriesTooltipOptions.valueSuffix || seriesTooltipOptions.ySuffix;

		// loop over the variables defined on the form {series.name}, {point.y} etc
		for (i in match) {
			key = match[i];
			if (isString(key) && key !== pointFormat) { // IE matches more than just the variables

				// Split it further into parts
				parts = (' ' + key).split(splitter); // add empty string because IE and the rest handles it differently
				obj = { 'point': point, 'series': series}[parts[1]];
				prop = parts[2];

				// Add some preformatting
				if (obj === point && cfg.hasOwnProperty(prop)) {
					repOptionKey = cfg[prop] ? prop : 'value';
					replacement = (seriesTooltipOptions[repOptionKey + 'Prefix'] || '') +
						numberFormat(point[prop], pick(seriesTooltipOptions[repOptionKey + 'Decimals'], -1)) +
						(seriesTooltipOptions[repOptionKey + 'Suffix'] || '');

					// Automatic replacement
				} else {
					replacement = obj[prop];
				}

				pointFormat = pointFormat.replace(key, replacement);
			}
		}

		return pointFormat;
	},

	/**
	* Update the point with new options (typically x/y data) and optionally redraw the series.
	*
	* @param {Object} options Point options as defined in the series.data array
	* @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
	* @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	*    configuration
	*
	*/
	update: function (options, redraw, animation) {
		var point = this,
			series = point.series,
			graphic = point.graphic,
			i,
			data = series.data,
			dataLength = data.length,
			chart = series.chart;

		redraw = pick(redraw, true);

		// fire the event with a default handler of doing the update
		point.firePointEvent('update', { options: options }, function () {

			point.applyOptions(options);

			// update visuals
			if (isObject(options)) {
				series.getAttribs();
				if (graphic) {
					graphic.attr(point.pointAttr[series.state]);
				}
			}

			// record changes in the parallel arrays
			for (i = 0; i < dataLength; i++) {
				if (data[i] === point) {
					series.xData[i] = point.x;
					series.yData[i] = point.y;
					series.options.data[i] = options;
					break;
				}
			}

			// redraw
			series.isDirty = true;
			series.isDirtyData = true;
			if (redraw) {
				chart.redraw(animation);
			}
		});
	},

	/**
	* Remove a point and optionally redraw the series and if necessary the axes
	* @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
	* @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	*    configuration
	*/
	remove: function (redraw, animation) {
		var point = this,
			series = point.series,
			chart = series.chart,
			i,
			data = series.data,
			dataLength = data.length;

		setAnimation(animation, chart);
		redraw = pick(redraw, true);

		// fire the event with a default handler of removing the point
		point.firePointEvent('remove', null, function () {

			//erase(series.data, point);

			for (i = 0; i < dataLength; i++) {
				if (data[i] === point) {

					// splice all the parallel arrays
					data.splice(i, 1);
					series.options.data.splice(i, 1);
					series.xData.splice(i, 1);
					series.yData.splice(i, 1);
					break;
				}
			}

			point.destroy();


			// redraw
			series.isDirty = true;
			series.isDirtyData = true;
			if (redraw) {
				chart.redraw();
			}
		});


	},

	/**
	* Fire an event on the Point object. Must not be renamed to fireEvent, as this
	* causes a name clash in MooTools
	* @param {String} eventType
	* @param {Object} eventArgs Additional event arguments
	* @param {Function} defaultFunction Default event handler
	*/
	firePointEvent: function (eventType, eventArgs, defaultFunction) {
		var point = this,
			series = this.series,
			seriesOptions = series.options;

		// load event handlers on demand to save time on mouseover/out
		if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
			this.importEvents();
		}

		// add default handler if in selection mode
		if (eventType === 'click' && seriesOptions.allowPointSelect) {
			defaultFunction = function (event) {
				// Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
				point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
			};
		}

		fireEvent(this, eventType, eventArgs, defaultFunction);
	},
	/**
	* Import events from the series' and point's options. Only do it on
	* demand, to save processing time on hovering.
	*/
	importEvents: function () {
		if (!this.hasImportedEvents) {
			var point = this,
				options = merge(point.series.options.point, point.options),
				events = options.events,
				eventType;

			point.events = events;

			for (eventType in events) {
				addEvent(point, eventType, events[eventType]);
			}
			this.hasImportedEvents = true;

		}
	},

	/**
	* Set the point's state
	* @param {String} state
	*/
	setState: function (state) {
		var point = this,
			plotX = point.plotX,
			plotY = point.plotY,
			series = point.series,
			stateOptions = series.options.states,
			markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
			normalDisabled = markerOptions && !markerOptions.enabled,
			markerStateOptions = markerOptions && markerOptions.states[state],
			stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
			stateMarkerGraphic = series.stateMarkerGraphic,
			chart = series.chart,
			radius,
			pointAttr = point.pointAttr;

		state = state || NORMAL_STATE; // empty string

		if (
		// already has this state
				state === point.state ||
		// selected points don't respond to hover
				(point.selected && state !== SELECT_STATE) ||
		// series' state options is disabled
				(stateOptions[state] && stateOptions[state].enabled === false) ||
		// point marker's state options is disabled
				(state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))

			) {
			return;
		}

		// apply hover styles to the existing point
		if (point.graphic) {
			radius = markerOptions && point.graphic.symbolName && pointAttr[state].r;
			point.graphic.attr(merge(
				pointAttr[state],
				radius ? { // new symbol attributes (#507, #612)
					x: plotX - radius,
					y: plotY - radius,
					width: 2 * radius,
					height: 2 * radius
				} : {}
			));
		} else {
			// if a graphic is not applied to each point in the normal state, create a shared
			// graphic for the hover state
			if (state && markerStateOptions) {
				radius = markerStateOptions.radius;
				if (!stateMarkerGraphic) { // add
					series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
						series.symbol,
						plotX - radius,
						plotY - radius,
						2 * radius,
						2 * radius
					)
					.attr(pointAttr[state])
					.add(series.markerGroup);

				} else { // update
					stateMarkerGraphic.attr({ // #1054
						x: plotX - radius,
						y: plotY - radius
					});
				}
			}

			if (stateMarkerGraphic) {
				stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();
			}
		}

		point.state = state;
	}
};

/**
* @classDescription The base function which all other series types inherit from. The data in the series is stored
* in various arrays.
*
* - First, series.options.data contains all the original config options for
* each point whether added by options or methods like series.addPoint.
* - Next, series.data contains those values converted to points, but in case the series data length
* exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It
* only contains the points that have been created on demand.
* - Then there's series.points that contains all currently visible point objects. In case of cropping,
* the cropped-away points are not part of this array. The series.points array starts at series.cropStart
* compared to series.data and series.options.data. If however the series data is grouped, these can't
* be correlated one to one.
* - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points.
* - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points.
*
* @param {Object} chart
* @param {Object} options
*/
var Series = function () { };

Series.prototype = {

	isCartesian: true,
	type: 'line',
	pointClass: Point,
	sorted: true, // requires the data to be sorted
	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
		stroke: 'lineColor',
		'stroke-width': 'lineWidth',
		fill: 'fillColor',
		r: 'radius'
	},
	init: function (chart, options) {
		var series = this,
			eventType,
			events;

		series.chart = chart;
		series.options = options = series.setOptions(options); // merge with plotOptions

		// bind the axes
		series.bindAxes();

		// set some variables
		extend(series, {
			name: options.name,
			state: NORMAL_STATE,
			pointAttr: {},
			visible: options.visible !== false, // true by default
			selected: options.selected === true // false by default
		});

		// special
		if (useCanVG) {
			options.animation = false;
		}

		// register event listeners
		events = options.events;
		for (eventType in events) {
			addEvent(series, eventType, events[eventType]);
		}
		if (
			(events && events.click) ||
			(options.point && options.point.events && options.point.events.click) ||
			options.allowPointSelect
		) {
			chart.runTrackerClick = true;
		}

		series.getColor();
		series.getSymbol();

		// set the data
		series.setData(options.data, false);

		// Mark cartesian
		if (series.isCartesian) {
			chart.hasCartesianSeries = true;
		}

		// Register it in the chart
		chart.series.push(series);

		// Sort series according to index option (#248, #1123)
		stableSort(chart.series, function (a, b) {
			return (a.options.index || 0) - (b.options.index || 0);
		});
		each(chart.series, function (series, i) {
			series.index = i;
			series.name = series.name || 'Series ' + (i + 1);
		});
	},

	/**
	* Set the xAxis and yAxis properties of cartesian series, and register the series
	* in the axis.series array
	*/
	bindAxes: function () {
		var series = this,
			seriesOptions = series.options,
			chart = series.chart,
			axisOptions;

		if (series.isCartesian) {

			each(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis

				each(chart[AXIS], function (axis) { // loop through the chart's axis objects

					axisOptions = axis.options;

					// apply if the series xAxis or yAxis option mathches the number of the 
					// axis, or if undefined, use the first axis
					if ((seriesOptions[AXIS] === axisOptions.index) ||
							(seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) {

						// register this series in the axis.series lookup
						axis.series.push(series);

						// set this series.xAxis or series.yAxis reference
						series[AXIS] = axis;

						// mark dirty for redraw
						axis.isDirty = true;
					}
				});

			});
		}
	},


	/**
	* Return an auto incremented x value based on the pointStart and pointInterval options.
	* This is only used if an x value is not given for the point that calls autoIncrement.
	*/
	autoIncrement: function () {
		var series = this,
			options = series.options,
			xIncrement = series.xIncrement;

		xIncrement = pick(xIncrement, options.pointStart, 0);

		series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);

		series.xIncrement = xIncrement + series.pointInterval;
		return xIncrement;
	},

	/**
	* Divide the series data into segments divided by null values.
	*/
	getSegments: function () {
		var series = this,
			lastNull = -1,
			segments = [],
			i,
			points = series.points,
			pointsLength = points.length;

		if (pointsLength) { // no action required for []

			// if connect nulls, just remove null points
			if (series.options.connectNulls) {
				i = pointsLength;
				while (i--) {
					if (points[i].y === null) {
						points.splice(i, 1);
					}
				}
				if (points.length) {
					segments = [points];
				}

				// else, split on null points
			} else {
				each(points, function (point, i) {
					if (point.y === null) {
						if (i > lastNull + 1) {
							segments.push(points.slice(lastNull + 1, i));
						}
						lastNull = i;
					} else if (i === pointsLength - 1) { // last value
						segments.push(points.slice(lastNull + 1, i + 1));
					}
				});
			}
		}

		// register it
		series.segments = segments;
	},
	/**
	* Set the series options by merging from the options tree
	* @param {Object} itemOptions
	*/
	setOptions: function (itemOptions) {
		var chart = this.chart,
			chartOptions = chart.options,
			plotOptions = chartOptions.plotOptions,
			typeOptions = plotOptions[this.type],
			data = itemOptions.data,
			options;

		itemOptions.data = null; // remove from merge to prevent looping over the data set

		options = merge(
			typeOptions,
			plotOptions.series,
			itemOptions
		);

		// Re-insert the data array to the options and the original config (#717)
		options.data = itemOptions.data = data;

		// the tooltip options are merged between global and series specific options
		this.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);

		// Delte marker object if not allowed (#1125)
		if (typeOptions.marker === null) {
			delete options.marker;
		}

		return options;

	},
	/**
	* Get the series' color
	*/
	getColor: function () {
		var options = this.options,
			defaultColors = this.chart.options.colors,
			counters = this.chart.counters;
		this.color = options.color ||
			(!options.colorByPoint && defaultColors[counters.color++]) || 'gray';
		counters.wrapColor(defaultColors.length);
	},
	/**
	* Get the series' symbol
	*/
	getSymbol: function () {
		var series = this,
			seriesMarkerOption = series.options.marker,
			chart = series.chart,
			defaultSymbols = chart.options.symbols,
			counters = chart.counters;
		series.symbol = seriesMarkerOption.symbol || defaultSymbols[counters.symbol++];

		// don't substract radius in image symbols (#604)
		if (/^url/.test(series.symbol)) {
			seriesMarkerOption.radius = 0;
		}
		counters.wrapSymbol(defaultSymbols.length);
	},

	/**
	* Get the series' symbol in the legend. This method should be overridable to create custom 
	* symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
	* 
	* @param {Object} legend The legend object
	*/
	drawLegendSymbol: function (legend) {

		var options = this.options,
			markerOptions = options.marker,
			radius,
			legendOptions = legend.options,
			legendSymbol,
			symbolWidth = legendOptions.symbolWidth,
			renderer = this.chart.renderer,
			legendItemGroup = this.legendGroup,
			baseline = legend.baseline,
			attr;

		// Draw the line
		if (options.lineWidth) {
			attr = {
				'stroke-width': options.lineWidth
			};
			if (options.dashStyle) {
				attr.dashstyle = options.dashStyle;
			}
			this.legendLine = renderer.path([
				M,
				0,
				baseline - 4,
				L,
				symbolWidth,
				baseline - 4
			])
			.attr(attr)
			.add(legendItemGroup);
		}

		// Draw the marker
		if (markerOptions && markerOptions.enabled) {
			radius = markerOptions.radius;
			this.legendSymbol = legendSymbol = renderer.symbol(
				this.symbol,
				(symbolWidth / 2) - radius,
				baseline - 4 - radius,
				2 * radius,
				2 * radius
			)
			.add(legendItemGroup);
		}
	},

	/**
	* Add a point dynamically after chart load time
	* @param {Object} options Point options as given in series.data
	* @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
	* @param {Boolean} shift If shift is true, a point is shifted off the start
	*    of the series as one is appended to the end.
	* @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	*    configuration
	*/
	addPoint: function (options, redraw, shift, animation) {
		var series = this,
			data = series.data,
			graph = series.graph,
			area = series.area,
			chart = series.chart,
			xData = series.xData,
			yData = series.yData,
			currentShift = (graph && graph.shift) || 0,
			dataOptions = series.options.data,
			point,
			proto = series.pointClass.prototype;

		setAnimation(animation, chart);

		// Make graph animate sideways
		if (graph && shift) {
			graph.shift = currentShift + 1;
		}
		if (area) {
			if (shift) { // #780
				area.shift = currentShift + 1;
			}
			area.isArea = true; // needed in animation, both with and without shift
		}

		// Optional redraw, defaults to true
		redraw = pick(redraw, true);

		// Get options and push the point to xData, yData and series.options. In series.generatePoints
		// the Point instance will be created on demand and pushed to the series.data array.
		point = { series: series };
		proto.applyOptions.apply(point, [options]);
		xData.push(point.x);
		yData.push(proto.toYData ? proto.toYData.call(point) : point.y);
		dataOptions.push(options);


		// Shift the first point off the parallel arrays
		// todo: consider series.removePoint(i) method
		if (shift) {
			if (data[0] && data[0].remove) {
				data[0].remove(false);
			} else {
				data.shift();
				xData.shift();
				yData.shift();
				dataOptions.shift();
			}
		}
		series.getAttribs();

		// redraw
		series.isDirty = true;
		series.isDirtyData = true;
		if (redraw) {
			chart.redraw();
		}
	},

	/**
	* Replace the series data with a new set of data
	* @param {Object} data
	* @param {Object} redraw
	*/
	setData: function (data, redraw) {
		var series = this,
			oldData = series.points,
			options = series.options,
			initialColor = series.initialColor,
			chart = series.chart,
			firstPoint = null,
			xAxis = series.xAxis,
			i,
			pointProto = series.pointClass.prototype;

		// reset properties
		series.xIncrement = null;
		series.pointRange = xAxis && xAxis.categories ? 1 : options.pointRange;

		if (defined(initialColor)) { // reset colors for pie
			chart.counters.color = initialColor;
		}

		// parallel arrays
		var xData = [],
			yData = [],
			dataLength = data ? data.length : [],
			turboThreshold = options.turboThreshold || 1000,
			pt,
			pointArrayMap = series.pointArrayMap,
			valueCount = pointArrayMap && pointArrayMap.length;

		// In turbo mode, only one- or twodimensional arrays of numbers are allowed. The
		// first value is tested, and we assume that all the rest are defined the same
		// way. Although the 'for' loops are similar, they are repeated inside each
		// if-else conditional for max performance.
		if (dataLength > turboThreshold) {

			// find the first non-null point
			i = 0;
			while (firstPoint === null && i < dataLength) {
				firstPoint = data[i];
				i++;
			}


			if (isNumber(firstPoint)) { // assume all points are numbers
				var x = pick(options.pointStart, 0),
					pointInterval = pick(options.pointInterval, 1);

				for (i = 0; i < dataLength; i++) {
					xData[i] = x;
					yData[i] = data[i];
					x += pointInterval;
				}
				series.xIncrement = x;
			} else if (isArray(firstPoint)) { // assume all points are arrays
				if (valueCount) { // [x, low, high] or [x, o, h, l, c]
					for (i = 0; i < dataLength; i++) {
						pt = data[i];
						xData[i] = pt[0];
						yData[i] = pt.slice(1, valueCount + 1);
					}
				} else { // [x, y]
					for (i = 0; i < dataLength; i++) {
						pt = data[i];
						xData[i] = pt[0];
						yData[i] = pt[1];
					}
				}
			} /* else {
				error(12); // Highcharts expects configs to be numbers or arrays in turbo mode
			}*/
		} else {
			for (i = 0; i < dataLength; i++) {
				pt = { series: series };
				pointProto.applyOptions.apply(pt, [data[i]]);
				xData[i] = pt.x;
				yData[i] = pointProto.toYData ? pointProto.toYData.call(pt) : pt.y;
			}
		}

		// Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON		
		if (isString(yData[0])) {
			error(14, true);
		}

		series.data = [];
		series.options.data = data;
		series.xData = xData;
		series.yData = yData;

		// destroy old points
		i = (oldData && oldData.length) || 0;
		while (i--) {
			if (oldData[i] && oldData[i].destroy) {
				oldData[i].destroy();
			}
		}

		// reset minRange (#878)
		if (xAxis) {
			xAxis.minRange = xAxis.userMinRange;
		}

		// redraw
		series.isDirty = series.isDirtyData = chart.isDirtyBox = true;
		if (pick(redraw, true)) {
			chart.redraw(false);
		}
	},

	/**
	* Remove a series and optionally redraw the chart
	*
	* @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
	* @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	*    configuration
	*/

	remove: function (redraw, animation) {
		var series = this,
			chart = series.chart;
		redraw = pick(redraw, true);

		if (!series.isRemoving) {  /* prevent triggering native event in jQuery
				(calling the remove function from the remove event) */
			series.isRemoving = true;

			// fire the event with a default handler of removing the point
			fireEvent(series, 'remove', null, function () {


				// destroy elements
				series.destroy();


				// redraw
				chart.isDirtyLegend = chart.isDirtyBox = true;
				if (redraw) {
					chart.redraw(animation);
				}
			});

		}
		series.isRemoving = false;
	},

	/**
	* Process the data by cropping away unused data points if the series is longer
	* than the crop threshold. This saves computing time for lage series.
	*/
	processData: function (force) {
		var series = this,
			processedXData = series.xData, // copied during slice operation below
			processedYData = series.yData,
			dataLength = processedXData.length,
			cropStart = 0,
			cropEnd = dataLength,
			cropped,
			distance,
			closestPointRange,
			xAxis = series.xAxis,
			i, // loop variable
			options = series.options,
			cropThreshold = options.cropThreshold,
			isCartesian = series.isCartesian;

		// If the series data or axes haven't changed, don't go through this. Return false to pass
		// the message on to override methods like in data grouping. 
		if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
			return false;
		}

		// optionally filter out points outside the plot area
		if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {
			var extremes = xAxis.getExtremes(),
				min = extremes.min,
				max = extremes.max;

			// it's outside current extremes
			if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
				processedXData = [];
				processedYData = [];

				// only crop if it's actually spilling out
			} else if (processedXData[0] < min || processedXData[dataLength - 1] > max) {

				// iterate up to find slice start
				for (i = 0; i < dataLength; i++) {
					if (processedXData[i] >= min) {
						cropStart = mathMax(0, i - 1);
						break;
					}
				}
				// proceed to find slice end
				for (; i < dataLength; i++) {
					if (processedXData[i] > max) {
						cropEnd = i + 1;
						break;
					}

				}
				processedXData = processedXData.slice(cropStart, cropEnd);
				processedYData = processedYData.slice(cropStart, cropEnd);
				cropped = true;
			}
		}


		// Find the closest distance between processed points
		for (i = processedXData.length - 1; i > 0; i--) {
			distance = processedXData[i] - processedXData[i - 1];
			if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
				closestPointRange = distance;
			}
		}

		// Record the properties
		series.cropped = cropped; // undefined or true
		series.cropStart = cropStart;
		series.processedXData = processedXData;
		series.processedYData = processedYData;

		if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
			series.pointRange = closestPointRange || 1;
		}
		series.closestPointRange = closestPointRange;

	},

	/**
	* Generate the data point after the data has been processed by cropping away
	* unused points and optionally grouped in Highcharts Stock.
	*/
	generatePoints: function () {
		var series = this,
			options = series.options,
			dataOptions = options.data,
			data = series.data,
			dataLength,
			processedXData = series.processedXData,
			processedYData = series.processedYData,
			pointClass = series.pointClass,
			processedDataLength = processedXData.length,
			cropStart = series.cropStart || 0,
			cursor,
			hasGroupedData = series.hasGroupedData,
			point,
			points = [],
			i;

		if (!data && !hasGroupedData) {
			var arr = [];
			arr.length = dataOptions.length;
			data = series.data = arr;
		}

		for (i = 0; i < processedDataLength; i++) {
			cursor = cropStart + i;
			if (!hasGroupedData) {
				if (data[cursor]) {
					point = data[cursor];
				} else if (dataOptions[cursor] !== UNDEFINED) { // #970
					data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]);
				}
				points[i] = point;
			} else {
				// splat the y data in case of ohlc data array
				points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i])));
			}
		}

		// Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when
		// swithching view from non-grouped data to grouped data (#637)	
		if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) {
			for (i = 0; i < dataLength; i++) {
				if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points
					i += processedDataLength;
				}
				if (data[i]) {
					data[i].destroyElements();
					data[i].plotX = UNDEFINED; // #1003
				}
			}
		}

		series.data = data;
		series.points = points;
	},

	/**
	* Translate data points from raw data values to chart specific positioning data
	* needed later in drawPoints, drawGraph and drawTracker.
	*/
	translate: function () {
		if (!this.processedXData) { // hidden series
			this.processData();
		}
		this.generatePoints();
		var series = this,
			chart = series.chart,
			options = series.options,
			stacking = options.stacking,
			xAxis = series.xAxis,
			categories = xAxis.categories,
			yAxis = series.yAxis,
			points = series.points,
			dataLength = points.length,
			hasModifyValue = !!series.modifyValue,
			isBottomSeries,
			allStackSeries = yAxis.series,
			i = allStackSeries.length,
			placeBetween = options.pointPlacement === 'between';
		//nextSeriesDown;

		// Is it the last visible series?
		while (i--) {
			if (allStackSeries[i].visible) {
				if (allStackSeries[i] === series) { // #809
					isBottomSeries = true;
				}
				break;
			}
		}

		// Translate each point
		for (i = 0; i < dataLength; i++) {
			var point = points[i],
				xValue = point.x,
				yValue = point.y,
				yBottom = point.low,
				stack = yAxis.stacks[(yValue < options.threshold ? '-' : '') + series.stackKey],
				pointStack,
				pointStackTotal;

			// get the plotX translation
			//point.plotX = mathRound(xAxis.translate(xValue, 0, 0, 0, 1) * 10) / 10; // Math.round fixes #591
			point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, placeBetween); // Math.round fixes #591

			// calculate the bottom y value for stacked series
			if (stacking && series.visible && stack && stack[xValue]) {
				pointStack = stack[xValue];
				pointStackTotal = pointStack.total;
				pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
				yValue = yBottom + yValue;

				if (isBottomSeries) {
					yBottom = pick(options.threshold, yAxis.min);
				}

				if (yAxis.isLog && yBottom <= 0) { // #1200, #1232
					yBottom = null;
				}

				if (stacking === 'percent') {
					yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
					yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
				}

				point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
				point.total = point.stackTotal = pointStackTotal;
				point.stackY = yValue;
			}

			// Set translated yBottom or remove it
			point.yBottom = defined(yBottom) ?
				yAxis.translate(yBottom, 0, 1, 0, 1) :
				null;

			// general hook, used for Highstock compare mode
			if (hasModifyValue) {
				yValue = series.modifyValue(yValue, point);
			}

			// Set the the plotY value, reset it for redraws
			point.plotY = (typeof yValue === 'number') ?
				mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591
				UNDEFINED;

			// set client related positions for mouse tracking
			point.clientX = chart.inverted ?
				chart.plotHeight - point.plotX :
				point.plotX; // for mouse tracking

			// some API data
			point.category = categories && categories[point.x] !== UNDEFINED ?
				categories[point.x] : point.x;


		}

		// now that we have the cropped data, build the segments
		series.getSegments();
	},
	/**
	* Memoize tooltip texts and positions
	*/
	setTooltipPoints: function (renew) {
		var series = this,
			points = [],
			pointsLength,
			low,
			high,
			xAxis = series.xAxis,
			axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar
			plotX = (xAxis && xAxis.tooltipPosName) || 'plotX',
			point,
			i,
			tooltipPoints = []; // a lookup array for each pixel in the x dimension

		// don't waste resources if tracker is disabled
		if (series.options.enableMouseTracking === false) {
			return;
		}

		// renew
		if (renew) {
			series.tooltipPoints = null;
		}

		// concat segments to overcome null values
		each(series.segments || series.points, function (segment) {
			points = points.concat(segment);
		});

		// Reverse the points in case the X axis is reversed
		if (xAxis && xAxis.reversed) {
			points = points.reverse();
		}

		// Assign each pixel position to the nearest point
		pointsLength = points.length;
		for (i = 0; i < pointsLength; i++) {
			point = points[i];
			// Set this range's low to the last range's high plus one
			low = points[i - 1] ? high + 1 : 0;
			// Now find the new high
			high = points[i + 1] ?
				mathMax(0, mathFloor((point[plotX] + (points[i + 1] ? points[i + 1][plotX] : axisLength)) / 2)) :
				axisLength;

			while (low >= 0 && low <= high) {
				tooltipPoints[low++] = point;
			}
		}
		series.tooltipPoints = tooltipPoints;
	},

	/**
	* Format the header of the tooltip
	*/
	tooltipHeaderFormatter: function (key) {
		var series = this,
			tooltipOptions = series.tooltipOptions,
			xDateFormat = tooltipOptions.xDateFormat,
			xAxis = series.xAxis,
			isDateTime = xAxis && xAxis.options.type === 'datetime',
			n;

		// Guess the best date format based on the closest point distance (#568)
		if (isDateTime && !xDateFormat) {
			for (n in timeUnits) {
				if (timeUnits[n] >= xAxis.closestPointRange) {
					xDateFormat = tooltipOptions.dateTimeLabelFormats[n];
					break;
				}
			}
		}

		return tooltipOptions.headerFormat
			.replace('{point.key}', isDateTime && isNumber(key) ? dateFormat(xDateFormat, key) : key)
			.replace('{series.name}', series.name)
			.replace('{series.color}', series.color);
	},

	/**
	* Series mouse over handler
	*/
	onMouseOver: function () {
		var series = this,
			chart = series.chart,
			hoverSeries = chart.hoverSeries;

		/*if (!hasTouch && chart.mouseIsDown) {
		return;
		}*/

		// set normal state to previous series
		if (hoverSeries && hoverSeries !== series) {
			hoverSeries.onMouseOut();
		}

		// trigger the event, but to save processing time,
		// only if defined
		if (series.options.events.mouseOver) {
			fireEvent(series, 'mouseOver');
		}

		// hover this
		series.setState(HOVER_STATE);
		chart.hoverSeries = series;
	},

	/**
	* Series mouse out handler
	*/
	onMouseOut: function () {
		// trigger the event only if listeners exist
		var series = this,
			options = series.options,
			chart = series.chart,
			tooltip = chart.tooltip,
			hoverPoint = chart.hoverPoint;

		// trigger mouse out on the point, which must be in this series
		if (hoverPoint) {
			hoverPoint.onMouseOut();
		}

		// fire the mouse out event
		if (series && options.events.mouseOut) {
			fireEvent(series, 'mouseOut');
		}


		// hide the tooltip
		if (tooltip && !options.stickyTracking && !tooltip.shared) {
			tooltip.hide();
		}

		// set normal state
		series.setState();
		chart.hoverSeries = null;
	},

	/**
	* Animate in the series
	*/
	animate: function (init) {
		var series = this,
			chart = series.chart,
			renderer = chart.renderer,
			clipRect,
			markerClipRect,
			animation = series.options.animation,
			clipBox = chart.clipBox,
			inverted = chart.inverted,
			sharedClipKey;

		// Animation option is set to true
		if (animation && !isObject(animation)) {
			animation = defaultPlotOptions[series.type].animation;
		}
		sharedClipKey = '_sharedClip' + animation.duration + animation.easing;

		// Initialize the animation. Set up the clipping rectangle.
		if (init) {

			// If a clipping rectangle with the same properties is currently present in the chart, use that. 
			clipRect = chart[sharedClipKey];
			markerClipRect = chart[sharedClipKey + 'm'];
			if (!clipRect) {
				chart[sharedClipKey] = clipRect = renderer.clipRect(
					extend(clipBox, { width: 0 })
				);

				chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(
					-99, // include the width of the first marker
					inverted ? -chart.plotLeft : -chart.plotTop,
					99,
					inverted ? chart.chartWidth : chart.chartHeight
				);
			}
			series.group.clip(clipRect);
			series.markerGroup.clip(markerClipRect);
			series.sharedClipKey = sharedClipKey;

			// Run the animation
		} else {
			clipRect = chart[sharedClipKey];
			if (clipRect) {
				clipRect.animate({
					width: chart.plotSizeX
				}, animation);
				chart[sharedClipKey + 'm'].animate({
					width: chart.plotSizeX + 99
				}, animation);
			}

			// Delete this function to allow it only once
			series.animate = null;

			// Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option
			// which should be available to the user).
			series.animationTimeout = setTimeout(function () {
				series.afterAnimate();
			}, animation.duration);
		}
	},

	/**
	* This runs after animation to land on the final plot clipping
	*/
	afterAnimate: function () {
		var chart = this.chart,
			sharedClipKey = this.sharedClipKey,
			group = this.group;

		if (group && this.options.clip !== false) {
			group.clip(chart.clipRect);
			this.markerGroup.clip(); // no clip
		}

		// Remove the shared clipping rectancgle when all series are shown		
		setTimeout(function () {
			if (sharedClipKey && chart[sharedClipKey]) {
				chart[sharedClipKey] = chart[sharedClipKey].destroy();
				chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
			}
		}, 100);
	},

	/**
	* Draw the markers
	*/
	drawPoints: function () {
		var series = this,
			pointAttr,
			points = series.points,
			chart = series.chart,
			plotX,
			plotY,
			i,
			point,
			radius,
			symbol,
			isImage,
			graphic,
			options = series.options,
			seriesMarkerOptions = options.marker,
			pointMarkerOptions,
			enabled,
			isInside,
			markerGroup = series.markerGroup;

		if (seriesMarkerOptions.enabled || series._hasPointMarkers) {

			i = points.length;
			while (i--) {
				point = points[i];
				plotX = point.plotX;
				plotY = point.plotY;
				graphic = point.graphic;
				pointMarkerOptions = point.marker || {};
				enabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled;
				isInside = chart.isInsidePlot(plotX, plotY, chart.inverted);

				// only draw the point if y is defined
				if (enabled && plotY !== UNDEFINED && !isNaN(plotY)) {

					// shortcuts
					pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
					radius = pointAttr.r;
					symbol = pick(pointMarkerOptions.symbol, series.symbol);
					isImage = symbol.indexOf('url') === 0;

					if (graphic) { // update
						graphic
							.attr({ // Since the marker group isn't clipped, each individual marker must be toggled
								visibility: isInside ? (hasSVG ? 'inherit' : VISIBLE) : HIDDEN
							})
							.animate(extend({
								x: plotX - radius,
								y: plotY - radius
							}, graphic.symbolName ? { // don't apply to image symbols #507
								width: 2 * radius,
								height: 2 * radius
							} : {}));
					} else if (isInside && (radius > 0 || isImage)) {
						point.graphic = graphic = chart.renderer.symbol(
							symbol,
							plotX - radius,
							plotY - radius,
							2 * radius,
							2 * radius
						)
						.attr(pointAttr)
						.add(markerGroup);
					}

				} else if (graphic) {
					point.graphic = graphic.destroy(); // #1269
				}
			}
		}

	},

	/**
	* Convert state properties from API naming conventions to SVG attributes
	*
	* @param {Object} options API options object
	* @param {Object} base1 SVG attribute object to inherit from
	* @param {Object} base2 Second level SVG attribute object to inherit from
	*/
	convertAttribs: function (options, base1, base2, base3) {
		var conversion = this.pointAttrToOptions,
			attr,
			option,
			obj = {};

		options = options || {};
		base1 = base1 || {};
		base2 = base2 || {};
		base3 = base3 || {};

		for (attr in conversion) {
			option = conversion[attr];
			obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);
		}
		return obj;
	},

	/**
	* Get the state attributes. Each series type has its own set of attributes
	* that are allowed to change on a point's state change. Series wide attributes are stored for
	* all series, and additionally point specific attributes are stored for all
	* points with individual marker options. If such options are not defined for the point,
	* a reference to the series wide attributes is stored in point.pointAttr.
	*/
	getAttribs: function () {
		var series = this,
			normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options,
			stateOptions = normalOptions.states,
			stateOptionsHover = stateOptions[HOVER_STATE],
			pointStateOptionsHover,
			seriesColor = series.color,
			normalDefaults = {
				stroke: seriesColor,
				fill: seriesColor
			},
			points = series.points || [], // #927
			i,
			point,
			seriesPointAttr = [],
			pointAttr,
			pointAttrToOptions = series.pointAttrToOptions,
			hasPointSpecificOptions,
			key;

		// series type specific modifications
		if (series.options.marker) { // line, spline, area, areaspline, scatter

			// if no hover radius is given, default to normal radius + 2
			stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
			stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;

		} else { // column, bar, pie

			// if no hover color is given, brighten the normal color
			stateOptionsHover.color = stateOptionsHover.color ||
				Color(stateOptionsHover.color || seriesColor)
					.brighten(stateOptionsHover.brightness).get();
		}

		// general point attributes for the series normal state
		seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);

		// HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
		each([HOVER_STATE, SELECT_STATE], function (state) {
			seriesPointAttr[state] =
					series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
		});

		// set it
		series.pointAttr = seriesPointAttr;


		// Generate the point-specific attribute collections if specific point
		// options are given. If not, create a referance to the series wide point
		// attributes
		i = points.length;
		while (i--) {
			point = points[i];
			normalOptions = (point.options && point.options.marker) || point.options;
			if (normalOptions && normalOptions.enabled === false) {
				normalOptions.radius = 0;
			}
			hasPointSpecificOptions = series.options.colorByPoint; // #868

			// check if the point has specific visual options
			if (point.options) {
				for (key in pointAttrToOptions) {
					if (defined(normalOptions[pointAttrToOptions[key]])) {
						hasPointSpecificOptions = true;
					}
				}
			}



			// a specific marker config object is defined for the individual point:
			// create it's own attribute collection
			if (hasPointSpecificOptions) {
				normalOptions = normalOptions || {};
				pointAttr = [];
				stateOptions = normalOptions.states || {}; // reassign for individual point
				pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};

				// Handle colors for column and pies
				if (!series.options.marker) { // column, bar, point
					// if no hover color is given, brighten the normal color
					pointStateOptionsHover.color =
						Color(pointStateOptionsHover.color || point.color)
							.brighten(pointStateOptionsHover.brightness ||
								stateOptionsHover.brightness).get();

				}

				// normal point state inherits series wide normal state
				pointAttr[NORMAL_STATE] = series.convertAttribs(extend({
					color: point.color // #868
				}, normalOptions), seriesPointAttr[NORMAL_STATE]);

				// inherit from point normal and series hover
				pointAttr[HOVER_STATE] = series.convertAttribs(
					stateOptions[HOVER_STATE],
					seriesPointAttr[HOVER_STATE],
					pointAttr[NORMAL_STATE]
				);
				// inherit from point normal and series hover
				pointAttr[SELECT_STATE] = series.convertAttribs(
					stateOptions[SELECT_STATE],
					seriesPointAttr[SELECT_STATE],
					pointAttr[NORMAL_STATE]
				);



				// no marker config object is created: copy a reference to the series-wide
				// attribute collection
			} else {
				pointAttr = seriesPointAttr;
			}

			point.pointAttr = pointAttr;

		}

	},


	/**
	* Clear DOM objects and free up memory
	*/
	destroy: function () {
		var series = this,
			chart = series.chart,
			issue134 = /AppleWebKit\/533/.test(userAgent),
			destroy,
			i,
			data = series.data || [],
			point,
			prop,
			axis;

		// add event hook
		fireEvent(series, 'destroy');

		// remove all events
		removeEvent(series);

		// erase from axes
		each(['xAxis', 'yAxis'], function (AXIS) {
			axis = series[AXIS];
			if (axis) {
				erase(axis.series, series);
				axis.isDirty = true;
			}
		});

		// remove legend items
		if (series.legendItem) {
			series.chart.legend.destroyItem(series);
		}

		// destroy all points with their elements
		i = data.length;
		while (i--) {
			point = data[i];
			if (point && point.destroy) {
				point.destroy();
			}
		}
		series.points = null;

		// Clear the animation timeout if we are destroying the series during initial animation
		clearTimeout(series.animationTimeout);

		// destroy all SVGElements associated to the series
		each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker', 'trackerGroup'], function (prop) {
			if (series[prop]) {

				// issue 134 workaround
				destroy = issue134 && prop === 'group' ?
					'hide' :
					'destroy';

				series[prop][destroy]();
			}
		});

		// remove from hoverSeries
		if (chart.hoverSeries === series) {
			chart.hoverSeries = null;
		}
		erase(chart.series, series);

		// clear all members
		for (prop in series) {
			delete series[prop];
		}
	},

	/**
	* Draw the data labels
	*/
	drawDataLabels: function () {

		var series = this,
			seriesOptions = series.options,
			options = seriesOptions.dataLabels,
			points = series.points,
			pointOptions,
			generalOptions,
			str,
			dataLabelsGroup;

		if (options.enabled || series._hasPointLabels) {

			// Process default alignment of data labels for columns
			if (series.dlProcessOptions) {
				series.dlProcessOptions(options);
			}

			// Create a separate group for the data labels to avoid rotation
			dataLabelsGroup = series.plotGroup(
				'dataLabelsGroup',
				'data-labels',
				series.visible ? VISIBLE : HIDDEN,
				6
			);

			// Make the labels for each point
			generalOptions = options;
			each(points, function (point) {

				var enabled,
					dataLabel = point.dataLabel,
					attr,
					name,
					rotation,
					isNew = true;

				// Determine if each data label is enabled
				pointOptions = point.options && point.options.dataLabels;
				enabled = generalOptions.enabled || (pointOptions && pointOptions.enabled);


				// If the point is outside the plot area, destroy it. #678, #820
				if (dataLabel && !enabled) {
					point.dataLabel = dataLabel.destroy();

					// Individual labels are disabled if the are explicitly disabled 
					// in the point options, or if they fall outside the plot area.
				} else if (enabled) {

					rotation = options.rotation;

					// Create individual options structure that can be extended without 
					// affecting others
					options = merge(generalOptions, pointOptions);

					// Get the string
					str = options.formatter.call(point.getLabelConfig(), options);

					// Determine the color
					options.style.color = pick(options.color, options.style.color, series.color, 'black');


					// update existing label
					if (dataLabel) {
						// vertically centered
						dataLabel
							.attr({
								text: str
							});
						isNew = false;
						// create new label
					} else if (defined(str)) {
						attr = {
							//align: align,
							fill: options.backgroundColor,
							stroke: options.borderColor,
							'stroke-width': options.borderWidth,
							r: options.borderRadius || 0,
							rotation: rotation,
							padding: options.padding,
							zIndex: 1
						};
						// Remove unused attributes (#947)
						for (name in attr) {
							if (attr[name] === UNDEFINED) {
								delete attr[name];
							}
						}

						dataLabel = point.dataLabel = series.chart.renderer[rotation ? 'text' : 'label']( // labels don't support rotation
							str,
							0,
							-999,
							null,
							null,
							null,
							options.useHTML
						)
						.attr(attr)
						.css(options.style)
						.add(dataLabelsGroup)
						.shadow(options.shadow);

					}

					// Now the data label is created and placed at 0,0, so we need to align it
					if (dataLabel) {
						series.alignDataLabel(point, dataLabel, options, null, isNew);
					}
				}
			});
		}
	},

	/**
	* Align each individual data label
	*/
	alignDataLabel: function (point, dataLabel, options, alignTo, isNew) {
		var chart = this.chart,
			inverted = chart.inverted,
			plotX = pick(point.plotX, -999),
			plotY = pick(point.plotY, -999),
			bBox = dataLabel.getBBox(),
			alignAttr; // the final position;

		// The alignment box is a singular point
		alignTo = extend({
			x: inverted ? chart.plotWidth - plotY : plotX,
			y: mathRound(inverted ? chart.plotHeight - plotX : plotY),
			width: 0,
			height: 0
		}, alignTo);

		// Add the text size for alignment calculation
		extend(options, {
			width: bBox.width,
			height: bBox.height
		});

		// Allow a hook for changing alignment in the last moment, then do the alignment
		if (options.rotation) { // Fancy box alignment isn't supported for rotated text
			alignAttr = {
				align: options.align,
				x: alignTo.x + options.x + alignTo.width / 2,
				y: alignTo.y + options.y + alignTo.height / 2
			};
			dataLabel[isNew ? 'attr' : 'animate'](alignAttr);
		} else {
			dataLabel.align(options, null, alignTo);
			alignAttr = dataLabel.alignAttr;
		}

		// Show or hide based on the final aligned position
		dataLabel.attr({
			visibility: options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) || chart.isInsidePlot(plotX, plotY, inverted) ?
				(hasSVG ? 'inherit' : VISIBLE) :
				HIDDEN
		});

	},

	/**
	* Return the graph path of a segment
	*/
	getSegmentPath: function (segment) {
		var series = this,
			segmentPath = [];

		// build the segment line
		each(segment, function (point, i) {

			if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
				segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));

			} else {

				// moveTo or lineTo
				segmentPath.push(i ? L : M);

				// step line?
				if (i && series.options.step) {
					var lastPoint = segment[i - 1];
					segmentPath.push(
						point.plotX,
						lastPoint.plotY
					);
				}

				// normal line to next point
				segmentPath.push(
					point.plotX,
					point.plotY
				);
			}
		});

		return segmentPath;
	},

	/**
	* Get the graph path
	*/
	getGraphPath: function () {
		var series = this,
			graphPath = [],
			segmentPath,
			singlePoints = []; // used in drawTracker

		// Divide into segments and build graph and area paths
		each(series.segments, function (segment) {

			segmentPath = series.getSegmentPath(segment);

			// add the segment to the graph, or a single point for tracking
			if (segment.length > 1) {
				graphPath = graphPath.concat(segmentPath);
			} else {
				singlePoints.push(segment[0]);
			}
		});

		// Record it for use in drawGraph and drawTracker, and return graphPath
		series.singlePoints = singlePoints;
		series.graphPath = graphPath;

		return graphPath;

	},

	/**
	* Draw the actual graph
	*/
	drawGraph: function () {
		var options = this.options,
			graph = this.graph,
			group = this.group,
			color = options.lineColor || this.color,
			lineWidth = options.lineWidth,
			dashStyle = options.dashStyle,
			attribs,
			graphPath = this.getGraphPath();


		// draw the graph
		if (graph) {
			stop(graph); // cancel running animations, #459
			graph.animate({ d: graphPath });

		} else {
			if (lineWidth) {
				attribs = {
					stroke: color,
					'stroke-width': lineWidth,
					zIndex: 1 // #1069
				};
				if (dashStyle) {
					attribs.dashstyle = dashStyle;
				}

				this.graph = this.chart.renderer.path(graphPath)
					.attr(attribs).add(group).shadow(options.shadow);
			}
		}
	},

	/**
	* Initialize and perform group inversion on series.group and series.trackerGroup
	*/
	invertGroups: function () {
		var series = this,
			chart = series.chart;

		// A fixed size is needed for inversion to work
		function setInvert() {
			var size = {
				width: series.yAxis.len,
				height: series.xAxis.len
			};

			each(['group', 'trackerGroup', 'markerGroup'], function (groupName) {
				if (series[groupName]) {
					series[groupName].attr(size).invert();
				}
			});
		}

		addEvent(chart, 'resize', setInvert); // do it on resize
		addEvent(series, 'destroy', function () {
			removeEvent(chart, 'resize', setInvert);
		});

		// Do it now
		setInvert(); // do it now

		// On subsequent render and redraw, just do setInvert without setting up events again
		series.invertGroups = setInvert;
	},

	/**
	* General abstraction for creating plot groups like series.group, series.trackerGroup, series.dataLabelsGroup and 
	* series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size.
	*/
	plotGroup: function (prop, name, visibility, zIndex, parent) {
		var group = this[prop],
			chart = this.chart,
			xAxis = this.xAxis,
			yAxis = this.yAxis;

		// Generate it on first call
		if (!group) {
			this[prop] = group = chart.renderer.g(name)
				.attr({
					visibility: visibility,
					zIndex: zIndex || 0.1 // IE8 needs this
				})
				.add(parent);
		}
		// Place it on first and subsequent (redraw) calls
		group.translate(
			xAxis ? xAxis.left : chart.plotLeft,
			yAxis ? yAxis.top : chart.plotTop
		);

		return group;

	},

	/**
	* Render the graph and markers
	*/
	render: function () {
		var series = this,
			chart = series.chart,
			group,
			options = series.options,
			animation = options.animation,
			doAnimation = animation && !!series.animate,
			visibility = series.visible ? VISIBLE : HIDDEN,
			zIndex = options.zIndex,
			hasRendered = series.hasRendered,
			chartSeriesGroup = chart.seriesGroup;

		// the group
		group = series.plotGroup(
			'group',
			'series',
			visibility,
			zIndex,
			chartSeriesGroup
		);

		series.markerGroup = series.plotGroup(
			'markerGroup',
			'markers',
			visibility,
			zIndex,
			chartSeriesGroup
		);

		// initiate the animation
		if (doAnimation) {
			series.animate(true);
		}

		// cache attributes for shapes
		series.getAttribs();

		// SVGRenderer needs to know this before drawing elements (#1089)
		group.inverted = chart.inverted;

		// draw the graph if any
		if (series.drawGraph) {
			series.drawGraph();
		}

		// draw the points
		series.drawPoints();

		// draw the data labels
		series.drawDataLabels();


		// draw the mouse tracking area
		if (series.options.enableMouseTracking !== false) {
			series.drawTracker();
		}

		// Handle inverted series and tracker groups
		if (chart.inverted) {
			series.invertGroups();
		}

		// Initial clipping, must be defined after inverting groups for VML
		if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
			group.clip(chart.clipRect);
			if (this.trackerGroup) {
				this.trackerGroup.clip(chart.clipRect);
			}
		}

		// Run the animation
		if (doAnimation) {
			series.animate();
		} else if (!hasRendered) {
			series.afterAnimate();
		}

		series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
		// (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
		series.hasRendered = true;
	},

	/**
	* Redraw the series after an update in the axes.
	*/
	redraw: function () {
		var series = this,
			chart = series.chart,
			wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after
			group = series.group;

		// reposition on resize
		if (group) {
			if (chart.inverted) {
				group.attr({
					width: chart.plotWidth,
					height: chart.plotHeight
				});
			}

			group.animate({
				translateX: series.xAxis.left,
				translateY: series.yAxis.top
			});
		}

		series.translate();
		series.setTooltipPoints(true);

		series.render();
		if (wasDirtyData) {
			fireEvent(series, 'updatedData');
		}
	},

	/**
	* Set the state of the graph
	*/
	setState: function (state) {
		var series = this,
			options = series.options,
			graph = series.graph,
			stateOptions = options.states,
			lineWidth = options.lineWidth;

		state = state || NORMAL_STATE;

		if (series.state !== state) {
			series.state = state;

			if (stateOptions[state] && stateOptions[state].enabled === false) {
				return;
			}

			if (state) {
				lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
			}

			if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
				graph.attr({ // use attr because animate will cause any other animation on the graph to stop
					'stroke-width': lineWidth
				}, state ? 0 : 500);
			}
		}
	},

	/**
	* Set the visibility of the graph
	*
	* @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
	*        the visibility is toggled.
	*/
	setVisible: function (vis, redraw) {
		var series = this,
			chart = series.chart,
			legendItem = series.legendItem,
			seriesGroup = series.group,
			seriesTracker = series.tracker,
			dataLabelsGroup = series.dataLabelsGroup,
			markerGroup = series.markerGroup,
			showOrHide,
			i,
			points = series.points,
			point,
			ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
			oldVisibility = series.visible;

		// if called without an argument, toggle visibility
		series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis;
		showOrHide = vis ? 'show' : 'hide';

		// show or hide series
		if (seriesGroup) { // pies don't have one
			seriesGroup[showOrHide]();
		}
		if (markerGroup) {
			markerGroup[showOrHide]();
		}

		// show or hide trackers
		if (seriesTracker) {
			seriesTracker[showOrHide]();
		} else if (points) {
			i = points.length;
			while (i--) {
				point = points[i];
				if (point.tracker) {
					point.tracker[showOrHide]();
				}
			}
		}


		if (dataLabelsGroup) {
			dataLabelsGroup[showOrHide]();
		}

		if (legendItem) {
			chart.legend.colorizeItem(series, vis);
		}


		// rescale or adapt to resized chart
		series.isDirty = true;
		// in a stack, all other series are affected
		if (series.options.stacking) {
			each(chart.series, function (otherSeries) {
				if (otherSeries.options.stacking && otherSeries.visible) {
					otherSeries.isDirty = true;
				}
			});
		}

		if (ignoreHiddenSeries) {
			chart.isDirtyBox = true;
		}
		if (redraw !== false) {
			chart.redraw();
		}

		fireEvent(series, showOrHide);
	},

	/**
	* Show the graph
	*/
	show: function () {
		this.setVisible(true);
	},

	/**
	* Hide the graph
	*/
	hide: function () {
		this.setVisible(false);
	},


	/**
	* Set the selected state of the graph
	*
	* @param selected {Boolean} True to select the series, false to unselect. If
	*        UNDEFINED, the selection state is toggled.
	*/
	select: function (selected) {
		var series = this;
		// if called without an argument, toggle
		series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;

		if (series.checkbox) {
			series.checkbox.checked = selected;
		}

		fireEvent(series, selected ? 'select' : 'unselect');
	},

	/**
	* Draw the tracker object that sits above all data labels and markers to
	* track mouse events on the graph or points. For the line type charts
	* the tracker uses the same graphPath, but with a greater stroke width
	* for better control.
	*/
	drawTracker: function () {
		var series = this,
			options = series.options,
			trackByArea = options.trackByArea,
			trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
			trackerPathLength = trackerPath.length,
			chart = series.chart,
			renderer = chart.renderer,
			snap = chart.options.tooltip.snap,
			tracker = series.tracker,
			cursor = options.cursor,
			css = cursor && { cursor: cursor },
			singlePoints = series.singlePoints,
			trackerGroup = this.isCartesian && this.plotGroup('trackerGroup', null, VISIBLE, options.zIndex || 1, chart.trackerGroup),
			singlePoint,
			i;

		// Extend end points. A better way would be to use round linecaps,
		// but those are not clickable in VML.
		if (trackerPathLength && !trackByArea) {
			i = trackerPathLength + 1;
			while (i--) {
				if (trackerPath[i] === M) { // extend left side
					trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
				}
				if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
					trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
				}
			}
		}

		// handle single points
		for (i = 0; i < singlePoints.length; i++) {
			singlePoint = singlePoints[i];
			trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
				L, singlePoint.plotX + snap, singlePoint.plotY);
		}



		// draw the tracker
		if (tracker) {
			tracker.attr({ d: trackerPath });

		} else { // create

			series.tracker = renderer.path(trackerPath)
				.attr({
					isTracker: true,
					'stroke-linejoin': 'bevel',
					visibility: series.visible ? VISIBLE : HIDDEN,
					stroke: TRACKER_FILL,
					fill: trackByArea ? TRACKER_FILL : NONE,
					'stroke-width': options.lineWidth + (trackByArea ? 0 : 2 * snap)
				})
				.on(hasTouch ? 'touchstart' : 'mouseover', function () {
					if (chart.hoverSeries !== series) {
						series.onMouseOver();
					}
				})
				.on('mouseout', function () {
					if (!options.stickyTracking) {
						series.onMouseOut();
					}
				})
				.css(css)
				.add(trackerGroup);
		}

	}

}; // end Series prototype


/**
* LineSeries object
*/
var LineSeries = extendClass(Series);
seriesTypes.line = LineSeries;

/**
* Set the default options for area
*/
defaultPlotOptions.area = merge(defaultSeriesOptions, {
	threshold: 0
	// trackByArea: false,
	// lineColor: null, // overrides color, but lets fillColor be unaltered
	// fillOpacity: 0.75,
	// fillColor: null
});

/**
* AreaSeries object
*/
var AreaSeries = extendClass(Series, {
	type: 'area',

	/**
	* Extend the base Series getSegmentPath method by adding the path for the area.
	* This path is pushed to the series.areaPath property.
	*/
	getSegmentPath: function (segment) {

		var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method
			areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path
			i,
			options = this.options,
			segLength = segmentPath.length;

		if (segLength === 3) { // for animation from 1 to two points
			areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
		}
		if (options.stacking && !this.closedStacks) {

			// Follow stack back. Todo: implement areaspline. A general solution could be to 
			// reverse the entire graphPath of the previous series, though may be hard with
			// splines and with series with different extremes
			for (i = segment.length - 1; i >= 0; i--) {

				// step line?
				if (i < segment.length - 1 && options.step) {
					areaSegmentPath.push(segment[i + 1].plotX, segment[i].yBottom);
				}

				areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
			}

		} else { // follow zero line back
			this.closeSegment(areaSegmentPath, segment);
		}
		this.areaPath = this.areaPath.concat(areaSegmentPath);

		return segmentPath;
	},

	/**
	* Extendable method to close the segment path of an area. This is overridden in polar 
	* charts.
	*/
	closeSegment: function (path, segment) {
		var translatedThreshold = this.yAxis.getThreshold(this.options.threshold);
		path.push(
			L,
			segment[segment.length - 1].plotX,
			translatedThreshold,
			L,
			segment[0].plotX,
			translatedThreshold
		);
	},

	/**
	* Draw the graph and the underlying area. This method calls the Series base
	* function and adds the area. The areaPath is calculated in the getSegmentPath
	* method called from Series.prototype.drawGraph.
	*/
	drawGraph: function () {

		// Define or reset areaPath
		this.areaPath = [];

		// Call the base method
		Series.prototype.drawGraph.apply(this);

		// Define local variables
		var areaPath = this.areaPath,
			options = this.options,
			area = this.area;

		// Create or update the area
		if (area) { // update
			area.animate({ d: areaPath });

		} else { // create
			this.area = this.chart.renderer.path(areaPath)
				.attr({
					fill: pick(
						options.fillColor,
						Color(this.color).setOpacity(options.fillOpacity || 0.75).get()
					),
					zIndex: 0 // #1069
				}).add(this.group);
		}
	},

	/**
	* Get the series' symbol in the legend
	* 
	* @param {Object} legend The legend object
	* @param {Object} item The series (this) or point
	*/
	drawLegendSymbol: function (legend, item) {

		item.legendSymbol = this.chart.renderer.rect(
			0,
			legend.baseline - 11,
			legend.options.symbolWidth,
			12,
			2
		).attr({
			zIndex: 3
		}).add(item.legendGroup);

	}
});

seriesTypes.area = AreaSeries; /**
 * Set the default options for spline
 */
defaultPlotOptions.spline = merge(defaultSeriesOptions);

/**
* SplineSeries object
*/
var SplineSeries = extendClass(Series, {
	type: 'spline',

	/**
	* Get the spline segment from a given point's previous neighbour to the given point
	*/
	getPointSpline: function (segment, point, i) {
		var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
			denom = smoothing + 1,
			plotX = point.plotX,
			plotY = point.plotY,
			lastPoint = segment[i - 1],
			nextPoint = segment[i + 1],
			leftContX,
			leftContY,
			rightContX,
			rightContY,
			ret;

		// find control points
		if (lastPoint && nextPoint) {

			var lastX = lastPoint.plotX,
				lastY = lastPoint.plotY,
				nextX = nextPoint.plotX,
				nextY = nextPoint.plotY,
				correction;

			leftContX = (smoothing * plotX + lastX) / denom;
			leftContY = (smoothing * plotY + lastY) / denom;
			rightContX = (smoothing * plotX + nextX) / denom;
			rightContY = (smoothing * plotY + nextY) / denom;

			// have the two control points make a straight line through main point
			correction = ((rightContY - leftContY) * (rightContX - plotX)) /
				(rightContX - leftContX) + plotY - rightContY;

			leftContY += correction;
			rightContY += correction;

			// to prevent false extremes, check that control points are between
			// neighbouring points' y values
			if (leftContY > lastY && leftContY > plotY) {
				leftContY = mathMax(lastY, plotY);
				rightContY = 2 * plotY - leftContY; // mirror of left control point
			} else if (leftContY < lastY && leftContY < plotY) {
				leftContY = mathMin(lastY, plotY);
				rightContY = 2 * plotY - leftContY;
			}
			if (rightContY > nextY && rightContY > plotY) {
				rightContY = mathMax(nextY, plotY);
				leftContY = 2 * plotY - rightContY;
			} else if (rightContY < nextY && rightContY < plotY) {
				rightContY = mathMin(nextY, plotY);
				leftContY = 2 * plotY - rightContY;
			}

			// record for drawing in next point
			point.rightContX = rightContX;
			point.rightContY = rightContY;

		}

		// Visualize control points for debugging
		/*
		if (leftContX) {
		this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2)
		.attr({
		stroke: 'red',
		'stroke-width': 1,
		fill: 'none'
		})
		.add();
		this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop,
		'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
		.attr({
		stroke: 'red',
		'stroke-width': 1
		})
		.add();
		this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2)
		.attr({
		stroke: 'green',
		'stroke-width': 1,
		fill: 'none'
		})
		.add();
		this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop,
		'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
		.attr({
		stroke: 'green',
		'stroke-width': 1
		})
		.add();
		}
		// */

		// moveTo or lineTo
		if (!i) {
			ret = [M, plotX, plotY];
		} else { // curve from last point to this
			ret = [
				'C',
				lastPoint.rightContX || lastPoint.plotX,
				lastPoint.rightContY || lastPoint.plotY,
				leftContX || plotX,
				leftContY || plotY,
				plotX,
				plotY
			];
			lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
		}
		return ret;
	}
});
seriesTypes.spline = SplineSeries;

/**
* Set the default options for areaspline
*/
defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);

/**
* AreaSplineSeries object
*/
var areaProto = AreaSeries.prototype,
	AreaSplineSeries = extendClass(SplineSeries, {
		type: 'areaspline',
		closedStacks: true, // instead of following the previous graph back, follow the threshold back

		// Mix in methods from the area series
		getSegmentPath: areaProto.getSegmentPath,
		closeSegment: areaProto.closeSegment,
		drawGraph: areaProto.drawGraph
	});
seriesTypes.areaspline = AreaSplineSeries;

/**
* Set the default options for column
*/
defaultPlotOptions.column = merge(defaultSeriesOptions, {
	borderColor: '#FFFFFF',
	borderWidth: 1,
	borderRadius: 0,
	//colorByPoint: undefined,
	groupPadding: 0.2,
	//grouping: true,
	marker: null, // point options are specified in the base options
	pointPadding: 0.1,
	//pointWidth: null,
	minPointLength: 0,
	cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes
	pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories
	states: {
		hover: {
			brightness: 0.1,
			shadow: false
		},
		select: {
			color: '#C0C0C0',
			borderColor: '#000000',
			shadow: false
		}
	},
	dataLabels: {
		align: null, // auto
		verticalAlign: null, // auto
		y: null
	},
	threshold: 0
});

/**
* ColumnSeries object
*/
var ColumnSeries = extendClass(Series, {
	type: 'column',
	tooltipOutsidePlot: true,
	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
		stroke: 'borderColor',
		'stroke-width': 'borderWidth',
		fill: 'color',
		r: 'borderRadius'
	},

	/**
	* Initialize the series
	*/
	init: function () {
		Series.prototype.init.apply(this, arguments);

		var series = this,
			chart = series.chart;

		// if the series is added dynamically, force redraw of other
		// series affected by a new column
		if (chart.hasRendered) {
			each(chart.series, function (otherSeries) {
				if (otherSeries.type === series.type) {
					otherSeries.isDirty = true;
				}
			});
		}
	},

	/**
	* Translate each point to the plot area coordinate system and find shape positions
	*/
	translate: function () {
		var series = this,
			chart = series.chart,
			options = series.options,
			stacking = options.stacking,
			borderWidth = options.borderWidth,
			columnCount = 0,
			xAxis = series.xAxis,
			reversedXAxis = xAxis.reversed,
			stackGroups = {},
			stackKey,
			columnIndex;

		Series.prototype.translate.apply(series);

		// Get the total number of column type series.
		// This is called on every series. Consider moving this logic to a
		// chart.orderStacks() function and call it on init, addSeries and removeSeries
		if (options.grouping === false) {
			columnCount = 1;

		} else {
			each(chart.series, function (otherSeries) {
				var otherOptions = otherSeries.options;
				if (otherSeries.type === series.type && otherSeries.visible &&
						series.options.group === otherOptions.group) { // used in Stock charts navigator series
					if (otherOptions.stacking) {
						stackKey = otherSeries.stackKey;
						if (stackGroups[stackKey] === UNDEFINED) {
							stackGroups[stackKey] = columnCount++;
						}
						columnIndex = stackGroups[stackKey];
					} else if (otherOptions.grouping !== false) { // #1162
						columnIndex = columnCount++;
					}
					otherSeries.columnIndex = columnIndex;
				}
			});
		}

		// calculate the width and position of each column based on
		// the number of column series in the plot, the groupPadding
		// and the pointPadding options
		var points = series.points,
			categoryWidth = mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1),
			groupPadding = categoryWidth * options.groupPadding,
			groupWidth = categoryWidth - 2 * groupPadding,
			pointOffsetWidth = groupWidth / columnCount,
			optionPointWidth = options.pointWidth,
			pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
				pointOffsetWidth * options.pointPadding,
			pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts
			barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
			colIndex = (reversedXAxis ? columnCount -
				series.columnIndex : series.columnIndex) || 0,
			pointXOffset = pointPadding + (groupPadding + colIndex *
				pointOffsetWidth - (categoryWidth / 2)) *
				(reversedXAxis ? -1 : 1),
			threshold = options.threshold,
			translatedThreshold = series.translatedThreshold = series.yAxis.getThreshold(threshold),
			minPointLength = pick(options.minPointLength, 5);

		// record the new values
		each(points, function (point) {
			var plotY = point.plotY,
				yBottom = pick(point.yBottom, translatedThreshold),
				barX = point.plotX + pointXOffset,
				barY = mathCeil(mathMin(plotY, yBottom)),
				barH = mathCeil(mathMax(plotY, yBottom) - barY),
				stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
				shapeArgs;

			// Record the offset'ed position and width of the bar to be able to align the stacking total correctly
			if (stacking && series.visible && stack && stack[point.x]) {
				stack[point.x].setOffset(pointXOffset, barW);
			}

			// handle options.minPointLength
			if (mathAbs(barH) < minPointLength) {
				if (minPointLength) {
					barH = minPointLength;
					barY =
						mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
							yBottom - minPointLength : // keep position
							translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0);
				}
			}

			point.barX = barX;
			point.pointWidth = pointWidth;

			// create shape type and shape args that are reused in drawPoints and drawTracker
			point.shapeType = 'rect';
			point.shapeArgs = shapeArgs = chart.renderer.Element.prototype.crisp.call(0, borderWidth, barX, barY, barW, barH);

			if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
				shapeArgs.y -= 1;
				shapeArgs.height += 1;
			}

			// make small columns responsive to mouse
			point.trackerArgs = mathAbs(barH) < 3 && merge(point.shapeArgs, {
				height: 6,
				y: barY - 3
			});
		});

	},

	getSymbol: noop,

	/**
	* Use a solid rectangle like the area series types
	*/
	drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,


	/**
	* Columns have no graph
	*/
	drawGraph: noop,

	/**
	* Draw the columns. For bars, the series.group is rotated, so the same coordinates
	* apply for columns and bars. This method is inherited by scatter series.
	*
	*/
	drawPoints: function () {
		var series = this,
			options = series.options,
			renderer = series.chart.renderer,
			shapeArgs;


		// draw the columns
		each(series.points, function (point) {
			var plotY = point.plotY,
				graphic = point.graphic;
			if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
				shapeArgs = point.shapeArgs;
				if (graphic) { // update
					stop(graphic);
					graphic.animate(merge(shapeArgs));

				} else {
					point.graphic = graphic = renderer[point.shapeType](shapeArgs)
						.attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
						.add(series.group)
						.shadow(options.shadow, null, options.stacking && !options.borderRadius);
				}

			} else if (graphic) {
				point.graphic = graphic.destroy(); // #1269
			}
		});
	},
	/**
	* Draw the individual tracker elements.
	* This method is inherited by scatter and pie charts too.
	*/
	drawTracker: function () {
		var series = this,
			chart = series.chart,
			renderer = chart.renderer,
			shapeArgs,
			tracker,
			trackerLabel = +new Date(),
			options = series.options,
			cursor = options.cursor,
			css = cursor && { cursor: cursor },
			trackerGroup = series.isCartesian && series.plotGroup('trackerGroup', null, VISIBLE, options.zIndex || 1, chart.trackerGroup),
			rel,
			plotY,
			validPlotY;

		each(series.points, function (point) {
			tracker = point.tracker;
			shapeArgs = point.trackerArgs || point.shapeArgs;
			plotY = point.plotY;
			validPlotY = !series.isCartesian || (plotY !== UNDEFINED && !isNaN(plotY));
			delete shapeArgs.strokeWidth;
			if (point.y !== null && validPlotY) {
				if (tracker) {// update
					tracker.attr(shapeArgs);

				} else {
					point.tracker =
						renderer[point.shapeType](shapeArgs)
						.attr({
							isTracker: trackerLabel,
							fill: TRACKER_FILL,
							visibility: series.visible ? VISIBLE : HIDDEN
						})
						.on(hasTouch ? 'touchstart' : 'mouseover', function (event) {
							rel = event.relatedTarget || event.fromElement;
							if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) {
								series.onMouseOver();
							}
							point.onMouseOver();

						})
						.on('mouseout', function (event) {
							if (!options.stickyTracking) {
								rel = event.relatedTarget || event.toElement;
								if (attr(rel, 'isTracker') !== trackerLabel) {
									series.onMouseOut();
								}
							}
						})
						.css(css)
						.add(point.group || trackerGroup); // pies have point group - see issue #118
				}
			}
		});
	},

	/** 
	* Override the basic data label alignment by adjusting for the position of the column
	*/
	alignDataLabel: function (point, dataLabel, options, alignTo, isNew) {
		var chart = this.chart,
			inverted = chart.inverted,
			below = point.below || (point.plotY > (this.translatedThreshold || chart.plotSizeY)),
			inside = (this.options.stacking || options.inside); // draw it inside the box?

		// Align to the column itself, or the top of it
		if (point.shapeArgs) { // Area range uses this method but not alignTo
			alignTo = merge(point.shapeArgs);
			if (inverted) {
				alignTo = {
					x: chart.plotWidth - alignTo.y - alignTo.height,
					y: chart.plotHeight - alignTo.x - alignTo.width,
					width: alignTo.height,
					height: alignTo.width
				};
			}

			// Compute the alignment box
			if (!inside) {
				if (inverted) {
					alignTo.x += below ? 0 : alignTo.width;
					alignTo.width = 0;
				} else {
					alignTo.y += below ? alignTo.height : 0;
					alignTo.height = 0;
				}
			}
		}

		// When alignment is undefined (typically columns and bars), display the individual 
		// point below or above the point depending on the threshold
		options.align = pick(
			options.align,
			!inverted || inside ? 'center' : below ? 'right' : 'left'
		);
		options.verticalAlign = pick(
			options.verticalAlign,
			inverted || inside ? 'middle' : below ? 'top' : 'bottom'
		);

		// Call the parent method
		Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
	},


	/**
	* Animate the column heights one by one from zero
	* @param {Boolean} init Whether to initialize the animation or run it
	*/
	animate: function (init) {
		var series = this,
			points = series.points,
			options = series.options;

		if (!init) { // run the animation
			/*
			* Note: Ideally the animation should be initialized by calling
			* series.group.hide(), and then calling series.group.show()
			* after the animation was started. But this rendered the shadows
			* invisible in IE8 standards mode. If the columns flicker on large
			* datasets, this is the cause.
			*/

			each(points, function (point) {
				var graphic = point.graphic,
					shapeArgs = point.shapeArgs,
					yAxis = series.yAxis,
					threshold = options.threshold;

				if (graphic) {
					// start values
					graphic.attr({
						height: 0,
						y: defined(threshold) ?
							yAxis.getThreshold(threshold) :
							yAxis.translate(yAxis.getExtremes().min, 0, 1, 0, 1)
					});

					// animate
					graphic.animate({
						height: shapeArgs.height,
						y: shapeArgs.y
					}, options.animation);
				}
			});


			// delete this function to allow it only once
			series.animate = null;
		}

	},
	/**
	* Remove this series from the chart
	*/
	remove: function () {
		var series = this,
			chart = series.chart;

		// column and bar series affects other series of the same type
		// as they are either stacked or grouped
		if (chart.hasRendered) {
			each(chart.series, function (otherSeries) {
				if (otherSeries.type === series.type) {
					otherSeries.isDirty = true;
				}
			});
		}

		Series.prototype.remove.apply(series, arguments);
	}
});
seriesTypes.column = ColumnSeries;
/**
* Set the default options for bar
*/
defaultPlotOptions.bar = merge(defaultPlotOptions.column);
/**
* The Bar series class
*/
var BarSeries = extendClass(ColumnSeries, {
	type: 'bar',
	inverted: true
});
seriesTypes.bar = BarSeries;

/**
* Set the default options for scatter
*/
defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
	lineWidth: 0,
	states: {
		hover: {
			lineWidth: 0
		}
	},
	tooltip: {
		headerFormat: '<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',
		pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
	}
});

/**
* The scatter series class
*/
var ScatterSeries = extendClass(Series, {
	type: 'scatter',
	sorted: false,
	/**
	* Extend the base Series' translate method by adding shape type and
	* arguments for the point trackers
	*/
	translate: function () {
		var series = this;

		Series.prototype.translate.apply(series);

		each(series.points, function (point) {
			point.shapeType = 'circle';
			point.shapeArgs = {
				x: point.plotX,
				y: point.plotY,
				r: series.chart.options.tooltip.snap
			};
		});
	},

	/**
	* Add tracking event listener to the series group, so the point graphics
	* themselves act as trackers
	*/
	drawTracker: function () {
		var series = this,
			cursor = series.options.cursor,
			css = cursor && { cursor: cursor },
			points = series.points,
			i = points.length,
			graphic;

		// Set an expando property for the point index, used below
		while (i--) {
			graphic = points[i].graphic;
			if (graphic) { // doesn't exist for null points
				graphic.element._i = i;
			}
		}

		// Add the event listeners, we need to do this only once
		if (!series._hasTracking) {
			series.markerGroup
				.attr({
					isTracker: true
				})
				.on(hasTouch ? 'touchstart' : 'mouseover', function (e) {
					series.onMouseOver();
					if (e.target._i !== UNDEFINED) { // undefined on graph in scatterchart
						points[e.target._i].onMouseOver();
					}
				})
				.on('mouseout', function () {
					if (!series.options.stickyTracking) {
						series.onMouseOut();
					}
				})
				.css(css);
		} else {
			series._hasTracking = true;
		}
	}
});
seriesTypes.scatter = ScatterSeries;

/**
* Set the default options for pie
*/
defaultPlotOptions.pie = merge(defaultSeriesOptions, {
	borderColor: '#FFFFFF',
	borderWidth: 1,
	center: ['50%', '50%'],
	colorByPoint: true, // always true for pies
	dataLabels: {
		// align: null,
		// connectorWidth: 1,
		// connectorColor: point.color,
		// connectorPadding: 5,
		distance: 30,
		enabled: true,
		formatter: function () {
			return this.point.name;
		}
		// softConnector: true,
		//y: 0
	},
	//innerSize: 0,
	legendType: 'point',
	marker: null, // point options are specified in the base options
	size: '75%',
	showInLegend: false,
	slicedOffset: 10,
	states: {
		hover: {
			brightness: 0.1,
			shadow: false
		}
	}
});

/**
* Extended point object for pies
*/
var PiePoint = extendClass(Point, {
	/**
	* Initiate the pie slice
	*/
	init: function () {

		Point.prototype.init.apply(this, arguments);

		var point = this,
			toggleSlice;

		//visible: options.visible !== false,
		extend(point, {
			visible: point.visible !== false,
			name: pick(point.name, 'Slice')
		});

		// add event listener for select
		toggleSlice = function () {
			point.slice();
		};
		addEvent(point, 'select', toggleSlice);
		addEvent(point, 'unselect', toggleSlice);

		return point;
	},

	/**
	* Toggle the visibility of the pie slice
	* @param {Boolean} vis Whether to show the slice or not. If undefined, the
	*    visibility is toggled
	*/
	setVisible: function (vis) {
		var point = this,
			series = point.series,
			chart = series.chart,
			tracker = point.tracker,
			dataLabel = point.dataLabel,
			connector = point.connector,
			shadowGroup = point.shadowGroup,
			method;

		// if called without an argument, toggle visibility
		point.visible = vis = vis === UNDEFINED ? !point.visible : vis;

		method = vis ? 'show' : 'hide';

		point.group[method]();
		if (tracker) {
			tracker[method]();
		}
		if (dataLabel) {
			dataLabel[method]();
		}
		if (connector) {
			connector[method]();
		}
		if (shadowGroup) {
			shadowGroup[method]();
		}
		if (point.legendItem) {
			chart.legend.colorizeItem(point, vis);
		}

		// Handle ignore hidden slices
		if (!series.isDirty && series.options.ignoreHiddenPoint) {
			series.isDirty = true;
			chart.redraw();
		}
	},

	/**
	* Set or toggle whether the slice is cut out from the pie
	* @param {Boolean} sliced When undefined, the slice state is toggled
	* @param {Boolean} redraw Whether to redraw the chart. True by default.
	*/
	slice: function (sliced, redraw, animation) {
		var point = this,
			series = point.series,
			chart = series.chart,
			slicedTranslation = point.slicedTranslation,
			translation;

		setAnimation(animation, chart);

		// redraw is true by default
		redraw = pick(redraw, true);

		// if called without an argument, toggle
		sliced = point.sliced = defined(sliced) ? sliced : !point.sliced;

		translation = {
			translateX: (sliced ? slicedTranslation[0] : chart.plotLeft),
			translateY: (sliced ? slicedTranslation[1] : chart.plotTop)
		};
		point.group.animate(translation);
		if (point.shadowGroup) {
			point.shadowGroup.animate(translation);
		}

	}
});

/**
* The Pie series class
*/
var PieSeries = {
	type: 'pie',
	isCartesian: false,
	pointClass: PiePoint,
	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
		stroke: 'borderColor',
		'stroke-width': 'borderWidth',
		fill: 'color'
	},

	/**
	* Pies have one color each point
	*/
	getColor: function () {
		// record first color for use in setData
		this.initialColor = this.chart.counters.color;
	},

	/**
	* Animate the pies in
	*/
	animate: function () {
		var series = this,
			points = series.points;

		each(points, function (point) {
			var graphic = point.graphic,
				args = point.shapeArgs,
				up = -mathPI / 2;

			if (graphic) {
				// start values
				graphic.attr({
					r: 0,
					start: up,
					end: up
				});

				// animate
				graphic.animate({
					r: args.r,
					start: args.start,
					end: args.end
				}, series.options.animation);
			}
		});

		// delete this function to allow it only once
		series.animate = null;

	},

	/**
	* Extend the basic setData method by running processData and generatePoints immediately,
	* in order to access the points from the legend.
	*/
	setData: function (data, redraw) {
		Series.prototype.setData.call(this, data, false);
		this.processData();
		this.generatePoints();
		if (pick(redraw, true)) {
			this.chart.redraw();
		}
	},

	/**
	* Get the center of the pie based on the size and center options relative to the  
	* plot area. Borrowed by the polar and gauge series types.
	*/
	getCenter: function () {

		var options = this.options,
			chart = this.chart,
			plotWidth = chart.plotWidth,
			plotHeight = chart.plotHeight,
			positions = options.center.concat([options.size, options.innerSize || 0]),
			smallestSize = mathMin(plotWidth, plotHeight),
			isPercent;

		return map(positions, function (length, i) {

			isPercent = /%$/.test(length);
			return isPercent ?
			// i == 0: centerX, relative to width
			// i == 1: centerY, relative to height
			// i == 2: size, relative to smallestSize
			// i == 4: innerSize, relative to smallestSize
				[plotWidth, plotHeight, smallestSize, smallestSize][i] *
					pInt(length) / 100 :
				length;
		});
	},

	/**
	* Do translation for pie slices
	*/
	translate: function () {
		this.generatePoints();

		var total = 0,
			series = this,
			cumulative = -0.25, // start at top
			precision = 1000, // issue #172
			options = series.options,
			slicedOffset = options.slicedOffset,
			connectorOffset = slicedOffset + options.borderWidth,
			positions,
			chart = series.chart,
			start,
			end,
			angle,
			points = series.points,
			circ = 2 * mathPI,
			fraction,
			radiusX, // the x component of the radius vector for a given point
			radiusY,
			labelDistance = options.dataLabels.distance,
			ignoreHiddenPoint = options.ignoreHiddenPoint,
			i,
			len = points.length,
			point;

		// get positions - either an integer or a percentage string must be given
		series.center = positions = series.getCenter();

		// utility for getting the x value from a given y, used for anticollision logic in data labels
		series.getX = function (y, left) {

			angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));

			return positions[0] +
				(left ? -1 : 1) *
				(mathCos(angle) * (positions[2] / 2 + labelDistance));
		};

		// get the total sum
		for (i = 0; i < len; i++) {
			point = points[i];
			total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y;
		}

		// Calculate the geometry for each point
		for (i = 0; i < len; i++) {

			point = points[i];

			// set start and end angle
			fraction = total ? point.y / total : 0;
			start = mathRound(cumulative * circ * precision) / precision;
			if (!ignoreHiddenPoint || point.visible) {
				cumulative += fraction;
			}
			end = mathRound(cumulative * circ * precision) / precision;

			// set the shape
			point.shapeType = 'arc';
			point.shapeArgs = {
				x: positions[0],
				y: positions[1],
				r: positions[2] / 2,
				innerR: positions[3] / 2,
				start: start,
				end: end
			};

			// center for the sliced out slice
			angle = (end + start) / 2;
			point.slicedTranslation = map([
				mathCos(angle) * slicedOffset + chart.plotLeft,
				mathSin(angle) * slicedOffset + chart.plotTop
			], mathRound);

			// set the anchor point for tooltips
			radiusX = mathCos(angle) * positions[2] / 2;
			radiusY = mathSin(angle) * positions[2] / 2;
			point.tooltipPos = [
				positions[0] + radiusX * 0.7,
				positions[1] + radiusY * 0.7
			];

			// set the anchor point for data labels
			point.labelPos = [
				positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
				positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
				positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
				positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
				positions[0] + radiusX, // landing point for connector
				positions[1] + radiusY, // a/a
				labelDistance < 0 ? // alignment
					'center' :
					angle < circ / 4 ? 'left' : 'right', // alignment
				angle // center angle
			];

			// API properties
			point.percentage = fraction * 100;
			point.total = total;

		}


		this.setTooltipPoints();
	},

	/**
	* Render the slices
	*/
	render: function () {
		var series = this;

		// cache attributes for shapes
		series.getAttribs();

		this.drawPoints();

		// draw the mouse tracking area
		if (series.options.enableMouseTracking !== false) {
			series.drawTracker();
		}

		this.drawDataLabels();

		if (series.options.animation && series.animate) {
			series.animate();
		}

		// (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
		series.isDirty = false; // means data is in accordance with what you see
	},

	/**
	* Draw the data points
	*/
	drawPoints: function () {
		var series = this,
			chart = series.chart,
			renderer = chart.renderer,
			groupTranslation,
		//center,
			graphic,
			group,
			shadow = series.options.shadow,
			shadowGroup,
			shapeArgs;

		// draw the slices
		each(series.points, function (point) {
			graphic = point.graphic;
			shapeArgs = point.shapeArgs;
			group = point.group;
			shadowGroup = point.shadowGroup;

			// put the shadow behind all points
			if (shadow && !shadowGroup) {
				shadowGroup = point.shadowGroup = renderer.g('shadow')
					.attr({ zIndex: 4 })
					.add();
			}

			// create the group the first time
			if (!group) {
				group = point.group = renderer.g('point')
					.attr({ zIndex: 5 })
					.add();
			}

			// if the point is sliced, use special translation, else use plot area traslation
			groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop];
			group.translate(groupTranslation[0], groupTranslation[1]);
			if (shadowGroup) {
				shadowGroup.translate(groupTranslation[0], groupTranslation[1]);
			}

			// draw the slice
			if (graphic) {
				graphic.animate(shapeArgs);
			} else {
				point.graphic = graphic = renderer.arc(shapeArgs)
					.setRadialReference(series.center)
					.attr(extend(
						point.pointAttr[NORMAL_STATE],
						{ 'stroke-linejoin': 'round' }
					))
					.add(point.group)
					.shadow(shadow, shadowGroup);

			}

			// detect point specific visibility
			if (point.visible === false) {
				point.setVisible(false);
			}

		});

	},

	/**
	* Override the base drawDataLabels method by pie specific functionality
	*/
	drawDataLabels: function () {
		var series = this,
			data = series.data,
			point,
			chart = series.chart,
			options = series.options.dataLabels,
			connectorPadding = pick(options.connectorPadding, 10),
			connectorWidth = pick(options.connectorWidth, 1),
			connector,
			connectorPath,
			softConnector = pick(options.softConnector, true),
			distanceOption = options.distance,
			seriesCenter = series.center,
			radius = seriesCenter[2] / 2,
			centerY = seriesCenter[1],
			outside = distanceOption > 0,
			dataLabel,
			labelPos,
			labelHeight,
			halves = [// divide the points into right and left halves for anti collision
				[], // right
				[]  // left
			],
			x,
			y,
			visibility,
			rankArr,
			sort,
			i = 2,
			j;

		// get out if not enabled
		if (!options.enabled && !series._hasPointLabels) {
			return;
		}

		// run parent method
		Series.prototype.drawDataLabels.apply(series);

		// arrange points for detection collision
		each(data, function (point) {
			if (point.dataLabel) { // it may have been cancelled in the base method (#407)
				halves[
					point.labelPos[7] < mathPI / 2 ? 0 : 1
				].push(point);
			}
		});
		halves[1].reverse();

		// define the sorting algorithm
		sort = function (a, b) {
			return b.y - a.y;
		};

		// assume equal label heights
		labelHeight = halves[0][0] && halves[0][0].dataLabel && (halves[0][0].dataLabel.getBBox().height || 21); // 21 is for #968

		/* Loop over the points in each half, starting from the top and bottom
		* of the pie to detect overlapping labels.
		*/
		while (i--) {

			var slots = [],
				slotsLength,
				usedSlots = [],
				points = halves[i],
				pos,
				length = points.length,
				slotIndex;

			// Only do anti-collision when we are outside the pie and have connectors (#856)
			if (distanceOption > 0) {

				// build the slots
				for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {
					slots.push(pos);
					// visualize the slot
					/*
					var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),
					slotY = pos + chart.plotTop;
					if (!isNaN(slotX)) {
					chart.renderer.rect(slotX, slotY - 7, 100, labelHeight)
					.attr({
					'stroke-width': 1,
					stroke: 'silver'
					})
					.add();
					chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)
					.attr({
					fill: 'silver'
					}).add();
					}
					// */
				}
				slotsLength = slots.length;

				// if there are more values than available slots, remove lowest values
				if (length > slotsLength) {
					// create an array for sorting and ranking the points within each quarter
					rankArr = [].concat(points);
					rankArr.sort(sort);
					j = length;
					while (j--) {
						rankArr[j].rank = j;
					}
					j = length;
					while (j--) {
						if (points[j].rank >= slotsLength) {
							points.splice(j, 1);
						}
					}
					length = points.length;
				}

				// The label goes to the nearest open slot, but not closer to the edge than
				// the label's index.
				for (j = 0; j < length; j++) {

					point = points[j];
					labelPos = point.labelPos;

					var closest = 9999,
						distance,
						slotI;

					// find the closest slot index
					for (slotI = 0; slotI < slotsLength; slotI++) {
						distance = mathAbs(slots[slotI] - labelPos[1]);
						if (distance < closest) {
							closest = distance;
							slotIndex = slotI;
						}
					}

					// if that slot index is closer to the edges of the slots, move it
					// to the closest appropriate slot
					if (slotIndex < j && slots[j] !== null) { // cluster at the top
						slotIndex = j;
					} else if (slotsLength < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom
						slotIndex = slotsLength - length + j;
						while (slots[slotIndex] === null) { // make sure it is not taken
							slotIndex++;
						}
					} else {
						// Slot is taken, find next free slot below. In the next run, the next slice will find the
						// slot above these, because it is the closest one
						while (slots[slotIndex] === null) { // make sure it is not taken
							slotIndex++;
						}
					}

					usedSlots.push({ i: slotIndex, y: slots[slotIndex] });
					slots[slotIndex] = null; // mark as taken
				}
				// sort them in order to fill in from the top
				usedSlots.sort(sort);
			}

			// now the used slots are sorted, fill them up sequentially
			for (j = 0; j < length; j++) {

				var slot, naturalY;

				point = points[j];
				labelPos = point.labelPos;
				dataLabel = point.dataLabel;
				visibility = point.visible === false ? HIDDEN : VISIBLE;
				naturalY = labelPos[1];

				if (distanceOption > 0) {
					slot = usedSlots.pop();
					slotIndex = slot.i;

					// if the slot next to currrent slot is free, the y value is allowed
					// to fall back to the natural position
					y = slot.y;
					if ((naturalY > y && slots[slotIndex + 1] !== null) ||
							(naturalY < y && slots[slotIndex - 1] !== null)) {
						y = naturalY;
					}

				} else {
					y = naturalY;
				}

				// get the x - use the natural x position for first and last slot, to prevent the top
				// and botton slice connectors from touching each other on either side
				x = options.justify ?
					seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) :
					series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);

				// move or place the data label
				dataLabel
					.attr({
						visibility: visibility,
						align: labelPos[6]
					})[dataLabel.moved ? 'animate' : 'attr']({
						x: x + options.x +
							({ left: connectorPadding, right: -connectorPadding}[labelPos[6]] || 0),
						y: y + options.y - 10 // 10 is for the baseline (label vs text)
					});
				dataLabel.moved = true;

				// draw the connector
				if (outside && connectorWidth) {
					connector = point.connector;

					connectorPath = softConnector ? [
						M,
						x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
						'C',
						x, y, // first break, next to the label
						2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
						labelPos[2], labelPos[3], // second break
						L,
						labelPos[4], labelPos[5] // base
					] : [
						M,
						x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
						L,
						labelPos[2], labelPos[3], // second break
						L,
						labelPos[4], labelPos[5] // base
					];

					if (connector) {
						connector.animate({ d: connectorPath });
						connector.attr('visibility', visibility);

					} else {
						point.connector = connector = series.chart.renderer.path(connectorPath).attr({
							'stroke-width': connectorWidth,
							stroke: options.connectorColor || point.color || '#606060',
							visibility: visibility,
							zIndex: 3
						})
						.translate(chart.plotLeft, chart.plotTop)
						.add();
					}
				}
			}
		}
	},

	alignDataLabel: noop,

	/**
	* Draw point specific tracker objects. Inherit directly from column series.
	*/
	drawTracker: ColumnSeries.prototype.drawTracker,

	/**
	* Use a simple symbol from column prototype
	*/
	drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,

	/**
	* Pies don't have point marker symbols
	*/
	getSymbol: function () { }

};
PieSeries = extendClass(Series, PieSeries);
seriesTypes.pie = PieSeries;


// global variables
extend(Highcharts, {

	// Constructors
	Axis: Axis,
	CanVGRenderer: CanVGRenderer,
	Chart: Chart,
	Color: Color,
	Legend: Legend,
	MouseTracker: MouseTracker,
	Point: Point,
	Tick: Tick,
	Tooltip: Tooltip,
	Renderer: Renderer,
	Series: Series,
	SVGRenderer: SVGRenderer,
	VMLRenderer: VMLRenderer,

	// Various
	dateFormat: dateFormat,
	pathAnim: pathAnim,
	getOptions: getOptions,
	hasBidiBug: hasBidiBug,
	numberFormat: numberFormat,
	seriesTypes: seriesTypes,
	setOptions: setOptions,
	addEvent: addEvent,
	removeEvent: removeEvent,
	createElement: createElement,
	discardElement: discardElement,
	css: css,
	each: each,
	extend: extend,
	map: map,
	merge: merge,
	pick: pick,
	splat: splat,
	extendClass: extendClass,
	pInt: pInt,
	wrap: wrap,
	svg: hasSVG,
	canvas: useCanVG,
	vml: !hasSVG && !useCanVG,
	product: 'Highcharts',
	version: '2.3.3'
});
} ());

// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS

/**
* @license Highcharts JS v2.3.3 (2012-10-04)
*
* (c) 2009-2011 Torstein Hønsi
*
* License: www.highcharts.com/license
*/

// JSLint options:
/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */

(function (Highcharts, UNDEFINED) {
	var each = Highcharts.each,
	extend = Highcharts.extend,
	merge = Highcharts.merge,
	map = Highcharts.map,
	pick = Highcharts.pick,
	pInt = Highcharts.pInt,
	defaultPlotOptions = Highcharts.getOptions().plotOptions,
	seriesTypes = Highcharts.seriesTypes,
	extendClass = Highcharts.extendClass,
	splat = Highcharts.splat,
	wrap = Highcharts.wrap,
	Axis = Highcharts.Axis,
	Tick = Highcharts.Tick,
	Series = Highcharts.Series,
	colProto = seriesTypes.column.prototype,
	noop = function () { }; /**
 * The Pane object allows options that are common to a set of X and Y axes.
 * 
 * In the future, this can be extended to basic Highcharts and Highstock.
 */
	function Pane(options, chart, firstAxis) {
		this.init.call(this, options, chart, firstAxis);
	}

	// Extend the Pane prototype
	extend(Pane.prototype, {

		/**
		* Initiate the Pane object
		*/
		init: function (options, chart, firstAxis) {
			var pane = this,
			backgroundOption,
			defaultOptions = pane.defaultOptions;

			pane.chart = chart;

			// Set options
			if (chart.angular) { // gauges
				defaultOptions.background = {}; // gets extended by this.defaultBackgroundOptions
			}
			pane.options = options = merge(defaultOptions, options);

			backgroundOption = options.background;

			// To avoid having weighty logic to place, update and remove the backgrounds,
			// push them to the first axis' plot bands and borrow the existing logic there.
			if (backgroundOption) {
				each([].concat(splat(backgroundOption)).reverse(), function (config) {
					var backgroundColor = config.backgroundColor; // if defined, replace the old one (specific for gradients)
					config = merge(pane.defaultBackgroundOptions, config);
					if (backgroundColor) {
						config.backgroundColor = backgroundColor;
					}
					config.color = config.backgroundColor; // due to naming in plotBands
					firstAxis.options.plotBands.unshift(config);
				});
			}
		},

		/**
		* The default options object
		*/
		defaultOptions: {
			// background: {conditional},
			center: ['50%', '50%'],
			size: '85%',
			startAngle: 0
			//endAngle: startAngle + 360
		},

		/**
		* The default background options
		*/
		defaultBackgroundOptions: {
			shape: 'circle',
			borderWidth: 1,
			borderColor: 'silver',
			backgroundColor: {
				linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
				stops: [
				[0, '#FFF'],
				[1, '#DDD']
			]
			},
			from: Number.MIN_VALUE, // corrected to axis min
			innerRadius: 0,
			to: Number.MAX_VALUE, // corrected to axis max
			outerRadius: '105%'
		}

	});
	var axisProto = Axis.prototype,
	tickProto = Tick.prototype;

	/**
	* Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges
	*/
	var hiddenAxisMixin = {
		getOffset: noop,
		redraw: function () {
			this.isDirty = false; // prevent setting Y axis dirty
		},
		render: function () {
			this.isDirty = false; // prevent setting Y axis dirty
		},
		setScale: noop,
		setCategories: noop,
		setTitle: noop
	};

	/**
	* Augmented methods for the value axis
	*/
	/*jslint unparam: true*/
	var radialAxisMixin = {
		isRadial: true,

		/**
		* The default options extend defaultYAxisOptions
		*/
		defaultRadialGaugeOptions: {
			labels: {
				align: 'center',
				x: 0,
				y: null // auto
			},
			minorGridLineWidth: 0,
			minorTickInterval: 'auto',
			minorTickLength: 10,
			minorTickPosition: 'inside',
			minorTickWidth: 1,
			plotBands: [],
			tickLength: 10,
			tickPosition: 'inside',
			tickWidth: 2,
			title: {
				rotation: 0
			},
			zIndex: 2 // behind dials, points in the series group
		},

		// Circular axis around the perimeter of a polar chart
		defaultRadialXOptions: {
			gridLineWidth: 1, // spokes
			labels: {
				align: null, // auto
				distance: 15,
				x: 0,
				y: null // auto
			},
			maxPadding: 0,
			minPadding: 0,
			plotBands: [],
			showLastLabel: false,
			tickLength: 0
		},

		// Radial axis, like a spoke in a polar chart
		defaultRadialYOptions: {
			gridLineInterpolation: 'circle',
			labels: {
				align: 'right',
				x: -3,
				y: -2
			},
			plotBands: [],
			showLastLabel: false,
			title: {
				x: 4,
				text: null,
				rotation: 90
			}
		},

		/**
		* Merge and set options
		*/
		setOptions: function (userOptions) {

			this.options = merge(
			this.defaultOptions,
			this.defaultRadialOptions,
			userOptions
		);

		},

		/**
		* Wrap the getOffset method to return zero offset for title or labels in a radial 
		* axis
		*/
		getOffset: function () {
			// Call the Axis prototype method (the method we're in now is on the instance)
			axisProto.getOffset.call(this);

			// Title or label offsets are not counted
			this.chart.axisOffset[this.side] = 0;

			// Set the center array
			this.center = this.pane.center = seriesTypes.pie.prototype.getCenter.call(this.pane);
		},


		/**
		* Get the path for the axis line. This method is also referenced in the getPlotLinePath
		* method.
		*/
		getLinePath: function (lineWidth, radius) {
			var center = this.center;
			radius = pick(radius, center[2] / 2 - this.offset);

			return this.chart.renderer.symbols.arc(
			this.left + center[0],
			this.top + center[1],
			radius,
			radius,
			{
				start: this.startAngleRad,
				end: this.endAngleRad,
				open: true,
				innerR: 0
			}
		);
		},

		/**
		* Override setAxisTranslation by setting the translation to the difference
		* in rotation. This allows the translate method to return angle for 
		* any given value.
		*/
		setAxisTranslation: function () {

			// Call uber method		
			axisProto.setAxisTranslation.call(this);

			// Set transA and minPixelPadding
			if (this.center) { // it's not defined the first time
				if (this.isCircular) {

					this.transA = (this.endAngleRad - this.startAngleRad) /
					((this.max - this.min) || 1);


				} else {
					this.transA = (this.center[2] / 2) / ((this.max - this.min) || 1);
				}

				if (this.isXAxis) {
					this.minPixelPadding = this.transA * this.minPointOffset +
					(this.reversed ? (this.endAngleRad - this.startAngleRad) / 4 : 0); // ???
				}
			}
		},

		/**
		* In case of auto connect, add one closestPointRange to the max value right before
		* tickPositions are computed, so that ticks will extend passed the real max.
		*/
		beforeSetTickPositions: function () {
			if (this.autoConnect) {
				this.max += (this.categories && 1) || this.pointRange || this.closestPointRange; // #1197
			}
		},

		/**
		* Override the setAxisSize method to use the arc's circumference as length. This
		* allows tickPixelInterval to apply to pixel lengths along the perimeter
		*/
		setAxisSize: function () {

			axisProto.setAxisSize.call(this);

			if (this.center) { // it's not defined the first time
				this.len = this.width = this.height = this.isCircular ?
				this.center[2] * (this.endAngleRad - this.startAngleRad) / 2 :
				this.center[2] / 2;
			}
		},

		/**
		* Returns the x, y coordinate of a point given by a value and a pixel distance
		* from center
		*/
		getPosition: function (value, length) {
			if (!this.isCircular) {
				length = this.translate(value);
				value = this.min;
			}

			return this.postTranslate(
			this.translate(value),
			pick(length, this.center[2] / 2) - this.offset
		);
		},

		/**
		* Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates. 
		*/
		postTranslate: function (angle, radius) {

			var chart = this.chart,
			center = this.center;

			angle = this.startAngleRad + angle;

			return {
				x: chart.plotLeft + center[0] + Math.cos(angle) * radius,
				y: chart.plotTop + center[1] + Math.sin(angle) * radius
			};

		},

		/**
		* Find the path for plot bands along the radial axis
		*/
		getPlotBandPath: function (from, to, options) {
			var center = this.center,
			startAngleRad = this.startAngleRad,
			fullRadius = center[2] / 2,
			radii = [
				pick(options.outerRadius, '100%'),
				options.innerRadius,
				pick(options.thickness, 10)
			],
			percentRegex = /%$/,
			start,
			end,
			open,
			isCircular = this.isCircular, // X axis in a polar chart
			ret;

			// Polygonal plot bands
			if (this.options.gridLineInterpolation === 'polygon') {
				ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));

				// Circular grid bands
			} else {

				// Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from
				if (!isCircular) {
					radii[0] = this.translate(from);
					radii[1] = this.translate(to);
				}

				// Convert percentages to pixel values
				radii = map(radii, function (radius) {
					if (percentRegex.test(radius)) {
						radius = (pInt(radius, 10) * fullRadius) / 100;
					}
					return radius;
				});

				// Handle full circle
				if (options.shape === 'circle' || !isCircular) {
					start = -Math.PI / 2;
					end = Math.PI * 1.5;
					open = true;
				} else {
					start = startAngleRad + this.translate(from);
					end = startAngleRad + this.translate(to);
				}


				ret = this.chart.renderer.symbols.arc(
				this.left + center[0],
				this.top + center[1],
				radii[0],
				radii[0],
				{
					start: start,
					end: end,
					innerR: pick(radii[1], radii[0] - radii[2]),
					open: open
				}
			);
			}

			return ret;
		},

		/**
		* Find the path for plot lines perpendicular to the radial axis.
		*/
		getPlotLinePath: function (value, reverse) {
			var axis = this,
			center = axis.center,
			chart = axis.chart,
			end = axis.getPosition(value),
			xAxis,
			xy,
			tickPositions,
			ret;

			// Spokes
			if (axis.isCircular) {
				ret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];

				// Concentric circles			
			} else if (axis.options.gridLineInterpolation === 'circle') {
				value = axis.translate(value);
				if (value) { // a value of 0 is in the center
					ret = axis.getLinePath(0, value);
				}
				// Concentric polygons 
			} else {
				xAxis = chart.xAxis[0];
				ret = [];
				value = axis.translate(value);
				tickPositions = xAxis.tickPositions;
				if (xAxis.autoConnect) {
					tickPositions = tickPositions.concat([tickPositions[0]]);
				}
				// Reverse the positions for concatenation of polygonal plot bands
				if (reverse) {
					tickPositions = [].concat(tickPositions).reverse();
				}

				each(tickPositions, function (pos, i) {
					xy = xAxis.getPosition(pos, value);
					ret.push(i ? 'L' : 'M', xy.x, xy.y);
				});

			}
			return ret;
		},

		/**
		* Find the position for the axis title, by default inside the gauge
		*/
		getTitlePosition: function () {
			var center = this.center,
			chart = this.chart,
			titleOptions = this.options.title;

			return {
				x: chart.plotLeft + center[0] + (titleOptions.x || 0),
				y: chart.plotTop + center[1] - ({ high: 0.5, middle: 0.25, low: 0}[titleOptions.align] *
				center[2]) + (titleOptions.y || 0)
			};
		}

	};
	/*jslint unparam: false*/

	/**
	* Override axisProto.init to mix in special axis instance functions and function overrides
	*/
	wrap(axisProto, 'init', function (proceed, chart, userOptions) {
		var axis = this,
		angular = chart.angular,
		polar = chart.polar,
		isX = userOptions.isX,
		isHidden = angular && isX,
		isCircular,
		startAngleRad,
		endAngleRad,
		options,
		chartOptions = chart.options,
		paneIndex = userOptions.pane || 0,
		pane,
		paneOptions;

		// Before prototype.init
		if (angular) {
			extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);
			isCircular = !isX;
			if (isCircular) {
				this.defaultRadialOptions = this.defaultRadialGaugeOptions;
			}

		} else if (polar) {
			//extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin);
			extend(this, radialAxisMixin);
			isCircular = isX;
			this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions);

		}

		// Run prototype.init
		proceed.call(this, chart, userOptions);

		if (!isHidden && (angular || polar)) {
			options = this.options;

			// Create the pane and set the pane options.
			if (!chart.panes) {
				chart.panes = [];
			}
			this.pane = chart.panes[paneIndex] = pane = new Pane(
			splat(chartOptions.pane)[paneIndex],
			chart,
			axis
		);
			paneOptions = pane.options;


			// Disable certain features on angular and polar axes
			chart.inverted = false;
			chartOptions.chart.zoomType = null;

			// Start and end angle options are
			// given in degrees relative to top, while internal computations are
			// in radians relative to right (like SVG).
			this.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180;
			this.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180;
			this.offset = options.offset || 0;

			this.isCircular = isCircular;

			// Automatically connect grid lines?
			if (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) {
				this.autoConnect = true;
			}
		}

	});

	/**
	* Add special cases within the Tick class' methods for radial axes.
	*/
	wrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) {
		var axis = this.axis;

		return axis.getPosition ?
		axis.getPosition(pos) :
		proceed.call(this, horiz, pos, tickmarkOffset, old);
	});

	/**
	* Wrap the getLabelPosition function to find the center position of the label
	* based on the distance option
	*/
	wrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
		var axis = this.axis,
		optionsY = labelOptions.y,
		ret,
		align = labelOptions.align,
		angle = (axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180;

		if (axis.isRadial) {
			ret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25));

			// Automatically rotated
			if (labelOptions.rotation === 'auto') {
				label.attr({
					rotation: angle
				});

				// Vertically centered
			} else if (optionsY === null) {
				optionsY = pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;

			}

			// Automatic alignment
			if (align === null) {
				if (axis.isCircular) {
					if (angle > 20 && angle < 160) {
						align = 'left'; // right hemisphere
					} else if (angle > 200 && angle < 340) {
						align = 'right'; // left hemisphere
					} else {
						align = 'center'; // top or bottom
					}
				} else {
					align = 'center';
				}
				label.attr({
					align: align
				});
			}

			ret.x += labelOptions.x;
			ret.y += optionsY;

		} else {
			ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
		}
		return ret;
	});

	/**
	* Wrap the getMarkPath function to return the path of the radial marker
	*/
	wrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) {
		var axis = this.axis,
		endPoint,
		ret;

		if (axis.isRadial) {
			endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);
			ret = [
			'M',
			x,
			y,
			'L',
			endPoint.x,
			endPoint.y
		];
		} else {
			ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);
		}
		return ret;
	}); /* 
 * The AreaRangeSeries class
 * 
 */

	/**
	* Extend the default options with map options
	*/
	defaultPlotOptions.arearange = merge(defaultPlotOptions.area, {
		lineWidth: 1,
		marker: null,
		threshold: null,
		tooltip: {
			pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.low}</b> - <b>{point.high}</b><br/>'
		},
		trackByArea: true,
		dataLabels: {
			verticalAlign: null,
			xLow: 0,
			xHigh: 0,
			yLow: 0,
			yHigh: 0
		},
		shadow: false
	});

	/**
	* Extend the point object
	*/
	var RangePoint = Highcharts.extendClass(Highcharts.Point, {
		/**
		* Apply the options containing the x and low/high data and possible some extra properties.
		* This is called on point init or from point.update. Extends base Point by adding
		* multiple y-like values.
		*
		* @param {Object} options
		*/
		applyOptions: function (options, x) {
			var point = this,
			series = point.series,
			pointArrayMap = series.pointArrayMap,
			i = 0,
			j = 0,
			valueCount = pointArrayMap.length;


			// object input
			if (typeof options === 'object' && typeof options.length !== 'number') {

				// copy options directly to point
				extend(point, options);

				point.options = options;

			} else if (options.length) { // array
				// with leading x value
				if (options.length > valueCount) {
					if (typeof options[0] === 'string') {
						point.name = options[0];
					} else if (typeof options[0] === 'number') {
						point.x = options[0];
					}
					i++;
				}
				while (j < valueCount) {
					point[pointArrayMap[j++]] = options[i++];
				}
			}

			// Handle null and make low alias y
			/*if (point.high === null) {
			point.low = null;
			}*/
			point.y = point[series.pointValKey];

			// If no x is set by now, get auto incremented value. All points must have an
			// x value, however the y value can be null to create a gap in the series
			if (point.x === UNDEFINED && series) {
				point.x = x === UNDEFINED ? series.autoIncrement() : x;
			}

			return point;
		},

		/**
		* Return a plain array for speedy calculation
		*/
		toYData: function () {
			return [this.low, this.high];
		}
	});

	/**
	* Add the series type
	*/
	seriesTypes.arearange = Highcharts.extendClass(seriesTypes.area, {
		type: 'arearange',
		pointArrayMap: ['low', 'high'],
		pointClass: RangePoint,
		pointValKey: 'low',

		/**
		* Translate data points from raw values x and y to plotX and plotY
		*/
		translate: function () {
			var series = this,
			yAxis = series.yAxis;

			seriesTypes.area.prototype.translate.apply(series);

			// Set plotLow and plotHigh
			each(series.points, function (point) {

				if (point.y !== null) {
					point.plotLow = point.plotY;
					point.plotHigh = yAxis.translate(point.high, 0, 1, 0, 1);
				}
			});
		},

		/**
		* Extend the line series' getSegmentPath method by applying the segment
		* path to both lower and higher values of the range
		*/
		getSegmentPath: function (segment) {

			var highSegment = [],
			i = segment.length,
			baseGetSegmentPath = Series.prototype.getSegmentPath,
			point,
			linePath,
			lowerPath,
			options = this.options,
			step = options.step,
			higherPath;

			// Make a segment with plotX and plotY for the top values
			while (i--) {
				point = segment[i];
				highSegment.push({
					plotX: point.plotX,
					plotY: point.plotHigh
				});
			}

			// Get the paths
			lowerPath = baseGetSegmentPath.call(this, segment);
			if (step) {
				if (step === true) {
					step = 'left';
				}
				options.step = { left: 'right', center: 'center', right: 'left'}[step]; // swap for reading in getSegmentPath
			}
			higherPath = baseGetSegmentPath.call(this, highSegment);
			options.step = step;

			// Create a line on both top and bottom of the range
			linePath = [].concat(lowerPath, higherPath);

			// For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo'
			higherPath[0] = 'L'; // this probably doesn't work for spline			
			this.areaPath = this.areaPath.concat(lowerPath, higherPath);

			return linePath;
		},

		/**
		* Extend the basic drawDataLabels method by running it for both lower and higher
		* values.
		*/
		drawDataLabels: function () {

			var data = this.data,
			length = data.length,
			i,
			originalDataLabels = [],
			seriesProto = Series.prototype,
			dataLabelOptions = this.options.dataLabels,
			point,
			inverted = this.chart.inverted;

			if (dataLabelOptions.enabled || this._hasPointLabels) {

				// Step 1: set preliminary values for plotY and dataLabel and draw the upper labels
				i = length;
				while (i--) {
					point = data[i];

					// Set preliminary values
					point.y = point.high;
					point.plotY = point.plotHigh;

					// Store original data labels and set preliminary label objects to be picked up 
					// in the uber method
					originalDataLabels[i] = point.dataLabel;
					point.dataLabel = point.dataLabelUpper;

					// Set the default offset
					point.below = false;
					if (inverted) {
						dataLabelOptions.align = 'left';
						dataLabelOptions.x = dataLabelOptions.xHigh;
					} else {
						dataLabelOptions.y = dataLabelOptions.yHigh;
					}
				}
				seriesProto.drawDataLabels.apply(this, arguments); // #1209

				// Step 2: reorganize and handle data labels for the lower values
				i = length;
				while (i--) {
					point = data[i];

					// Move the generated labels from step 1, and reassign the original data labels
					point.dataLabelUpper = point.dataLabel;
					point.dataLabel = originalDataLabels[i];

					// Reset values
					point.y = point.low;
					point.plotY = point.plotLow;

					// Set the default offset
					point.below = true;
					if (inverted) {
						dataLabelOptions.align = 'right';
						dataLabelOptions.x = dataLabelOptions.xLow;
					} else {
						dataLabelOptions.y = dataLabelOptions.yLow;
					}
				}
				seriesProto.drawDataLabels.apply(this, arguments);
			}

		},

		alignDataLabel: seriesTypes.column.prototype.alignDataLabel,

		getSymbol: seriesTypes.column.prototype.getSymbol,

		drawPoints: noop
	}); /**
 * The AreaSplineRangeSeries class
 */

	defaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange);

	/**
	* AreaSplineRangeSeries object
	*/
	seriesTypes.areasplinerange = extendClass(seriesTypes.arearange, {
		type: 'areasplinerange',
		getPointSpline: seriesTypes.spline.prototype.getPointSpline
	}); /**
 * The ColumnRangeSeries class
 */
	defaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, {
		lineWidth: 1,
		pointRange: null,
		minPointLength: 5
	});

	/**
	* ColumnRangeSeries object
	*/
	seriesTypes.columnrange = extendClass(seriesTypes.arearange, {
		type: 'columnrange',
		/**
		* Translate data points from raw values x and y to plotX and plotY
		*/
		translate: function () {
			var series = this,
			yAxis = series.yAxis,
			plotHigh;

			colProto.translate.apply(series);

			// Set plotLow and plotHigh
			each(series.points, function (point) {
				var shapeArgs = point.shapeArgs;

				point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1);
				point.plotLow = point.plotY;

				// adjust shape
				shapeArgs.y = plotHigh;
				var height = point.plotY - plotHigh;
				shapeArgs.height = height > series.options.minPointLength ? height : series.options.minPointLength;

				point.trackerArgs = shapeArgs;
			});
		},
		drawGraph: noop,
		pointAttrToOptions: colProto.pointAttrToOptions,
		drawPoints: colProto.drawPoints,
		drawTracker: colProto.drawTracker,
		animate: colProto.animate
	}); /* 
 * The GaugeSeries class
 */



	/**
	* Extend the default options
	*/
	defaultPlotOptions.gauge = merge(defaultPlotOptions.line, {
		dataLabels: {
			enabled: true,
			y: 15,
			borderWidth: 1,
			borderColor: 'silver',
			borderRadius: 3,
			style: {
				fontWeight: 'bold'
			},
			verticalAlign: 'top'
		},
		dial: {
			// radius: '80%',
			// backgroundColor: 'black',
			// borderColor: 'silver',
			// borderWidth: 0,
			// baseWidth: 3,
			// topWidth: 1,
			// baseLength: '70%' // of radius
			// rearLength: '10%'
		},
		pivot: {
			//radius: 5,
			//borderWidth: 0
			//borderColor: 'silver',
			//backgroundColor: 'black'
		},
		tooltip: {
			headerFormat: ''
		},
		showInLegend: false
	});

	/**
	* Extend the point object
	*/
	var GaugePoint = Highcharts.extendClass(Highcharts.Point, {
		/**
		* Don't do any hover colors or anything
		*/
		setState: function (state) {
			this.state = state;
		}
	});


	/**
	* Add the series type
	*/
	var GaugeSeries = {
		type: 'gauge',
		pointClass: GaugePoint,

		// chart.angular will be set to true when a gauge series is present, and this will
		// be used on the axes
		angular: true,

		/* *
		* Extend the bindAxes method by adding radial features to the axes
		* /
		_bindAxes: function () {
		Series.prototype.bindAxes.call(this);
		
		extend(this.xAxis, gaugeXAxisMixin);
		extend(this.yAxis, radialAxisMixin);
		this.yAxis.onBind();
		},*/

		/**
		* Calculate paths etc
		*/
		translate: function () {

			var series = this,
			yAxis = series.yAxis,
			center = yAxis.center;

			series.generatePoints();

			each(series.points, function (point) {

				var dialOptions = merge(series.options.dial, point.dial),
				radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200,
				baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100,
				rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100,
				baseWidth = dialOptions.baseWidth || 3,
				topWidth = dialOptions.topWidth || 1;

				point.shapeType = 'path';
				point.shapeArgs = {
					d: dialOptions.path || [
					'M',
					-rearLength, -baseWidth / 2,
					'L',
					baseLength, -baseWidth / 2,
					radius, -topWidth / 2,
					radius, topWidth / 2,
					baseLength, baseWidth / 2,
					-rearLength, baseWidth / 2,
					'z'
				],
					translateX: center[0],
					translateY: center[1],
					rotation: (yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true)) * 180 / Math.PI
				};

				// Positions for data label
				point.plotX = center[0];
				point.plotY = center[1];
			});
		},

		/**
		* Draw the points where each point is one needle
		*/
		drawPoints: function () {

			var series = this,
			center = series.yAxis.center,
			pivot = series.pivot,
			options = series.options,
			pivotOptions = options.pivot,
			renderer = series.chart.renderer;

			each(series.points, function (point) {

				var graphic = point.graphic,
				shapeArgs = point.shapeArgs,
				d = shapeArgs.d,
				dialOptions = merge(options.dial, point.dial); // #1233

				if (graphic) {
					graphic.animate(shapeArgs);
					shapeArgs.d = d; // animate alters it
				} else {
					point.graphic = renderer[point.shapeType](shapeArgs)
					.attr({
						stroke: dialOptions.borderColor || 'none',
						'stroke-width': dialOptions.borderWidth || 0,
						fill: dialOptions.backgroundColor || 'black',
						rotation: shapeArgs.rotation // required by VML when animation is false
					})
					.add(series.group);
				}
			});

			// Add or move the pivot
			if (pivot) {
				pivot.animate({ // #1235
					translateX: center[0],
					translateY: center[1]
				});
			} else {
				series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5))
				.attr({
					'stroke-width': pivotOptions.borderWidth || 0,
					stroke: pivotOptions.borderColor || 'silver',
					fill: pivotOptions.backgroundColor || 'black'
				})
				.translate(center[0], center[1])
				.add(series.group);
			}
		},

		/**
		* Animate the arrow up from startAngle
		*/
		animate: function () {
			var series = this;

			each(series.points, function (point) {
				var graphic = point.graphic;

				if (graphic) {
					// start value
					graphic.attr({
						rotation: series.yAxis.startAngleRad * 180 / Math.PI
					});

					// animate
					graphic.animate({
						rotation: point.shapeArgs.rotation
					}, series.options.animation);
				}
			});

			// delete this function to allow it only once
			series.animate = null;
		},

		render: function () {
			this.group = this.plotGroup(
			'group',
			'series',
			this.visible ? 'visible' : 'hidden',
			this.options.zIndex,
			this.chart.seriesGroup
		);
			seriesTypes.pie.prototype.render.call(this);
			this.group.clip(this.chart.clipRect);
		},

		setData: seriesTypes.pie.prototype.setData,
		drawTracker: seriesTypes.column.prototype.drawTracker
	};
	seriesTypes.gauge = Highcharts.extendClass(seriesTypes.line, GaugeSeries); /**
 * Extensions for polar charts. Additionally, much of the geometry required for polar charts is
 * gathered in RadialAxes.js.
 * 
 */

	var seriesProto = Series.prototype,
	mouseTrackerProto = Highcharts.MouseTracker.prototype;



	/**
	* Translate a point's plotX and plotY from the internal angle and radius measures to 
	* true plotX, plotY coordinates
	*/
	seriesProto.toXY = function (point) {
		var xy,
		chart = this.chart,
		plotX = point.plotX,
		plotY = point.plotY;

		// Save rectangular plotX, plotY for later computation
		point.rectPlotX = plotX;
		point.rectPlotY = plotY;

		// Record the angle in degrees for use in tooltip
		point.deg = plotX / Math.PI * 180;

		// Find the polar plotX and plotY
		xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);
		point.plotX = point.polarPlotX = xy.x - chart.plotLeft;
		point.plotY = point.polarPlotY = xy.y - chart.plotTop;
	};


	/**
	* Add some special init logic to areas and areasplines
	*/
	function initArea(proceed, chart, options) {
		proceed.call(this, chart, options);
		if (this.chart.polar) {

			/**
			* Overridden method to close a segment path. While in a cartesian plane the area 
			* goes down to the threshold, in the polar chart it goes to the center.
			*/
			this.closeSegment = function (path) {
				var center = this.xAxis.center;
				path.push(
				'L',
				center[0],
				center[1]
			);
			};

			// Instead of complicated logic to draw an area around the inner area in a stack,
			// just draw it behind
			this.closedStacks = true;
		}
	}
	wrap(seriesTypes.area.prototype, 'init', initArea);
	wrap(seriesTypes.areaspline.prototype, 'init', initArea);


	/**
	* Overridden method for calculating a spline from one point to the next
	*/
	wrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) {

		var ret,
		smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;
		denom = smoothing + 1,
		plotX,
		plotY,
		lastPoint,
		nextPoint,
		lastX,
		lastY,
		nextX,
		nextY,
		leftContX,
		leftContY,
		rightContX,
		rightContY,
		distanceLeftControlPoint,
		distanceRightControlPoint,
		leftContAngle,
		rightContAngle,
		jointAngle;


		if (this.chart.polar) {

			plotX = point.plotX;
			plotY = point.plotY;
			lastPoint = segment[i - 1];
			nextPoint = segment[i + 1];

			// Connect ends
			if (this.connectEnds) {
				if (!lastPoint) {
					lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected
				}
				if (!nextPoint) {
					nextPoint = segment[1];
				}
			}

			// find control points
			if (lastPoint && nextPoint) {

				lastX = lastPoint.plotX;
				lastY = lastPoint.plotY;
				nextX = nextPoint.plotX;
				nextY = nextPoint.plotY;
				leftContX = (smoothing * plotX + lastX) / denom;
				leftContY = (smoothing * plotY + lastY) / denom;
				rightContX = (smoothing * plotX + nextX) / denom;
				rightContY = (smoothing * plotY + nextY) / denom;
				distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));
				distanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));
				leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);
				rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);
				jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);


				// Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle
				if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {
					jointAngle -= Math.PI;
				}

				// Find the corrected control points for a spline straight through the point
				leftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint;
				leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint;
				rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint;
				rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint;

				// Record for drawing in next point
				point.rightContX = rightContX;
				point.rightContY = rightContY;

			}


			// moveTo or lineTo
			if (!i) {
				ret = ['M', plotX, plotY];
			} else { // curve from last point to this
				ret = [
				'C',
				lastPoint.rightContX || lastPoint.plotX,
				lastPoint.rightContY || lastPoint.plotY,
				leftContX || plotX,
				leftContY || plotY,
				plotX,
				plotY
			];
				lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
			}


		} else {
			ret = proceed.call(this, segment, point, i);
		}
		return ret;
	});

	/**
	* Extend translate. The plotX and plotY values are computed as if the polar chart were a
	* cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from
	* center. 
	*/
	wrap(seriesProto, 'translate', function (proceed) {

		// Run uber method
		proceed.call(this);

		// Postprocess plot coordinates
		if (this.chart.polar && !this.preventPostTranslate) {
			var points = this.points,
			i = points.length;
			while (i--) {
				// Translate plotX, plotY from angle and radius to true plot coordinates
				this.toXY(points[i]);
			}
		}
	});

	/** 
	* Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in 
	* line-like series.
	*/
	wrap(seriesProto, 'getSegmentPath', function (proceed, segment) {

		var points = this.points;

		// Connect the path
		if (this.chart.polar && this.options.connectEnds !== false &&
			segment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) {
			this.connectEnds = true; // re-used in splines
			segment = [].concat(segment, [points[0]]);
		}

		// Run uber method
		return proceed.call(this, segment);

	});


	function polarAnimate(proceed, init) {
		var chart = this.chart,
		animation = this.options.animation,
		group = this.group,
		markerGroup = this.markerGroup,
		center = this.xAxis.center,
		plotLeft = chart.plotLeft,
		plotTop = chart.plotTop,
		attribs;

		// Specific animation for polar charts
		if (chart.polar) {

			// Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation
			// would be so slow it would't matter.
			if (chart.renderer.isSVG) {

				if (animation === true) {
					animation = {};
				}

				// Initialize the animation
				if (init) {

					// Create an SVG specific attribute setter for scaleX and scaleY
					group.attrSetters.scaleX = group.attrSetters.scaleY = function (value, key) {
						this[key] = value;
						if (this.scaleX !== UNDEFINED && this.scaleY !== UNDEFINED) {
							this.element.setAttribute('transform', 'translate(' + this.translateX + ',' + this.translateY + ') scale(' +
							this.scaleX + ',' + this.scaleY + ')');
						}
						return false;
					};

					// Scale down the group and place it in the center
					attribs = {
						translateX: center[0] + plotLeft,
						translateY: center[1] + plotTop,
						scaleX: 0,
						scaleY: 0
					};

					group.attr(attribs);
					if (markerGroup) {
						markerGroup.attrSetters = group.attrSetters;
						markerGroup.attr(attribs);
					}

					// Run the animation
				} else {
					attribs = {
						translateX: plotLeft,
						translateY: plotTop,
						scaleX: 1,
						scaleY: 1
					};
					group.animate(attribs, animation);
					if (markerGroup) {
						markerGroup.animate(attribs, animation);
					}

					// Delete this function to allow it only once
					this.animate = null;
				}
			}

			// For non-polar charts, revert to the basic animation
		} else {
			proceed.call(this, init);
		}
	}

	// Define the animate method for both regular series and column series and their derivatives
	wrap(seriesProto, 'animate', polarAnimate);
	wrap(colProto, 'animate', polarAnimate);


	/**
	* Throw in a couple of properties to let setTooltipPoints know we're indexing the points
	* in degrees (0-360), not plot pixel width.
	*/
	wrap(seriesProto, 'setTooltipPoints', function (proceed, renew) {

		if (this.chart.polar) {
			extend(this.xAxis, {
				tooltipLen: 360, // degrees are the resolution unit of the tooltipPoints array
				tooltipPosName: 'deg'
			});
		}

		// Run uber method
		return proceed.call(this, renew);
	});


	/**
	* Extend the column prototype's translate method
	*/
	wrap(colProto, 'translate', function (proceed) {

		var xAxis = this.xAxis,
		len = this.yAxis.len,
		center = xAxis.center,
		startAngleRad = xAxis.startAngleRad,
		renderer = this.chart.renderer,
		start,
		points,
		point,
		i;

		this.preventPostTranslate = true;

		// Run uber method
		proceed.call(this);

		// Postprocess plot coordinates
		if (xAxis.isRadial) {
			points = this.points;
			i = points.length;
			while (i--) {
				point = points[i];
				start = point.barX + startAngleRad;
				point.shapeType = 'path';
				point.shapeArgs = {
					d: renderer.symbols.arc(
					center[0],
					center[1],
					len - point.plotY,
					null,
					{
						start: start,
						end: start + point.pointWidth,
						innerR: len - pick(point.yBottom, len)
					}
				)
				};
				this.toXY(point); // provide correct plotX, plotY for tooltip
			}
		}
	});


	/**
	* Align column data labels outside the columns. #1199.
	*/
	wrap(colProto, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo, isNew) {

		if (this.chart.polar) {
			var angle = point.rectPlotX / Math.PI * 180,
			align,
			verticalAlign;

			// Align nicely outside the perimeter of the columns
			if (options.align === null) {
				if (angle > 20 && angle < 160) {
					align = 'left'; // right hemisphere
				} else if (angle > 200 && angle < 340) {
					align = 'right'; // left hemisphere
				} else {
					align = 'center'; // top or bottom
				}
				options.align = align;
			}
			if (options.verticalAlign === null) {
				if (angle < 45 || angle > 315) {
					verticalAlign = 'bottom'; // top part
				} else if (angle > 135 && angle < 225) {
					verticalAlign = 'top'; // bottom part
				} else {
					verticalAlign = 'middle'; // left or right
				}
				options.verticalAlign = verticalAlign;
			}

			seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
		} else {
			proceed.call(this, point, dataLabel, options, alignTo, isNew);
		}

	});

	/**
	* Extend the mouse tracker to return the tooltip position index in terms of
	* degrees rather than pixels
	*/
	wrap(mouseTrackerProto, 'getIndex', function (proceed, e) {
		var ret,
		chart = this.chart,
		center,
		x,
		y;

		if (chart.polar) {
			center = chart.xAxis[0].center;
			x = e.chartX - center[0] - chart.plotLeft;
			y = e.chartY - center[1] - chart.plotTop;

			ret = 180 - Math.round(Math.atan2(x, y) / Math.PI * 180);

		} else {

			// Run uber method
			ret = proceed.call(this, e);
		}
		return ret;
	});

	/**
	* Extend getMouseCoordinates to prepare for polar axis values
	*/
	wrap(mouseTrackerProto, 'getMouseCoordinates', function (proceed, e) {
		var chart = this.chart,
		ret = {
			xAxis: [],
			yAxis: []
		};

		if (chart.polar) {

			each(chart.axes, function (axis) {
				var isXAxis = axis.isXAxis,
				center = axis.center,
				x = e.chartX - center[0] - chart.plotLeft,
				y = e.chartY - center[1] - chart.plotTop;

				ret[isXAxis ? 'xAxis' : 'yAxis'].push({
					axis: axis,
					value: axis.translate(
					isXAxis ?
						Math.PI - Math.atan2(x, y) : // angle 
						Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center
					true
				)
				});
			});

		} else {
			ret = proceed.call(this, e);
		}

		return ret;
	});
} (Highcharts));
// 
//! MarkdownDeep - http://www.toptensoftware.com/markdowndeep
//! Copyright (C) 2010-2011 Topten Software
// 
//   Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in 
//   compliance with the License. You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software distributed under the License is 
//   distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
//   See the License for the specific language governing permissions and limitations under the License.
//


/////////////////////////////////////////////////////////////////////////////
// Markdown

var MarkdownDeep = new function () {


    function array_indexOf(array, obj) {
        if (array.indexOf !== undefined)
            return array.indexOf(obj);

        for (var i = 0; i < array.length; i++) {
            if (array[i] === obj)
                return i;
        }

        return -1;
    };

    // private:p.
    // private:.m_*

    function Markdown() {
        this.m_SpanFormatter = new SpanFormatter(this);
        this.m_SpareBlocks = [];
        this.m_StringBuilder = new StringBuilder();
        this.m_StringBuilderFinal = new StringBuilder();
    }

    Markdown.prototype =
    {
        SafeMode: false,
        ExtraMode: false,
        MarkdownInHtml: false,
        AutoHeadingIDs: false,
        UrlBaseLocation: null,
        UrlRootLocation: null,
        NewWindowForExternalLinks: false,
        NewWindowForLocalLinks: false,
        NoFollowLinks: false,
        HtmlClassFootnotes: "footnotes",
        HtmlClassTitledImages: null,
        RenderingTitledImage: false,
        FormatCodeBlockAttributes: null,
        FormatCodeBlock: null,
        ExtractHeadBlocks: false,
        HeadBlockContent: ""
    };

    var p = Markdown.prototype;

    function splice_array(dest, position, del, ins) {
        return dest.slice(0, position).concat(ins).concat(dest.slice(position + del));
    }

    Markdown.prototype.GetListItems = function (input, offset) {
        // Parse content into blocks
        var blocks = this.ProcessBlocks(input);


        // Find the block        
        var i;
        for (i = 0; i < blocks.length; i++) {
            var b = blocks[i];

            if ((b.blockType == BlockType_Composite || b.blockType == BlockType_html || b.blockType == BlockType_HtmlTag) && b.children) {
                blocks = splice_array(blocks, i, 1, b.children);
                i--;
                continue;
            }

            if (offset < b.lineStart) {
                break;
            }
        }

        i--;

        // Quit if at top
        if (i < 0)
            return null;

        // Get the block before
        var block = blocks[i];

        // Check if it's a list
        if (block.blockType != BlockType_ul && block.blockType != BlockType_ol)
            return null;

        // Build list of line offsets
        var list = [];
        var items = block.children;
        for (var j = 0; j < items.length; j++) {
            list.push(items[j].lineStart);
        }

        // Also push the line offset of the following block
        i++;
        if (i < blocks.length) {
            list.push(blocks[i].lineStart);
        }
        else {
            list.push(input.length);
        }

        return list;
    }

    // Main entry point    
    Markdown.prototype.Transform = function (input) {
        // Normalize line ends
        var rpos = input.indexOf("\r");
        if (rpos >= 0) {
            var npos = input.indexOf("\n");
            if (npos >= 0) {
                if (npos < rpos) {
                    input = input.replace(/\n\r/g, "\n");
                }
                else {
                    input = input.replace(/\r\n/g, "\n");
                }
            }

            input = input.replace(/\r/g, "\n");
        }

        this.HeadBlockContent = "";

        var blocks = this.ProcessBlocks(input);

        // Sort abbreviations by length, longest to shortest
        if (this.m_Abbreviations != null) {
            var list = [];
            for (var a in this.m_Abbreviations) {
                list.push(this.m_Abbreviations[a]);
            }
            list.sort(
		        function (a, b) {
		            return b.Abbr.length - a.Abbr.length;
		        }
            );
            this.m_Abbreviations = list;
        }

        // Render
        var sb = this.m_StringBuilderFinal;
        sb.Clear();
        for (var i = 0; i < blocks.length; i++) {
            var b = blocks[i];
            b.Render(this, sb);
        }

        // Render footnotes
        if (this.m_UsedFootnotes.length > 0) {

            sb.Append("\n<div class=\"");
            sb.Append(this.HtmlClassFootnotes);
            sb.Append("\">\n");
            sb.Append("<hr />\n");
            sb.Append("<ol>\n");
            for (var i = 0; i < this.m_UsedFootnotes.length; i++) {
                var fn = this.m_UsedFootnotes[i];

                sb.Append("<li id=\"#fn:");
                sb.Append(fn.data); // footnote id
                sb.Append("\">\n");


                // We need to get the return link appended to the last paragraph
                // in the footnote
                var strReturnLink = "<a href=\"#fnref:" + fn.data + "\" rev=\"footnote\">&#8617;</a>";

                // Get the last child of the footnote
                var child = fn.children[fn.children.length - 1];
                if (child.blockType == BlockType_p) {
                    child.blockType = BlockType_p_footnote;
                    child.data = strReturnLink;
                }
                else {
                    child = new Block();
                    child.contentLen = 0;
                    child.blockType = BlockType_p_footnote;
                    child.data = strReturnLink;
                    fn.children.push(child);
                }


                fn.Render(this, sb);

                sb.Append("</li>\n");
            }
            sb.Append("</ol\n");
            sb.Append("</div>\n");
        }


        // Done
        return sb.ToString();
    }

    Markdown.prototype.OnQualifyUrl = function (url) {
        // Is the url already fully qualified?
        if (IsUrlFullyQualified(url))
            return url;

        if (starts_with(url, "/")) {
            var rootLocation = this.UrlRootLocation;
            if (!rootLocation) {
                // Quit if we don't have a base location
                if (!this.UrlBaseLocation)
                    return url;

                // Need to find domain root
                var pos = this.UrlBaseLocation.indexOf("://");
                if (pos == -1)
                    pos = 0;
                else
                    pos += 3;

                // Find the first slash after the protocol separator
                pos = this.UrlBaseLocation.indexOf('/', pos);

                // Get the domain name
                rootLocation = pos < 0 ? this.UrlBaseLocation : this.UrlBaseLocation.substr(0, pos);
            }

            // Join em
            return rootLocation + url;
        }
        else {
            // Quit if we don't have a base location
            if (!this.UrlBaseLocation)
                return url;

            if (!ends_with(this.UrlBaseLocation, "/"))
                return this.UrlBaseLocation + "/" + url;
            else
                return this.UrlBaseLocation + url;
        }
    }


    // Override and return an object with width and height properties
    Markdown.prototype.OnGetImageSize = function (image, TitledImage) {
        return null;
    }

    Markdown.prototype.OnPrepareLink = function (tag) {
        var url = tag.attributes["href"];

        // No follow?
        if (this.NoFollowLinks) {
            tag.attributes["rel"] = "nofollow";
        }

        // New window?
        if ((this.NewWindowForExternalLinks && IsUrlFullyQualified(url)) ||
			 (this.NewWindowForLocalLinks && !IsUrlFullyQualified(url))) {
            tag.attributes["target"] = "_blank";
        }

        // Qualify url
        tag.attributes["href"] = this.OnQualifyUrl(url);
    }

    Markdown.prototype.OnPrepareImage = function (tag, TitledImage) {
        // Try to determine width and height
        var size = this.OnGetImageSize(tag.attributes["src"], TitledImage);
        if (size != null) {
            tag.attributes["width"] = size.width;
            tag.attributes["height"] = size.height;
        }

        // Now qualify the url
        tag.attributes["src"] = this.OnQualifyUrl(tag.attributes["src"]);
    }

    // Get a link definition
    Markdown.prototype.GetLinkDefinition = function (id) {
        var x = this.m_LinkDefinitions[id];
        if (x == undefined)
            return null;
        else
            return x;
    }



    p.ProcessBlocks = function (str) {
        // Reset the list of link definitions
        this.m_LinkDefinitions = [];
        this.m_Footnotes = [];
        this.m_UsedFootnotes = [];
        this.m_UsedHeaderIDs = [];
        this.m_Abbreviations = null;

        // Process blocks
        return new BlockProcessor(this, this.MarkdownInHtml).Process(str);
    }

    // Add a link definition
    p.AddLinkDefinition = function (link) {
        this.m_LinkDefinitions[link.id] = link;
    }

    p.AddFootnote = function (footnote) {
        this.m_Footnotes[footnote.data] = footnote;
    }

    // Look up a footnote, claim it and return it's index (or -1 if not found)
    p.ClaimFootnote = function (id) {
        var footnote = this.m_Footnotes[id];
        if (footnote != undefined) {
            // Move the foot note to the used footnote list
            this.m_UsedFootnotes.push(footnote);
            delete this.m_Footnotes[id];

            // Return it's display index
            return this.m_UsedFootnotes.length - 1;
        }
        else
            return -1;
    }

    p.AddAbbreviation = function (abbr, title) {
        if (this.m_Abbreviations == null) {
            this.m_Abbreviations = [];
        }

        // Store abbreviation
        this.m_Abbreviations[abbr] = { Abbr: abbr, Title: title };
    }

    p.GetAbbreviations = function () {
        return this.m_Abbreviations;
    }




    // private
    p.MakeUniqueHeaderID = function (strHeaderText, startOffset, length) {
        if (!this.AutoHeadingIDs)
            return null;

        // Extract a pandoc style cleaned header id from the header text
        var strBase = this.m_SpanFormatter.MakeID(strHeaderText, startOffset, length);

        // If nothing left, use "section"
        if (!strBase)
            strBase = "section";

        // Make sure it's unique by append -n counter
        var strWithSuffix = strBase;
        var counter = 1;
        while (this.m_UsedHeaderIDs[strWithSuffix] != undefined) {
            strWithSuffix = strBase + "-" + counter.toString();
            counter++;
        }

        // Store it
        this.m_UsedHeaderIDs[strWithSuffix] = true;

        // Return it
        return strWithSuffix;
    }


    // private
    p.GetStringBuilder = function () {
        this.m_StringBuilder.Clear();
        return this.m_StringBuilder;
    }

    /////////////////////////////////////////////////////////////////////////////
    // CharTypes

    function is_digit(ch) {
        return ch >= '0' && ch <= '9';
    }
    function is_hex(ch) {
        return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
    }
    function is_alpha(ch) {
        return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
    }
    function is_alphadigit(ch) {
        return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9');
    }
    function is_whitespace(ch) {
        return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
    }
    function is_linespace(ch) {
        return (ch == ' ' || ch == '\t');
    }
    function is_lineend(ch) {
        return (ch == '\r' || ch == '\n');
    }
    function is_emphasis(ch) {
        return (ch == '*' || ch == '_');
    }
    function is_escapable(ch, ExtraMode) {
        switch (ch) {
            case '\\':
            case '`':
            case '*':
            case '_':
            case '{':
            case '}':
            case '[':
            case ']':
            case '(':
            case ')':
            case '>':
            case '#':
            case '+':
            case '-':
            case '.':
            case '!':
                return true;

            case ':':
            case '|':
            case '=':
            case '<':
                return ExtraMode;
        }

        return false;
    }


    // Utility functions

    // Check if str[pos] looks like a html entity
    // Returns -1 if not, or offset of character after it yes.
    function SkipHtmlEntity(str, pos) {
        if (str.charAt(pos) != '&')
            return -1;

        var save = pos;
        pos++;

        var fn_test;
        if (str.charAt(pos) == '#') {
            pos++;
            if (str.charAt(pos) == 'x' || str.charAt(pos) == 'X') {
                pos++;
                fn_test = is_hex;
            }
            else {
                fn_test = is_digit;
            }
        }
        else {
            fn_test = is_alphadigit;
        }

        if (fn_test(str.charAt(pos))) {
            pos++;
            while (fn_test(str.charAt(pos)))
                pos++;

            if (str.charAt(pos) == ';') {
                pos++;
                return pos;
            }
        }

        pos = save;
        return -1;
    }

    function UnescapeString(str, ExtraMode) {
        // Find first backslash
        var bspos = str.indexOf('\\');
        if (bspos < 0)
            return str;

        // Build new string with escapable backslashes removed
        var b = new StringBuilder();
        var piece = 0;
        while (bspos >= 0) {
            if (is_escapable(str.charAt(bspos + 1), ExtraMode)) {
                if (bspos > piece)
                    b.Append(str.substr(piece, bspos - piece));

                piece = bspos + 1;
            }

            bspos = str.indexOf('\\', bspos + 1);
        }

        if (piece < str.length)
            b.Append(str.substr(piece, str.length - piece));

        return b.ToString();
    }

    function Trim(str) {
        var i = 0;
        var l = str.length;

        while (i < l && is_whitespace(str.charAt(i)))
            i++;
        while (l - 1 > i && is_whitespace(str.charAt(l - 1)))
            l--;

        return str.substr(i, l - i);
    }


    /*
    * These two functions IsEmailAddress and IsWebAddress
    * are intended as a quick and dirty way to tell if a 
    * <autolink> url is email, web address or neither.
    * 
    * They are not intended as validating checks.
    * 
    * (use of Regex for more correct test unnecessarily
    *  slowed down some test documents by up to 300%.)
    */

    // Check if a string looks like an email address
    function IsEmailAddress(str) {
        var posAt = str.indexOf('@');
        if (posAt < 0)
            return false;

        var posLastDot = str.lastIndexOf('.');
        if (posLastDot < posAt)
            return false;

        return true;
    }

    // Check if a string looks like a url
    function IsWebAddress(str) {
        str = str.toLowerCase();
        if (str.substr(0, 7) == "http://")
            return true;
        if (str.substr(0, 8) == "https://")
            return true;
        if (str.substr(0, 6) == "ftp://")
            return true;
        if (str.substr(0, 7) == "file://")
            return true;

        return false;
    }


    // Check if a string is a valid HTML ID identifier
    function IsValidHtmlID(str) {
        if (!str)
            return false;

        // Must start with a letter
        if (!is_alpha(str.charAt(0)))
            return false;

        // Check the rest
        for (var i = 0; i < str.length; i++) {
            var ch = str.charAt(i);
            if (is_alphadigit(ch) || ch == '_' || ch == '-' || ch == ':' || ch == '.')
                continue;

            return false;
        }

        // OK
        return true;
    }

    // Strip the trailing HTML ID from a header string
    // ie:      ## header text ##			{#<idhere>}
    //			^start           ^out end              ^end
    //
    // Returns null if no header id
    function StripHtmlID(str, start, end) {
        // Skip trailing whitespace
        var pos = end - 1;
        while (pos >= start && is_whitespace(str.charAt(pos))) {
            pos--;
        }

        // Skip closing '{'
        if (pos < start || str.charAt(pos) != '}')
            return null;

        var endId = pos;
        pos--;

        // Find the opening '{'
        while (pos >= start && str.charAt(pos) != '{')
            pos--;

        // Check for the #
        if (pos < start || str.charAt(pos + 1) != '#')
            return null;

        // Extract and check the ID
        var startId = pos + 2;
        var strID = str.substr(startId, endId - startId);
        if (!IsValidHtmlID(strID))
            return null;

        // Skip any preceeding whitespace
        while (pos > start && is_whitespace(str.charAt(pos - 1)))
            pos--;

        // Done!
        return { id: strID, end: pos };
    }

    function starts_with(str, match) {
        return str.substr(0, match.length) == match;
    }

    function ends_with(str, match) {
        return str.substr(-match.length) == match;
    }

    function IsUrlFullyQualified(url) {
        return url.indexOf("://") >= 0 || starts_with(url, "mailto:");
    }


    /////////////////////////////////////////////////////////////////////////////
    // StringBuilder

    function StringBuilder() {
        this.m_content = [];
    }

    p = StringBuilder.prototype;

    p.Append = function (value) {
        if (value)
            this.m_content.push(value);
    }
    p.Clear = function () {
        this.m_content.length = 0;
    }
    p.ToString = function () {
        return this.m_content.join("");
    }

    p.HtmlRandomize = function (url) {
        // Randomize
        var len = url.length;
        for (var i = 0; i < len; i++) {
            var x = Math.random();
            if (x > 0.90 && url.charAt(i) != '@') {
                this.Append(url.charAt(i));
            }
            else if (x > 0.45) {
                this.Append("&#");
                this.Append(url.charCodeAt(i).toString());
                this.Append(";");
            }
            else {
                this.Append("&#x");
                this.Append(url.charCodeAt(i).toString(16));
                this.Append(";");
            }
        }
    }

    p.HtmlEncode = function (str, startOffset, length) {
        var end = startOffset + length;
        var piece = startOffset;
        var i;
        for (i = startOffset; i < end; i++) {
            switch (str.charAt(i)) {
                case '&':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&amp;");
                    piece = i + 1;
                    break;

                case '<':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&lt;");
                    piece = i + 1;
                    break;

                case '>':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&gt;");
                    piece = i + 1;
                    break;

                case '\"':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&quot;");
                    piece = i + 1;
                    break;
            }
        }

        if (i > piece)
            this.Append(str.substr(piece, i - piece));
    }

    p.SmartHtmlEncodeAmpsAndAngles = function (str, startOffset, length) {
        var end = startOffset + length;
        var piece = startOffset;
        var i;
        for (i = startOffset; i < end; i++) {
            switch (str.charAt(i)) {
                case '&':
                    var after = SkipHtmlEntity(str, i);
                    if (after < 0) {
                        if (i > piece) {
                            this.Append(str.substr(piece, i - piece));
                        }
                        this.Append("&amp;");
                        piece = i + 1;
                    }
                    else {
                        i = after - 1;
                    }
                    break;

                case '<':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&lt;");
                    piece = i + 1;
                    break;

                case '>':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&gt;");
                    piece = i + 1;
                    break;

                case '\"':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&quot;");
                    piece = i + 1;
                    break;
            }
        }

        if (i > piece)
            this.Append(str.substr(piece, i - piece));
    }

    p.SmartHtmlEncodeAmps = function (str, startOffset, length) {
        var end = startOffset + length;
        var piece = startOffset;
        var i;
        for (i = startOffset; i < end; i++) {
            switch (str.charAt(i)) {
                case '&':
                    var after = SkipHtmlEntity(str, i);
                    if (after < 0) {
                        if (i > piece) {
                            this.Append(str.substr(piece, i - piece));
                        }
                        this.Append("&amp;");
                        piece = i + 1;
                    }
                    else {
                        i = after - 1;
                    }
                    break;
            }
        }

        if (i > piece)
            this.Append(str.substr(piece, i - piece));
    }


    p.HtmlEncodeAndConvertTabsToSpaces = function (str, startOffset, length) {
        var end = startOffset + length;
        var piece = startOffset;
        var pos = 0;
        var i;
        for (i = startOffset; i < end; i++) {
            switch (str.charAt(i)) {
                case '\t':

                    if (i > piece) {
                        this.Append(str.substr(piece, i - piece));
                    }
                    piece = i + 1;

                    this.Append(' ');
                    pos++;
                    while ((pos % 4) != 0) {
                        this.Append(' ');
                        pos++;
                    }
                    pos--; 	// Compensate for the pos++ below
                    break;

                case '\r':
                case '\n':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append('\n');
                    piece = i + 1;
                    continue;

                case '&':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&amp;");
                    piece = i + 1;
                    break;

                case '<':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&lt;");
                    piece = i + 1;
                    break;

                case '>':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&gt;");
                    piece = i + 1;
                    break;

                case '\"':
                    if (i > piece)
                        this.Append(str.substr(piece, i - piece));
                    this.Append("&quot;");
                    piece = i + 1;
                    break;
            }

            pos++;
        }

        if (i > piece)
            this.Append(str.substr(piece, i - piece));
    }




    /////////////////////////////////////////////////////////////////////////////
    // StringScanner

    function StringScanner() {
        this.reset.apply(this, arguments);
    }

    p = StringScanner.prototype;
    p.bof = function () {
        return this.m_position == this.start;
    }

    p.eof = function () {
        return this.m_position >= this.end;
    }

    p.eol = function () {
        if (this.m_position >= this.end)
            return true;
        var ch = this.buf.charAt(this.m_position);
        return ch == '\r' || ch == '\n' || ch == undefined || ch == '';
    }

    p.reset = function (/*string, position, length*/) {
        this.buf = arguments.length > 0 ? arguments[0] : null;
        this.start = arguments.length > 1 ? arguments[1] : 0;
        this.end = arguments.length > 2 ? this.start + arguments[2] : (this.buf == null ? 0 : this.buf.length);
        this.m_position = this.start;
        this.charset_offsets = {};
    }

    p.current = function () {
        if (this.m_position >= this.end)
            return "\0";
        return this.buf.charAt(this.m_position);
    }

    p.remainder = function () {
        return this.buf.substr(this.m_position);
    }

    p.SkipToEof = function () {
        this.m_position = this.end;
    }

    p.SkipForward = function (count) {
        this.m_position += count;
    }

    p.SkipToEol = function () {
        this.m_position = this.buf.indexOf('\n', this.m_position);
        if (this.m_position < 0)
            this.m_position = this.end;
    }

    p.SkipEol = function () {
        var save = this.m_position;
        if (this.buf.charAt(this.m_position) == '\r')
            this.m_position++;
        if (this.buf.charAt(this.m_position) == '\n')
            this.m_position++;
        return this.m_position != save;
    }

    p.SkipToNextLine = function () {
        this.SkipToEol();
        this.SkipEol();
    }

    p.CharAtOffset = function (offset) {
        if (this.m_position + offset >= this.end)
            return "\0";
        return this.buf.charAt(this.m_position + offset);
    }

    p.SkipChar = function (ch) {
        if (this.buf.charAt(this.m_position) == ch) {
            this.m_position++;
            return true;
        }
        return false;
    }
    p.SkipString = function (s) {
        if (this.buf.substr(this.m_position, s.length) == s) {
            this.m_position += s.length;
            return true;
        }
        return false;
    }
    p.SkipWhitespace = function () {
        var save = this.m_position;
        while (true) {
            var ch = this.buf.charAt(this.m_position);
            if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n')
                break;
            this.m_position++;
        }
        return this.m_position != save;
    }
    p.SkipLinespace = function () {
        var save = this.m_position;
        while (true) {
            var ch = this.buf.charAt(this.m_position);
            if (ch != ' ' && ch != '\t')
                break;
            this.m_position++;
        }
        return this.m_position != save;
    }
    p.FindRE = function (re) {
        re.lastIndex = this.m_position;
        var result = re.exec(this.buf);
        if (result == null) {
            this.m_position = this.end;
            return false;
        }

        if (result.index + result[0].length > this.end) {
            this.m_position = this.end;
            return false;
        }

        this.m_position = result.index;
        return true;
    }
    p.FindOneOf = function (charset) {
        var next = -1;
        for (var ch in charset) {
            var charset_info = charset[ch];

            // Setup charset_info for this character
            if (charset_info == null) {
                charset_info = {};
                charset_info.m_searched_from = -1;
                charset_info.m_found_at = -1;
                charset[ch] = charset_info;
            }

            // Search again?
            if (charset_info.m_searched_from == -1 ||
                this.m_position < charset_info.m_searched_from ||
                (this.m_position >= charset_info.m_found_at && charset_info.m_found_at != -1)) {
                charset_info.m_searched_from = this.m_position;
                charset_info.m_found_at = this.buf.indexOf(ch, this.m_position);
            }

            // Is this character next?            
            if (next == -1 || charset_info.m_found_at < next) {
                next = charset_info.m_found_at;
            }

        }

        if (next == -1) {
            next = this.end;
            return false;
        }

        p.m_position = next;
        return true;
    }
    p.Find = function (s) {
        this.m_position = this.buf.indexOf(s, this.m_position);
        if (this.m_position < 0) {
            this.m_position = this.end;
            return false;
        }
        return true;
    }
    p.Mark = function () {
        this.mark = this.m_position;
    }
    p.Extract = function () {
        if (this.mark >= this.m_position)
            return "";
        else
            return this.buf.substr(this.mark, this.m_position - this.mark);
    }
    p.SkipIdentifier = function () {
        var ch = this.buf.charAt(this.m_position);
        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_') {
            this.m_position++;
            while (true) {
                ch = this.buf.charAt(this.m_position);
                if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' || (ch >= '0' && ch <= '9'))
                    this.m_position++;
                else
                    return true;
            }
        }
        return false;
    }

    p.SkipFootnoteID = function () {
        var savepos = this.m_position;

        this.SkipLinespace();

        this.Mark();

        while (true) {
            var ch = this.current();
            if (is_alphadigit(ch) || ch == '-' || ch == '_' || ch == ':' || ch == '.' || ch == ' ')
                this.SkipForward(1);
            else
                break;
        }

        if (this.m_position > this.mark) {
            var id = Trim(this.Extract());
            if (id.length > 0) {
                this.SkipLinespace();
                return id;
            }
        }

        this.m_position = savepos;
        return null;
    }

    p.SkipHtmlEntity = function () {
        if (this.buf.charAt(this.m_position) != '&')
            return false;

        var newpos = SkipHtmlEntity(this.buf, this.m_position);
        if (newpos < 0)
            return false;

        this.m_position = newpos;
        return true;
    }

    p.SkipEscapableChar = function (ExtraMode) {
        if (this.buf.charAt(this.m_position) == '\\' && is_escapable(this.buf.charAt(this.m_position + 1), ExtraMode)) {
            this.m_position += 2;
            return true;
        }
        else {
            if (this.m_position < this.end)
                this.m_position++;
            return false;
        }
    }


    /////////////////////////////////////////////////////////////////////////////
    // HtmlTag

    var HtmlTagFlags_Block = 0x0001; 	// Block tag
    var HtmlTagFlags_Inline = 0x0002; 	// Inline tag
    var HtmlTagFlags_NoClosing = 0x0004; 	// No closing tag (eg: <hr> and <!-- -->)
    var HtmlTagFlags_ContentAsSpan = 0x0008;        // When markdown=1 treat content as span, not block


    function HtmlTag(name) {
        this.name = name;
        this.attributes = {};
        this.flags = 0;
        this.closed = false;
        this.closing = false;
    }

    p = HtmlTag.prototype;

    p.attributeCount = function () {
        if (!this.attributes)
            return 0;

        var count = 0;
        for (var x in this.attributes)
            count++;

        return count;
    }

    p.get_Flags = function () {
        if (this.flags == 0) {
            this.flags = tag_flags[this.name.toLowerCase()];
            if (this.flags == undefined) {
                this.flags = HtmlTagFlags_Inline;
            }
        }
        return this.flags;
    }

    p.IsSafe = function () {
        var name_lower = this.name.toLowerCase();

        // Check if tag is in whitelist
        if (!allowed_tags[name_lower])
            return false;

        // Find allowed attributes
        var allowed = allowed_attributes[name_lower];
        if (!allowed) {
            return this.attributeCount() == 0;
        }

        // No attributes?
        if (!this.attributes)
            return true;

        // Check all are allowed
        for (var i in this.attributes) {
            if (!allowed[i.toLowerCase()])
                return false;
        }

        // Check href attribute is ok
        if (this.attributes["href"]) {
            if (!IsSafeUrl(this.attributes["href"]))
                return false;
        }

        if (this.attributes["src"]) {
            if (!IsSafeUrl(this.attributes["src"]))
                return false;
        }

        // Passed all white list checks, allow it
        return true;
    }

    // Render opening tag (eg: <tag attr="value">
    p.RenderOpening = function (dest) {
        dest.Append("<");
        dest.Append(this.name);
        for (var i in this.attributes) {
            dest.Append(" ");
            dest.Append(i);
            dest.Append("=\"");
            dest.Append(this.attributes[i]);
            dest.Append("\"");
        }

        if (this.closed)
            dest.Append(" />");
        else
            dest.Append(">");
    }

    // Render closing tag (eg: </tag>)
    p.RenderClosing = function (dest) {
        dest.Append("</");
        dest.Append(this.name);
        dest.Append(">");
    }



    function IsSafeUrl(url) {
        url = url.toLowerCase();
        return (url.substr(0, 7) == "http://" ||
                url.substr(0, 8) == "https://" ||
                url.substr(0, 6) == "ftp://");
    }

    function ParseHtmlTag(p) {
        // Save position
        var savepos = p.m_position;

        // Parse it
        var ret = ParseHtmlTagHelper(p);
        if (ret != null)
            return ret;

        // Rewind if failed
        p.m_position = savepos;
        return null;
    }

    function ParseHtmlTagHelper(p) {
        // Does it look like a tag?
        if (p.current() != '<')
            return null;

        // Skip '<'
        p.SkipForward(1);

        // Is it a comment?
        if (p.SkipString("!--")) {
            p.Mark();

            if (p.Find("-->")) {
                var t = new HtmlTag("!");
                t.attributes["content"] = p.Extract();
                t.closed = true;
                p.SkipForward(3);
                return t;
            }
        }

        // Is it a closing tag eg: </div>
        var bClosing = p.SkipChar('/');

        // Get the tag name
        p.Mark();
        if (!p.SkipIdentifier())
            return null;

        // Probably a tag, create the HtmlTag object now
        var tag = new HtmlTag(p.Extract());
        tag.closing = bClosing;

        // If it's a closing tag, no attributes
        if (bClosing) {
            if (p.current() != '>')
                return null;

            p.SkipForward(1);
            return tag;
        }


        while (!p.eof()) {
            // Skip whitespace
            p.SkipWhitespace();

            // Check for closed tag eg: <hr />
            if (p.SkipString("/>")) {
                tag.closed = true;
                return tag;
            }

            // End of tag?
            if (p.SkipChar('>')) {
                return tag;
            }

            // attribute name
            p.Mark();
            if (!p.SkipIdentifier())
                return null;
            var attributeName = p.Extract();

            // Skip whitespace
            p.SkipWhitespace();

            // Skip equal sign
            if (!p.SkipChar('='))
                return null;

            // Skip whitespace
            p.SkipWhitespace();

            // Optional quotes
            if (p.SkipChar('\"')) {
                // Scan the value
                p.Mark();
                if (!p.Find('\"'))
                    return null;

                // Store the value
                tag.attributes[attributeName] = p.Extract();

                // Skip closing quote
                p.SkipForward(1);
            }
            else {
                // Scan the value
                p.Mark();
                while (!p.eof() && !is_whitespace(p.current()) && p.current() != '>' && p.current() != '/')
                    p.SkipForward(1);

                if (!p.eof()) {
                    // Store the value
                    tag.attributes[attributeName] = p.Extract();
                }
            }
        }

        return null;
    }


    var allowed_tags = {
        "b": 1, "blockquote": 1, "code": 1, "dd": 1, "dt": 1, "dl": 1, "del": 1, "em": 1,
        "h1": 1, "h2": 1, "h3": 1, "h4": 1, "h5": 1, "h6": 1, "i": 1, "kbd": 1, "li": 1, "ol": 1, "ul": 1,
        "p": 1, "pre": 1, "s": 1, "sub": 1, "sup": 1, "strong": 1, "strike": 1, "img": 1, "a": 1
    };

    var allowed_attributes = {
        "a": { "href": 1, "title": 1 },
        "img": { "src": 1, "width": 1, "height": 1, "alt": 1, "title": 1 }
    };

    var b = HtmlTagFlags_Block;
    var i = HtmlTagFlags_Inline;
    var n = HtmlTagFlags_NoClosing;
    var s = HtmlTagFlags_ContentAsSpan;
    var tag_flags = {
        "p": b | s,
        "div": b,
        "h1": b | s,
        "h2": b | s,
        "h3": b | s,
        "h4": b | s,
        "h5": b | s,
        "h6": b | s,
        "blockquote": b,
        "pre": b,
        "table": b,
        "dl": b,
        "ol": b,
        "ul": b,
        "form": b,
        "fieldset": b,
        "iframe": b,
        "script": b | i,
        "noscript": b | i,
        "math": b | i,
        "ins": b | i,
        "del": b | i,
        "img": b | i,
        "li": s,
        "dd": s,
        "dt": s,
        "td": s,
        "th": s,
        "legend": s,
        "address": s,
        "hr": b | n,
        "!": b | n,
        "head": b
    };
    delete b;
    delete i;
    delete n;



    /////////////////////////////////////////////////////////////////////////////
    // LinkDefinition

    function LinkDefinition(id, url, title) {
        this.id = id;
        this.url = url;
        if (title == undefined)
            this.title = null;
        else
            this.title = title;
    }

    p = LinkDefinition.prototype;
    p.RenderLink = function (m, b, link_text) {
        if (this.url.substr(0, 7).toLowerCase() == "mailto:") {
            b.Append("<a href=\"");
            b.HtmlRandomize(this.url);
            b.Append('\"');
            if (this.title) {
                b.Append(" title=\"");
                b.SmartHtmlEncodeAmpsAndAngles(this.title, 0, this.title.length);
                b.Append('\"');
            }
            b.Append('>');
            b.HtmlRandomize(link_text);
            b.Append("</a>");
        }
        else {
            var tag = new HtmlTag("a");

            // encode url
            var sb = m.GetStringBuilder();
            sb.SmartHtmlEncodeAmpsAndAngles(this.url, 0, this.url.length);
            tag.attributes["href"] = sb.ToString();

            // encode title
            if (this.title) {
                sb.Clear();
                sb.SmartHtmlEncodeAmpsAndAngles(this.title, 0, this.title.length);
                tag.attributes["title"] = sb.ToString();
            }

            // Do user processing
            m.OnPrepareLink(tag);

            // Render the opening tag
            tag.RenderOpening(b);

            b.Append(link_text);   // Link text already escaped by SpanFormatter
            b.Append("</a>");
        }
    }

    p.RenderImg = function (m, b, alt_text) {
        var tag = new HtmlTag("img");

        // encode url
        var sb = m.GetStringBuilder();
        sb.SmartHtmlEncodeAmpsAndAngles(this.url, 0, this.url.length);
        tag.attributes["src"] = sb.ToString();

        // encode alt text
        if (alt_text) {
            sb.Clear();
            sb.SmartHtmlEncodeAmpsAndAngles(alt_text, 0, alt_text.length);
            tag.attributes["alt"] = sb.ToString();
        }

        // encode title
        if (this.title) {
            sb.Clear();
            sb.SmartHtmlEncodeAmpsAndAngles(this.title, 0, this.title.length);
            tag.attributes["title"] = sb.ToString();
        }

        tag.closed = true;

        m.OnPrepareImage(tag, m.RenderingTitledImage);

        tag.RenderOpening(b);

        /*
        b.Append("<img src=\"");
        b.SmartHtmlEncodeAmpsAndAngles(this.url, 0, this.url.length);
        b.Append('\"');
        if (alt_text)
        {
        b.Append(" alt=\"");
        b.SmartHtmlEncodeAmpsAndAngles(alt_text, 0, alt_text.length);
        b.Append('\"');
        }
        if (this.title)
        {
        b.Append(" title=\"");
        b.SmartHtmlEncodeAmpsAndAngles(this.title, 0, this.title.length);
        b.Append('\"');
        }
        b.Append(" />");
        */
    }

    function ParseLinkDefinition(p, ExtraMode) {
        var savepos = p.m_position;
        var l = ParseLinkDefinitionInternal(p, ExtraMode);
        if (l == null)
            p.m_position = savepos;
        return l;
    }

    function ParseLinkDefinitionInternal(p, ExtraMode) {
        // Skip leading white space
        p.SkipWhitespace();

        // Must start with an opening square bracket
        if (!p.SkipChar('['))
            return null;

        // Extract the id
        p.Mark();
        if (!p.Find(']'))
            return null;
        var id = p.Extract();
        if (id.length == 0)
            return null;
        if (!p.SkipString("]:"))
            return null;

        // Parse the url and title
        var link = ParseLinkTarget(p, id, ExtraMode);

        // and trailing whitespace
        p.SkipLinespace();

        // Trailing crap, not a valid link reference...
        if (!p.eol())
            return null;

        return link;
    }

    // Parse just the link target
    // For reference link definition, this is the bit after "[id]: thisbit"
    // For inline link, this is the bit in the parens: [link text](thisbit)
    function ParseLinkTarget(p, id, ExtraMode) {
        // Skip whitespace
        p.SkipWhitespace();

        // End of string?
        if (p.eol())
            return null;

        // Create the link definition
        var r = new LinkDefinition(id);

        // Is the url enclosed in angle brackets
        if (p.SkipChar('<')) {
            // Extract the url
            p.Mark();

            // Find end of the url
            while (p.current() != '>') {
                if (p.eof())
                    return null;
                p.SkipEscapableChar(ExtraMode);
            }

            var url = p.Extract();
            if (!p.SkipChar('>'))
                return null;

            // Unescape it
            r.url = UnescapeString(Trim(url), ExtraMode);

            // Skip whitespace
            p.SkipWhitespace();
        }
        else {
            // Find end of the url
            p.Mark();
            var paren_depth = 1;
            while (!p.eol()) {
                var ch = p.current();
                if (is_whitespace(ch))
                    break;
                if (id == null) {
                    if (ch == '(')
                        paren_depth++;
                    else if (ch == ')') {
                        paren_depth--;
                        if (paren_depth == 0)
                            break;
                    }
                }

                p.SkipEscapableChar(ExtraMode);
            }

            r.url = UnescapeString(Trim(p.Extract()), ExtraMode);
        }

        p.SkipLinespace();

        // End of inline target
        if (p.current() == ')')
            return r;

        var bOnNewLine = p.eol();
        var posLineEnd = p.m_position;
        if (p.eol()) {
            p.SkipEol();
            p.SkipLinespace();
        }

        // Work out what the title is delimited with
        var delim;
        switch (p.current()) {
            case '\'':
            case '\"':
                delim = p.current();
                break;

            case '(':
                delim = ')';
                break;

            default:
                if (bOnNewLine) {
                    p.m_position = posLineEnd;
                    return r;
                }
                else
                    return null;
        }

        // Skip the opening title delimiter
        p.SkipForward(1);

        // Find the end of the title
        p.Mark();
        while (true) {
            if (p.eol())
                return null;

            if (p.current() == delim) {

                if (delim != ')') {
                    var savepos = p.m_position;

                    // Check for embedded quotes in title

                    // Skip the quote and any trailing whitespace
                    p.SkipForward(1);
                    p.SkipLinespace();

                    // Next we expect either the end of the line for a link definition
                    // or the close bracket for an inline link
                    if ((id == null && p.current() != ')') ||
					    (id != null && !p.eol())) {
                        continue;
                    }

                    p.m_position = savepos;
                }

                // End of title
                break;
            }

            p.SkipEscapableChar(ExtraMode);
        }

        // Store the title
        r.title = UnescapeString(p.Extract(), ExtraMode);

        // Skip closing quote
        p.SkipForward(1);

        // Done!
        return r;
    }


    /////////////////////////////////////////////////////////////////////////////
    // LinkInfo

    function LinkInfo(def, link_text) {
        this.def = def;
        this.link_text = link_text;
    }


    /////////////////////////////////////////////////////////////////////////////
    // Token

    var TokenType_Text = 0;
    var TokenType_HtmlTag = 1;
    var TokenType_Html = 2;
    var TokenType_open_em = 3;
    var TokenType_close_em = 4;
    var TokenType_open_strong = 5;
    var TokenType_close_strong = 6;
    var TokenType_code_span = 7;
    var TokenType_br = 8;
    var TokenType_link = 9;
    var TokenType_img = 10;
    var TokenType_opening_mark = 11;
    var TokenType_closing_mark = 12;
    var TokenType_internal_mark = 13;
    var TokenType_footnote = 14;
    var TokenType_abbreviation = 15;

    function Token(type, startOffset, length) {
        this.type = type;
        this.startOffset = startOffset;
        this.length = length;
        this.data = null;
    }

    /////////////////////////////////////////////////////////////////////////////
    // SpanFormatter

    function SpanFormatter(markdown) {
        this.m_Markdown = markdown;
        this.m_Scanner = new StringScanner();
        this.m_SpareTokens = [];
        this.m_DisableLinks = false;
        this.m_Tokens = [];
    }

    p = SpanFormatter.prototype;

    p.FormatParagraph = function (dest, str, start, len) {
        // Parse the string into a list of tokens
        this.Tokenize(str, start, len);

        // Titled image?
        if (this.m_Tokens.length == 1 && this.m_Markdown.HtmlClassTitledImages != null && this.m_Tokens[0].type == TokenType_img) {
            // Grab the link info
            var li = this.m_Tokens[0].data;

            // Render the div opening
            dest.Append("<div class=\"");
            dest.Append(this.m_Markdown.HtmlClassTitledImages);
            dest.Append("\">\n");

            // Render the img
            this.m_Markdown.RenderingTitledImage = true;
            this.Render(dest, str);
            this.m_Markdown.RenderingTitledImage = false;
            dest.Append("\n");

            // Render the title
            if (li.def.title) {
                dest.Append("<p>");
                dest.SmartHtmlEncodeAmpsAndAngles(li.def.title, 0, li.def.title.length);
                dest.Append("</p>\n");
            }

            dest.Append("</div>\n");
        }
        else {
            // Render the paragraph
            dest.Append("<p>");
            this.Render(dest, str);
            dest.Append("</p>\n");
        }

    }

    // Format part of a string into a destination string builder
    p.Format2 = function (dest, str) {
        this.Format(dest, str, 0, str.length);
    }

    // Format part of a string into a destination string builder
    p.Format = function (dest, str, start, len) {
        // Parse the string into a list of tokens
        this.Tokenize(str, start, len);

        // Render all tokens
        this.Render(dest, str);
    }

    // Format a string and return it as a new string
    // (used in formatting the text of links)
    p.FormatDirect = function (str) {
        var dest = new StringBuilder();
        this.Format(dest, str, 0, str.length);
        return dest.ToString();
    }

    p.MakeID = function (str, start, len) {
        // Parse the string into a list of tokens
        this.Tokenize(str, start, len);
        var tokens = this.m_Tokens;

        var sb = new StringBuilder();
        for (var i = 0; i < tokens.length; i++) {
            var t = tokens[i];
            switch (t.type) {
                case TokenType_Text:
                    sb.Append(str.substr(t.startOffset, t.length));
                    break;

                case TokenType_link:
                    sb.Append(t.data.link_text);
                    break;
            }
            this.FreeToken(t);
        }

        // Now clean it using the same rules as pandoc
        var p = this.m_Scanner;
        p.reset(sb.ToString());

        // Skip everything up to the first letter
        while (!p.eof()) {
            if (is_alpha(p.current()))
                break;
            p.SkipForward(1);
        }

        // Process all characters
        sb.Clear();
        while (!p.eof()) {
            var ch = p.current();
            if (is_alphadigit(ch) || ch == '_' || ch == '-' || ch == '.')
                sb.Append(ch.toLowerCase());
            else if (ch == ' ')
                sb.Append("-");
            else if (is_lineend(ch)) {
                sb.Append("-");
                p.SkipEol();
                continue;
            }

            p.SkipForward(1);
        }

        return sb.ToString();
    }



    // Render a list of tokens to a destination string builder.
    p.Render = function (sb, str) {
        var tokens = this.m_Tokens;
        var len = tokens.length;
        for (var i = 0; i < len; i++) {
            var t = tokens[i];
            switch (t.type) {
                case TokenType_Text:
                    // Append encoded text
                    sb.HtmlEncode(str, t.startOffset, t.length);
                    break;

                case TokenType_HtmlTag:
                    // Append html as is
                    sb.SmartHtmlEncodeAmps(str, t.startOffset, t.length);
                    break;

                case TokenType_Html:
                case TokenType_opening_mark:
                case TokenType_closing_mark:
                case TokenType_internal_mark:
                    // Append html as is
                    sb.Append(str.substr(t.startOffset, t.length));
                    break;

                case TokenType_br:
                    sb.Append("<br />\n");
                    break;

                case TokenType_open_em:
                    sb.Append("<em>");
                    break;

                case TokenType_close_em:
                    sb.Append("</em>");
                    break;

                case TokenType_open_strong:
                    sb.Append("<strong>");
                    break;

                case TokenType_close_strong:
                    sb.Append("</strong>");
                    break;

                case TokenType_code_span:
                    sb.Append("<code>");
                    sb.HtmlEncode(str, t.startOffset, t.length);
                    sb.Append("</code>");
                    break;

                case TokenType_link:
                    var li = t.data;
                    var sf = new SpanFormatter(this.m_Markdown);
                    sf.m_DisableLinks = true;

                    li.def.RenderLink(this.m_Markdown, sb, sf.FormatDirect(li.link_text));
                    break;

                case TokenType_img:
                    var li = t.data;
                    li.def.RenderImg(this.m_Markdown, sb, li.link_text);
                    break;

                case TokenType_footnote:
                    var r = t.data;
                    sb.Append("<sup id=\"fnref:");
                    sb.Append(r.id);
                    sb.Append("\"><a href=\"#fn:");
                    sb.Append(r.id);
                    sb.Append("\" rel=\"footnote\">");
                    sb.Append(r.index + 1);
                    sb.Append("</a></sup>");
                    break;

                case TokenType_abbreviation:
                    var a = t.data;
                    sb.Append("<abbr");
                    if (a.Title) {
                        sb.Append(" title=\"");
                        sb.HtmlEncode(a.Title, 0, a.Title.length);
                        sb.Append("\"");
                    }
                    sb.Append(">");
                    sb.HtmlEncode(a.Abbr, 0, a.Abbr.length);
                    sb.Append("</abbr>");
                    break;


            }

            this.FreeToken(t);
        }
    }

    p.Tokenize = function (str, start, len) {
        // Reset the string scanner
        var p = this.m_Scanner;
        p.reset(str, start, len);

        var tokens = this.m_Tokens;
        tokens.length = 0;

        var emphasis_marks = null;
        var Abbreviations = this.m_Markdown.GetAbbreviations();

        var re = Abbreviations == null ? /[\*\_\`\[\!\<\&\ \\]/g : null;

        var ExtraMode = this.m_Markdown.ExtraMode;

        // Scan string
        var start_text_token = p.m_position;
        while (!p.eof()) {
            if (re != null && !p.FindRE(re))
                break;

            var end_text_token = p.m_position;

            // Work out token
            var token = null;
            switch (p.current()) {
                case '*':
                case '_':

                    // Create emphasis mark
                    token = this.CreateEmphasisMark();

                    if (token != null) {
                        // Store marks in a separate list the we'll resolve later
                        switch (token.type) {
                            case TokenType_internal_mark:
                            case TokenType_opening_mark:
                            case TokenType_closing_mark:
                                if (emphasis_marks == null) {
                                    emphasis_marks = [];
                                }
                                emphasis_marks.push(token);
                                break;
                        }
                    }
                    break;

                case '`':
                    token = this.ProcessCodeSpan();
                    break;

                case '[':
                case '!':
                    // Process link reference
                    var linkpos = p.m_position;
                    token = this.ProcessLinkOrImageOrFootnote();

                    // Rewind if invalid syntax
                    // (the '[' or '!' will be treated as a regular character and processed below)
                    if (token == null)
                        p.m_position = linkpos;
                    break;

                case '<':
                    // Is it a valid html tag?
                    var save = p.m_position;
                    var tag = ParseHtmlTag(p);
                    if (tag != null) {
                        // Yes, create a token for it
                        if (!this.m_Markdown.SafeMode || tag.IsSafe()) {
                            // Yes, create a token for it
                            token = this.CreateToken(TokenType_HtmlTag, save, p.m_position - save);
                        }
                        else {
                            // No, rewrite and encode it
                            p.m_position = save;
                        }
                    }
                    else {
                        // No, rewind and check if it's a valid autolink eg: <google.com>
                        p.m_position = save;
                        token = this.ProcessAutoLink();

                        if (token == null)
                            p.m_position = save;
                    }
                    break;

                case '&':
                    // Is it a valid html entity
                    var save = p.m_position;
                    if (p.SkipHtmlEntity()) {
                        // Yes, create a token for it
                        token = this.CreateToken(TokenType_Html, save, p.m_position - save);
                    }

                    break;

                case ' ':
                    // Check for double space at end of a line
                    if (p.CharAtOffset(1) == ' ' && is_lineend(p.CharAtOffset(2))) {
                        // Yes, skip it
                        p.SkipForward(2);

                        // Don't put br's at the end of a paragraph
                        if (!p.eof()) {
                            p.SkipEol();
                            token = this.CreateToken(TokenType_br, end_text_token, 0);
                        }
                    }
                    break;

                case '\\':
                    // Check followed by an escapable character
                    if (is_escapable(p.CharAtOffset(1), ExtraMode)) {
                        token = this.CreateToken(TokenType_Text, p.m_position + 1, 1);
                        p.SkipForward(2);
                    }
                    break;
            }

            // Look for abbreviations.
            if (token == null && Abbreviations != null && !is_alphadigit(p.CharAtOffset(-1))) {
                var savepos = p.m_position;
                for (var i in Abbreviations) {
                    var abbr = Abbreviations[i];
                    if (p.SkipString(abbr.Abbr) && !is_alphadigit(p.current())) {
                        token = this.CreateDataToken(TokenType_abbreviation, abbr);
                        break;
                    }

                    p.position = savepos;
                }
            }


            // If token found, append any preceeding text and the new token to the token list
            if (token != null) {
                // Create a token for everything up to the special character
                if (end_text_token > start_text_token) {
                    tokens.push(this.CreateToken(TokenType_Text, start_text_token, end_text_token - start_text_token));
                }

                // Add the new token
                tokens.push(token);

                // Remember where the next text token starts
                start_text_token = p.m_position;
            }
            else {
                // Skip a single character and keep looking
                p.SkipForward(1);
            }
        }

        // Append a token for any trailing text after the last token.
        if (p.m_position > start_text_token) {
            tokens.push(this.CreateToken(TokenType_Text, start_text_token, p.m_position - start_text_token));
        }

        // Do we need to resolve and emphasis marks?
        if (emphasis_marks != null) {
            this.ResolveEmphasisMarks(tokens, emphasis_marks);
        }
    }

    /*
    * Resolving emphasis tokens is a two part process
    * 
    * 1. Find all valid sequences of * and _ and create `mark` tokens for them
    *		this is done by CreateEmphasisMarks during the initial character scan
    *		done by Tokenize
    *		
    * 2. Looks at all these emphasis marks and tries to pair them up
    *		to make the actual <em> and <strong> tokens
    *		
    * Any unresolved emphasis marks are rendered unaltered as * or _
    */

    // Create emphasis mark for sequences of '*' and '_' (part 1)
    p.CreateEmphasisMark = function () {
        var p = this.m_Scanner;

        // Capture current state
        var ch = p.current();
        var altch = ch == '*' ? '_' : '*';
        var savepos = p.m_position;

        // Check for a consecutive sequence of just '_' and '*'
        if (p.bof() || is_whitespace(p.CharAtOffset(-1))) {
            while (is_emphasis(p.current()))
                p.SkipForward(1);

            if (p.eof() || is_whitespace(p.current())) {
                return this.CreateToken(TokenType_Html, savepos, p.m_position - savepos);
            }

            // Rewind
            p.m_position = savepos;
        }

        // Scan backwards and see if we have space before
        while (is_emphasis(p.CharAtOffset(-1)))
            p.SkipForward(-1);
        var bSpaceBefore = p.bof() || is_whitespace(p.CharAtOffset(-1));
        p.m_position = savepos;

        // Count how many matching emphasis characters
        while (p.current() == ch) {
            p.SkipForward(1);
        }
        var count = p.m_position - savepos;

        // Scan forwards and see if we have space after
        while (is_emphasis(p.CharAtOffset(1)))
            p.SkipForward(1);
        var bSpaceAfter = p.eof() || is_whitespace(p.current());
        p.m_position = savepos + count;

        if (bSpaceBefore) {
            return this.CreateToken(TokenType_opening_mark, savepos, p.m_position - savepos);
        }

        if (bSpaceAfter) {
            return this.CreateToken(TokenType_closing_mark, savepos, p.m_position - savepos);
        }

        if (this.m_Markdown.ExtraMode && ch == '_')
            return null;


        return this.CreateToken(TokenType_internal_mark, savepos, p.m_position - savepos);
    }

    // Split mark token
    p.SplitMarkToken = function (tokens, marks, token, position) {
        // Create the new rhs token
        var tokenRhs = this.CreateToken(token.type, token.startOffset + position, token.length - position);

        // Adjust down the length of this token
        token.length = position;

        // Insert the new token into each of the parent collections
        marks.splice(array_indexOf(marks, token) + 1, 0, tokenRhs);
        tokens.splice(array_indexOf(tokens, token) + 1, 0, tokenRhs);

        // Return the new token
        return tokenRhs;
    }

    // Resolve emphasis marks (part 2)
    p.ResolveEmphasisMarks = function (tokens, marks) {
        var input = this.m_Scanner.buf;

        var bContinue = true;
        while (bContinue) {
            bContinue = false;
            for (var i = 0; i < marks.length; i++) {
                // Get the next opening or internal mark
                var opening_mark = marks[i];
                if (opening_mark.type != TokenType_opening_mark && opening_mark.type != TokenType_internal_mark)
                    continue;

                // Look for a matching closing mark
                for (var j = i + 1; j < marks.length; j++) {
                    // Get the next closing or internal mark
                    var closing_mark = marks[j];
                    if (closing_mark.type != TokenType_closing_mark && closing_mark.type != TokenType_internal_mark)
                        break;

                    // Ignore if different type (ie: `*` vs `_`)
                    if (input.charAt(opening_mark.startOffset) != input.charAt(closing_mark.startOffset))
                        continue;

                    // strong or em?
                    var style = Math.min(opening_mark.length, closing_mark.length);

                    // Triple or more on both ends?
                    if (style >= 3) {
                        style = (style % 2) == 1 ? 1 : 2;
                    }

                    // Split the opening mark, keeping the RHS
                    if (opening_mark.length > style) {
                        opening_mark = this.SplitMarkToken(tokens, marks, opening_mark, opening_mark.length - style);
                        i--;
                    }

                    // Split the closing mark, keeping the LHS
                    if (closing_mark.length > style) {
                        this.SplitMarkToken(tokens, marks, closing_mark, style);
                    }

                    // Connect them
                    opening_mark.type = style == 1 ? TokenType_open_em : TokenType_open_strong;
                    closing_mark.type = style == 1 ? TokenType_close_em : TokenType_close_strong;

                    // Remove the matched marks
                    marks.splice(array_indexOf(marks, opening_mark), 1);
                    marks.splice(array_indexOf(marks, closing_mark), 1);
                    bContinue = true;

                    break;
                }
            }
        }
    }

    // Process auto links eg: <google.com>
    p.ProcessAutoLink = function () {
        if (this.m_DisableLinks)
            return null;

        var p = this.m_Scanner;

        // Skip the angle bracket and remember the start
        p.SkipForward(1);
        p.Mark();

        var ExtraMode = this.m_Markdown.ExtraMode;

        // Allow anything up to the closing angle, watch for escapable characters
        while (!p.eof()) {
            var ch = p.current();

            // No whitespace allowed
            if (is_whitespace(ch))
                break;

            // End found?
            if (ch == '>') {
                var url = UnescapeString(p.Extract(), ExtraMode);

                var li = null;
                if (IsEmailAddress(url)) {
                    var link_text;
                    if (url.toLowerCase().substr(0, 7) == "mailto:") {
                        link_text = url.substr(7);
                    }
                    else {
                        link_text = url;
                        url = "mailto:" + url;
                    }

                    li = new LinkInfo(new LinkDefinition("auto", url, null), link_text);
                }
                else if (IsWebAddress(url)) {
                    li = new LinkInfo(new LinkDefinition("auto", url, null), url);
                }

                if (li != null) {
                    p.SkipForward(1);
                    return this.CreateDataToken(TokenType_link, li);
                }

                return null;
            }

            p.SkipEscapableChar(ExtraMode);
        }

        // Didn't work
        return null;
    }

    // Process [link] and ![image] directives
    p.ProcessLinkOrImageOrFootnote = function () {
        var p = this.m_Scanner;

        // Link or image?
        var token_type = p.SkipChar('!') ? TokenType_img : TokenType_link;

        // Opening '['
        if (!p.SkipChar('['))
            return null;

        // Is it a foonote?
        var savepos = this.m_position;
        if (this.m_Markdown.ExtraMode && token_type == TokenType_link && p.SkipChar('^')) {
            p.SkipLinespace();

            // Parse it
            p.Mark();
            var id = p.SkipFootnoteID();
            if (id != null && p.SkipChar(']')) {
                // Look it up and create footnote reference token
                var footnote_index = this.m_Markdown.ClaimFootnote(id);
                if (footnote_index >= 0) {
                    // Yes it's a footnote
                    return this.CreateDataToken(TokenType_footnote, { index: footnote_index, id: id });
                }
            }

            // Rewind
            this.m_position = savepos;
        }

        if (this.m_DisableLinks)
            return null;

        var ExtraMode = this.m_Markdown.ExtraMode;

        // Find the closing square bracket, allowing for nesting, watching for 
        // escapable characters
        p.Mark();
        var depth = 1;
        while (!p.eof()) {
            var ch = p.current();
            if (ch == '[') {
                depth++;
            }
            else if (ch == ']') {
                depth--;
                if (depth == 0)
                    break;
            }

            p.SkipEscapableChar(ExtraMode);
        }

        // Quit if end
        if (p.eof())
            return null;

        // Get the link text and unescape it
        var link_text = UnescapeString(p.Extract(), ExtraMode);

        // The closing ']'
        p.SkipForward(1);

        // Save position in case we need to rewind
        savepos = p.m_position;

        // Inline links must follow immediately
        if (p.SkipChar('(')) {
            // Extract the url and title
            var link_def = ParseLinkTarget(p, null, this.m_Markdown.ExtraMode);
            if (link_def == null)
                return null;

            // Closing ')'
            p.SkipWhitespace();
            if (!p.SkipChar(')'))
                return null;

            // Create the token
            return this.CreateDataToken(token_type, new LinkInfo(link_def, link_text));
        }

        // Optional space or tab
        if (!p.SkipChar(' '))
            p.SkipChar('\t');

        // If there's line end, we're allow it and as must line space as we want
        // before the link id.
        if (p.eol()) {
            p.SkipEol();
            p.SkipLinespace();
        }

        // Reference link?
        var link_id = null;
        if (p.current() == '[') {
            // Skip the opening '['
            p.SkipForward(1);

            // Find the start/end of the id
            p.Mark();
            if (!p.Find(']'))
                return null;

            // Extract the id
            link_id = p.Extract();

            // Skip closing ']'
            p.SkipForward(1);
        }
        else {
            // Rewind to just after the closing ']'
            p.m_position = savepos;
        }

        // Link id not specified?
        if (!link_id) {
            link_id = link_text;

            // Convert all whitespace+line end to a single space
            while (true) {
                // Find carriage return
                var i = link_id.indexOf("\n");
                if (i < 0)
                    break;

                var start = i;
                while (start > 0 && is_whitespace(link_id.charAt(start - 1)))
                    start--;

                var end = i;
                while (end < link_id.length && is_whitespace(link_id.charAt(end)))
                    end++;

                link_id = link_id.substr(0, start) + " " + link_id.substr(end);
            }
        }

        // Find the link definition, abort if not defined
        var def = this.m_Markdown.GetLinkDefinition(link_id);
        if (def == null)
            return null;

        // Create a token
        return this.CreateDataToken(token_type, new LinkInfo(def, link_text));
    }

    // Process a ``` code span ```
    p.ProcessCodeSpan = function () {
        var p = this.m_Scanner;
        var start = p.m_position;

        // Count leading ticks
        var tickcount = 0;
        while (p.SkipChar('`')) {
            tickcount++;
        }

        // Skip optional leading space...
        p.SkipWhitespace();

        // End?
        if (p.eof())
            return this.CreateToken(TokenType_Text, start, p.m_position - start);

        var startofcode = p.m_position;

        // Find closing ticks
        if (!p.Find(p.buf.substr(start, tickcount)))
            return this.CreateToken(TokenType_Text, start, p.m_position - start);

        // Save end position before backing up over trailing whitespace
        var endpos = p.m_position + tickcount;
        while (is_whitespace(p.CharAtOffset(-1)))
            p.SkipForward(-1);

        // Create the token, move back to the end and we're done
        var ret = this.CreateToken(TokenType_code_span, startofcode, p.m_position - startofcode);
        p.m_position = endpos;
        return ret;
    }

    p.CreateToken = function (type, startOffset, length) {
        if (this.m_SpareTokens.length != 0) {
            var t = this.m_SpareTokens.pop();
            t.type = type;
            t.startOffset = startOffset;
            t.length = length;
            t.data = null;
            return t;
        }
        else
            return new Token(type, startOffset, length);
    }

    // CreateToken - create or re-use a token object
    p.CreateDataToken = function (type, data) {
        if (this.m_SpareTokens.length != 0) {
            var t = this.m_SpareTokens.pop();
            t.type = type;
            t.data = data;
            return t;
        }
        else {
            var t = new Token(type, 0, 0);
            t.data = data;
            return t;
        }
    }

    // FreeToken - return a token to the spare token pool
    p.FreeToken = function (token) {
        token.data = null;
        this.m_SpareTokens.push(token);
    }



    /////////////////////////////////////////////////////////////////////////////
    // Block

    var BlockType_Blank = 0;
    var BlockType_h1 = 1;
    var BlockType_h2 = 2;
    var BlockType_h3 = 3;
    var BlockType_h4 = 4;
    var BlockType_h5 = 5;
    var BlockType_h6 = 6;
    var BlockType_post_h1 = 7;
    var BlockType_post_h2 = 8;
    var BlockType_quote = 9;
    var BlockType_ol_li = 10;
    var BlockType_ul_li = 11;
    var BlockType_p = 12;
    var BlockType_indent = 13;
    var BlockType_hr = 14;
    var BlockType_html = 15;
    var BlockType_unsafe_html = 16;
    var BlockType_span = 17;
    var BlockType_codeblock = 18;
    var BlockType_li = 19;
    var BlockType_ol = 20;
    var BlockType_ul = 21;
    var BlockType_HtmlTag = 22;
    var BlockType_Composite = 23;
    var BlockType_table_spec = 24;
    var BlockType_dd = 25;
    var BlockType_dt = 26;
    var BlockType_dl = 27;
    var BlockType_footnote = 28;
    var BlockType_p_footnote = 29;


    function Block() {
    }


    p = Block.prototype;
    p.buf = null;
    p.blockType = BlockType_Blank;
    p.contentStart = 0;
    p.contentLen = 0;
    p.lineStart = 0;
    p.lineLen = 0;
    p.children = null;
    p.data = null;

    p.get_Content = function () {
        if (this.buf == null)
            return null;
        if (this.contentStart == -1)
            return this.buf;

        return this.buf.substr(this.contentStart, this.contentLen);
    }


    p.get_CodeContent = function () {
        var s = new StringBuilder();
        for (var i = 0; i < this.children.length; i++) {
            s.Append(this.children[i].get_Content());
            s.Append('\n');
        }
        return s.ToString();
    }


    p.RenderChildren = function (m, b) {
        for (var i = 0; i < this.children.length; i++) {
            this.children[i].Render(m, b);
        }
    }

    p.ResolveHeaderID = function (m) {
        // Already resolved?
        if (this.data != null)
            return this.data;

        // Approach 1 - PHP Markdown Extra style header id
        var res = StripHtmlID(this.buf, this.contentStart, this.get_contentEnd());
        var id = null;
        if (res != null) {
            this.set_contentEnd(res.end);
            id = res.id;
        }
        else {
            // Approach 2 - pandoc style header id
            id = m.MakeUniqueHeaderID(this.buf, this.contentStart, this.contentLen);
        }

        this.data = id;
        return id;
    }

    p.Render = function (m, b) {
        switch (this.blockType) {
            case BlockType_Blank:
                return;

            case BlockType_p:
                m.m_SpanFormatter.FormatParagraph(b, this.buf, this.contentStart, this.contentLen);
                break;

            case BlockType_span:
                m.m_SpanFormatter.Format(b, this.buf, this.contentStart, this.contentLen);
                b.Append("\n");
                break;

            case BlockType_h1:
            case BlockType_h2:
            case BlockType_h3:
            case BlockType_h4:
            case BlockType_h5:
            case BlockType_h6:
                if (m.ExtraMode && !m.SafeMode) {
                    b.Append("<h" + (this.blockType - BlockType_h1 + 1).toString());
                    var id = this.ResolveHeaderID(m);
                    if (id) {
                        b.Append(" id=\"");
                        b.Append(id);
                        b.Append("\">");
                    }
                    else {
                        b.Append(">");
                    }
                }
                else {
                    b.Append("<h" + (this.blockType - BlockType_h1 + 1).toString() + ">");
                }
                m.m_SpanFormatter.Format(b, this.buf, this.contentStart, this.contentLen);
                b.Append("</h" + (this.blockType - BlockType_h1 + 1).toString() + ">\n");
                break;

            case BlockType_hr:
                b.Append("<hr />\n");
                return;

            case BlockType_ol_li:
            case BlockType_ul_li:
                b.Append("<li>");
                m.m_SpanFormatter.Format(b, this.buf, this.contentStart, this.contentLen);
                b.Append("</li>\n");
                break;

            case BlockType_html:
                b.Append(this.buf.substr(this.contentStart, this.contentLen));
                return;

            case BlockType_unsafe_html:
                b.HtmlEncode(this.buf, this.contentStart, this.contentLen);
                return;

            case BlockType_codeblock:
                b.Append("<pre");
                if (m.FormatCodeBlockAttributes != null) {
                    b.Append(m.FormatCodeBlockAttributes(this.data));
                }
                b.Append("><code>");

                var btemp = b;
                if (m.FormatCodeBlock) {
                    btemp = b;
                    b = new StringBuilder();
                }

                for (var i = 0; i < this.children.length; i++) {
                    var line = this.children[i];
                    b.HtmlEncodeAndConvertTabsToSpaces(line.buf, line.contentStart, line.contentLen);
                    b.Append("\n");
                }

                if (m.FormatCodeBlock) {
                    btemp.Append(m.FormatCodeBlock(b.ToString(), this.data));
                    b = btemp;
                }
                b.Append("</code></pre>\n\n");
                return;

            case BlockType_quote:
                b.Append("<blockquote>\n");
                this.RenderChildren(m, b);
                b.Append("</blockquote>\n");
                return;

            case BlockType_li:
                b.Append("<li>\n");
                this.RenderChildren(m, b);
                b.Append("</li>\n");
                return;

            case BlockType_ol:
                b.Append("<ol>\n");
                this.RenderChildren(m, b);
                b.Append("</ol>\n");
                return;

            case BlockType_ul:
                b.Append("<ul>\n");
                this.RenderChildren(m, b);
                b.Append("</ul>\n");
                return;

            case BlockType_HtmlTag:
                var tag = this.data;

                // Prepare special tags
                var name = tag.name.toLowerCase();
                if (name == "a") {
                    m.OnPrepareLink(tag);
                }
                else if (name == "img") {
                    m.OnPrepareImage(tag, m.RenderingTitledImage);
                }

                tag.RenderOpening(b);
                b.Append("\n");
                this.RenderChildren(m, b);
                tag.RenderClosing(b);
                b.Append("\n");
                return;

            case BlockType_Composite:
            case BlockType_footnote:
                this.RenderChildren(m, b);
                return;

            case BlockType_table_spec:
                this.data.Render(m, b);
                return;

            case BlockType_dd:
                b.Append("<dd>");
                if (this.children != null) {
                    b.Append("\n");
                    this.RenderChildren(m, b);
                }
                else
                    m.m_SpanFormatter.Format(b, this.buf, this.contentStart, this.contentLen);
                b.Append("</dd>\n");
                break;

            case BlockType_dt:
                if (this.children == null) {
                    var lines = this.get_Content().split("\n");
                    for (var i = 0; i < lines.length; i++) {
                        var l = lines[i];
                        b.Append("<dt>");
                        m.m_SpanFormatter.Format2(b, Trim(l));
                        b.Append("</dt>\n");
                    }
                }
                else {
                    b.Append("<dt>\n");
                    this.RenderChildren(m, b);
                    b.Append("</dt>\n");
                }
                break;

            case BlockType_dl:
                b.Append("<dl>\n");
                this.RenderChildren(m, b);
                b.Append("</dl>\n");
                return;

            case BlockType_p_footnote:
                b.Append("<p>");
                if (this.contentLen > 0) {
                    m.m_SpanFormatter.Format(b, this.buf, this.contentStart, this.contentLen);
                    b.Append("&nbsp;");
                }
                b.Append(this.data);
                b.Append("</p>\n");
                break;

        }
    }

    p.RevertToPlain = function () {
        this.blockType = BlockType_p;
        this.contentStart = this.lineStart;
        this.contentLen = this.lineLen;
    }

    p.get_contentEnd = function () {
        return this.contentStart + this.contentLen;
    }

    p.set_contentEnd = function (value) {
        this.contentLen = value - this.contentStart;
    }

    // Count the leading spaces on a block
    // Used by list item evaluation to determine indent levels
    // irrespective of indent line type.
    p.get_leadingSpaces = function () {
        var count = 0;
        for (var i = this.lineStart; i < this.lineStart + this.lineLen; i++) {
            if (this.buf.charAt(i) == ' ') {
                count++;
            }
            else {
                break;
            }
        }
        return count;
    }

    p.CopyFrom = function (other) {
        this.blockType = other.blockType;
        this.buf = other.buf;
        this.contentStart = other.contentStart;
        this.contentLen = other.contentLen;
        this.lineStart = other.lineStart;
        this.lineLen = other.lineLen;
        return this;
    }

    /////////////////////////////////////////////////////////////////////////////
    // BlockProcessor


    function BlockProcessor(m, MarkdownInHtml) {
        this.m_Markdown = m;
        this.m_parentType = BlockType_Blank;
        this.m_bMarkdownInHtml = MarkdownInHtml;
    }

    p = BlockProcessor.prototype;

    p.Process = function (str) {
        // Reset string scanner
        var p = new StringScanner(str);

        return this.ScanLines(p);
    }

    p.ProcessRange = function (str, startOffset, len) {
        // Reset string scanner
        var p = new StringScanner(str, startOffset, len);

        return this.ScanLines(p);
    }

    p.StartTable = function (p, spec, lines) {
        // Mustn't have more than 1 preceeding line
        if (lines.length > 1)
            return false;

        // Rewind, parse the header row then fast forward back to current pos
        if (lines.length == 1) {
            var savepos = p.m_position;
            p.m_position = lines[0].lineStart;
            spec.m_Headers = spec.ParseRow(p);
            if (spec.m_Headers == null)
                return false;
            p.m_position = savepos;
            lines.length = 0;
        }

        // Parse all .m_Rows
        while (true) {
            var savepos = p.m_position;

            var row = spec.ParseRow(p);
            if (row != null) {
                spec.m_Rows.push(row);
                continue;
            }

            p.m_position = savepos;
            break;
        }

        return true;
    }



    p.ScanLines = function (p) {
        // The final set of blocks will be collected here
        var blocks = [];

        // The current paragraph/list/codeblock etc will be accumulated here
        // before being collapsed into a block and store in above `blocks` list
        var lines = [];

        // Add all blocks
        var PrevBlockType = -1;
        while (!p.eof()) {
            // Remember if the previous line was blank
            var bPreviousBlank = PrevBlockType == BlockType_Blank;

            // Get the next block
            var b = this.EvaluateLine(p);
            PrevBlockType = b.blockType;

            // For dd blocks, we need to know if it was preceeded by a blank line
            // so store that fact as the block's data.
            if (b.blockType == BlockType_dd) {
                b.data = bPreviousBlank;
            }


            // SetExt header?
            if (b.blockType == BlockType_post_h1 || b.blockType == BlockType_post_h2) {
                if (lines.length > 0) {
                    // Remove the previous line and collapse the current paragraph
                    var prevline = lines.pop();
                    this.CollapseLines(blocks, lines);

                    // If previous line was blank, 
                    if (prevline.blockType != BlockType_Blank) {
                        // Convert the previous line to a heading and add to block list
                        prevline.RevertToPlain();
                        prevline.blockType = b.blockType == BlockType_post_h1 ? BlockType_h1 : BlockType_h2;
                        blocks.push(prevline);
                        continue;
                    }
                }


                // Couldn't apply setext header to a previous line

                if (b.blockType == BlockType_post_h1) {
                    // `===` gets converted to normal paragraph
                    b.RevertToPlain();
                    lines.push(b);
                }
                else {
                    // `---` gets converted to hr
                    if (b.contentLen >= 3) {
                        b.blockType = BlockType_hr;
                        blocks.push(b);
                    }
                    else {
                        b.RevertToPlain();
                        lines.push(b);
                    }
                }

                continue;
            }


            // Work out the current paragraph type
            var currentBlockType = lines.length > 0 ? lines[0].blockType : BlockType_Blank;

            // Starting a table?
            if (b.blockType == BlockType_table_spec) {
                // Get the table spec, save position
                var spec = b.data;
                var savepos = p.m_position;
                if (!this.StartTable(p, spec, lines)) {
                    // Not a table, revert the tablespec row to plain,
                    // fast forward back to where we were up to and continue
                    // on as if nothing happened
                    p.m_position = savepos;
                    b.RevertToPlain();
                }
                else {
                    blocks.push(b);
                    continue;
                }
            }

            // Process this line
            switch (b.blockType) {
                case BlockType_Blank:
                    switch (currentBlockType) {
                        case BlockType_Blank:
                            this.FreeBlock(b);
                            break;

                        case BlockType_p:
                            this.CollapseLines(blocks, lines);
                            this.FreeBlock(b);
                            break;

                        case BlockType_quote:
                        case BlockType_ol_li:
                        case BlockType_ul_li:
                        case BlockType_dd:
                        case BlockType_footnote:
                        case BlockType_indent:
                            lines.push(b);
                            break;
                    }
                    break;

                case BlockType_p:
                    switch (currentBlockType) {
                        case BlockType_Blank:
                        case BlockType_p:
                            lines.push(b);
                            break;

                        case BlockType_quote:
                        case BlockType_ol_li:
                        case BlockType_ul_li:
                        case BlockType_dd:
                        case BlockType_footnote:
                            var prevline = lines[lines.length - 1];
                            if (prevline.blockType == BlockType_Blank) {
                                this.CollapseLines(blocks, lines);
                                lines.push(b);
                            }
                            else {
                                lines.push(b);
                            }
                            break;

                        case BlockType_indent:
                            this.CollapseLines(blocks, lines);
                            lines.push(b);
                            break;
                    }
                    break;

                case BlockType_indent:
                    switch (currentBlockType) {
                        case BlockType_Blank:
                            // Start a code block
                            lines.push(b);
                            break;

                        case BlockType_p:
                        case BlockType_quote:
                            var prevline = lines[lines.length - 1];
                            if (prevline.blockType == BlockType_Blank) {
                                // Start a code block after a paragraph
                                this.CollapseLines(blocks, lines);
                                lines.push(b);
                            }
                            else {
                                // indented line in paragraph, just continue it
                                b.RevertToPlain();
                                lines.push(b);
                            }
                            break;


                        case BlockType_ol_li:
                        case BlockType_ul_li:
                        case BlockType_indent:
                        case BlockType_dd:
                        case BlockType_footnote:
                            lines.push(b);
                            break;
                    }
                    break;

                case BlockType_quote:
                    if (currentBlockType != BlockType_quote) {
                        this.CollapseLines(blocks, lines);
                    }
                    lines.push(b);
                    break;

                case BlockType_ol_li:
                case BlockType_ul_li:
                    switch (currentBlockType) {
                        case BlockType_Blank:
                            lines.push(b);
                            break;

                        case BlockType_p:
                        case BlockType_quote:
                            var prevline = lines[lines.length - 1];
                            if (prevline.blockType == BlockType_Blank || this.m_parentType == BlockType_ol_li || this.m_parentType == BlockType_ul_li || this.m_parentType == BlockType_dd) {
                                // List starting after blank line after paragraph or quote
                                this.CollapseLines(blocks, lines);
                                lines.push(b);
                            }
                            else {
                                // List's can't start in middle of a paragraph
                                b.RevertToPlain();
                                lines.push(b);
                            }
                            break;

                        case BlockType_ol_li:
                        case BlockType_ul_li:
                        case BlockType_dd:
                        case BlockType_footnote:
                            if (b.blockType != currentBlockType) {
                                this.CollapseLines(blocks, lines);
                            }
                            lines.push(b);
                            break;

                        case BlockType_indent:
                            // List after code block
                            this.CollapseLines(blocks, lines);
                            lines.push(b);
                            break;
                    }
                    break;

                case BlockType_dd:
                case BlockType_footnote:
                    switch (currentBlockType) {
                        case BlockType_Blank:
                        case BlockType_p:
                        case BlockType_dd:
                        case BlockType_footnote:
                            this.CollapseLines(blocks, lines);
                            lines.push(b);
                            break;

                        default:
                            b.RevertToPlain();
                            lines.push(b);
                            break;
                    }
                    break;

                default:
                    this.CollapseLines(blocks, lines);
                    blocks.push(b);
                    break;
            }
        }

        this.CollapseLines(blocks, lines);

        if (this.m_Markdown.ExtraMode) {
            this.BuildDefinitionLists(blocks);
        }

        return blocks;
    }

    p.CreateBlock = function (lineStart) {
        var b;
        if (this.m_Markdown.m_SpareBlocks.length > 1) {
            b = this.m_Markdown.m_SpareBlocks.pop();
        }
        else {
            b = new Block();
        }
        b.lineStart = lineStart;
        return b;
    }

    p.FreeBlock = function (b) {
        this.m_Markdown.m_SpareBlocks.push(b);
    }

    p.FreeBlocks = function (blocks) {
        for (var i = 0; i < blocks.length; i++)
            this.m_Markdown.m_SpareBlocks.push(blocks[i]);
        blocks.length = 0;
    }

    p.RenderLines = function (lines) {
        var b = this.m_Markdown.GetStringBuilder();
        for (var i = 0; i < lines.length; i++) {
            var l = lines[i];
            b.Append(l.buf.substr(l.contentStart, l.contentLen));
            b.Append('\n');
        }
        return b.ToString();
    }

    p.CollapseLines = function (blocks, lines) {
        // Remove trailing blank lines
        while (lines.length > 0 && lines[lines.length - 1].blockType == BlockType_Blank) {
            this.FreeBlock(lines.pop());
        }

        // Quit if empty
        if (lines.length == 0) {
            return;
        }


        // What sort of block?
        switch (lines[0].blockType) {
            case BlockType_p:
                // Collapse all lines into a single paragraph
                var para = this.CreateBlock(lines[0].lineStart);
                para.blockType = BlockType_p;
                para.buf = lines[0].buf;
                para.contentStart = lines[0].contentStart;
                para.set_contentEnd(lines[lines.length - 1].get_contentEnd());
                blocks.push(para);
                this.FreeBlocks(lines);
                break;

            case BlockType_quote:
                // Get the content
                var str = this.RenderLines(lines);

                // Create the new block processor
                var bp = new BlockProcessor(this.m_Markdown, this.m_bMarkdownInHtml);
                bp.m_parentType = BlockType_quote;

                // Create a new quote block
                var quote = this.CreateBlock(lines[0].lineStart);
                quote.blockType = BlockType_quote;
                quote.children = bp.Process(str);
                this.FreeBlocks(lines);
                blocks.push(quote);
                break;

            case BlockType_ol_li:
            case BlockType_ul_li:
                blocks.push(this.BuildList(lines));
                break;

            case BlockType_dd:
                if (blocks.length > 0) {
                    var prev = blocks[blocks.length - 1];
                    switch (prev.blockType) {
                        case BlockType_p:
                            prev.blockType = BlockType_dt;
                            break;

                        case BlockType_dd:
                            break;

                        default:
                            var wrapper = this.CreateBlock(prev.lineStart);
                            wrapper.blockType = BlockType_dt;
                            wrapper.children = [];
                            wrapper.children.push(prev);
                            blocks.pop();
                            blocks.push(wrapper);
                            break;
                    }

                }
                blocks.push(this.BuildDefinition(lines));
                break;

            case BlockType_footnote:
                this.m_Markdown.AddFootnote(this.BuildFootnote(lines));
                break;


            case BlockType_indent:
                var codeblock = this.CreateBlock(lines[0].lineStart);
                codeblock.blockType = BlockType_codeblock;
                codeblock.children = [];
                var firstline = lines[0].get_Content();
                if (firstline.substr(0, 2) == "{{" && firstline.substr(firstline.length - 2, 2) == "}}") {
                    codeblock.data = firstline.substr(2, firstline.length - 4);
                    lines.splice(0, 1);
                }
                for (var i = 0; i < lines.length; i++) {
                    codeblock.children.push(lines[i]);
                }
                blocks.push(codeblock);
                lines.length = 0;
                break;
        }
    }

    p.EvaluateLine = function (p) {
        // Create a block
        var b = this.CreateBlock(p.m_position);

        // Store line start
        b.buf = p.buf;

        // Scan the line
        b.contentStart = p.m_position;
        b.contentLen = -1;
        b.blockType = this.EvaluateLineInternal(p, b);


        // If end of line not returned, do it automatically
        if (b.contentLen < 0) {
            // Move to end of line
            p.SkipToEol();
            b.contentLen = p.m_position - b.contentStart;
        }

        // Setup line length
        b.lineLen = p.m_position - b.lineStart;

        // Next line
        p.SkipEol();

        // Create block
        return b;
    }

    p.EvaluateLineInternal = function (p, b) {
        // Empty line?
        if (p.eol())
            return BlockType_Blank;

        // Save start of line position
        var line_start = p.m_position;

        // ## Heading ##		
        var ch = p.current();
        if (ch == '#') {
            // Work out heading level
            var level = 1;
            p.SkipForward(1);
            while (p.current() == '#') {
                level++;
                p.SkipForward(1);
            }

            // Limit of 6
            if (level > 6)
                level = 6;

            // Skip any whitespace
            p.SkipLinespace();

            // Save start position
            b.contentStart = p.m_position;

            // Jump to end
            p.SkipToEol();

            // In extra mode, check for a trailing HTML ID
            if (this.m_Markdown.ExtraMode && !this.m_Markdown.SafeMode) {
                var res = StripHtmlID(p.buf, b.contentStart, p.m_position);
                if (res != null) {
                    b.data = res.id;
                    p.m_position = res.end;
                }
            }

            // Rewind over trailing hashes
            while (p.m_position > b.contentStart && p.CharAtOffset(-1) == '#') {
                p.SkipForward(-1);
            }

            // Rewind over trailing spaces
            while (p.m_position > b.contentStart && is_whitespace(p.CharAtOffset(-1))) {
                p.SkipForward(-1);
            }

            // Create the heading block
            b.contentLen = p.m_position - b.contentStart;

            p.SkipToEol();
            return BlockType_h1 + (level - 1);
        }

        // Check for entire line as - or = for setext h1 and h2
        if (ch == '-' || ch == '=') {
            // Skip all matching characters
            var chType = ch;
            while (p.current() == chType) {
                p.SkipForward(1);
            }

            // Trailing whitespace allowed
            p.SkipLinespace();

            // If not at eol, must have found something other than setext header
            if (p.eol()) {
                return chType == '=' ? BlockType_post_h1 : BlockType_post_h2;
            }

            p.m_position = line_start;
        }

        if (this.m_Markdown.ExtraMode) {
            // MarkdownExtra Table row indicator?
            var spec = TableSpec_Parse(p);
            if (spec != null) {
                b.data = spec;
                return BlockType_table_spec;
            }

            p.m_position = line_start;


            // Fenced code blocks?
            if (ch == '~') {
                if (this.ProcessFencedCodeBlock(p, b))
                    return b.blockType;

                // Rewind
                p.m_position = line_start;
            }
        }

        // Scan the leading whitespace, remembering how many spaces and where the first tab is
        var tabPos = -1;
        var leadingSpaces = 0;
        while (!p.eol()) {
            if (p.current() == ' ') {
                if (tabPos < 0)
                    leadingSpaces++;
            }
            else if (p.current() == '\t') {
                if (tabPos < 0)
                    tabPos = p.m_position;
            }
            else {
                // Something else, get out
                break;
            }
            p.SkipForward(1);
        }

        // Blank line?
        if (p.eol()) {
            b.contentLen = 0;
            return BlockType_Blank;
        }

        // 4 leading spaces?
        if (leadingSpaces >= 4) {
            b.contentStart = line_start + 4;
            return BlockType_indent;
        }

        // Tab in the first 4 characters?
        if (tabPos >= 0 && tabPos - line_start < 4) {
            b.contentStart = tabPos + 1;
            return BlockType_indent;
        }

        // Treat start of line as after leading whitespace
        b.contentStart = p.m_position;

        // Get the next character
        ch = p.current();

        // Html block?
        if (ch == '<') {
            if (this.ScanHtml(p, b))
                return b.blockType;

            // Rewind
            p.m_position = b.contentStart;
        }

        // Block quotes start with '>' and have one space or one tab following
        if (ch == '>') {
            // Block quote followed by space
            if (is_linespace(p.CharAtOffset(1))) {
                // Skip it and create quote block
                p.SkipForward(2);
                b.contentStart = p.m_position;
                return BlockType_quote;
            }

            p.SkipForward(1);
            b.contentStart = p.m_position;
            return BlockType_quote;
        }

        // Horizontal rule - a line consisting of 3 or more '-', '_' or '*' with optional spaces and nothing else
        if (ch == '-' || ch == '_' || ch == '*') {
            var count = 0;
            while (!p.eol()) {
                var chType = p.current();
                if (p.current() == ch) {
                    count++;
                    p.SkipForward(1);
                    continue;
                }

                if (is_linespace(p.current())) {
                    p.SkipForward(1);
                    continue;
                }

                break;
            }

            if (p.eol() && count >= 3) {
                return BlockType_hr;
            }

            // Rewind
            p.m_position = b.contentStart;
        }

        // Abbreviation definition?
        if (this.m_Markdown.ExtraMode && ch == '*' && p.CharAtOffset(1) == '[') {
            p.SkipForward(2);
            p.SkipLinespace();

            p.Mark();
            while (!p.eol() && p.current() != ']') {
                p.SkipForward(1);
            }

            var abbr = Trim(p.Extract());
            if (p.current() == ']' && p.CharAtOffset(1) == ':' && abbr) {
                p.SkipForward(2);
                p.SkipLinespace();

                p.Mark();

                p.SkipToEol();

                var title = p.Extract();

                this.m_Markdown.AddAbbreviation(abbr, title);

                return BlockType_Blank;
            }

            p.m_position = b.contentStart;
        }


        // Unordered list
        if ((ch == '*' || ch == '+' || ch == '-') && is_linespace(p.CharAtOffset(1))) {
            // Skip it
            p.SkipForward(1);
            p.SkipLinespace();
            b.contentStart = p.m_position;
            return BlockType_ul_li;
        }

        // Definition
        if (ch == ':' && this.m_Markdown.ExtraMode && is_linespace(p.CharAtOffset(1))) {
            p.SkipForward(1);
            p.SkipLinespace();
            b.contentStart = p.m_position;
            return BlockType_dd;
        }

        // Ordered list
        if (is_digit(ch)) {
            // Ordered list?  A line starting with one or more digits, followed by a '.' and a space or tab

            // Skip all digits
            p.SkipForward(1);
            while (is_digit(p.current()))
                p.SkipForward(1);

            if (p.SkipChar('.') && p.SkipLinespace()) {
                b.contentStart = p.m_position;
                return BlockType_ol_li;
            }

            p.m_position = b.contentStart;
        }

        // Reference link definition?
        if (ch == '[') {
            // Footnote definition?
            if (this.m_Markdown.ExtraMode && p.CharAtOffset(1) == '^') {
                var savepos = p.m_position;

                p.SkipForward(2);

                var id = p.SkipFootnoteID();
                if (id != null && p.SkipChar(']') && p.SkipChar(':')) {
                    p.SkipLinespace();
                    b.contentStart = p.m_position;
                    b.data = id;
                    return BlockType_footnote;
                }

                p.m_position = savepos;
            }

            // Parse a link definition
            var l = ParseLinkDefinition(p, this.m_Markdown.ExtraMode);
            if (l != null) {
                this.m_Markdown.AddLinkDefinition(l);
                return BlockType_Blank;
            }
        }

        // Nothing special
        return BlockType_p;
    }

    var MarkdownInHtmlMode_NA = 0;
    var MarkdownInHtmlMode_Block = 1;
    var MarkdownInHtmlMode_Span = 2;
    var MarkdownInHtmlMode_Deep = 3;
    var MarkdownInHtmlMode_Off = 4;

    p.GetMarkdownMode = function (tag) {
        // Get the markdown attribute
        var md = tag.attributes["markdown"];
        if (md == undefined) {
            if (this.m_bMarkdownInHtml)
                return MarkdownInHtmlMode_Deep;
            else
                return MarkdownInHtmlMode_NA;
        }

        // Remove it
        delete tag.attributes["markdown"];

        // Parse mode
        if (md == "1")
            return (tag.get_Flags() & HtmlTagFlags_ContentAsSpan) != 0 ? MarkdownInHtmlMode_Span : MarkdownInHtmlMode_Block;

        if (md == "block")
            return MarkdownInHtmlMode_Block;

        if (md == "deep")
            return MarkdownInHtmlMode_Deep;

        if (md == "span")
            return MarkdownInHtmlMode_Span;

        return MarkdownInHtmlMode_Off;
    }

    p.ProcessMarkdownEnabledHtml = function (p, b, openingTag, mode) {
        // Current position is just after the opening tag

        // Scan until we find matching closing tag
        var inner_pos = p.m_position;
        var depth = 1;
        var bHasUnsafeContent = false;
        while (!p.eof()) {
            // Find next angle bracket
            if (!p.Find('<'))
                break;

            // Is it a html tag?
            var tagpos = p.m_position;
            var tag = ParseHtmlTag(p);
            if (tag == null) {
                // Nope, skip it 
                p.SkipForward(1);
                continue;
            }

            // In markdown off mode, we need to check for unsafe tags
            if (this.m_Markdown.SafeMode && mode == MarkdownInHtmlMode_Off && !bHasUnsafeContent) {
                if (!tag.IsSafe())
                    bHasUnsafeContent = true;
            }

            // Ignore self closing tags
            if (tag.closed)
                continue;

            // Same tag?
            if (tag.name == openingTag.name) {
                if (tag.closing) {
                    depth--;
                    if (depth == 0) {
                        // End of tag?
                        p.SkipLinespace();
                        p.SkipEol();

                        b.blockType = BlockType_HtmlTag;
                        b.data = openingTag;
                        b.set_contentEnd(p.m_position);

                        switch (mode) {
                            case MarkdownInHtmlMode_Span:
                                var span = this.CreateBlock(inner_pos);
                                span.buf = p.buf;
                                span.blockType = BlockType_span;
                                span.contentStart = inner_pos;
                                span.contentLen = tagpos - inner_pos;

                                b.children = [];
                                b.children.push(span);
                                break;

                            case MarkdownInHtmlMode_Block:
                            case MarkdownInHtmlMode_Deep:
                                // Scan the internal content
                                var bp = new BlockProcessor(this.m_Markdown, mode == MarkdownInHtmlMode_Deep);
                                b.children = bp.ProcessRange(p.buf, inner_pos, tagpos - inner_pos);
                                break;

                            case MarkdownInHtmlMode_Off:
                                if (bHasUnsafeContent) {
                                    b.blockType = BlockType_unsafe_html;
                                    b.set_contentEnd(p.m_position);
                                }
                                else {
                                    var span = this.CreateBlock(inner_pos);
                                    span.buf = p.buf;
                                    span.blockType = BlockType_html;
                                    span.contentStart = inner_pos;
                                    span.contentLen = tagpos - inner_pos;

                                    b.children = [];
                                    b.children.push(span);
                                }
                                break;
                        }


                        return true;
                    }
                }
                else {
                    depth++;
                }
            }
        }

        // Missing closing tag(s).  
        return false;
    }

    p.ScanHtml = function (p, b) {
        // Remember start of html
        var posStartPiece = p.m_position;

        // Parse a HTML tag
        var openingTag = ParseHtmlTag(p);
        if (openingTag == null)
            return false;

        // Closing tag?
        if (openingTag.closing)
            return false;

        // Safe mode?
        var bHasUnsafeContent = false;
        if (this.m_Markdown.SafeMode && !openingTag.IsSafe())
            bHasUnsafeContent = true;

        var flags = openingTag.get_Flags();

        // Is it a block level tag?
        if ((flags & HtmlTagFlags_Block) == 0)
            return false;

        // Closed tag, hr or comment?
        if ((flags & HtmlTagFlags_NoClosing) != 0 || openingTag.closed) {
            p.SkipLinespace();
            p.SkipEol();
            b.contentLen = p.m_position - b.contentStart;
            b.blockType = bHasUnsafeContent ? BlockType_unsafe_html : BlockType_html;
            return true;
        }

        // Can it also be an inline tag?
        if ((flags & HtmlTagFlags_Inline) != 0) {
            // Yes, opening tag must be on a line by itself
            p.SkipLinespace();
            if (!p.eol())
                return false;
        }

        // Head block extraction?
        var bHeadBlock = this.m_Markdown.ExtractHeadBlocks && openingTag.name.toLowerCase() == "head";
        var headStart = p.m_position;

        // Work out the markdown mode for this element
        if (!bHeadBlock && this.m_Markdown.ExtraMode) {
            var MarkdownMode = this.GetMarkdownMode(openingTag);
            if (MarkdownMode != MarkdownInHtmlMode_NA) {
                return this.ProcessMarkdownEnabledHtml(p, b, openingTag, MarkdownMode);
            }
        }

        var childBlocks = null;

        // Now capture everything up to the closing tag and put it all in a single HTML block
        var depth = 1;

        while (!p.eof()) {
            if (!p.Find('<'))
                break;

            // Save position of current tag
            var posStartCurrentTag = p.m_position;

            var tag = ParseHtmlTag(p);
            if (tag == null) {
                p.SkipForward(1);
                continue;
            }

            // Safe mode checks
            if (this.m_Markdown.SafeMode && !tag.IsSafe())
                bHasUnsafeContent = true;


            // Ignore self closing tags
            if (tag.closed)
                continue;

            // Markdown enabled content?
            if (!bHeadBlock && !tag.closing && this.m_Markdown.ExtraMode && !bHasUnsafeContent) {
                var MarkdownMode = this.GetMarkdownMode(tag);
                if (MarkdownMode != MarkdownInHtmlMode_NA) {
                    var markdownBlock = this.CreateBlock(posStartPiece);
                    if (this.ProcessMarkdownEnabledHtml(p, markdownBlock, tag, MarkdownMode)) {
                        if (childBlocks == null) {
                            childBlocks = [];
                        }

                        // Create a block for everything before the markdown tag
                        if (posStartCurrentTag > posStartPiece) {
                            var htmlBlock = this.CreateBlock(posStartPiece);
                            htmlBlock.buf = p.buf;
                            htmlBlock.blockType = BlockType_html;
                            htmlBlock.contentStart = posStartPiece;
                            htmlBlock.contentLen = posStartCurrentTag - posStartPiece;

                            childBlocks.push(htmlBlock);
                        }

                        // Add the markdown enabled child block
                        childBlocks.push(markdownBlock);

                        // Remember start of the next piece
                        posStartPiece = p.m_position;

                        continue;
                    }
                    else {
                        this.FreeBlock(markdownBlock);
                    }
                }
            }

            // Same tag?
            if (tag.name == openingTag.name && !tag.closed) {
                if (tag.closing) {
                    depth--;
                    if (depth == 0) {
                        // End of tag?
                        p.SkipLinespace();
                        p.SkipEol();

                        // If anything unsafe detected, just encode the whole block
                        if (bHasUnsafeContent) {
                            b.blockType = BlockType_unsafe_html;
                            b.set_contentEnd(p.m_position);
                            return true;
                        }

                        // Did we create any child blocks
                        if (childBlocks != null) {
                            // Create a block for the remainder
                            if (p.m_position > posStartPiece) {
                                var htmlBlock = this.CreateBlock(posStartPiece);
                                htmlBlock.buf = p.buf;
                                htmlBlock.blockType = BlockType_html;
                                htmlBlock.contentStart = posStartPiece;
                                htmlBlock.contentLen = p.m_position - posStartPiece;

                                childBlocks.push(htmlBlock);
                            }

                            // Return a composite block
                            b.blockType = BlockType_Composite;
                            b.set_contentEnd(p.m_position);
                            b.children = childBlocks;
                            return true;
                        }

                        // Extract the head block content
                        if (bHeadBlock) {
                            var content = p.buf.substr(headStart, posStartCurrentTag - headStart);
                            this.m_Markdown.HeadBlockContent = this.m_Markdown.HeadBlockContent + Trim(content) + "\n";
                            b.blockType = BlockType_html;
                            b.contentStart = p.position;
                            b.contentEnd = p.position;
                            b.lineStart = p.position;
                            return true;
                        }

                        // Straight html block
                        b.blockType = BlockType_html;
                        b.contentLen = p.m_position - b.contentStart;
                        return true;
                    }
                }
                else {
                    depth++;
                }
            }
        }

        // Missing closing tag(s).  
        return BlockType_Blank;
    }

    /* 
    * BuildList - build a single <ol> or <ul> list
    */
    p.BuildList = function (lines) {
        // What sort of list are we dealing with
        var listType = lines[0].blockType;

        // Preprocess
        // 1. Collapse all plain lines (ie: handle hardwrapped lines)
        // 2. Promote any unindented lines that have more leading space 
        //    than the original list item to indented, including leading 
        //    special chars
        var leadingSpace = lines[0].get_leadingSpaces();
        for (var i = 1; i < lines.length; i++) {
            // Join plain paragraphs
            if ((lines[i].blockType == BlockType_p) &&
				(lines[i - 1].blockType == BlockType_p || lines[i - 1].blockType == listType)) {
                lines[i - 1].set_contentEnd(lines[i].get_contentEnd());
                this.FreeBlock(lines[i]);
                lines.splice(i, 1);
                i--;
                continue;
            }

            if (lines[i].blockType != BlockType_indent && lines[i].blockType != BlockType_Blank) {
                var thisLeadingSpace = lines[i].get_leadingSpaces();
                if (thisLeadingSpace > leadingSpace) {
                    // Change line to indented, including original leading chars 
                    // (eg: '* ', '>', '1.' etc...)
                    lines[i].blockType = BlockType_indent;
                    var saveend = lines[i].get_contentEnd();
                    lines[i].contentStart = lines[i].lineStart + thisLeadingSpace;
                    lines[i].set_contentEnd(saveend);
                }
            }
        }


        // Create the wrapping list item
        var List = this.CreateBlock(0);
        List.blockType = (listType == BlockType_ul_li ? BlockType_ul : BlockType_ol);
        List.children = [];

        // Process all lines in the range		
        for (var i = 0; i < lines.length; i++) {
            // Find start of item, including leading blanks
            var start_of_li = i;
            while (start_of_li > 0 && lines[start_of_li - 1].blockType == BlockType_Blank)
                start_of_li--;

            // Find end of the item, including trailing blanks
            var end_of_li = i;
            while (end_of_li < lines.length - 1 && lines[end_of_li + 1].blockType != listType)
                end_of_li++;

            // Is this a simple or complex list item?
            if (start_of_li == end_of_li) {
                // It's a simple, single line item item
                List.children.push(this.CreateBlock().CopyFrom(lines[i]));
            }
            else {
                // Build a new string containing all child items
                var bAnyBlanks = false;
                var sb = this.m_Markdown.GetStringBuilder();
                for (var j = start_of_li; j <= end_of_li; j++) {
                    var l = lines[j];
                    sb.Append(l.buf.substr(l.contentStart, l.contentLen));
                    sb.Append('\n');

                    if (lines[j].blockType == BlockType_Blank) {
                        bAnyBlanks = true;
                    }
                }

                // Create the item and process child blocks
                var item = this.CreateBlock();
                item.blockType = BlockType_li;
                item.lineStart = lines[start_of_li].lineStart;
                var bp = new BlockProcessor(this.m_Markdown);
                bp.m_parentType = listType;
                item.children = bp.Process(sb.ToString());

                // If no blank lines, change all contained paragraphs to plain text
                if (!bAnyBlanks) {
                    for (var j = 0; j < item.children.length; j++) {
                        var child = item.children[j];
                        if (child.blockType == BlockType_p) {
                            child.blockType = BlockType_span;
                        }
                    }
                }

                // Add the complex item
                List.children.push(item);
            }

            // Continue processing from end of li
            i = end_of_li;
        }

        List.lineStart = List.children[0].lineStart;

        this.FreeBlocks(lines);
        lines.length = 0;

        // Continue processing after this item
        return List;
    }

    /* 
    * BuildDefinition - build a single <dd> item
    */
    p.BuildDefinition = function (lines) {
        // Collapse all plain lines (ie: handle hardwrapped lines)
        for (var i = 1; i < lines.length; i++) {
            // Join plain paragraphs
            if ((lines[i].blockType == BlockType_p) &&
				(lines[i - 1].blockType == BlockType_p || lines[i - 1].blockType == BlockType_dd)) {
                lines[i - 1].set_contentEnd(lines[i].get_contentEnd());
                this.FreeBlock(lines[i]);
                lines.splice(i, 1);
                i--;
                continue;
            }
        }

        // Single line definition
        var bPreceededByBlank = lines[0].data;
        if (lines.length == 1 && !bPreceededByBlank) {
            var ret = lines[0];
            lines.length = 0;
            return ret;
        }

        // Build a new string containing all child items
        var sb = this.m_Markdown.GetStringBuilder();
        for (var i = 0; i < lines.length; i++) {
            var l = lines[i];
            sb.Append(l.buf.substr(l.contentStart, l.contentLen));
            sb.Append('\n');
        }

        // Create the item and process child blocks
        var item = this.CreateBlock(lines[0].lineStart);
        item.blockType = BlockType_dd;
        var bp = new BlockProcessor(this.m_Markdown);
        bp.m_parentType = BlockType_dd;
        item.children = bp.Process(sb.ToString());

        this.FreeBlocks(lines);
        lines.length = 0;

        // Continue processing after this item
        return item;
    }

    p.BuildDefinitionLists = function (blocks) {
        var currentList = null;
        for (var i = 0; i < blocks.length; i++) {
            switch (blocks[i].blockType) {
                case BlockType_dt:
                case BlockType_dd:
                    if (currentList == null) {
                        currentList = this.CreateBlock(blocks[i].lineStart);
                        currentList.blockType = BlockType_dl;
                        currentList.children = [];
                        blocks.splice(i, 0, currentList);
                        i++;
                    }

                    currentList.children.push(blocks[i]);
                    blocks.splice(i, 1);
                    i--;
                    break;

                default:
                    currentList = null;
                    break;
            }
        }
    }


    p.BuildFootnote = function (lines) {
        // Collapse all plain lines (ie: handle hardwrapped lines)
        for (var i = 1; i < lines.length; i++) {
            // Join plain paragraphs
            if ((lines[i].blockType == BlockType_p) &&
				(lines[i - 1].blockType == BlockType_p || lines[i - 1].blockType == BlockType_footnote)) {
                lines[i - 1].set_contentEnd(lines[i].get_contentEnd());
                this.FreeBlock(lines[i]);
                lines.splice(i, 1);
                i--;
                continue;
            }
        }

        // Build a new string containing all child items
        var sb = this.m_Markdown.GetStringBuilder();
        for (var i = 0; i < lines.length; i++) {
            var l = lines[i];
            sb.Append(l.buf.substr(l.contentStart, l.contentLen));
            sb.Append('\n');
        }

        var bp = new BlockProcessor(this.m_Markdown);
        bp.m_parentType = BlockType_footnote;

        // Create the item and process child blocks
        var item = this.CreateBlock(lines[0].lineStart);
        item.blockType = BlockType_footnote;
        item.data = lines[0].data;
        item.children = bp.Process(sb.ToString());

        this.FreeBlocks(lines);
        lines.length = 0;

        // Continue processing after this item
        return item;
    }


    p.ProcessFencedCodeBlock = function (p, b) {
        var fenceStart = p.m_position;

        // Extract the fence
        p.Mark();
        while (p.current() == '~')
            p.SkipForward(1);
        var strFence = p.Extract();

        // Must be at least 3 long
        if (strFence.length < 3)
            return false;

        // Rest of line must be blank
        p.SkipLinespace();
        if (!p.eol())
            return false;

        // Skip the eol and remember start of code
        p.SkipEol();
        var startCode = p.m_position;

        // Find the end fence
        if (!p.Find(strFence))
            return false;

        // Character before must be a eol char
        if (!is_lineend(p.CharAtOffset(-1)))
            return false;

        var endCode = p.m_position;

        // Skip the fence
        p.SkipForward(strFence.length);

        // Whitespace allowed at end
        p.SkipLinespace();
        if (!p.eol())
            return false;

        // Create the code block
        b.blockType = BlockType_codeblock;
        b.children = [];

        // Remove the trailing line end
        // (Javascript version has already normalized line ends to \n)
        endCode--;

        // Create the child block with the entire content
        var child = this.CreateBlock(fenceStart);
        child.blockType = BlockType_indent;
        child.buf = p.buf;
        child.contentStart = startCode;
        child.contentLen = endCode - startCode;
        b.children.push(child);

        // Done
        return true;
    }


    var ColumnAlignment_NA = 0;
    var ColumnAlignment_Left = 1;
    var ColumnAlignment_Right = 2;
    var ColumnAlignment_Center = 3;

    function TableSpec() {
        this.m_Columns = [];
        this.m_Headers = null;
        this.m_Rows = [];
    }

    p = TableSpec.prototype;

    p.LeadingBar = false;
    p.TrailingBar = false;

    p.ParseRow = function (p) {
        p.SkipLinespace();

        if (p.eol())
            return null; 	// Blank line ends the table

        var bAnyBars = this.LeadingBar;
        if (this.LeadingBar && !p.SkipChar('|')) {
            bAnyBars = true;
            return null;
        }

        // Create the row
        var row = [];

        // Parse all columns except the last

        while (!p.eol()) {
            // Find the next vertical bar
            p.Mark();
            while (!p.eol() && p.current() != '|')
                p.SkipForward(1);

            row.push(Trim(p.Extract()));

            bAnyBars |= p.SkipChar('|');
        }

        // Require at least one bar to continue the table
        if (!bAnyBars)
            return null;

        // Add missing columns
        while (row.length < this.m_Columns.length) {
            row.push("&nbsp;");
        }

        p.SkipEol();
        return row;
    }

    p.RenderRow = function (m, b, row, type) {
        for (var i = 0; i < row.length; i++) {
            b.Append("\t<");
            b.Append(type);

            if (i < this.m_Columns.length) {
                switch (this.m_Columns[i]) {
                    case ColumnAlignment_Left:
                        b.Append(" align=\"left\"");
                        break;
                    case ColumnAlignment_Right:
                        b.Append(" align=\"right\"");
                        break;
                    case ColumnAlignment_Center:
                        b.Append(" align=\"center\"");
                        break;
                }
            }

            b.Append(">");
            m.m_SpanFormatter.Format2(b, row[i]);
            b.Append("</");
            b.Append(type);
            b.Append(">\n");
        }
    }

    p.Render = function (m, b) {
        b.Append("<table>\n");
        if (this.m_Headers != null) {
            b.Append("<thead>\n<tr>\n");
            this.RenderRow(m, b, this.m_Headers, "th");
            b.Append("</tr>\n</thead>\n");
        }

        b.Append("<tbody>\n");
        for (var i = 0; i < this.m_Rows.length; i++) {
            var row = this.m_Rows[i];
            b.Append("<tr>\n");
            this.RenderRow(m, b, row, "td");
            b.Append("</tr>\n");
        }
        b.Append("</tbody>\n");

        b.Append("</table>\n");
    }

    function TableSpec_Parse(p) {
        // Leading line space allowed
        p.SkipLinespace();

        // Quick check for typical case
        if (p.current() != '|' && p.current() != ':' && p.current() != '-')
            return null;

        // Don't create the spec until it at least looks like one
        var spec = null;

        // Leading bar, looks like a table spec
        if (p.SkipChar('|')) {
            spec = new TableSpec();
            spec.LeadingBar = true;
        }


        // Process all columns
        while (true) {
            // Parse column spec
            p.SkipLinespace();

            // Must have something in the spec
            if (p.current() == '|')
                return null;

            var AlignLeft = p.SkipChar(':');
            while (p.current() == '-')
                p.SkipForward(1);
            var AlignRight = p.SkipChar(':');
            p.SkipLinespace();

            // Work out column alignment
            var col = ColumnAlignment_NA;
            if (AlignLeft && AlignRight)
                col = ColumnAlignment_Center;
            else if (AlignLeft)
                col = ColumnAlignment_Left;
            else if (AlignRight)
                col = ColumnAlignment_Right;

            if (p.eol()) {
                // Not a spec?
                if (spec == null)
                    return null;

                // Add the final spec?
                spec.m_Columns.push(col);
                return spec;
            }

            // We expect a vertical bar
            if (!p.SkipChar('|'))
                return null;

            // Create the table spec
            if (spec == null)
                spec = new TableSpec();

            // Add the column
            spec.m_Columns.push(col);

            // Check for trailing vertical bar
            p.SkipLinespace();
            if (p.eol()) {
                spec.TrailingBar = true;
                return spec;
            }

            // Next column
        }
    }

    // Exposed stuff
    this.Markdown = Markdown;
    this.HtmlTag = HtmlTag;
} ();


// 
//! MarkdownDeep - http://www.toptensoftware.com/markdowndeep
//! Copyright (C) 2010-2011 Topten Software
// 
//   Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in 
//   compliance with the License. You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software distributed under the License is 
//   distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
//   See the License for the specific language governing permissions and limitations under the License.
//

/*
Usage:

// 1. Create the editor an bind to a text area, output div and an optional source view div
        - text area: the text area that user types to.
        - output div: a div where the transformed html will be displayed
        - source view view: an optional div where a "source" view of the rendered html will be placed
      
    var editor=new MarkdownDeepEditor.Editor(textarea_element, output_div, source_div)
    
// 2. Optionally set options

        editor.disableShortCutKeys=true;    // Disable Ctrl+B, Ctrl+I etc...
        editor.disableAutoIndent=true;      // Disable auto indent on enter key
        editor.disableTabHandling=true;     // Disable tab/shift+tab for indent
    
// 3. Optionally install hooks
    
        editor.onPreTransform=function(editor, markdown) {}
        editor.onPostTransform=function(editor, html) {}
        editor.onPostUpdateDom=function(editor) {}

// 4. Optionally create a toolbar/UI that calls editor.InvokeCommand(cmd) where cmd is one of:        

        - "undo",
        - "redo",
        - "bold",
        - "italic",
        - "heading",
        - "code",
        - "ullist",
        - "ollist",
        - "indent",
        - "outdent",
        - "link",
        - "img",
        - "hr",
        - "h0",
        - "h1",
        - "h2",
        - "h3",
        - "h4",
        - "h5",
        - "h6"
        
      eg: editor.InvokeCommand("heading") to toggle heading style of selection
    
*/

var MarkdownDeepEditor=new function(){

    // private:priv.
    // private:.m_*
    // private:.m_listType
    // private:.m_prefixLen
    

    var ie=false;
    
    // Various keycodes
    var keycode_tab = 9;
    var keycode_enter = 13;
    var keycode_pgup = 33;
    var keycode_pgdn = 34;
    var keycode_home = 36;
    var keycode_end = 35;
    var keycode_left = 37;
    var keycode_right = 39;
    var keycode_up = 38;
    var keycode_down = 40;
    var keycode_backspace = 8;
    var keycode_delete = 46;
    
    // Undo modes for the undo stack
    var undomode_unknown = 0;
    var undomode_text = 1;
    var undomode_erase = 2;
    var undomode_navigate = 3;
    var undomode_whitespace = 4;
    
    // Shortcut keys Ctrl+key
    var shortcut_keys={
        "Z": "undo",
        "Y": "redo",
        "B": "bold",
        "I": "italic",
        "H": "heading",
        "K": "code",
        "U": "ullist",
        "O": "ollist",
        "Q": "indent",
        "E": "outdent",
        "L": "link",
        "G": "img",
        "R": "hr",
        "0": "h0",
        "1": "h1",
        "2": "h2",
        "3": "h3",
        "4": "h4",
        "5": "h5",
        "6": "h6"
    }

    function starts_with(str, match)
    {
        return str.substr(0, match.length)==match;
    }
    
    function ends_with(str, match)
    {
        return str.substr(-match.length)==match;
    }

    function is_whitespace(ch)
    {
        return (ch==' ' || ch=='\t' || ch=='\r' || ch=='\n');
    }
    
    function is_crlf(ch)
    {
        return (ch=='\r' || ch=='\n');
    }
    
    function trim(str)
    {
        var i=0;
        var l=str.length;
        
        while (i<l && is_whitespace(str.charAt(i)))
            i++;
        while (l-1>i && is_whitespace(str.charAt(l-1)))
            l--;
            
        return str.substr(i, l-i);
    }


    // Helper for binding events
    function BindEvent(obj, event, handler)
    {
        if (obj.addEventListener)
        {
            obj.addEventListener(event, handler, false);
        }
        else if (obj.attachEvent)
        {
            obj.attachEvent("on"+event, handler);
        }
    }
    
    // Helper for unbinding events
    function UnbindEvent(obj, event, handler)
    {
        if (obj.removeEventListener)
        {
            obj.removeEventListener(event, handler, false);
        }
        else if (obj.detachEvent)
        {
            obj.detachEvent("on"+event, handler);
        }
    }
    
    function PreventEventDefault(event)
    {
        if (event.preventDefault)
        {
            event.preventDefault();
        }
        if (event.cancelBubble!==undefined)
        {
            event.cancelBubble=true;
            event.keyCode=0;
            event.returnValue=false;
        }
        return false;
    }
    
    function offsetToRangeCharacterMove(el, offset) 
    {
        return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
    }

    // EditorState represents the initial and final state of an edit
    function EditorState()
    {
    }

    priv=EditorState.prototype;

    priv.InitFromTextArea=function(textarea)
    {
        this.m_textarea=textarea;
        if (ie)
        {
            var sel=document.selection.createRange();
            var temp=sel.duplicate();
            temp.moveToElementText(textarea);
            var basepos=-temp.moveStart('character', -10000000);
            
            this.m_selectionStart = -sel.moveStart('character', -10000000)-basepos;
            this.m_selectionEnd = -sel.moveEnd('character', -10000000)-basepos;
            this.m_text=textarea.value.replace(/\r\n/gm,"\n");
        }
        else
        {
            this.m_selectionStart = textarea.selectionStart;
            this.m_selectionEnd = textarea.selectionEnd;
            this.m_text=textarea.value;
        }
    }
    
    priv.Duplicate=function()
    {
        var other=new EditorState();
        other.m_textarea=this.m_textarea;
        other.m_selectionEnd=this.m_selectionEnd;
        other.m_selectionStart=this.m_selectionStart;
        other.m_text=this.m_text;
        return other;
    }
    
    priv.Apply=function()
    {
        if (ie)
        {
            this.m_textarea.value=this.m_text;
            this.m_textarea.focus();
            var r=this.m_textarea.createTextRange();
            r.collapse(true);
            r.moveEnd("character", this.m_selectionEnd);
            r.moveStart("character", this.m_selectionStart);
            r.select();
        }
        else
        {
            // Set the new text 
            var scrollTop=this.m_textarea.scrollTop;
            this.m_textarea.value=this.m_text;
            this.m_textarea.focus();
            this.m_textarea.setSelectionRange(this.m_selectionStart, this.m_selectionEnd);
            this.m_textarea.scrollTop=scrollTop;
        }
    }
    
    priv.ReplaceSelection=function(str)
    {
        this.m_text=this.m_text.substr(0, this.m_selectionStart) + str + this.m_text.substr(this.m_selectionEnd);
        this.m_selectionEnd=this.m_selectionStart + str.length;
    }

    function adjust_pos(pos2, editpos, del, ins)
    {
        if (pos2<editpos)
            return pos2;
        return pos2<editpos+del ? editpos : pos2 + ins - del;
    }
    
    priv.ReplaceAt=function(pos, len, str)
    {
        this.m_text=this.m_text.substr(0, pos) + str + this.m_text.substr(pos+len);
        this.m_selectionStart=adjust_pos(this.m_selectionStart, pos, len, str.length);
        this.m_selectionEnd=adjust_pos(this.m_selectionEnd, pos, len, str.length);
    }
    
    priv.getSelectedText=function()
    {
        return this.m_text.substr(this.m_selectionStart, this.m_selectionEnd-this.m_selectionStart);
    }
    
    priv.InflateSelection=function(ds, de)
    {
        this.m_selectionEnd+=de;
        this.m_selectionStart-=ds;
    }
    
    priv.PreceededBy=function(str)
    {
        return this.m_selectionStart >= str.length && this.m_text.substr(this.m_selectionStart-str.length, str.length)==str;
    }
    
    priv.FollowedBy=function(str)
    {
        return this.m_text.substr(this.m_selectionEnd, str.length)==str;
    }
    
    priv.TrimSelection=function()
    {
        while (is_whitespace(this.m_text.charAt(this.m_selectionStart)))
            this.m_selectionStart++;
        while (this.m_selectionEnd>this.m_selectionStart && is_whitespace(this.m_text.charAt(this.m_selectionEnd-1)))
            this.m_selectionEnd--;
    }
    
    priv.IsStartOfLine=function(pos)
    {
        return pos==0 || is_crlf(this.m_text.charAt(pos-1));
    }
    
    priv.FindStartOfLine=function(pos)
    {
        // Move start of selection back to line start
        while (pos>0 && !is_crlf(this.m_text.charAt(pos-1)))
        {  
            pos--;
        }
        return pos;
    }    
    
    priv.FindEndOfLine=function(pos)
    {
        while (pos<this.m_text.length && !is_crlf(this.m_text.charAt(pos)))
        {
            pos++;
        }
        return pos;
    }
    
    priv.FindNextLine=function(pos)
    {
        return this.SkipEol(this.FindEndOfLine(pos));
    }
    
    priv.SkipWhiteSpace=function(pos)
    {
        while (pos<this.m_text.length && is_whitespace(this.m_text.charAt(pos)))
            pos++;
        return pos;
    }
    
    priv.SkipEol=function(pos)
    {
        if (this.m_text.substr(pos, 2)=="\r\n")
            return pos+2;
        if (is_crlf(this.m_text.charAt(pos)))
            return pos+1;
        return pos;
    }
    
    priv.SkipPreceedingEol=function(pos)
    {
        if (pos>2 && this.m_text.substr(pos-2, 2)=="\r\n")
            return pos-2;
        if (pos>1 && is_crlf(this.m_text.charAt(pos-1)))
            return pos-1;
        return pos;
    }
    
    priv.SelectWholeLines=function()
    {
        // Move selection to start of line
        this.m_selectionStart=this.FindStartOfLine(this.m_selectionStart);
        
        // Move end of selection to start of the next line
        if (!this.IsStartOfLine(this.m_selectionEnd))
        {
            this.m_selectionEnd=this.SkipEol(this.FindEndOfLine(this.m_selectionEnd));
        }
    }
    
    priv.SkipPreceedingWhiteSpace=function(pos)
    {
        while (pos>0 && is_whitespace(this.m_text.charAt(pos-1)))
        {
            pos--;
        }
        return pos;
    }
    
    priv.SkipFollowingWhiteSpace=function(pos)
    {
        while (is_whitespace(this.m_text.charAt(pos)))
        {
            pos++;
        }
        return pos;
    }
    priv.SelectSurroundingWhiteSpace=function()
    {
        this.m_selectionStart=this.SkipPreceedingWhiteSpace(this.m_selectionStart);
        this.m_selectionEnd=this.SkipFollowingWhiteSpace(this.m_selectionEnd);
    }
    
    priv.CheckSimpleSelection=function()
    {
        var text=this.getSelectedText();
        var m=text.match(/\n[ \t\r]*\n/);
        
        if (m)
        {
            alert("Please make a selection that doesn't include a paragraph break");
            return false;
        }
        
        return true;
    }

    // Check if line is completely blank    
    priv.IsBlankLine=function(p)
    {
        var len=this.m_text.length;
        for (var i=p; i<len; i++)
        {
            var ch=this.m_text[i];
            if (is_crlf(ch))
                return true;
            if (!is_whitespace(this.m_text.charAt(i)))
                return false;
        }
        
        return true;
    }
    
    priv.FindStartOfParagraph=function(pos)
    {
        var savepos=pos;
        
        // Move to start of first line
        pos=this.FindStartOfLine(pos);
        
        if (this.IsBlankLine(pos))
            return pos;

        // Move to first line after blank line
        while (pos>0)
        {
            var p=this.FindStartOfLine(this.SkipPreceedingEol(pos));
            if (p==0)
                break;
            if (this.IsBlankLine(p))
                break;
            pos=p;
        }
        
        // Is it a list?
        if (this.DetectListType(pos).m_prefixLen!=0)
        {
            // Do it again, but stop at line with list prefix
            pos=this.FindStartOfLine(savepos);
            
            // Move to first line after blank line
            while (pos>0)
            {
                if (this.DetectListType(pos).m_prefixLen!=0)
                    return pos;
                    
                // go to line before
                pos=this.FindStartOfLine(this.SkipPreceedingEol(pos));
            }
        }
        
        return pos;
    }
    
    priv.FindEndOfParagraph=function(pos)
    {
        // Skip all lines that aren't blank
        while (pos<this.m_text.length)
        {
            if (this.IsBlankLine(pos))
                break;
                
            pos=this.FindNextLine(pos);
        }
        
        return pos;
    }
    
    // Select the paragraph
    priv.SelectParagraph=function()
    {
        this.m_selectionStart=this.FindStartOfParagraph(this.m_selectionStart);
        this.m_selectionEnd=this.FindEndOfParagraph(this.m_selectionStart);        
    }
    
    // Starting at position pos, return the list type
    // returns { m_listType, m_prefixLen } 
    priv.DetectListType=function(pos)
    {
        var prefix=this.m_text.substr(pos, 10);
        var m=prefix.match(/^\s{0,3}(\*|\d+\.)(?:\ |\t)*/);
        if (!m)
            return {m_listType:"", m_prefixLen:0};
            
        if (m[1]=='*')
            return {m_listType:"*", m_prefixLen:m[0].length};
        else
            return {m_listType:"1", m_prefixLen:m[0].length};
    }
    
    
    

    // Editor
    function Editor(textarea, div_html)
    {
        // Is it IE?
        if (!textarea.setSelectionRange)
        {
            ie=true;
        }
    
        // Initialize
        this.m_lastContent=null;
        this.m_undoStack=[];
        this.m_undoPos=0;
        this.m_undoMode=undomode_navigate;
        this.Markdown=new MarkdownDeep.Markdown();
        this.Markdown.SafeMode=false;
        this.Markdown.ExtraMode=true;
        this.Markdown.NewWindowForLocalLinks=true;
        this.Markdown.NewWindowForExternalLinks=true;
        
        // Store DOM elements
        this.m_textarea=textarea;
        this.m_divHtml=div_html;

        // Bind events
        var ed=this;
        BindEvent(textarea, "keyup", function(){ed.onMarkdownChanged();});
        BindEvent(textarea, "keydown", function(e){return ed.onKeyDown(e);});
        BindEvent(textarea, "paste", function(){ed.onMarkdownChanged();});
        BindEvent(textarea, "input", function(){ed.onMarkdownChanged();});
        BindEvent(textarea, "mousedown", function(){ed.SetUndoMode(undomode_navigate);});

        // Do initial update
        this.onMarkdownChanged();
    }

    var priv=Editor.prototype;
    var pub=Editor.prototype;
    
    
    priv.onKeyDown=function(e)
    {
        var newMode=null;
        var retv=true;
        
        // Normal keys only
        if(e.ctrlKey || e.metaKey)
        {
            var key=String.fromCharCode(e.charCode||e.keyCode);
                    
            // Built in short cut key?
            if (!this.disableShortCutKeys && shortcut_keys[key]!=undefined)
            {
                this.InvokeCommand(shortcut_keys[key]);
                return PreventEventDefault(e);
            }
            
            // Standard keys
            switch (key)
            {
                case "V":   // Paste
                    newMode=undomode_text;
                    break;
                    
                case "X":   // Cut
                    newMode=undomode_erase;
                    break;
            }
        }
        else
        {
            switch (e.keyCode)
            {
                case keycode_tab:
                    if (!this.disableTabHandling)
                    {
                        this.InvokeCommand(e.shiftKey ? "untab" : "tab");
                        return PreventEventDefault(e);
                    }
                    else
                    {
                        newMode=undomode_text;
                    }
                    break;
                        
            
                case keycode_left:
                case keycode_right:
                case keycode_up:
                case keycode_down:
                case keycode_home:
                case keycode_end:
                case keycode_pgup:
                case keycode_pgdn:
                    // Navigation mode
                    newMode=undomode_navigate;
                    break;

                case keycode_backspace:
                case keycode_delete:
                    // Delete mode
                    newMode=undomode_erase;
                    break;
                
                case keycode_enter:
                    // New lines mode
                    newMode=undomode_whitespace;
                    break;
                
                default:
                    // Text mode
                    newMode=undomode_text;
            }
        }

        if (newMode!=null)
            this.SetUndoMode(newMode);

        // Special handling for enter key
        if (!this.disableAutoIndent)
        {
            if (e.keyCode==keycode_enter && (!ie || e.ctrlKey))
            {
                this.IndentNewLine();
            }
        }
    } 

    priv.SetUndoMode=function(newMode)
    {
        // Same mode?
        if (this.m_undoMode==newMode)
            return;
            
        // Enter new mode, after capturing current state
        this.m_undoMode=newMode;
        
        // Capture undo state
        this.CaptureUndoState();
    }

    
    priv.CaptureUndoState=function()
    {
        // Store a copy on the undo stack
        var state=new EditorState();
        state.InitFromTextArea(this.m_textarea);
        this.m_undoStack.splice(this.m_undoPos, this.m_undoStack.length-this.m_undoPos, state);        
        this.m_undoPos=this.m_undoStack.length;
    }
    
    priv.onMarkdownChanged=function(bCreateUndoUnit)
    {
        // Get the markdown, see if it's changed
        var new_content=this.m_textarea.value;
        if (new_content===this.m_lastContent && this.m_lastContent!==null)
	        return;
	        
    	// Call pre hook
    	if (this.onPreTransform)
    	    this.onPreTransform(this, new_content);
    
        // Transform
        var output=this.Markdown.Transform(new_content);

        // Call post hook
    	if (this.onPostTransform)
    	    this.onPostTransform(this, output);

    	// Update the DOM
        if (this.m_divHtml)
            this.m_divHtml.innerHTML=output;
            /*
        if (this.m_divSource)
        {
            this.m_divSource.innerHTML="";
            this.m_divSource.appendChild(document.createTextNode(output));
        }
        */
        
        // Call post update dom handler
        if (this.onPostUpdateDom)
            this.onPostUpdateDom(this);

        // Save previous content
        this.m_lastContent=new_content;
    }

    // Public method, should be called by client code if any of the MarkdownDeep
    // transform options have changed
    pub.onOptionsChanged=function()
    {
        this.m_lastContent=null;
        this.onMarkdownChanged();
    }
    
    pub.cmd_undo=function()
    {
        if (this.m_undoPos > 0)
        {
            // Capture current state at end of undo buffer.
            if (this.m_undoPos==this.m_undoStack.length)
            {
                this.CaptureUndoState();
                this.m_undoPos--;
            }

            this.m_undoPos--;
            this.m_undoStack[this.m_undoPos].Apply();
            this.m_undoMode=undomode_unknown;

            // Update markdown rendering
            this.onMarkdownChanged();
        }
    }
    
    pub.cmd_redo=function()
    {
        if (this.m_undoPos+1 < this.m_undoStack.length)
        {
            this.m_undoPos++;
            this.m_undoStack[this.m_undoPos].Apply();
            this.m_undoMode=undomode_unknown;

            // Update markdown rendering
            this.onMarkdownChanged();

            // We're back at the current state            
            if (this.m_undoPos==this.m_undoStack.length-1)
            {
                this.m_undoStack.pop();
            }
        }
    }
    
    priv.setHeadingLevel=function(state, headingLevel)
    {
        // Select the entire heading
        state.SelectParagraph();
        state.SelectSurroundingWhiteSpace();
        
        // Get the selected text
        var text=state.getSelectedText();
        
        // Trim all whitespace
        text=trim(text);

        var currentHeadingLevel=0;
        var m=text.match(/^(\#+)(.*?)(\#+)?$/);
        if (m)
        {
            text=trim(m[2]);
            currentHeadingLevel=m[1].length;
        }
        else
        {
            m=text.match(/^(.*?)(?:\r\n|\n|\r)\s*(\-*|\=*)$/);
            if (m)
            {
                text=trim(m[1]);
                currentHeadingLevel=m[2].charAt(0)=="=" ? 1 : 0;
            }
            else
            {
                // Remove blank lines        
                text=text.replace(/(\r\n|\n|\r)/gm,"");
                currentHeadingLevel=0;
            }
        }
        
        if (headingLevel==-1)
            headingLevel=(currentHeadingLevel+1) % 4;
        
        // Removing a heading
        var selOffset=0;
        var selLen=0;
        if (headingLevel==0)
        {
            // Deleting selection
            if (text=="Heading")
            {
                state.ReplaceSelection("");
                return true;
            }
            
            selLen=text.length;
            selOffset=0;
        }
        else
        {
            if (text=="")
                text="Heading";

            selOffset=headingLevel+1;
            selLen=text.length;
                
            var h="";
            for (var i=0; i<headingLevel; i++)
                h+="#";
                
            text=h + " " + text + " " + h;
            
        }
        
        // Require blank after
        text+="\n\n";

        if (state.m_selectionStart!=0)
        {
            text="\n\n" + text;
            selOffset+=2;
        }

        // Replace text
        state.ReplaceSelection(text);
        
        // Update selection
        state.m_selectionStart+=selOffset;
        state.m_selectionEnd=state.m_selectionStart + selLen;

        return true;
    }
    
    pub.cmd_heading=function(state)
    {
        return this.setHeadingLevel(state, -1);
    }
    
    pub.cmd_h0=function(state)
    {
        return this.setHeadingLevel(state, 0);
    }

    pub.cmd_h1=function(state)
    {
        return this.setHeadingLevel(state, 1);
    }

    pub.cmd_h2=function(state)
    {
        return this.setHeadingLevel(state, 2);
    }

    pub.cmd_h3=function(state)
    {
        return this.setHeadingLevel(state, 3);
    }

    pub.cmd_h4=function(state)
    {
        return this.setHeadingLevel(state, 4);
    }

    pub.cmd_h5=function(state)
    {
        return this.setHeadingLevel(state, 5);
    }

    pub.cmd_h6=function(state)
    {
        return this.setHeadingLevel(state, 6);
    }

    priv.IndentCodeBlock=function(state, indent)
    {
        // Make sure whole lines are selected
        state.SelectWholeLines();
        
        // Get the text, split into lines 
        var lines=state.getSelectedText().split("\n");
        
		// Convert leading tabs to spaces   
        for (var i=0; i<lines.length; i++)
        {
			if (lines[i].charAt(0)=="\t")
			{
				var newLead="";
				var p=0;
				while (lines[i].charAt(p)=="\t")
				{
					newLead+="    ";
					p++;
				}
				
				var newLine=newLead + lines[i].substr(p);
				lines.splice(i, 1, newLine);
			}
        }

        // Toggle indent/unindent?
        if (indent===null)
        {
            var i;
            for (i=0; i<lines.length; i++)
            {
                // Blank lines are allowed
                if (trim(lines[i])=="")
                    continue;
                 
				// Convert leading tabs to spaces   
				if (lines[i].charAt(0)=="\t")
				{
					var newLead="";
					var p=0;
					while (lines[i].charAt(p)=="\t")
					{
						newLead+="    ";
						p++;
					}
					
					var newLine=newLead + lines[i].substr(i);
					lines.splice(i, 1, newLine);
				}
					
                // Tabbed line
                if (!starts_with(lines[i], "    "))
                    break;
            }

            // Are we adding or removing indent
            indent=i!=lines.length;
        }
        
        // Apply the changes
        for (var i=0; i<lines.length; i++)
        {
            // Blank line?
            if (trim(lines[i])=="")
                continue;
                
            // Tabbed line
            var newline=lines[i];
            if (indent)
            {
                newline="    " + lines[i];
            }
            else
            {
                if (starts_with(lines[i], "\t"))
                    newline=lines[i].substr(1);
                else if (starts_with(lines[i], "    "))
                    newline=lines[i].substr(4);
            }
            
            lines.splice(i, 1, newline);
        }
        
        // Replace
        state.ReplaceSelection(lines.join("\n"));
    }
    
    // Code
    pub.cmd_code=function(state)
    {
        // Cursor on a blank line?
		if (state.m_selectionStart==state.m_selectionEnd)
		{
			var line=state.FindStartOfLine(state.m_selectionStart);
			if (state.IsBlankLine(line))
			{
				state.SelectSurroundingWhiteSpace();
				state.ReplaceSelection("\n\n    Code\n\n");
				state.m_selectionStart+=6;
				state.m_selectionEnd=state.m_selectionStart + 4;
				return true;
			}
		}       
	
        // If the current text is preceeded by a non-whitespace, or followed by a non-whitespace
        // then do an inline code
        if (state.getSelectedText().indexOf("\n")<0)
        {
            // Expand selection to include leading/trailing stars
            state.TrimSelection();
            if (state.PreceededBy("`"))
                state.m_selectionStart--;
            if (state.FollowedBy("`"))
                state.m_selectionEnd++;
            return this.bold_or_italic(state, "`");
        }
        
        this.IndentCodeBlock(state, null);    
        return true;        
    }
    
    pub.cmd_tab=function(state)
    {
        if (state.getSelectedText().indexOf("\n")>0)
        {
            this.IndentCodeBlock(state, true);
        }
        else
        {
            // If we're in the leading whitespace of a line
            // insert spaces instead of an actual tab character
            var lineStart=state.FindStartOfLine(state.m_selectionStart);
            var p;
            for (p=lineStart; p<state.m_selectionStart; p++)
            {
                if (state.m_text.charAt(p)!=' ')
                    break;
            }
            
            // All spaces?
            if (p==state.m_selectionStart)
            {
                var spacesToNextTabStop=4-((p-lineStart)%4);
                state.ReplaceSelection("    ".substr(0, spacesToNextTabStop));
            }
            else
            {  
                state.ReplaceSelection("\t");
            }
            state.m_selectionStart=state.m_selectionEnd;
        }
        return true;
    }
    
    pub.cmd_untab=function(state)
    {
        if (state.getSelectedText().indexOf("\n")>0)
        {
            this.IndentCodeBlock(state, false);
            return true;
        }
        return false;
    }
    
    priv.bold_or_italic=function(state, marker)
    {
        var t=state.m_text;
        var ml=marker.length;
        
        // Work out if we're adding or removing bold markers
        var text=state.getSelectedText();
        if (starts_with(text, marker) && ends_with(text, marker))
        {
            // Remove 
            state.ReplaceSelection(text.substr(ml, text.length-ml*2));
        }
        else
        {
            // Add
            state.TrimSelection();
            text=state.getSelectedText();
            if (!text)
                text="text";
            else
                text=text.replace(/(\r\n|\n|\r)/gm,"");
            state.ReplaceSelection(marker + text + marker);
            state.InflateSelection(-ml, -ml);
        }
        return true;
    }
    
    // Bold
    pub.cmd_bold=function(state)
    {
        if (!state.CheckSimpleSelection())
            return false;
        state.TrimSelection();
            
        // Expand selection to include leading/trailing stars
        if (state.PreceededBy("**"))
            state.m_selectionStart-=2;
        if (state.FollowedBy("**"))
            state.m_selectionEnd+=2;
            
        return this.bold_or_italic(state, "**");
    }
    
    // Italic
    pub.cmd_italic=function(state)
    {
        if (!state.CheckSimpleSelection())
            return false;
        state.TrimSelection();
            
        // Expand selection to include leading/trailing stars
        if ((state.PreceededBy("*") && !state.PreceededBy("**")) || state.PreceededBy("***"))
            state.m_selectionStart-=1;
        if ((state.FollowedBy("*") && !state.PreceededBy("**")) || state.FollowedBy("***"))
            state.m_selectionEnd+=1;
            
        return this.bold_or_italic(state, "*");
    }
    
    priv.indent_or_outdent=function(state, outdent)
    {
        if (false && state.m_selectionStart==state.m_selectionEnd)
        {
            state.SelectSurroundingWhiteSpace();
            state.ReplaceSelection("\n\n> Quote\n\n");
            state.m_selectionStart+=4;
            state.m_selectionEnd=state.m_selectionStart+5;
            return true;
        }
        
        // Make sure whole lines are selected
        state.SelectWholeLines();
        
        // Get the text, split into lines and check if all lines
        // are indented
        var lines=state.getSelectedText().split("\n");
        
        // Apply the changes
        for (var i=0; i<lines.length-1; i++)
        {
            // Tabbed line
            var newline=lines[i];
            if (outdent)
            {
                if (starts_with(lines[i], "> "))
                    newline=lines[i].substr(2);
            }
            else
            {
                newline="> " + lines[i];
            }
            
            lines.splice(i, 1, newline);
        }
        
        // Replace
        state.ReplaceSelection(lines.join("\n"));
        
        return true;        
    }
    
    // Quote
    pub.cmd_indent=function(state)
    {
        return this.indent_or_outdent(state, false);
    }
    
    pub.cmd_outdent=function(state)
    {
        return this.indent_or_outdent(state, true);
    }

    priv.handle_list=function(state, type)
    {
        // Build an array of selected line offsets        
        var lines=[];
        if (state.getSelectedText().indexOf("\n")>0)
        {
            state.SelectWholeLines();
            
            var line=state.m_selectionStart;
            lines.push(line);
            
            while (true)
            {
                line=state.FindNextLine(line);
                if (line>=state.m_selectionEnd)
                    break;  
                lines.push(line);
            }
        }
        else
        {
            lines.push(state.FindStartOfLine(state.m_selectionStart));
        }
        
        // Now work out the new list type
        // If the current selection only contains the current list type
        // then remove list items
        var prefix = type=="*" ? "* " : "1. ";
        for (var i=0; i<lines.length; i++)
        {
            var lt=state.DetectListType(lines[i]);
            if (lt.m_listType==type)
            {
                prefix="";
                break;
            }
        }

        // Update the prefix on all lines
        for (var i=lines.length-1; i>=0; i--)
        {
            var line=lines[i];
            var lt=state.DetectListType(line);
            state.ReplaceAt(line, lt.m_prefixLen, prefix);
        }
        
        // We now need to find any surrounding lists and renumber them
        var mdd=new MarkdownDeep.Markdown();
        mdd.ExtraMode=true;
        var listitems=mdd.GetListItems(state.m_text, state.m_selectionStart);
        
        while (listitems!=null)
        {
            // Process each list item
            var dx=0;
            for (var i=0; i<listitems.length-1; i++)
            {
                // Detect the list type
                var lt=state.DetectListType(listitems[i]+dx);
                if (lt.m_listType!="1")
                    break;
                    
                // Format new number prefix
                var newNumber=(i+1).toString() + ". ";
                
                // Replace it
                state.ReplaceAt(listitems[i]+dx, lt.m_prefixLen, newNumber);
                
                // Adjust things if new prefix is different length to the previos
                dx += newNumber.length - lt.m_prefixLen;
            }
            
            
            var newlistitems=mdd.GetListItems(state.m_text, listitems[listitems.length-1]+dx);
            if (newlistitems!=null && newlistitems[0]!=listitems[0])
                listitems=newlistitems;
            else
                listitems=null;
        }
        
        
        // Select lines
        if (lines.length>1)
        {
            state.SelectWholeLines();
        }
        
        return true;
    }
    
    
    pub.cmd_ullist=function(state)
    {
        return this.handle_list(state, "*");
    }
    
    pub.cmd_ollist=function(state)
    {
        return this.handle_list(state, "1");
    }
    
    pub.cmd_link=function(ctx)
    {
        ctx.TrimSelection();
        if (!ctx.CheckSimpleSelection())
            return false;
            
        var url=prompt("Enter the target URL:");
        if (url===null)
            return false;

        var text=ctx.getSelectedText();
        if (text.length==0)
        {
            text="link text";
        }
            
        var str="[" + text + "](" + url + ")";
        
        ctx.ReplaceSelection(str);
        ctx.m_selectionStart++;
        ctx.m_selectionEnd=ctx.m_selectionStart + text.length;
        return true;
    }
        
    pub.cmd_img=function(ctx)
    {
        ctx.TrimSelection();
        if (!ctx.CheckSimpleSelection())
            return false;

        var url=prompt("Enter the image URL");
        if (url===null)
            return false;
            
        var alttext=ctx.getSelectedText();
        if (alttext.length==0)
        {
            alttext="Image Text";
        }
        
        var str="![" + alttext + "](" + url + ")";
        
        ctx.ReplaceSelection(str);
        ctx.m_selectionStart+=2;
        ctx.m_selectionEnd=ctx.m_selectionStart + alttext.length;
        return true;
    }
        
    pub.cmd_hr=function(state)
    {
        state.SelectSurroundingWhiteSpace();
        if (state.m_selectionStart==0)
            state.ReplaceSelection("----------\n\n");
        else
            state.ReplaceSelection("\n\n----------\n\n");
        state.m_selectionStart=state.m_selectionEnd;;
        return true;
    }
    
    pub.IndentNewLine=function()
    {
        var editor=this;
        var timer;
        var handler=function() 
        {
            window.clearInterval(timer);
                    
            // Create an editor state from the current selection
            var state=new EditorState();
            state.InitFromTextArea(editor.m_textarea);

            // Find start of previous line
            var prevline=state.FindStartOfLine(state.SkipPreceedingEol(state.m_selectionStart));
            
            // Count spaces and tabs
            var i=prevline;
            while (true)
            {
                var ch=state.m_text.charAt(i);
                if (ch!=' ' && ch!='\t')
                    break;
                i++;
            }
            
            // Copy spaces and tabs to the new line
            if (i>prevline)
            {
                state.ReplaceSelection(state.m_text.substr(prevline, i-prevline));
                state.m_selectionStart=state.m_selectionEnd;
            }
            
            state.Apply();
        }

        timer=window.setInterval(handler, 1);

        return false;
    }
    
    pub.cmd_indented_newline=function(state)
    {
        // Do default new line
        state.ReplaceSelection("\n");
        state.m_selectionStart=state.m_selectionEnd;
        
        // Find start of previous line
        var prevline=state.FindStartOfLine(state.SkipPreceedingEol(state.m_selectionStart));
        
        // Count spaces and tabs
        var i=prevline;
        while (true)
        {
            var ch=state.m_text.charAt(i);
            if (ch!=' ' && ch!='\t')
                break;
            i++;
        }
        
        // Copy spaces and tabs to the new line
        if (i>prevline)
        {
            state.ReplaceSelection(state.m_text.substr(prevline, i-prevline));
            state.m_selectionStart=state.m_selectionEnd;
        }

        return true;
    }
    
    // Handle toolbar button
    pub.InvokeCommand=function(id)
    {
        // Special handling for undo and redo
        if (id=="undo" || id=="redo")
        {
            this["cmd_"+id]();
            this.m_textarea.focus();
            return;
        }
    
        // Create an editor state from the current selection
        var state=new EditorState();
        state.InitFromTextArea(this.m_textarea);

        // Create a copy for undo buffer
        var originalState=state.Duplicate();        
        
        // Call the handler and apply changes
        if (this["cmd_"+id](state))
        {
            // Save current state on undo stack
            this.m_undoMode=undomode_unknown;
            this.m_undoStack.splice(this.m_undoPos, this.m_undoStack.length-this.m_undoPos, originalState);        
            this.m_undoPos++;

            // Apply new state
            state.Apply();
            
            // Update markdown rendering
            this.onMarkdownChanged();
            
            return true;
        }
        else
        {
            this.m_textarea.focus();
            return false;
        }
    }
    
    delete priv;
    delete pub;

    // Exports
    this.Editor=Editor;
}();

/*global MarkdownDeepEditor */
/*global DialogForSelectingAStaticContentImage */
/*global DialogForSelectingAStaticContentDocument */
/*global DialogForCreatingAMailtoLink */
/*global DialogForLinkingStaticContent */

// Image selection dialog
MarkdownDeepEditor.Editor.prototype.cmd_imageSelector = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	var editor = this;
	var imageDialog = new DialogForSelectingAStaticContentImage(onImageSelected);
	imageDialog.show();

	function onImageSelected(resourceId, description) {
		if (resourceId === null) {
			return false;
		}

		$("#" + ctx.m_textarea.id).focus();
		var url = "/StaticContent/GetImage/" + resourceId;

		var alttext = ctx.getSelectedText();
		if (alttext.length === 0) {
			alttext = (description.length !== 0) ? description : "Image Text";
		}

		var str = "![" + alttext + "](" + url + ")";

		editor.cmd_img_core = function (state) {
			state.m_selectionStart = ctx.m_selectionStart;
			state.m_selectionEnd = ctx.m_selectionEnd;
			state.ReplaceSelection(str);
			state.m_selectionStart += 2;
			state.m_selectionEnd = state.m_selectionStart + alttext.length;
			return true;
		};

		editor.InvokeCommand("img_core");
		delete editor.cmd_img_core;
	}

	return false;
};

// Document selection dialog
MarkdownDeepEditor.Editor.prototype.cmd_documentSelector = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	var editor = this;
	var documentDialog = new DialogForSelectingAStaticContentDocument(onDocumentSelected);
	documentDialog.show();

	function onDocumentSelected(resourceId, description) {
		if (resourceId === null) {
			return false;
		}

		$("#" + ctx.m_textarea.id).focus();
		var url = "/StaticContent/GetDocument/" + resourceId;

		var text = ctx.getSelectedText();
		if (text.length === 0) {
			text = (description.length !== 0) ? description : "link text";
		}

		var str = "[" + text + "](" + url + ")";

		editor.cmd_document_core = function (state) {
			state.m_selectionStart = ctx.m_selectionStart;
			state.m_selectionEnd = ctx.m_selectionEnd;
			state.ReplaceSelection(str);
			state.m_selectionStart++;
			state.m_selectionEnd = state.m_selectionStart + text.length;
			return true;
		};

		editor.InvokeCommand("document_core");
		delete editor.cmd_document_core;

	}

	return true;
};

//Link selection dialog
MarkdownDeepEditor.Editor.prototype.cmd_linkSelector = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	var editor = this;
	var linkDialog = new DialogForLinkingStaticContent(onLinkSelected);
	linkDialog.show();

	function onLinkSelected(url, description) {
		if (url === null) {
			return false;
		}

		$("#" + ctx.m_textarea.id).focus();

		var text = ctx.getSelectedText();
		if (text.length === 0) {
			text = (description.length !== 0) ? description : "link text";
		}

		var str = "[" + text + "](" + url + ")";

		editor.cmd_document_core = function (state) {
			state.m_selectionStart = ctx.m_selectionStart;
			state.m_selectionEnd = ctx.m_selectionEnd;
			state.ReplaceSelection(str);
			state.m_selectionStart++;
			state.m_selectionEnd = state.m_selectionStart + text.length;
			return true;
		};

		editor.InvokeCommand("document_core");
		delete editor.cmd_document_core;

	}

	return true;
};

//MailTo dialog
MarkdownDeepEditor.Editor.prototype.cmd_mailto = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	var editor = this;
	var mailtoDialog = new DialogForCreatingAMailtoLink(onComplete);
	mailtoDialog.show(ctx.getSelectedText());

	function onComplete(email, subject) {

		if (email === null) {
			return false;
		}

		$("#" + ctx.m_textarea.id).focus();

		var text = ctx.getSelectedText();
		if (text.length === 0) {
			text = email;
		}

		var str = "[" + text + "](mailto:" + text;
		str += (!helper.form.isNullOrUndefined(subject) && subject.length > 0) ? "?subject=" + subject + ")" : ")";

		editor.cmd_mailto_core = function (state) {
			state.m_selectionStart = ctx.m_selectionStart;
			state.m_selectionEnd = ctx.m_selectionEnd;
			state.ReplaceSelection(str);
			state.m_selectionStart++;
			state.m_selectionEnd = state.m_selectionStart + text.length;
			return true;
		};

		editor.InvokeCommand("mailto_core");
		delete editor.cmd_mailto_core;
	}

	return true;
};


//underline text
MarkdownDeepEditor.Editor.prototype.cmd_underlineText = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}
	var editor = this;

	var text = ctx.getSelectedText();
	if (text.length === 0) {
		text = "underlined";
	}

	$("#" + ctx.m_textarea.id).focus();
	var startTag = "<u>";
	var endTag = "</u>";
	var str = startTag + text + endTag;

	editor.cmd_underlineText_core = function (state) {
		state.m_selectionStart = ctx.m_selectionStart;
		///state.m_selectionEnd = ctx.m_selectionEnd + endTag.length;
		state.ReplaceSelection(str);
		state.m_selectionStart = state.m_selectionStart + startTag.length;
		state.m_selectionEnd = state.m_selectionStart + text.length;
		return true;
	};

	editor.InvokeCommand("underlineText_core");
	delete editor.cmd_underlineText_core;

};



//underline test
MarkdownDeepEditor.Editor.prototype.cmd_linkTimesNew = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	if (!ctx.CheckSimpleSelection()) {
		return false;
	}
	var editor = this;

	var text = ctx.getSelectedText();
	if (text.length === 0) {
		text = "Times New Roman";
	}

	$("#" + ctx.m_textarea.id).focus();
	var startTag = "<span style='font-family:timesnewroman'>";
	var endTag = "</span>";
	var str = startTag + text + endTag;

	editor.cmd_linkTimesNew_core = function (state) {
		state.m_selectionStart = ctx.m_selectionStart;
		///state.m_selectionEnd = ctx.m_selectionEnd + endTag.length;
		state.ReplaceSelection(str);
		state.m_selectionStart = state.m_selectionStart + startTag.length;
		state.m_selectionEnd = state.m_selectionStart + text.length;
		return true;
	};

	editor.InvokeCommand("linkTimesNew_core");
	delete editor.cmd_linkTimesNew_core;
};

MarkdownDeepEditor.Editor.prototype.cmd_linkVerdana = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	if (!ctx.CheckSimpleSelection()) {
		return false;
	}
	var editor = this;

	var text = ctx.getSelectedText();
	if (text.length === 0) {
		text = "Verdana";
	}

	$("#" + ctx.m_textarea.id).focus();
	var startTag = "<span style='font-family:verdana'>";
	var endTag = "</span>";
	var str = startTag + text + endTag;

	editor.cmd_linkVerdana_core = function (state) {
		state.m_selectionStart = ctx.m_selectionStart;
		///state.m_selectionEnd = ctx.m_selectionEnd + endTag.length;
		state.ReplaceSelection(str);
		state.m_selectionStart = state.m_selectionStart + startTag.length;
		state.m_selectionEnd = state.m_selectionStart + text.length;
		return true;
	};

	editor.InvokeCommand("linkVerdana_core");
	delete editor.cmd_linkVerdana_core;
	
};

MarkdownDeepEditor.Editor.prototype.cmd_linkSans = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	if (!ctx.CheckSimpleSelection()) {
		return false;
	}
	var editor = this;

	var text = ctx.getSelectedText();
	if (text.length === 0) {
		text = "Sans";
	}

	$("#" + ctx.m_textarea.id).focus();
	var startTag = "<span style='font-family:Sans'>";
	var endTag = "</span>";
	var str = startTag + text + endTag;

	editor.cmd_linkSans_core = function (state) {
		state.m_selectionStart = ctx.m_selectionStart;
		///state.m_selectionEnd = ctx.m_selectionEnd + endTag.length;
		state.ReplaceSelection(str);
		state.m_selectionStart = state.m_selectionStart + startTag.length;
		state.m_selectionEnd = state.m_selectionStart + text.length;
		return true;
	};

	editor.InvokeCommand("linkSans_core");
	delete editor.cmd_linkSans_core;

};

MarkdownDeepEditor.Editor.prototype.cmd_linkSansSerif = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	if (!ctx.CheckSimpleSelection()) {
		return false;
	}
	var editor = this;

	var text = ctx.getSelectedText();
	if (text.length === 0) {
		text = "Sans Serif";
	}

	$("#" + ctx.m_textarea.id).focus();
	var startTag = "<span style='font-family:sans-serif'>";
	var endTag = "</span>";
	var str = startTag + text + endTag;

	editor.cmd_linkSansSerif_core = function (state) {
		state.m_selectionStart = ctx.m_selectionStart;
		///state.m_selectionEnd = ctx.m_selectionEnd + endTag.length;
		state.ReplaceSelection(str);
		state.m_selectionStart = state.m_selectionStart + startTag.length;
		state.m_selectionEnd = state.m_selectionStart + text.length;
		return true;
	};

	editor.InvokeCommand("linkSansSerif_core");
	delete editor.cmd_linkSansSerif_core;

};


MarkdownDeepEditor.Editor.prototype.cmd_linkHelvetica = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	if (!ctx.CheckSimpleSelection()) {
		return false;
	}
	var editor = this;

	var text = ctx.getSelectedText();
	if (text.length === 0) {
		text = "Helvetica";
	}

	$("#" + ctx.m_textarea.id).focus();

	var startTag = "<span style='font-family:helvetica'>";
	var endTag = "</span>";
	var str = startTag + text + endTag;

	editor.cmd_linkHelvetica_core = function (state) {
		state.m_selectionStart = ctx.m_selectionStart;
		///state.m_selectionEnd = ctx.m_selectionEnd + endTag.length;
		state.ReplaceSelection(str);
		state.m_selectionStart = state.m_selectionStart + startTag.length;
		state.m_selectionEnd = state.m_selectionStart + text.length;
		return true;
	};

	editor.InvokeCommand("linkHelvetica_core");
	delete editor.cmd_linkHelvetica_core;

};

MarkdownDeepEditor.Editor.prototype.cmd_linkTahoma = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	if (!ctx.CheckSimpleSelection()) {
		return false;
	}
	var editor = this;

	var text = ctx.getSelectedText();
	if (text.length === 0) {
		text = "Tahoma";
	}

	$("#" + ctx.m_textarea.id).focus();

	var startTag = "<span style='font-family:tahoma'>";
	var endTag = "</span>";
	var str = startTag + text + endTag;

	editor.cmd_linkTahoma_core = function (state) {
		state.m_selectionStart = ctx.m_selectionStart;
		state.m_selectionEnd = ctx.m_selectionEnd + endTag.length;
		state.ReplaceSelection(str);
		state.m_selectionStart = state.m_selectionStart + startTag.length;
		state.m_selectionEnd = state.m_selectionStart + text.length;
		return true;
	};

	editor.InvokeCommand("linkTahoma_core");
	delete editor.cmd_linkTahoma_core;
};


MarkdownDeepEditor.Editor.prototype.cmd_linkGeorgia = function (ctx) {
	ctx.TrimSelection();
	if (!ctx.CheckSimpleSelection()) {
		return false;
	}

	if (!ctx.CheckSimpleSelection()) {
		return false;
	}
	var editor = this;

	var text = ctx.getSelectedText();
	if (text.length === 0) {
		text = "Georgia";
	}

	$("#" + ctx.m_textarea.id).focus();

	var startTag = "<span style='font-family:georgia'>";
	var endTag = "</span>";
	var str = startTag + text + endTag;

	editor.cmd_linkGeorgia_core = function (state) {
		state.m_selectionStart = ctx.m_selectionStart;
		state.m_selectionEnd = ctx.m_selectionEnd + endTag.length;
		state.ReplaceSelection(str);
		state.m_selectionStart = state.m_selectionStart + startTag.length;
		state.m_selectionEnd = state.m_selectionStart + text.length;
		return true;
	};

	editor.InvokeCommand("linkGeorgia_core");
	delete editor.cmd_linkGeorgia_core;
};

MarkdownDeepEditor.Editor.prototype.cmd_dropdownClick = function (ctx) {
	ctx.TrimSelection();
	return false;

};
